blob: 5e7d6ae7b97f6a84a0287c80abe739791065e1b9 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassb50e5612017-11-13 18:54:54 -07002# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glassb50e5612017-11-13 18:54:54 -07005# Handle various things related to ELF images
6#
7
8from collections import namedtuple, OrderedDict
Simon Glassd8d40742019-07-08 13:18:35 -06009import io
Simon Glassb50e5612017-11-13 18:54:54 -070010import os
11import re
Simon Glassf58558a2019-07-08 13:18:34 -060012import shutil
Simon Glassb50e5612017-11-13 18:54:54 -070013import struct
Simon Glassf58558a2019-07-08 13:18:34 -060014import tempfile
Simon Glassb50e5612017-11-13 18:54:54 -070015
Simon Glassbf776672020-04-17 18:09:04 -060016from patman import command
17from patman import tools
18from patman import tout
Simon Glassb50e5612017-11-13 18:54:54 -070019
Simon Glassd8d40742019-07-08 13:18:35 -060020ELF_TOOLS = True
21try:
22 from elftools.elf.elffile import ELFFile
Simon Glass4d38dd72022-02-08 11:49:55 -070023 from elftools.elf.elffile import ELFError
Simon Glassd8d40742019-07-08 13:18:35 -060024 from elftools.elf.sections import SymbolTableSection
25except: # pragma: no cover
26 ELF_TOOLS = False
27
Simon Glass056f0ef2021-11-03 21:09:16 -060028# Information about an EFL symbol:
29# section (str): Name of the section containing this symbol
30# address (int): Address of the symbol (its value)
31# size (int): Size of the symbol in bytes
32# weak (bool): True if the symbol is weak
33# offset (int or None): Offset of the symbol's data in the ELF file, or None if
34# not known
35Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset'])
Simon Glassb50e5612017-11-13 18:54:54 -070036
Simon Glassd8d40742019-07-08 13:18:35 -060037# Information about an ELF file:
38# data: Extracted program contents of ELF file (this would be loaded by an
39# ELF loader when reading this file
40# load: Load address of code
41# entry: Entry address of code
42# memsize: Number of bytes in memory occupied by loading this ELF file
43ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
44
Simon Glassb50e5612017-11-13 18:54:54 -070045
46def GetSymbols(fname, patterns):
47 """Get the symbols from an ELF file
48
49 Args:
50 fname: Filename of the ELF file to read
51 patterns: List of regex patterns to search for, each a string
52
53 Returns:
54 None, if the file does not exist, or Dict:
55 key: Name of symbol
56 value: Hex value of symbol
57 """
Simon Glassc1aa66e2022-01-29 14:14:04 -070058 stdout = tools.run('objdump', '-t', fname)
Simon Glassb50e5612017-11-13 18:54:54 -070059 lines = stdout.splitlines()
60 if patterns:
61 re_syms = re.compile('|'.join(patterns))
62 else:
63 re_syms = None
64 syms = {}
65 syms_started = False
66 for line in lines:
67 if not line or not syms_started:
68 if 'SYMBOL TABLE' in line:
69 syms_started = True
70 line = None # Otherwise code coverage complains about 'continue'
71 continue
72 if re_syms and not re_syms.search(line):
73 continue
74
75 space_pos = line.find(' ')
76 value, rest = line[:space_pos], line[space_pos + 1:]
77 flags = rest[:7]
78 parts = rest[7:].split()
79 section, size = parts[:2]
80 if len(parts) > 2:
Simon Glass39c8e472019-08-24 07:22:46 -060081 name = parts[2] if parts[2] != '.hidden' else parts[3]
Simon Glass056f0ef2021-11-03 21:09:16 -060082 syms[name] = Symbol(section, int(value, 16), int(size, 16),
83 flags[1] == 'w', None)
84
85 # Sort dict by address
86 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
87
88def GetSymbolFileOffset(fname, patterns):
89 """Get the symbols from an ELF file
90
91 Args:
92 fname: Filename of the ELF file to read
93 patterns: List of regex patterns to search for, each a string
94
95 Returns:
96 None, if the file does not exist, or Dict:
97 key: Name of symbol
98 value: Hex value of symbol
99 """
100 def _GetFileOffset(elf, addr):
101 for seg in elf.iter_segments():
102 seg_end = seg['p_vaddr'] + seg['p_filesz']
103 if seg.header['p_type'] == 'PT_LOAD':
104 if addr >= seg['p_vaddr'] and addr < seg_end:
105 return addr - seg['p_vaddr'] + seg['p_offset']
106
107 if not ELF_TOOLS:
108 raise ValueError('Python elftools package is not available')
109
110 syms = {}
111 with open(fname, 'rb') as fd:
112 elf = ELFFile(fd)
113
114 re_syms = re.compile('|'.join(patterns))
115 for section in elf.iter_sections():
116 if isinstance(section, SymbolTableSection):
117 for symbol in section.iter_symbols():
118 if not re_syms or re_syms.search(symbol.name):
119 addr = symbol.entry['st_value']
120 syms[symbol.name] = Symbol(
121 section.name, addr, symbol.entry['st_size'],
122 symbol.entry['st_info']['bind'] == 'STB_WEAK',
123 _GetFileOffset(elf, addr))
Simon Glass46d61a22018-07-17 13:25:24 -0600124
125 # Sort dict by address
Simon Glass50979152019-05-14 15:53:41 -0600126 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
Simon Glassb50e5612017-11-13 18:54:54 -0700127
128def GetSymbolAddress(fname, sym_name):
129 """Get a value of a symbol from an ELF file
130
131 Args:
132 fname: Filename of the ELF file to read
133 patterns: List of regex patterns to search for, each a string
134
135 Returns:
136 Symbol value (as an integer) or None if not found
137 """
138 syms = GetSymbols(fname, [sym_name])
139 sym = syms.get(sym_name)
140 if not sym:
141 return None
142 return sym.address
Simon Glass19790632017-11-13 18:55:01 -0700143
Simon Glassf55382b2018-06-01 09:38:13 -0600144def LookupAndWriteSymbols(elf_fname, entry, section):
Simon Glass19790632017-11-13 18:55:01 -0700145 """Replace all symbols in an entry with their correct values
146
147 The entry contents is updated so that values for referenced symbols will be
Simon Glass3ab95982018-08-01 15:22:37 -0600148 visible at run time. This is done by finding out the symbols offsets in the
149 entry (using the ELF file) and replacing them with values from binman's data
150 structures.
Simon Glass19790632017-11-13 18:55:01 -0700151
152 Args:
153 elf_fname: Filename of ELF image containing the symbol information for
154 entry
155 entry: Entry to process
Simon Glassf55382b2018-06-01 09:38:13 -0600156 section: Section which can be used to lookup symbol values
Simon Glass19790632017-11-13 18:55:01 -0700157 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700158 fname = tools.get_input_filename(elf_fname)
Simon Glass19790632017-11-13 18:55:01 -0700159 syms = GetSymbols(fname, ['image', 'binman'])
160 if not syms:
161 return
162 base = syms.get('__image_copy_start')
163 if not base:
164 return
Simon Glass50979152019-05-14 15:53:41 -0600165 for name, sym in syms.items():
Simon Glass19790632017-11-13 18:55:01 -0700166 if name.startswith('_binman'):
Simon Glassf55382b2018-06-01 09:38:13 -0600167 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
168 (section.GetPath(), name, entry.GetPath()))
Simon Glass19790632017-11-13 18:55:01 -0700169 offset = sym.address - base.address
170 if offset < 0 or offset + sym.size > entry.contents_size:
171 raise ValueError('%s has offset %x (size %x) but the contents '
172 'size is %x' % (entry.GetPath(), offset,
173 sym.size, entry.contents_size))
174 if sym.size == 4:
175 pack_string = '<I'
176 elif sym.size == 8:
177 pack_string = '<Q'
178 else:
179 raise ValueError('%s has size %d: only 4 and 8 are supported' %
180 (msg, sym.size))
181
182 # Look up the symbol in our entry tables.
Simon Glass870a9ea2021-01-06 21:35:15 -0700183 value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
184 base.address)
Simon Glass15c981c2019-10-20 21:31:34 -0600185 if value is None:
Simon Glass19790632017-11-13 18:55:01 -0700186 value = -1
187 pack_string = pack_string.lower()
188 value_bytes = struct.pack(pack_string, value)
Simon Glassf3385a52022-01-29 14:14:15 -0700189 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
Simon Glass9f297b02019-07-20 12:23:36 -0600190 (msg, name, offset, value, len(value_bytes)))
Simon Glass19790632017-11-13 18:55:01 -0700191 entry.data = (entry.data[:offset] + value_bytes +
192 entry.data[offset + sym.size:])
Simon Glassf58558a2019-07-08 13:18:34 -0600193
194def MakeElf(elf_fname, text, data):
195 """Make an elf file with the given data in a single section
196
197 The output file has a several section including '.text' and '.data',
198 containing the info provided in arguments.
199
200 Args:
201 elf_fname: Output filename
202 text: Text (code) to put in the file's .text section
203 data: Data to put in the file's .data section
204 """
205 outdir = tempfile.mkdtemp(prefix='binman.elf.')
206 s_file = os.path.join(outdir, 'elf.S')
207
208 # Spilt the text into two parts so that we can make the entry point two
209 # bytes after the start of the text section
Simon Glass6a4ccad2020-11-08 20:36:19 -0700210 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
211 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
212 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
Simon Glassf58558a2019-07-08 13:18:34 -0600213 with open(s_file, 'w') as fd:
214 print('''/* Auto-generated C program to produce an ELF file for testing */
215
216.section .text
217.code32
218.globl _start
219.type _start, @function
220%s
221_start:
222%s
223.ident "comment"
224
225.comm fred,8,4
226
227.section .empty
228.globl _empty
229_empty:
230.byte 1
231
232.globl ernie
233.data
234.type ernie, @object
235.size ernie, 4
236ernie:
237%s
238''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
239 file=fd)
240 lds_file = os.path.join(outdir, 'elf.lds')
241
242 # Use a linker script to set the alignment and text address.
243 with open(lds_file, 'w') as fd:
244 print('''/* Auto-generated linker script to produce an ELF file for testing */
245
246PHDRS
247{
248 text PT_LOAD ;
249 data PT_LOAD ;
250 empty PT_LOAD FLAGS ( 6 ) ;
251 note PT_NOTE ;
252}
253
254SECTIONS
255{
256 . = 0xfef20000;
257 ENTRY(_start)
258 .text . : SUBALIGN(0)
259 {
260 *(.text)
261 } :text
262 .data : {
263 *(.data)
264 } :data
265 _bss_start = .;
266 .empty : {
267 *(.empty)
268 } :empty
Simon Glass9d44a7e2019-08-24 07:22:45 -0600269 /DISCARD/ : {
270 *(.note.gnu.property)
271 }
Simon Glassf58558a2019-07-08 13:18:34 -0600272 .note : {
273 *(.comment)
274 } :note
275 .bss _bss_start (OVERLAY) : {
276 *(.bss)
277 }
278}
279''', file=fd)
280 # -static: Avoid requiring any shared libraries
281 # -nostdlib: Don't link with C library
282 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
283 # text section at the start
284 # -m32: Build for 32-bit x86
285 # -T...: Specifies the link script, which sets the start address
Simon Glassc1aa66e2022-01-29 14:14:04 -0700286 cc, args = tools.get_target_compile_tool('cc')
Alper Nebi Yasak1e4687a2020-09-06 14:46:05 +0300287 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
288 lds_file, '-o', elf_fname, s_file]
Simon Glassd9800692022-01-29 14:14:05 -0700289 stdout = command.output(cc, *args)
Simon Glassf58558a2019-07-08 13:18:34 -0600290 shutil.rmtree(outdir)
Simon Glassd8d40742019-07-08 13:18:35 -0600291
292def DecodeElf(data, location):
293 """Decode an ELF file and return information about it
294
295 Args:
296 data: Data from ELF file
297 location: Start address of data to return
298
299 Returns:
300 ElfInfo object containing information about the decoded ELF file
301 """
302 file_size = len(data)
303 with io.BytesIO(data) as fd:
304 elf = ELFFile(fd)
305 data_start = 0xffffffff;
306 data_end = 0;
307 mem_end = 0;
308 virt_to_phys = 0;
309
310 for i in range(elf.num_segments()):
311 segment = elf.get_segment(i)
312 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
313 skipped = 1 # To make code-coverage see this line
314 continue
315 start = segment['p_paddr']
316 mend = start + segment['p_memsz']
317 rend = start + segment['p_filesz']
318 data_start = min(data_start, start)
319 data_end = max(data_end, rend)
320 mem_end = max(mem_end, mend)
321 if not virt_to_phys:
322 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
323
324 output = bytearray(data_end - data_start)
325 for i in range(elf.num_segments()):
326 segment = elf.get_segment(i)
327 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
328 skipped = 1 # To make code-coverage see this line
329 continue
330 start = segment['p_paddr']
331 offset = 0
332 if start < location:
333 offset = location - start
334 start = location
335 # A legal ELF file can have a program header with non-zero length
336 # but zero-length file size and a non-zero offset which, added
337 # together, are greater than input->size (i.e. the total file size).
338 # So we need to not even test in the case that p_filesz is zero.
339 # Note: All of this code is commented out since we don't have a test
340 # case for it.
341 size = segment['p_filesz']
342 #if not size:
343 #continue
344 #end = segment['p_offset'] + segment['p_filesz']
345 #if end > file_size:
346 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
347 #file_size, end)
348 output[start - data_start:start - data_start + size] = (
349 segment.data()[offset:])
350 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
351 mem_end - data_start)
Simon Glass0427bed2021-11-03 21:09:18 -0600352
353def UpdateFile(infile, outfile, start_sym, end_sym, insert):
Simon Glassf3385a52022-01-29 14:14:15 -0700354 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
Simon Glass0427bed2021-11-03 21:09:18 -0600355 (outfile, len(insert), len(insert), start_sym, end_sym))
356 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
357 if len(syms) != 2:
358 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
359 (start_sym, end_sym, len(syms),
360 ','.join(syms.keys())))
361
362 size = syms[end_sym].offset - syms[start_sym].offset
363 if len(insert) > size:
364 raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" %
365 (infile, len(insert), len(insert), size, size))
366
Simon Glassc1aa66e2022-01-29 14:14:04 -0700367 data = tools.read_file(infile)
Simon Glass0427bed2021-11-03 21:09:18 -0600368 newdata = data[:syms[start_sym].offset]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700369 newdata += insert + tools.get_bytes(0, size - len(insert))
Simon Glass0427bed2021-11-03 21:09:18 -0600370 newdata += data[syms[end_sym].offset:]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700371 tools.write_file(outfile, newdata)
Simon Glassf3385a52022-01-29 14:14:15 -0700372 tout.info('Written to offset %#x' % syms[start_sym].offset)
Simon Glass4d38dd72022-02-08 11:49:55 -0700373
374def read_segments(data):
375 """Read segments from an ELF file
376
377 Args:
378 data (bytes): Contents of file
379
380 Returns:
381 tuple:
382 list of segments, each:
383 int: Segment number (0 = first)
384 int: Start address of segment in memory
385 bytes: Contents of segment
386 int: entry address for image
387
388 Raises:
389 ValueError: elftools is not available
390 """
391 if not ELF_TOOLS:
392 raise ValueError('Python elftools package is not available')
393 with io.BytesIO(data) as inf:
394 try:
395 elf = ELFFile(inf)
396 except ELFError as err:
397 raise ValueError(err)
398 entry = elf.header['e_entry']
399 segments = []
400 for i in range(elf.num_segments()):
401 segment = elf.get_segment(i)
402 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
403 skipped = 1 # To make code-coverage see this line
404 continue
405 start = segment['p_offset']
406 rend = start + segment['p_filesz']
407 segments.append((i, segment['p_paddr'], data[start:rend]))
408 return segments, entry