blob: 73f318b81d21bbfd7f3a8d4e8b4a965e307aa6ae [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 Glassd2afb9e2022-10-20 18:22:47 -0600213def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False):
Simon Glass19790632017-11-13 18:55:01 -0700214 """Replace all symbols in an entry with their correct values
215
216 The entry contents is updated so that values for referenced symbols will be
Simon Glass3ab95982018-08-01 15:22:37 -0600217 visible at run time. This is done by finding out the symbols offsets in the
218 entry (using the ELF file) and replacing them with values from binman's data
219 structures.
Simon Glass19790632017-11-13 18:55:01 -0700220
221 Args:
222 elf_fname: Filename of ELF image containing the symbol information for
223 entry
224 entry: Entry to process
Simon Glassf55382b2018-06-01 09:38:13 -0600225 section: Section which can be used to lookup symbol values
Simon Glass19790632017-11-13 18:55:01 -0700226 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700227 fname = tools.get_input_filename(elf_fname)
Simon Glass19790632017-11-13 18:55:01 -0700228 syms = GetSymbols(fname, ['image', 'binman'])
Simon Glassd2afb9e2022-10-20 18:22:47 -0600229 if is_elf:
230 if not ELF_TOOLS:
231 msg = ("Section '%s': entry '%s'" %
232 (section.GetPath(), entry.GetPath()))
233 raise ValueError(f'{msg}: Cannot write symbols to an ELF file without Python elftools')
234 new_syms = {}
235 with open(fname, 'rb') as fd:
236 elf = ELFFile(fd)
237 for name, sym in syms.items():
238 offset = _GetFileOffset(elf, sym.address)
239 new_syms[name] = Symbol(sym.section, sym.address, sym.size,
240 sym.weak, offset)
241 syms = new_syms
242
Simon Glass19790632017-11-13 18:55:01 -0700243 if not syms:
Simon Glassd2afb9e2022-10-20 18:22:47 -0600244 tout.debug('LookupAndWriteSymbols: no syms')
Simon Glass19790632017-11-13 18:55:01 -0700245 return
246 base = syms.get('__image_copy_start')
Simon Glassd2afb9e2022-10-20 18:22:47 -0600247 if not base and not is_elf:
248 tout.debug('LookupAndWriteSymbols: no base')
Simon Glass19790632017-11-13 18:55:01 -0700249 return
Simon Glassd2afb9e2022-10-20 18:22:47 -0600250 base_addr = 0 if is_elf else base.address
Simon Glass50979152019-05-14 15:53:41 -0600251 for name, sym in syms.items():
Simon Glass19790632017-11-13 18:55:01 -0700252 if name.startswith('_binman'):
Simon Glassf55382b2018-06-01 09:38:13 -0600253 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
254 (section.GetPath(), name, entry.GetPath()))
Simon Glassd2afb9e2022-10-20 18:22:47 -0600255 if is_elf:
256 # For ELF files, use the file offset
257 offset = sym.offset
258 else:
259 # For blobs use the offset of the symbol, calculated by
260 # subtracting the base address which by definition is at the
261 # start
262 offset = sym.address - base.address
263 if offset < 0 or offset + sym.size > entry.contents_size:
264 raise ValueError('%s has offset %x (size %x) but the contents '
265 'size is %x' % (entry.GetPath(), offset,
266 sym.size,
267 entry.contents_size))
Simon Glass28565792022-10-20 18:22:44 -0600268 pack_string = GetPackString(sym, msg)
Alper Nebi Yasak367ecbf2022-06-18 15:13:11 +0300269 if name == '_binman_sym_magic':
270 value = BINMAN_SYM_MAGIC_VALUE
271 else:
272 # Look up the symbol in our entry tables.
273 value = section.GetImage().LookupImageSymbol(name, sym.weak,
Simon Glassd2afb9e2022-10-20 18:22:47 -0600274 msg, base_addr)
Simon Glass15c981c2019-10-20 21:31:34 -0600275 if value is None:
Simon Glass19790632017-11-13 18:55:01 -0700276 value = -1
277 pack_string = pack_string.lower()
278 value_bytes = struct.pack(pack_string, value)
Simon Glassf3385a52022-01-29 14:14:15 -0700279 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
Simon Glass9f297b02019-07-20 12:23:36 -0600280 (msg, name, offset, value, len(value_bytes)))
Simon Glass19790632017-11-13 18:55:01 -0700281 entry.data = (entry.data[:offset] + value_bytes +
282 entry.data[offset + sym.size:])
Simon Glassf58558a2019-07-08 13:18:34 -0600283
Simon Glass28565792022-10-20 18:22:44 -0600284def GetSymbolValue(sym, data, msg):
285 """Get the value of a symbol
286
287 This can only be used on symbols with an integer value.
288
289 Args:
290 sym (Symbol): Symbol to check
291 data (butes): Data for the ELF file - the symbol data appears at offset
292 sym.offset
293 @msg (str): String which indicates the entry being processed, used for
294 errors
295
296 Returns:
297 int: Value of the symbol
298
299 Raises:
300 ValueError: Symbol has an unexpected size
301 """
302 pack_string = GetPackString(sym, msg)
303 value = struct.unpack(pack_string, data[sym.offset:sym.offset + sym.size])
304 return value[0]
305
Simon Glassf58558a2019-07-08 13:18:34 -0600306def MakeElf(elf_fname, text, data):
307 """Make an elf file with the given data in a single section
308
309 The output file has a several section including '.text' and '.data',
310 containing the info provided in arguments.
311
312 Args:
313 elf_fname: Output filename
314 text: Text (code) to put in the file's .text section
315 data: Data to put in the file's .data section
316 """
317 outdir = tempfile.mkdtemp(prefix='binman.elf.')
318 s_file = os.path.join(outdir, 'elf.S')
319
320 # Spilt the text into two parts so that we can make the entry point two
321 # bytes after the start of the text section
Simon Glass6a4ccad2020-11-08 20:36:19 -0700322 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
323 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
324 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
Simon Glassf58558a2019-07-08 13:18:34 -0600325 with open(s_file, 'w') as fd:
326 print('''/* Auto-generated C program to produce an ELF file for testing */
327
328.section .text
329.code32
330.globl _start
331.type _start, @function
332%s
333_start:
334%s
335.ident "comment"
336
337.comm fred,8,4
338
339.section .empty
340.globl _empty
341_empty:
342.byte 1
343
344.globl ernie
345.data
346.type ernie, @object
347.size ernie, 4
348ernie:
349%s
350''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
351 file=fd)
352 lds_file = os.path.join(outdir, 'elf.lds')
353
354 # Use a linker script to set the alignment and text address.
355 with open(lds_file, 'w') as fd:
356 print('''/* Auto-generated linker script to produce an ELF file for testing */
357
358PHDRS
359{
360 text PT_LOAD ;
361 data PT_LOAD ;
362 empty PT_LOAD FLAGS ( 6 ) ;
363 note PT_NOTE ;
364}
365
366SECTIONS
367{
368 . = 0xfef20000;
369 ENTRY(_start)
370 .text . : SUBALIGN(0)
371 {
372 *(.text)
373 } :text
374 .data : {
375 *(.data)
376 } :data
377 _bss_start = .;
378 .empty : {
379 *(.empty)
380 } :empty
Simon Glass9d44a7e2019-08-24 07:22:45 -0600381 /DISCARD/ : {
382 *(.note.gnu.property)
383 }
Simon Glassf58558a2019-07-08 13:18:34 -0600384 .note : {
385 *(.comment)
386 } :note
387 .bss _bss_start (OVERLAY) : {
388 *(.bss)
389 }
390}
391''', file=fd)
392 # -static: Avoid requiring any shared libraries
393 # -nostdlib: Don't link with C library
394 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
395 # text section at the start
396 # -m32: Build for 32-bit x86
397 # -T...: Specifies the link script, which sets the start address
Simon Glassc1aa66e2022-01-29 14:14:04 -0700398 cc, args = tools.get_target_compile_tool('cc')
Alper Nebi Yasak1e4687a2020-09-06 14:46:05 +0300399 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
400 lds_file, '-o', elf_fname, s_file]
Simon Glassd9800692022-01-29 14:14:05 -0700401 stdout = command.output(cc, *args)
Simon Glassf58558a2019-07-08 13:18:34 -0600402 shutil.rmtree(outdir)
Simon Glassd8d40742019-07-08 13:18:35 -0600403
404def DecodeElf(data, location):
405 """Decode an ELF file and return information about it
406
407 Args:
408 data: Data from ELF file
409 location: Start address of data to return
410
411 Returns:
412 ElfInfo object containing information about the decoded ELF file
413 """
414 file_size = len(data)
415 with io.BytesIO(data) as fd:
416 elf = ELFFile(fd)
417 data_start = 0xffffffff;
418 data_end = 0;
419 mem_end = 0;
420 virt_to_phys = 0;
421
422 for i in range(elf.num_segments()):
423 segment = elf.get_segment(i)
424 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
425 skipped = 1 # To make code-coverage see this line
426 continue
427 start = segment['p_paddr']
428 mend = start + segment['p_memsz']
429 rend = start + segment['p_filesz']
430 data_start = min(data_start, start)
431 data_end = max(data_end, rend)
432 mem_end = max(mem_end, mend)
433 if not virt_to_phys:
434 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
435
436 output = bytearray(data_end - data_start)
437 for i in range(elf.num_segments()):
438 segment = elf.get_segment(i)
439 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
440 skipped = 1 # To make code-coverage see this line
441 continue
442 start = segment['p_paddr']
443 offset = 0
444 if start < location:
445 offset = location - start
446 start = location
447 # A legal ELF file can have a program header with non-zero length
448 # but zero-length file size and a non-zero offset which, added
449 # together, are greater than input->size (i.e. the total file size).
450 # So we need to not even test in the case that p_filesz is zero.
451 # Note: All of this code is commented out since we don't have a test
452 # case for it.
453 size = segment['p_filesz']
454 #if not size:
455 #continue
456 #end = segment['p_offset'] + segment['p_filesz']
457 #if end > file_size:
458 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
459 #file_size, end)
460 output[start - data_start:start - data_start + size] = (
461 segment.data()[offset:])
462 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
463 mem_end - data_start)
Simon Glass0427bed2021-11-03 21:09:18 -0600464
465def UpdateFile(infile, outfile, start_sym, end_sym, insert):
Simon Glassf3385a52022-01-29 14:14:15 -0700466 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
Simon Glass0427bed2021-11-03 21:09:18 -0600467 (outfile, len(insert), len(insert), start_sym, end_sym))
468 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
469 if len(syms) != 2:
470 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
471 (start_sym, end_sym, len(syms),
472 ','.join(syms.keys())))
473
474 size = syms[end_sym].offset - syms[start_sym].offset
475 if len(insert) > size:
476 raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" %
477 (infile, len(insert), len(insert), size, size))
478
Simon Glassc1aa66e2022-01-29 14:14:04 -0700479 data = tools.read_file(infile)
Simon Glass0427bed2021-11-03 21:09:18 -0600480 newdata = data[:syms[start_sym].offset]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700481 newdata += insert + tools.get_bytes(0, size - len(insert))
Simon Glass0427bed2021-11-03 21:09:18 -0600482 newdata += data[syms[end_sym].offset:]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700483 tools.write_file(outfile, newdata)
Simon Glassf3385a52022-01-29 14:14:15 -0700484 tout.info('Written to offset %#x' % syms[start_sym].offset)
Simon Glass4d38dd72022-02-08 11:49:55 -0700485
Simon Glass17b4ffc2022-03-05 20:18:57 -0700486def read_loadable_segments(data):
Simon Glass4d38dd72022-02-08 11:49:55 -0700487 """Read segments from an ELF file
488
489 Args:
490 data (bytes): Contents of file
491
492 Returns:
493 tuple:
494 list of segments, each:
495 int: Segment number (0 = first)
496 int: Start address of segment in memory
497 bytes: Contents of segment
498 int: entry address for image
499
500 Raises:
501 ValueError: elftools is not available
502 """
503 if not ELF_TOOLS:
Simon Glass17b4ffc2022-03-05 20:18:57 -0700504 raise ValueError("Python: No module named 'elftools'")
Simon Glass4d38dd72022-02-08 11:49:55 -0700505 with io.BytesIO(data) as inf:
506 try:
507 elf = ELFFile(inf)
508 except ELFError as err:
509 raise ValueError(err)
510 entry = elf.header['e_entry']
511 segments = []
512 for i in range(elf.num_segments()):
513 segment = elf.get_segment(i)
514 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
515 skipped = 1 # To make code-coverage see this line
516 continue
517 start = segment['p_offset']
518 rend = start + segment['p_filesz']
519 segments.append((i, segment['p_paddr'], data[start:rend]))
520 return segments, entry
Simon Glass39f4a852023-01-07 14:07:13 -0700521
522def is_valid(data):
523 """Check if some binary data is a valid ELF file
524
525 Args:
526 data (bytes): Bytes to check
527
528 Returns:
529 bool: True if a valid Elf file, False if not
530 """
531 try:
532 DecodeElf(data, 0)
533 return True
534 except ELFError:
535 return False