1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Handle various things related to ELF images
8 from collections import namedtuple, OrderedDict
16 from u_boot_pylib import command
17 from u_boot_pylib import tools
18 from u_boot_pylib import tout
22 from elftools.elf.elffile import ELFFile
23 from elftools.elf.elffile import ELFError
24 from elftools.elf.sections import SymbolTableSection
25 except: # pragma: no cover
28 # BSYM in little endian, keep in sync with include/binman_sym.h
29 BINMAN_SYM_MAGIC_VALUE = 0x4d595342
31 # Information about an EFL symbol:
32 # section (str): Name of the section containing this symbol
33 # address (int): Address of the symbol (its value)
34 # size (int): Size of the symbol in bytes
35 # weak (bool): True if the symbol is weak
36 # offset (int or None): Offset of the symbol's data in the ELF file, or None if
38 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset'])
40 # Information about an ELF file:
41 # data: Extracted program contents of ELF file (this would be loaded by an
42 # ELF loader when reading this file
43 # load: Load address of code
44 # entry: Entry address of code
45 # memsize: Number of bytes in memory occupied by loading this ELF file
46 ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
49 def GetSymbols(fname, patterns):
50 """Get the symbols from an ELF file
53 fname: Filename of the ELF file to read
54 patterns: List of regex patterns to search for, each a string
57 None, if the file does not exist, or Dict:
59 value: Hex value of symbol
61 stdout = tools.run('objdump', '-t', fname)
62 lines = stdout.splitlines()
64 re_syms = re.compile('|'.join(patterns))
70 if not line or not syms_started:
71 if 'SYMBOL TABLE' in line:
73 line = None # Otherwise code coverage complains about 'continue'
75 if re_syms and not re_syms.search(line):
78 space_pos = line.find(' ')
79 value, rest = line[:space_pos], line[space_pos + 1:]
81 parts = rest[7:].split()
82 section, size = parts[:2]
84 name = parts[2] if parts[2] != '.hidden' else parts[3]
85 syms[name] = Symbol(section, int(value, 16), int(size, 16),
86 flags[1] == 'w', None)
88 # Sort dict by address
89 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
91 def _GetFileOffset(elf, addr):
92 """Get the file offset for an address
95 elf (ELFFile): ELF file to check
96 addr (int): Address to search for
99 int: Offset of that address in the ELF file, or None if not valid
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']
107 def GetFileOffset(fname, addr):
108 """Get the file offset for an address
111 fname (str): Filename of ELF file to check
112 addr (int): Address to search for
115 int: Offset of that address in the ELF file, or None if not valid
118 raise ValueError("Python: No module named 'elftools'")
119 with open(fname, 'rb') as fd:
121 return _GetFileOffset(elf, addr)
123 def GetSymbolFromAddress(fname, addr):
124 """Get the symbol at a particular address
127 fname (str): Filename of ELF file to check
128 addr (int): Address to search for
131 str: Symbol name, or None if no symbol at that address
134 raise ValueError("Python: No module named 'elftools'")
135 with open(fname, 'rb') as fd:
137 syms = GetSymbols(fname, None)
138 for name, sym in syms.items():
139 if sym.address == addr:
142 def GetSymbolFileOffset(fname, patterns):
143 """Get the symbols from an ELF file
146 fname: Filename of the ELF file to read
147 patterns: List of regex patterns to search for, each a string
150 None, if the file does not exist, or Dict:
152 value: Hex value of symbol
155 raise ValueError("Python: No module named 'elftools'")
158 with open(fname, 'rb') as fd:
161 re_syms = re.compile('|'.join(patterns))
162 for section in elf.iter_sections():
163 if isinstance(section, SymbolTableSection):
164 for symbol in section.iter_symbols():
165 if not re_syms or re_syms.search(symbol.name):
166 addr = symbol.entry['st_value']
167 syms[symbol.name] = Symbol(
168 section.name, addr, symbol.entry['st_size'],
169 symbol.entry['st_info']['bind'] == 'STB_WEAK',
170 _GetFileOffset(elf, addr))
172 # Sort dict by address
173 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
175 def GetSymbolAddress(fname, sym_name):
176 """Get a value of a symbol from an ELF file
179 fname: Filename of the ELF file to read
180 patterns: List of regex patterns to search for, each a string
183 Symbol value (as an integer) or None if not found
185 syms = GetSymbols(fname, [sym_name])
186 sym = syms.get(sym_name)
191 def GetPackString(sym, msg):
192 """Get the struct.pack/unpack string to use with a given symbol
195 sym (Symbol): Symbol to check. Only the size member is checked
196 @msg (str): String which indicates the entry being processed, used for
200 str: struct string to use, .e.g. '<I'
203 ValueError: Symbol has an unexpected size
210 raise ValueError('%s has size %d: only 4 and 8 are supported' %
213 def GetSymbolOffset(elf_fname, sym_name, base_sym=None):
214 """Read the offset of a symbol compared to base symbol
216 This is useful for obtaining the value of a single symbol relative to the
217 base of a binary blob.
220 elf_fname: Filename of the ELF file to read
221 sym_name (str): Name of symbol to read
222 base_sym (str): Base symbol to sue to calculate the offset (or None to
223 use '__image_copy_start'
226 int: Offset of the symbol relative to the base symbol
229 base_sym = '__image_copy_start'
230 fname = tools.get_input_filename(elf_fname)
231 syms = GetSymbols(fname, [base_sym, sym_name])
232 base = syms[base_sym].address
233 val = syms[sym_name].address
236 def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False,
238 """Replace all symbols in an entry with their correct values
240 The entry contents is updated so that values for referenced symbols will be
241 visible at run time. This is done by finding out the symbols offsets in the
242 entry (using the ELF file) and replacing them with values from binman's data
246 elf_fname: Filename of ELF image containing the symbol information for
248 entry: Entry to process
249 section: Section which can be used to lookup symbol values
250 base_sym: Base symbol marking the start of the image
253 int: Number of symbols written
256 base_sym = '__image_copy_start'
257 fname = tools.get_input_filename(elf_fname)
258 syms = GetSymbols(fname, ['image', 'binman'])
261 msg = ("Section '%s': entry '%s'" %
262 (section.GetPath(), entry.GetPath()))
263 raise ValueError(f'{msg}: Cannot write symbols to an ELF file without Python elftools')
265 with open(fname, 'rb') as fd:
267 for name, sym in syms.items():
268 offset = _GetFileOffset(elf, sym.address)
269 new_syms[name] = Symbol(sym.section, sym.address, sym.size,
274 tout.debug('LookupAndWriteSymbols: no syms')
276 base = syms.get(base_sym)
277 if not base and not is_elf:
278 tout.debug('LookupAndWriteSymbols: no base')
280 base_addr = 0 if is_elf else base.address
282 for name, sym in syms.items():
283 if name.startswith('_binman'):
284 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
285 (section.GetPath(), name, entry.GetPath()))
287 # For ELF files, use the file offset
290 # For blobs use the offset of the symbol, calculated by
291 # subtracting the base address which by definition is at the
293 offset = sym.address - base.address
294 if offset < 0 or offset + sym.size > entry.contents_size:
295 raise ValueError('%s has offset %x (size %x) but the contents '
296 'size is %x' % (entry.GetPath(), offset,
298 entry.contents_size))
299 pack_string = GetPackString(sym, msg)
300 if name == '_binman_sym_magic':
301 value = BINMAN_SYM_MAGIC_VALUE
303 # Look up the symbol in our entry tables.
304 value = section.GetImage().LookupImageSymbol(name, sym.weak,
308 pack_string = pack_string.lower()
309 value_bytes = struct.pack(pack_string, value)
310 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
311 (msg, name, offset, value, len(value_bytes)))
312 entry.data = (entry.data[:offset] + value_bytes +
313 entry.data[offset + sym.size:])
317 f"Section '{section.GetPath()}': entry '{entry.GetPath()}' : {count} symbols")
320 def GetSymbolValue(sym, data, msg):
321 """Get the value of a symbol
323 This can only be used on symbols with an integer value.
326 sym (Symbol): Symbol to check
327 data (butes): Data for the ELF file - the symbol data appears at offset
329 @msg (str): String which indicates the entry being processed, used for
333 int: Value of the symbol
336 ValueError: Symbol has an unexpected size
338 pack_string = GetPackString(sym, msg)
339 value = struct.unpack(pack_string, data[sym.offset:sym.offset + sym.size])
342 def MakeElf(elf_fname, text, data):
343 """Make an elf file with the given data in a single section
345 The output file has a several section including '.text' and '.data',
346 containing the info provided in arguments.
349 elf_fname: Output filename
350 text: Text (code) to put in the file's .text section
351 data: Data to put in the file's .data section
353 outdir = tempfile.mkdtemp(prefix='binman.elf.')
354 s_file = os.path.join(outdir, 'elf.S')
356 # Spilt the text into two parts so that we can make the entry point two
357 # bytes after the start of the text section
358 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
359 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
360 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
361 with open(s_file, 'w') as fd:
362 print('''/* Auto-generated C program to produce an ELF file for testing */
367 .type _start, @function
386 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
388 lds_file = os.path.join(outdir, 'elf.lds')
390 # Use a linker script to set the alignment and text address.
391 with open(lds_file, 'w') as fd:
392 print('''/* Auto-generated linker script to produce an ELF file for testing */
398 empty PT_LOAD FLAGS ( 6 ) ;
406 .text . : SUBALIGN(0)
418 *(.note.gnu.property)
423 .bss _bss_start (OVERLAY) : {
428 # -static: Avoid requiring any shared libraries
429 # -nostdlib: Don't link with C library
430 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
431 # text section at the start
432 # -m32: Build for 32-bit x86
433 # -T...: Specifies the link script, which sets the start address
434 cc, args = tools.get_target_compile_tool('cc')
435 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
436 lds_file, '-o', elf_fname, s_file]
437 stdout = command.output(cc, *args)
438 shutil.rmtree(outdir)
440 def DecodeElf(data, location):
441 """Decode an ELF file and return information about it
444 data: Data from ELF file
445 location: Start address of data to return
448 ElfInfo object containing information about the decoded ELF file
451 raise ValueError("Python: No module named 'elftools'")
452 file_size = len(data)
453 with io.BytesIO(data) as fd:
455 data_start = 0xffffffff
460 for i in range(elf.num_segments()):
461 segment = elf.get_segment(i)
462 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
463 skipped = 1 # To make code-coverage see this line
465 start = segment['p_paddr']
466 mend = start + segment['p_memsz']
467 rend = start + segment['p_filesz']
468 data_start = min(data_start, start)
469 data_end = max(data_end, rend)
470 mem_end = max(mem_end, mend)
472 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
474 output = bytearray(data_end - data_start)
475 for i in range(elf.num_segments()):
476 segment = elf.get_segment(i)
477 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
478 skipped = 1 # To make code-coverage see this line
480 start = segment['p_paddr']
483 offset = location - start
485 # A legal ELF file can have a program header with non-zero length
486 # but zero-length file size and a non-zero offset which, added
487 # together, are greater than input->size (i.e. the total file size).
488 # So we need to not even test in the case that p_filesz is zero.
489 # Note: All of this code is commented out since we don't have a test
491 size = segment['p_filesz']
494 #end = segment['p_offset'] + segment['p_filesz']
496 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
498 output[start - data_start:start - data_start + size] = (
499 segment.data()[offset:])
500 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
501 mem_end - data_start)
503 def UpdateFile(infile, outfile, start_sym, end_sym, insert):
504 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
505 (outfile, len(insert), len(insert), start_sym, end_sym))
506 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
508 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
509 (start_sym, end_sym, len(syms),
510 ','.join(syms.keys())))
512 size = syms[end_sym].offset - syms[start_sym].offset
513 if len(insert) > size:
514 raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" %
515 (infile, len(insert), len(insert), size, size))
517 data = tools.read_file(infile)
518 newdata = data[:syms[start_sym].offset]
519 newdata += insert + tools.get_bytes(0, size - len(insert))
520 newdata += data[syms[end_sym].offset:]
521 tools.write_file(outfile, newdata)
522 tout.info('Written to offset %#x' % syms[start_sym].offset)
524 def read_loadable_segments(data):
525 """Read segments from an ELF file
528 data (bytes): Contents of file
532 list of segments, each:
533 int: Segment number (0 = first)
534 int: Start address of segment in memory
535 bytes: Contents of segment
536 int: entry address for image
539 ValueError: elftools is not available
542 raise ValueError("Python: No module named 'elftools'")
543 with io.BytesIO(data) as inf:
546 except ELFError as err:
547 raise ValueError(err)
548 entry = elf.header['e_entry']
550 for i in range(elf.num_segments()):
551 segment = elf.get_segment(i)
552 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
553 skipped = 1 # To make code-coverage see this line
555 start = segment['p_offset']
556 rend = start + segment['p_filesz']
557 segments.append((i, segment['p_paddr'], data[start:rend]))
558 return segments, entry
561 """Check if some binary data is a valid ELF file
564 data (bytes): Bytes to check
567 bool: True if a valid Elf file, False if not