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.sections import SymbolTableSection
24 except: # pragma: no cover
27 # Information about an EFL symbol:
28 # section (str): Name of the section containing this symbol
29 # address (int): Address of the symbol (its value)
30 # size (int): Size of the symbol in bytes
31 # weak (bool): True if the symbol is weak
32 # offset (int or None): Offset of the symbol's data in the ELF file, or None if
34 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset'])
36 # Information about an ELF file:
37 # data: Extracted program contents of ELF file (this would be loaded by an
38 # ELF loader when reading this file
39 # load: Load address of code
40 # entry: Entry address of code
41 # memsize: Number of bytes in memory occupied by loading this ELF file
42 ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
45 def GetSymbols(fname, patterns):
46 """Get the symbols from an ELF file
49 fname: Filename of the ELF file to read
50 patterns: List of regex patterns to search for, each a string
53 None, if the file does not exist, or Dict:
55 value: Hex value of symbol
57 stdout = tools.run('objdump', '-t', fname)
58 lines = stdout.splitlines()
60 re_syms = re.compile('|'.join(patterns))
66 if not line or not syms_started:
67 if 'SYMBOL TABLE' in line:
69 line = None # Otherwise code coverage complains about 'continue'
71 if re_syms and not re_syms.search(line):
74 space_pos = line.find(' ')
75 value, rest = line[:space_pos], line[space_pos + 1:]
77 parts = rest[7:].split()
78 section, size = parts[:2]
80 name = parts[2] if parts[2] != '.hidden' else parts[3]
81 syms[name] = Symbol(section, int(value, 16), int(size, 16),
82 flags[1] == 'w', None)
84 # Sort dict by address
85 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
87 def GetSymbolFileOffset(fname, patterns):
88 """Get the symbols from an ELF file
91 fname: Filename of the ELF file to read
92 patterns: List of regex patterns to search for, each a string
95 None, if the file does not exist, or Dict:
97 value: Hex value of symbol
99 def _GetFileOffset(elf, addr):
100 for seg in elf.iter_segments():
101 seg_end = seg['p_vaddr'] + seg['p_filesz']
102 if seg.header['p_type'] == 'PT_LOAD':
103 if addr >= seg['p_vaddr'] and addr < seg_end:
104 return addr - seg['p_vaddr'] + seg['p_offset']
107 raise ValueError('Python elftools package is not available')
110 with open(fname, 'rb') as fd:
113 re_syms = re.compile('|'.join(patterns))
114 for section in elf.iter_sections():
115 if isinstance(section, SymbolTableSection):
116 for symbol in section.iter_symbols():
117 if not re_syms or re_syms.search(symbol.name):
118 addr = symbol.entry['st_value']
119 syms[symbol.name] = Symbol(
120 section.name, addr, symbol.entry['st_size'],
121 symbol.entry['st_info']['bind'] == 'STB_WEAK',
122 _GetFileOffset(elf, addr))
124 # Sort dict by address
125 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
127 def GetSymbolAddress(fname, sym_name):
128 """Get a value of a symbol from an ELF file
131 fname: Filename of the ELF file to read
132 patterns: List of regex patterns to search for, each a string
135 Symbol value (as an integer) or None if not found
137 syms = GetSymbols(fname, [sym_name])
138 sym = syms.get(sym_name)
143 def LookupAndWriteSymbols(elf_fname, entry, section):
144 """Replace all symbols in an entry with their correct values
146 The entry contents is updated so that values for referenced symbols will be
147 visible at run time. This is done by finding out the symbols offsets in the
148 entry (using the ELF file) and replacing them with values from binman's data
152 elf_fname: Filename of ELF image containing the symbol information for
154 entry: Entry to process
155 section: Section which can be used to lookup symbol values
157 fname = tools.get_input_filename(elf_fname)
158 syms = GetSymbols(fname, ['image', 'binman'])
161 base = syms.get('__image_copy_start')
164 for name, sym in syms.items():
165 if name.startswith('_binman'):
166 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
167 (section.GetPath(), name, entry.GetPath()))
168 offset = sym.address - base.address
169 if offset < 0 or offset + sym.size > entry.contents_size:
170 raise ValueError('%s has offset %x (size %x) but the contents '
171 'size is %x' % (entry.GetPath(), offset,
172 sym.size, entry.contents_size))
178 raise ValueError('%s has size %d: only 4 and 8 are supported' %
181 # Look up the symbol in our entry tables.
182 value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
186 pack_string = pack_string.lower()
187 value_bytes = struct.pack(pack_string, value)
188 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
189 (msg, name, offset, value, len(value_bytes)))
190 entry.data = (entry.data[:offset] + value_bytes +
191 entry.data[offset + sym.size:])
193 def MakeElf(elf_fname, text, data):
194 """Make an elf file with the given data in a single section
196 The output file has a several section including '.text' and '.data',
197 containing the info provided in arguments.
200 elf_fname: Output filename
201 text: Text (code) to put in the file's .text section
202 data: Data to put in the file's .data section
204 outdir = tempfile.mkdtemp(prefix='binman.elf.')
205 s_file = os.path.join(outdir, 'elf.S')
207 # Spilt the text into two parts so that we can make the entry point two
208 # bytes after the start of the text section
209 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
210 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
211 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
212 with open(s_file, 'w') as fd:
213 print('''/* Auto-generated C program to produce an ELF file for testing */
218 .type _start, @function
237 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
239 lds_file = os.path.join(outdir, 'elf.lds')
241 # Use a linker script to set the alignment and text address.
242 with open(lds_file, 'w') as fd:
243 print('''/* Auto-generated linker script to produce an ELF file for testing */
249 empty PT_LOAD FLAGS ( 6 ) ;
257 .text . : SUBALIGN(0)
269 *(.note.gnu.property)
274 .bss _bss_start (OVERLAY) : {
279 # -static: Avoid requiring any shared libraries
280 # -nostdlib: Don't link with C library
281 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
282 # text section at the start
283 # -m32: Build for 32-bit x86
284 # -T...: Specifies the link script, which sets the start address
285 cc, args = tools.get_target_compile_tool('cc')
286 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
287 lds_file, '-o', elf_fname, s_file]
288 stdout = command.Output(cc, *args)
289 shutil.rmtree(outdir)
291 def DecodeElf(data, location):
292 """Decode an ELF file and return information about it
295 data: Data from ELF file
296 location: Start address of data to return
299 ElfInfo object containing information about the decoded ELF file
301 file_size = len(data)
302 with io.BytesIO(data) as fd:
304 data_start = 0xffffffff;
309 for i in range(elf.num_segments()):
310 segment = elf.get_segment(i)
311 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
312 skipped = 1 # To make code-coverage see this line
314 start = segment['p_paddr']
315 mend = start + segment['p_memsz']
316 rend = start + segment['p_filesz']
317 data_start = min(data_start, start)
318 data_end = max(data_end, rend)
319 mem_end = max(mem_end, mend)
321 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
323 output = bytearray(data_end - data_start)
324 for i in range(elf.num_segments()):
325 segment = elf.get_segment(i)
326 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
327 skipped = 1 # To make code-coverage see this line
329 start = segment['p_paddr']
332 offset = location - start
334 # A legal ELF file can have a program header with non-zero length
335 # but zero-length file size and a non-zero offset which, added
336 # together, are greater than input->size (i.e. the total file size).
337 # So we need to not even test in the case that p_filesz is zero.
338 # Note: All of this code is commented out since we don't have a test
340 size = segment['p_filesz']
343 #end = segment['p_offset'] + segment['p_filesz']
345 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
347 output[start - data_start:start - data_start + size] = (
348 segment.data()[offset:])
349 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
350 mem_end - data_start)
352 def UpdateFile(infile, outfile, start_sym, end_sym, insert):
353 tout.Notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
354 (outfile, len(insert), len(insert), start_sym, end_sym))
355 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
357 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
358 (start_sym, end_sym, len(syms),
359 ','.join(syms.keys())))
361 size = syms[end_sym].offset - syms[start_sym].offset
362 if len(insert) > size:
363 raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" %
364 (infile, len(insert), len(insert), size, size))
366 data = tools.read_file(infile)
367 newdata = data[:syms[start_sym].offset]
368 newdata += insert + tools.get_bytes(0, size - len(insert))
369 newdata += data[syms[end_sym].offset:]
370 tools.write_file(outfile, newdata)
371 tout.Info('Written to offset %#x' % syms[start_sym].offset)