blob: 3cc8a3844950e3f9905c2308bffb698f7ff400e5 [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
Alper Nebi Yasak367ecbf2022-06-18 15:13:11 +030028# BSYM in little endian, keep in sync with include/binman_sym.h
29BINMAN_SYM_MAGIC_VALUE = 0x4d595342
30
Simon Glass056f0ef2021-11-03 21:09:16 -060031# 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
37# not known
38Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset'])
Simon Glassb50e5612017-11-13 18:54:54 -070039
Simon Glassd8d40742019-07-08 13:18:35 -060040# 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
46ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
47
Simon Glassb50e5612017-11-13 18:54:54 -070048
49def GetSymbols(fname, patterns):
50 """Get the symbols from an ELF file
51
52 Args:
53 fname: Filename of the ELF file to read
54 patterns: List of regex patterns to search for, each a string
55
56 Returns:
57 None, if the file does not exist, or Dict:
58 key: Name of symbol
59 value: Hex value of symbol
60 """
Simon Glassc1aa66e2022-01-29 14:14:04 -070061 stdout = tools.run('objdump', '-t', fname)
Simon Glassb50e5612017-11-13 18:54:54 -070062 lines = stdout.splitlines()
63 if patterns:
64 re_syms = re.compile('|'.join(patterns))
65 else:
66 re_syms = None
67 syms = {}
68 syms_started = False
69 for line in lines:
70 if not line or not syms_started:
71 if 'SYMBOL TABLE' in line:
72 syms_started = True
73 line = None # Otherwise code coverage complains about 'continue'
74 continue
75 if re_syms and not re_syms.search(line):
76 continue
77
78 space_pos = line.find(' ')
79 value, rest = line[:space_pos], line[space_pos + 1:]
80 flags = rest[:7]
81 parts = rest[7:].split()
82 section, size = parts[:2]
83 if len(parts) > 2:
Simon Glass39c8e472019-08-24 07:22:46 -060084 name = parts[2] if parts[2] != '.hidden' else parts[3]
Simon Glass056f0ef2021-11-03 21:09:16 -060085 syms[name] = Symbol(section, int(value, 16), int(size, 16),
86 flags[1] == 'w', None)
87
88 # Sort dict by address
89 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
90
Simon Glass64defba2022-03-04 08:42:59 -070091def _GetFileOffset(elf, addr):
92 """Get the file offset for an address
93
94 Args:
95 elf (ELFFile): ELF file to check
96 addr (int): Address to search for
97
98 Returns
99 int: Offset of that address in the ELF file, or None if not valid
100 """
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
107def GetFileOffset(fname, addr):
108 """Get the file offset for an address
109
110 Args:
111 fname (str): Filename of ELF file to check
112 addr (int): Address to search for
113
114 Returns
115 int: Offset of that address in the ELF file, or None if not valid
116 """
117 if not ELF_TOOLS:
Simon Glass40def8a2022-03-18 19:19:49 -0600118 raise ValueError("Python: No module named 'elftools'")
Simon Glass64defba2022-03-04 08:42:59 -0700119 with open(fname, 'rb') as fd:
120 elf = ELFFile(fd)
121 return _GetFileOffset(elf, addr)
122
123def GetSymbolFromAddress(fname, addr):
124 """Get the symbol at a particular address
125
126 Args:
127 fname (str): Filename of ELF file to check
128 addr (int): Address to search for
129
130 Returns:
131 str: Symbol name, or None if no symbol at that address
132 """
133 if not ELF_TOOLS:
Simon Glass40def8a2022-03-18 19:19:49 -0600134 raise ValueError("Python: No module named 'elftools'")
Simon Glass64defba2022-03-04 08:42:59 -0700135 with open(fname, 'rb') as fd:
136 elf = ELFFile(fd)
137 syms = GetSymbols(fname, None)
138 for name, sym in syms.items():
139 if sym.address == addr:
140 return name
141
Simon Glass056f0ef2021-11-03 21:09:16 -0600142def GetSymbolFileOffset(fname, patterns):
143 """Get the symbols from an ELF file
144
145 Args:
146 fname: Filename of the ELF file to read
147 patterns: List of regex patterns to search for, each a string
148
149 Returns:
150 None, if the file does not exist, or Dict:
151 key: Name of symbol
152 value: Hex value of symbol
153 """
Simon Glass056f0ef2021-11-03 21:09:16 -0600154 if not ELF_TOOLS:
Simon Glass17b4ffc2022-03-05 20:18:57 -0700155 raise ValueError("Python: No module named 'elftools'")
Simon Glass056f0ef2021-11-03 21:09:16 -0600156
157 syms = {}
158 with open(fname, 'rb') as fd:
159 elf = ELFFile(fd)
160
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))
Simon Glass46d61a22018-07-17 13:25:24 -0600171
172 # Sort dict by address
Simon Glass50979152019-05-14 15:53:41 -0600173 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
Simon Glassb50e5612017-11-13 18:54:54 -0700174
175def GetSymbolAddress(fname, sym_name):
176 """Get a value of a symbol from an ELF file
177
178 Args:
179 fname: Filename of the ELF file to read
180 patterns: List of regex patterns to search for, each a string
181
182 Returns:
183 Symbol value (as an integer) or None if not found
184 """
185 syms = GetSymbols(fname, [sym_name])
186 sym = syms.get(sym_name)
187 if not sym:
188 return None
189 return sym.address
Simon Glass19790632017-11-13 18:55:01 -0700190
Simon Glass28565792022-10-20 18:22:44 -0600191def GetPackString(sym, msg):
192 """Get the struct.pack/unpack string to use with a given symbol
193
194 Args:
195 sym (Symbol): Symbol to check. Only the size member is checked
196 @msg (str): String which indicates the entry being processed, used for
197 errors
198
199 Returns:
200 str: struct string to use, .e.g. '<I'
201
202 Raises:
203 ValueError: Symbol has an unexpected size
204 """
205 if sym.size == 4:
206 return '<I'
207 elif sym.size == 8:
208 return '<Q'
209 else:
210 raise ValueError('%s has size %d: only 4 and 8 are supported' %
211 (msg, sym.size))
212
Simon Glass571bc4e2023-01-11 16:10:19 -0700213def GetSymbolOffset(elf_fname, sym_name, base_sym=None):
214 """Read the offset of a symbol compared to base symbol
215
216 This is useful for obtaining the value of a single symbol relative to the
217 base of a binary blob.
218
219 Args:
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'
224
225 Returns:
226 int: Offset of the symbol relative to the base symbol
227 """
228 if not base_sym:
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
234 return val - base
235
Simon Glassc1157862023-01-11 16:10:17 -0700236def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False,
237 base_sym=None):
Simon Glass19790632017-11-13 18:55:01 -0700238 """Replace all symbols in an entry with their correct values
239
240 The entry contents is updated so that values for referenced symbols will be
Simon Glass3ab95982018-08-01 15:22:37 -0600241 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
243 structures.
Simon Glass19790632017-11-13 18:55:01 -0700244
245 Args:
246 elf_fname: Filename of ELF image containing the symbol information for
247 entry
248 entry: Entry to process
Simon Glassf55382b2018-06-01 09:38:13 -0600249 section: Section which can be used to lookup symbol values
Simon Glassc1157862023-01-11 16:10:17 -0700250 base_sym: Base symbol marking the start of the image
Simon Glass19790632017-11-13 18:55:01 -0700251 """
Simon Glassc1157862023-01-11 16:10:17 -0700252 if not base_sym:
253 base_sym = '__image_copy_start'
Simon Glassc1aa66e2022-01-29 14:14:04 -0700254 fname = tools.get_input_filename(elf_fname)
Simon Glass19790632017-11-13 18:55:01 -0700255 syms = GetSymbols(fname, ['image', 'binman'])
Simon Glassd2afb9e2022-10-20 18:22:47 -0600256 if is_elf:
257 if not ELF_TOOLS:
258 msg = ("Section '%s': entry '%s'" %
259 (section.GetPath(), entry.GetPath()))
260 raise ValueError(f'{msg}: Cannot write symbols to an ELF file without Python elftools')
261 new_syms = {}
262 with open(fname, 'rb') as fd:
263 elf = ELFFile(fd)
264 for name, sym in syms.items():
265 offset = _GetFileOffset(elf, sym.address)
266 new_syms[name] = Symbol(sym.section, sym.address, sym.size,
267 sym.weak, offset)
268 syms = new_syms
269
Simon Glass19790632017-11-13 18:55:01 -0700270 if not syms:
Simon Glassd2afb9e2022-10-20 18:22:47 -0600271 tout.debug('LookupAndWriteSymbols: no syms')
Simon Glass19790632017-11-13 18:55:01 -0700272 return
Simon Glassc1157862023-01-11 16:10:17 -0700273 base = syms.get(base_sym)
Simon Glassd2afb9e2022-10-20 18:22:47 -0600274 if not base and not is_elf:
275 tout.debug('LookupAndWriteSymbols: no base')
Simon Glass19790632017-11-13 18:55:01 -0700276 return
Simon Glassd2afb9e2022-10-20 18:22:47 -0600277 base_addr = 0 if is_elf else base.address
Simon Glass50979152019-05-14 15:53:41 -0600278 for name, sym in syms.items():
Simon Glass19790632017-11-13 18:55:01 -0700279 if name.startswith('_binman'):
Simon Glassf55382b2018-06-01 09:38:13 -0600280 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
281 (section.GetPath(), name, entry.GetPath()))
Simon Glassd2afb9e2022-10-20 18:22:47 -0600282 if is_elf:
283 # For ELF files, use the file offset
284 offset = sym.offset
285 else:
286 # For blobs use the offset of the symbol, calculated by
287 # subtracting the base address which by definition is at the
288 # start
289 offset = sym.address - base.address
290 if offset < 0 or offset + sym.size > entry.contents_size:
291 raise ValueError('%s has offset %x (size %x) but the contents '
292 'size is %x' % (entry.GetPath(), offset,
293 sym.size,
294 entry.contents_size))
Simon Glass28565792022-10-20 18:22:44 -0600295 pack_string = GetPackString(sym, msg)
Alper Nebi Yasak367ecbf2022-06-18 15:13:11 +0300296 if name == '_binman_sym_magic':
297 value = BINMAN_SYM_MAGIC_VALUE
298 else:
299 # Look up the symbol in our entry tables.
300 value = section.GetImage().LookupImageSymbol(name, sym.weak,
Simon Glassd2afb9e2022-10-20 18:22:47 -0600301 msg, base_addr)
Simon Glass15c981c2019-10-20 21:31:34 -0600302 if value is None:
Simon Glass19790632017-11-13 18:55:01 -0700303 value = -1
304 pack_string = pack_string.lower()
305 value_bytes = struct.pack(pack_string, value)
Simon Glassf3385a52022-01-29 14:14:15 -0700306 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
Simon Glass9f297b02019-07-20 12:23:36 -0600307 (msg, name, offset, value, len(value_bytes)))
Simon Glass19790632017-11-13 18:55:01 -0700308 entry.data = (entry.data[:offset] + value_bytes +
309 entry.data[offset + sym.size:])
Simon Glassf58558a2019-07-08 13:18:34 -0600310
Simon Glass28565792022-10-20 18:22:44 -0600311def GetSymbolValue(sym, data, msg):
312 """Get the value of a symbol
313
314 This can only be used on symbols with an integer value.
315
316 Args:
317 sym (Symbol): Symbol to check
318 data (butes): Data for the ELF file - the symbol data appears at offset
319 sym.offset
320 @msg (str): String which indicates the entry being processed, used for
321 errors
322
323 Returns:
324 int: Value of the symbol
325
326 Raises:
327 ValueError: Symbol has an unexpected size
328 """
329 pack_string = GetPackString(sym, msg)
330 value = struct.unpack(pack_string, data[sym.offset:sym.offset + sym.size])
331 return value[0]
332
Simon Glassf58558a2019-07-08 13:18:34 -0600333def MakeElf(elf_fname, text, data):
334 """Make an elf file with the given data in a single section
335
336 The output file has a several section including '.text' and '.data',
337 containing the info provided in arguments.
338
339 Args:
340 elf_fname: Output filename
341 text: Text (code) to put in the file's .text section
342 data: Data to put in the file's .data section
343 """
344 outdir = tempfile.mkdtemp(prefix='binman.elf.')
345 s_file = os.path.join(outdir, 'elf.S')
346
347 # Spilt the text into two parts so that we can make the entry point two
348 # bytes after the start of the text section
Simon Glass6a4ccad2020-11-08 20:36:19 -0700349 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
350 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
351 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
Simon Glassf58558a2019-07-08 13:18:34 -0600352 with open(s_file, 'w') as fd:
353 print('''/* Auto-generated C program to produce an ELF file for testing */
354
355.section .text
356.code32
357.globl _start
358.type _start, @function
359%s
360_start:
361%s
362.ident "comment"
363
364.comm fred,8,4
365
366.section .empty
367.globl _empty
368_empty:
369.byte 1
370
371.globl ernie
372.data
373.type ernie, @object
374.size ernie, 4
375ernie:
376%s
377''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
378 file=fd)
379 lds_file = os.path.join(outdir, 'elf.lds')
380
381 # Use a linker script to set the alignment and text address.
382 with open(lds_file, 'w') as fd:
383 print('''/* Auto-generated linker script to produce an ELF file for testing */
384
385PHDRS
386{
387 text PT_LOAD ;
388 data PT_LOAD ;
389 empty PT_LOAD FLAGS ( 6 ) ;
390 note PT_NOTE ;
391}
392
393SECTIONS
394{
395 . = 0xfef20000;
396 ENTRY(_start)
397 .text . : SUBALIGN(0)
398 {
399 *(.text)
400 } :text
401 .data : {
402 *(.data)
403 } :data
404 _bss_start = .;
405 .empty : {
406 *(.empty)
407 } :empty
Simon Glass9d44a7e2019-08-24 07:22:45 -0600408 /DISCARD/ : {
409 *(.note.gnu.property)
410 }
Simon Glassf58558a2019-07-08 13:18:34 -0600411 .note : {
412 *(.comment)
413 } :note
414 .bss _bss_start (OVERLAY) : {
415 *(.bss)
416 }
417}
418''', file=fd)
419 # -static: Avoid requiring any shared libraries
420 # -nostdlib: Don't link with C library
421 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
422 # text section at the start
423 # -m32: Build for 32-bit x86
424 # -T...: Specifies the link script, which sets the start address
Simon Glassc1aa66e2022-01-29 14:14:04 -0700425 cc, args = tools.get_target_compile_tool('cc')
Alper Nebi Yasak1e4687a2020-09-06 14:46:05 +0300426 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
427 lds_file, '-o', elf_fname, s_file]
Simon Glassd9800692022-01-29 14:14:05 -0700428 stdout = command.output(cc, *args)
Simon Glassf58558a2019-07-08 13:18:34 -0600429 shutil.rmtree(outdir)
Simon Glassd8d40742019-07-08 13:18:35 -0600430
431def DecodeElf(data, location):
432 """Decode an ELF file and return information about it
433
434 Args:
435 data: Data from ELF file
436 location: Start address of data to return
437
438 Returns:
439 ElfInfo object containing information about the decoded ELF file
440 """
441 file_size = len(data)
442 with io.BytesIO(data) as fd:
443 elf = ELFFile(fd)
444 data_start = 0xffffffff;
445 data_end = 0;
446 mem_end = 0;
447 virt_to_phys = 0;
448
449 for i in range(elf.num_segments()):
450 segment = elf.get_segment(i)
451 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
452 skipped = 1 # To make code-coverage see this line
453 continue
454 start = segment['p_paddr']
455 mend = start + segment['p_memsz']
456 rend = start + segment['p_filesz']
457 data_start = min(data_start, start)
458 data_end = max(data_end, rend)
459 mem_end = max(mem_end, mend)
460 if not virt_to_phys:
461 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
462
463 output = bytearray(data_end - data_start)
464 for i in range(elf.num_segments()):
465 segment = elf.get_segment(i)
466 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
467 skipped = 1 # To make code-coverage see this line
468 continue
469 start = segment['p_paddr']
470 offset = 0
471 if start < location:
472 offset = location - start
473 start = location
474 # A legal ELF file can have a program header with non-zero length
475 # but zero-length file size and a non-zero offset which, added
476 # together, are greater than input->size (i.e. the total file size).
477 # So we need to not even test in the case that p_filesz is zero.
478 # Note: All of this code is commented out since we don't have a test
479 # case for it.
480 size = segment['p_filesz']
481 #if not size:
482 #continue
483 #end = segment['p_offset'] + segment['p_filesz']
484 #if end > file_size:
485 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
486 #file_size, end)
487 output[start - data_start:start - data_start + size] = (
488 segment.data()[offset:])
489 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
490 mem_end - data_start)
Simon Glass0427bed2021-11-03 21:09:18 -0600491
492def UpdateFile(infile, outfile, start_sym, end_sym, insert):
Simon Glassf3385a52022-01-29 14:14:15 -0700493 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
Simon Glass0427bed2021-11-03 21:09:18 -0600494 (outfile, len(insert), len(insert), start_sym, end_sym))
495 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
496 if len(syms) != 2:
497 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
498 (start_sym, end_sym, len(syms),
499 ','.join(syms.keys())))
500
501 size = syms[end_sym].offset - syms[start_sym].offset
502 if len(insert) > size:
503 raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" %
504 (infile, len(insert), len(insert), size, size))
505
Simon Glassc1aa66e2022-01-29 14:14:04 -0700506 data = tools.read_file(infile)
Simon Glass0427bed2021-11-03 21:09:18 -0600507 newdata = data[:syms[start_sym].offset]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700508 newdata += insert + tools.get_bytes(0, size - len(insert))
Simon Glass0427bed2021-11-03 21:09:18 -0600509 newdata += data[syms[end_sym].offset:]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700510 tools.write_file(outfile, newdata)
Simon Glassf3385a52022-01-29 14:14:15 -0700511 tout.info('Written to offset %#x' % syms[start_sym].offset)
Simon Glass4d38dd72022-02-08 11:49:55 -0700512
Simon Glass17b4ffc2022-03-05 20:18:57 -0700513def read_loadable_segments(data):
Simon Glass4d38dd72022-02-08 11:49:55 -0700514 """Read segments from an ELF file
515
516 Args:
517 data (bytes): Contents of file
518
519 Returns:
520 tuple:
521 list of segments, each:
522 int: Segment number (0 = first)
523 int: Start address of segment in memory
524 bytes: Contents of segment
525 int: entry address for image
526
527 Raises:
528 ValueError: elftools is not available
529 """
530 if not ELF_TOOLS:
Simon Glass17b4ffc2022-03-05 20:18:57 -0700531 raise ValueError("Python: No module named 'elftools'")
Simon Glass4d38dd72022-02-08 11:49:55 -0700532 with io.BytesIO(data) as inf:
533 try:
534 elf = ELFFile(inf)
535 except ELFError as err:
536 raise ValueError(err)
537 entry = elf.header['e_entry']
538 segments = []
539 for i in range(elf.num_segments()):
540 segment = elf.get_segment(i)
541 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
542 skipped = 1 # To make code-coverage see this line
543 continue
544 start = segment['p_offset']
545 rend = start + segment['p_filesz']
546 segments.append((i, segment['p_paddr'], data[start:rend]))
547 return segments, entry
Simon Glass39f4a852023-01-07 14:07:13 -0700548
549def is_valid(data):
550 """Check if some binary data is a valid ELF file
551
552 Args:
553 data (bytes): Bytes to check
554
555 Returns:
556 bool: True if a valid Elf file, False if not
557 """
558 try:
559 DecodeElf(data, 0)
560 return True
561 except ELFError:
562 return False