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 patman import command
17 from patman import tools
18 from patman 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 # 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
35 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset'])
37 # 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
43 ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
46 def GetSymbols(fname, patterns):
47 """Get the symbols from an ELF file
50 fname: Filename of the ELF file to read
51 patterns: List of regex patterns to search for, each a string
54 None, if the file does not exist, or Dict:
56 value: Hex value of symbol
58 stdout = tools.run('objdump', '-t', fname)
59 lines = stdout.splitlines()
61 re_syms = re.compile('|'.join(patterns))
67 if not line or not syms_started:
68 if 'SYMBOL TABLE' in line:
70 line = None # Otherwise code coverage complains about 'continue'
72 if re_syms and not re_syms.search(line):
75 space_pos = line.find(' ')
76 value, rest = line[:space_pos], line[space_pos + 1:]
78 parts = rest[7:].split()
79 section, size = parts[:2]
81 name = parts[2] if parts[2] != '.hidden' else parts[3]
82 syms[name] = Symbol(section, int(value, 16), int(size, 16),
83 flags[1] == 'w', None)
85 # Sort dict by address
86 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
88 def _GetFileOffset(elf, addr):
89 """Get the file offset for an address
92 elf (ELFFile): ELF file to check
93 addr (int): Address to search for
96 int: Offset of that address in the ELF file, or None if not valid
98 for seg in elf.iter_segments():
99 seg_end = seg['p_vaddr'] + seg['p_filesz']
100 if seg.header['p_type'] == 'PT_LOAD':
101 if addr >= seg['p_vaddr'] and addr < seg_end:
102 return addr - seg['p_vaddr'] + seg['p_offset']
104 def GetFileOffset(fname, addr):
105 """Get the file offset for an address
108 fname (str): Filename of ELF file to check
109 addr (int): Address to search for
112 int: Offset of that address in the ELF file, or None if not valid
115 raise ValueError("Python: No module named 'elftools'")
116 with open(fname, 'rb') as fd:
118 return _GetFileOffset(elf, addr)
120 def GetSymbolFromAddress(fname, addr):
121 """Get the symbol at a particular address
124 fname (str): Filename of ELF file to check
125 addr (int): Address to search for
128 str: Symbol name, or None if no symbol at that address
131 raise ValueError("Python: No module named 'elftools'")
132 with open(fname, 'rb') as fd:
134 syms = GetSymbols(fname, None)
135 for name, sym in syms.items():
136 if sym.address == addr:
139 def GetSymbolFileOffset(fname, patterns):
140 """Get the symbols from an ELF file
143 fname: Filename of the ELF file to read
144 patterns: List of regex patterns to search for, each a string
147 None, if the file does not exist, or Dict:
149 value: Hex value of symbol
152 raise ValueError("Python: No module named 'elftools'")
155 with open(fname, 'rb') as fd:
158 re_syms = re.compile('|'.join(patterns))
159 for section in elf.iter_sections():
160 if isinstance(section, SymbolTableSection):
161 for symbol in section.iter_symbols():
162 if not re_syms or re_syms.search(symbol.name):
163 addr = symbol.entry['st_value']
164 syms[symbol.name] = Symbol(
165 section.name, addr, symbol.entry['st_size'],
166 symbol.entry['st_info']['bind'] == 'STB_WEAK',
167 _GetFileOffset(elf, addr))
169 # Sort dict by address
170 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
172 def GetSymbolAddress(fname, sym_name):
173 """Get a value of a symbol from an ELF file
176 fname: Filename of the ELF file to read
177 patterns: List of regex patterns to search for, each a string
180 Symbol value (as an integer) or None if not found
182 syms = GetSymbols(fname, [sym_name])
183 sym = syms.get(sym_name)
188 def LookupAndWriteSymbols(elf_fname, entry, section):
189 """Replace all symbols in an entry with their correct values
191 The entry contents is updated so that values for referenced symbols will be
192 visible at run time. This is done by finding out the symbols offsets in the
193 entry (using the ELF file) and replacing them with values from binman's data
197 elf_fname: Filename of ELF image containing the symbol information for
199 entry: Entry to process
200 section: Section which can be used to lookup symbol values
202 fname = tools.get_input_filename(elf_fname)
203 syms = GetSymbols(fname, ['image', 'binman'])
206 base = syms.get('__image_copy_start')
209 for name, sym in syms.items():
210 if name.startswith('_binman'):
211 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
212 (section.GetPath(), name, entry.GetPath()))
213 offset = sym.address - base.address
214 if offset < 0 or offset + sym.size > entry.contents_size:
215 raise ValueError('%s has offset %x (size %x) but the contents '
216 'size is %x' % (entry.GetPath(), offset,
217 sym.size, entry.contents_size))
223 raise ValueError('%s has size %d: only 4 and 8 are supported' %
226 # Look up the symbol in our entry tables.
227 value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
231 pack_string = pack_string.lower()
232 value_bytes = struct.pack(pack_string, value)
233 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
234 (msg, name, offset, value, len(value_bytes)))
235 entry.data = (entry.data[:offset] + value_bytes +
236 entry.data[offset + sym.size:])
238 def MakeElf(elf_fname, text, data):
239 """Make an elf file with the given data in a single section
241 The output file has a several section including '.text' and '.data',
242 containing the info provided in arguments.
245 elf_fname: Output filename
246 text: Text (code) to put in the file's .text section
247 data: Data to put in the file's .data section
249 outdir = tempfile.mkdtemp(prefix='binman.elf.')
250 s_file = os.path.join(outdir, 'elf.S')
252 # Spilt the text into two parts so that we can make the entry point two
253 # bytes after the start of the text section
254 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
255 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
256 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
257 with open(s_file, 'w') as fd:
258 print('''/* Auto-generated C program to produce an ELF file for testing */
263 .type _start, @function
282 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
284 lds_file = os.path.join(outdir, 'elf.lds')
286 # Use a linker script to set the alignment and text address.
287 with open(lds_file, 'w') as fd:
288 print('''/* Auto-generated linker script to produce an ELF file for testing */
294 empty PT_LOAD FLAGS ( 6 ) ;
302 .text . : SUBALIGN(0)
314 *(.note.gnu.property)
319 .bss _bss_start (OVERLAY) : {
324 # -static: Avoid requiring any shared libraries
325 # -nostdlib: Don't link with C library
326 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
327 # text section at the start
328 # -m32: Build for 32-bit x86
329 # -T...: Specifies the link script, which sets the start address
330 cc, args = tools.get_target_compile_tool('cc')
331 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
332 lds_file, '-o', elf_fname, s_file]
333 stdout = command.output(cc, *args)
334 shutil.rmtree(outdir)
336 def DecodeElf(data, location):
337 """Decode an ELF file and return information about it
340 data: Data from ELF file
341 location: Start address of data to return
344 ElfInfo object containing information about the decoded ELF file
346 file_size = len(data)
347 with io.BytesIO(data) as fd:
349 data_start = 0xffffffff;
354 for i in range(elf.num_segments()):
355 segment = elf.get_segment(i)
356 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
357 skipped = 1 # To make code-coverage see this line
359 start = segment['p_paddr']
360 mend = start + segment['p_memsz']
361 rend = start + segment['p_filesz']
362 data_start = min(data_start, start)
363 data_end = max(data_end, rend)
364 mem_end = max(mem_end, mend)
366 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
368 output = bytearray(data_end - data_start)
369 for i in range(elf.num_segments()):
370 segment = elf.get_segment(i)
371 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
372 skipped = 1 # To make code-coverage see this line
374 start = segment['p_paddr']
377 offset = location - start
379 # A legal ELF file can have a program header with non-zero length
380 # but zero-length file size and a non-zero offset which, added
381 # together, are greater than input->size (i.e. the total file size).
382 # So we need to not even test in the case that p_filesz is zero.
383 # Note: All of this code is commented out since we don't have a test
385 size = segment['p_filesz']
388 #end = segment['p_offset'] + segment['p_filesz']
390 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
392 output[start - data_start:start - data_start + size] = (
393 segment.data()[offset:])
394 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
395 mem_end - data_start)
397 def UpdateFile(infile, outfile, start_sym, end_sym, insert):
398 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
399 (outfile, len(insert), len(insert), start_sym, end_sym))
400 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
402 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
403 (start_sym, end_sym, len(syms),
404 ','.join(syms.keys())))
406 size = syms[end_sym].offset - syms[start_sym].offset
407 if len(insert) > size:
408 raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" %
409 (infile, len(insert), len(insert), size, size))
411 data = tools.read_file(infile)
412 newdata = data[:syms[start_sym].offset]
413 newdata += insert + tools.get_bytes(0, size - len(insert))
414 newdata += data[syms[end_sym].offset:]
415 tools.write_file(outfile, newdata)
416 tout.info('Written to offset %#x' % syms[start_sym].offset)
418 def read_loadable_segments(data):
419 """Read segments from an ELF file
422 data (bytes): Contents of file
426 list of segments, each:
427 int: Segment number (0 = first)
428 int: Start address of segment in memory
429 bytes: Contents of segment
430 int: entry address for image
433 ValueError: elftools is not available
436 raise ValueError("Python: No module named 'elftools'")
437 with io.BytesIO(data) as inf:
440 except ELFError as err:
441 raise ValueError(err)
442 entry = elf.header['e_entry']
444 for i in range(elf.num_segments()):
445 segment = elf.get_segment(i)
446 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
447 skipped = 1 # To make code-coverage see this line
449 start = segment['p_offset']
450 rend = start + segment['p_filesz']
451 segments.append((i, segment['p_paddr'], data[start:rend]))
452 return segments, entry