blob: fec2116880be09fd993fd6f30528b3326a3a0c81 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass24ad3652017-11-13 18:54:54 -07002# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glass24ad3652017-11-13 18:54:54 -07005# Handle various things related to ELF images
6#
7
8from collections import namedtuple, OrderedDict
Simon Glass567b6822019-07-08 13:18:35 -06009import io
Simon Glass24ad3652017-11-13 18:54:54 -070010import os
11import re
Simon Glass4f379ea2019-07-08 13:18:34 -060012import shutil
Simon Glass24ad3652017-11-13 18:54:54 -070013import struct
Simon Glass4f379ea2019-07-08 13:18:34 -060014import tempfile
Simon Glass24ad3652017-11-13 18:54:54 -070015
Simon Glassa997ea52020-04-17 18:09:04 -060016from patman import command
17from patman import tools
18from patman import tout
Simon Glass24ad3652017-11-13 18:54:54 -070019
Simon Glass567b6822019-07-08 13:18:35 -060020ELF_TOOLS = True
21try:
22 from elftools.elf.elffile import ELFFile
Simon Glass571adc82022-02-08 11:49:55 -070023 from elftools.elf.elffile import ELFError
Simon Glass567b6822019-07-08 13:18:35 -060024 from elftools.elf.sections import SymbolTableSection
25except: # pragma: no cover
26 ELF_TOOLS = False
27
Alper Nebi Yasak9634dc92022-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 Glassa4e259e2021-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 Glass24ad3652017-11-13 18:54:54 -070039
Simon Glass567b6822019-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 Glass24ad3652017-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 Glass80025522022-01-29 14:14:04 -070061 stdout = tools.run('objdump', '-t', fname)
Simon Glass24ad3652017-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 Glassc29a85d2019-08-24 07:22:46 -060084 name = parts[2] if parts[2] != '.hidden' else parts[3]
Simon Glassa4e259e2021-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 Glassbea8ef12022-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 Glassea64c022022-03-18 19:19:49 -0600118 raise ValueError("Python: No module named 'elftools'")
Simon Glassbea8ef12022-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 Glassea64c022022-03-18 19:19:49 -0600134 raise ValueError("Python: No module named 'elftools'")
Simon Glassbea8ef12022-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 Glassa4e259e2021-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 Glassa4e259e2021-11-03 21:09:16 -0600154 if not ELF_TOOLS:
Simon Glassacc03752022-03-05 20:18:57 -0700155 raise ValueError("Python: No module named 'elftools'")
Simon Glassa4e259e2021-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 Glasse6854aa2018-07-17 13:25:24 -0600171
172 # Sort dict by address
Simon Glass5f3645b2019-05-14 15:53:41 -0600173 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
Simon Glass24ad3652017-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 Glass4ca8e042017-11-13 18:55:01 -0700190
Simon Glasscb452b02022-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 Glass8a6f56e2018-06-01 09:38:13 -0600213def LookupAndWriteSymbols(elf_fname, entry, section):
Simon Glass4ca8e042017-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 Glasse8561af2018-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 Glass4ca8e042017-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 Glass8a6f56e2018-06-01 09:38:13 -0600225 section: Section which can be used to lookup symbol values
Simon Glass4ca8e042017-11-13 18:55:01 -0700226 """
Simon Glass80025522022-01-29 14:14:04 -0700227 fname = tools.get_input_filename(elf_fname)
Simon Glass4ca8e042017-11-13 18:55:01 -0700228 syms = GetSymbols(fname, ['image', 'binman'])
229 if not syms:
230 return
231 base = syms.get('__image_copy_start')
232 if not base:
233 return
Simon Glass5f3645b2019-05-14 15:53:41 -0600234 for name, sym in syms.items():
Simon Glass4ca8e042017-11-13 18:55:01 -0700235 if name.startswith('_binman'):
Simon Glass8a6f56e2018-06-01 09:38:13 -0600236 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
237 (section.GetPath(), name, entry.GetPath()))
Simon Glass4ca8e042017-11-13 18:55:01 -0700238 offset = sym.address - base.address
239 if offset < 0 or offset + sym.size > entry.contents_size:
240 raise ValueError('%s has offset %x (size %x) but the contents '
241 'size is %x' % (entry.GetPath(), offset,
242 sym.size, entry.contents_size))
Simon Glasscb452b02022-10-20 18:22:44 -0600243 pack_string = GetPackString(sym, msg)
Alper Nebi Yasak9634dc92022-06-18 15:13:11 +0300244 if name == '_binman_sym_magic':
245 value = BINMAN_SYM_MAGIC_VALUE
246 else:
247 # Look up the symbol in our entry tables.
248 value = section.GetImage().LookupImageSymbol(name, sym.weak,
249 msg, base.address)
Simon Glass33778202019-10-20 21:31:34 -0600250 if value is None:
Simon Glass4ca8e042017-11-13 18:55:01 -0700251 value = -1
252 pack_string = pack_string.lower()
253 value_bytes = struct.pack(pack_string, value)
Simon Glass011f1b32022-01-29 14:14:15 -0700254 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
Simon Glassb6dff4c2019-07-20 12:23:36 -0600255 (msg, name, offset, value, len(value_bytes)))
Simon Glass4ca8e042017-11-13 18:55:01 -0700256 entry.data = (entry.data[:offset] + value_bytes +
257 entry.data[offset + sym.size:])
Simon Glass4f379ea2019-07-08 13:18:34 -0600258
Simon Glasscb452b02022-10-20 18:22:44 -0600259def GetSymbolValue(sym, data, msg):
260 """Get the value of a symbol
261
262 This can only be used on symbols with an integer value.
263
264 Args:
265 sym (Symbol): Symbol to check
266 data (butes): Data for the ELF file - the symbol data appears at offset
267 sym.offset
268 @msg (str): String which indicates the entry being processed, used for
269 errors
270
271 Returns:
272 int: Value of the symbol
273
274 Raises:
275 ValueError: Symbol has an unexpected size
276 """
277 pack_string = GetPackString(sym, msg)
278 value = struct.unpack(pack_string, data[sym.offset:sym.offset + sym.size])
279 return value[0]
280
Simon Glass4f379ea2019-07-08 13:18:34 -0600281def MakeElf(elf_fname, text, data):
282 """Make an elf file with the given data in a single section
283
284 The output file has a several section including '.text' and '.data',
285 containing the info provided in arguments.
286
287 Args:
288 elf_fname: Output filename
289 text: Text (code) to put in the file's .text section
290 data: Data to put in the file's .data section
291 """
292 outdir = tempfile.mkdtemp(prefix='binman.elf.')
293 s_file = os.path.join(outdir, 'elf.S')
294
295 # Spilt the text into two parts so that we can make the entry point two
296 # bytes after the start of the text section
Simon Glassc27ee7c2020-11-08 20:36:19 -0700297 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
298 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
299 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
Simon Glass4f379ea2019-07-08 13:18:34 -0600300 with open(s_file, 'w') as fd:
301 print('''/* Auto-generated C program to produce an ELF file for testing */
302
303.section .text
304.code32
305.globl _start
306.type _start, @function
307%s
308_start:
309%s
310.ident "comment"
311
312.comm fred,8,4
313
314.section .empty
315.globl _empty
316_empty:
317.byte 1
318
319.globl ernie
320.data
321.type ernie, @object
322.size ernie, 4
323ernie:
324%s
325''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
326 file=fd)
327 lds_file = os.path.join(outdir, 'elf.lds')
328
329 # Use a linker script to set the alignment and text address.
330 with open(lds_file, 'w') as fd:
331 print('''/* Auto-generated linker script to produce an ELF file for testing */
332
333PHDRS
334{
335 text PT_LOAD ;
336 data PT_LOAD ;
337 empty PT_LOAD FLAGS ( 6 ) ;
338 note PT_NOTE ;
339}
340
341SECTIONS
342{
343 . = 0xfef20000;
344 ENTRY(_start)
345 .text . : SUBALIGN(0)
346 {
347 *(.text)
348 } :text
349 .data : {
350 *(.data)
351 } :data
352 _bss_start = .;
353 .empty : {
354 *(.empty)
355 } :empty
Simon Glassd349ada2019-08-24 07:22:45 -0600356 /DISCARD/ : {
357 *(.note.gnu.property)
358 }
Simon Glass4f379ea2019-07-08 13:18:34 -0600359 .note : {
360 *(.comment)
361 } :note
362 .bss _bss_start (OVERLAY) : {
363 *(.bss)
364 }
365}
366''', file=fd)
367 # -static: Avoid requiring any shared libraries
368 # -nostdlib: Don't link with C library
369 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
370 # text section at the start
371 # -m32: Build for 32-bit x86
372 # -T...: Specifies the link script, which sets the start address
Simon Glass80025522022-01-29 14:14:04 -0700373 cc, args = tools.get_target_compile_tool('cc')
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300374 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
375 lds_file, '-o', elf_fname, s_file]
Simon Glass840be732022-01-29 14:14:05 -0700376 stdout = command.output(cc, *args)
Simon Glass4f379ea2019-07-08 13:18:34 -0600377 shutil.rmtree(outdir)
Simon Glass567b6822019-07-08 13:18:35 -0600378
379def DecodeElf(data, location):
380 """Decode an ELF file and return information about it
381
382 Args:
383 data: Data from ELF file
384 location: Start address of data to return
385
386 Returns:
387 ElfInfo object containing information about the decoded ELF file
388 """
389 file_size = len(data)
390 with io.BytesIO(data) as fd:
391 elf = ELFFile(fd)
392 data_start = 0xffffffff;
393 data_end = 0;
394 mem_end = 0;
395 virt_to_phys = 0;
396
397 for i in range(elf.num_segments()):
398 segment = elf.get_segment(i)
399 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
400 skipped = 1 # To make code-coverage see this line
401 continue
402 start = segment['p_paddr']
403 mend = start + segment['p_memsz']
404 rend = start + segment['p_filesz']
405 data_start = min(data_start, start)
406 data_end = max(data_end, rend)
407 mem_end = max(mem_end, mend)
408 if not virt_to_phys:
409 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
410
411 output = bytearray(data_end - data_start)
412 for i in range(elf.num_segments()):
413 segment = elf.get_segment(i)
414 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
415 skipped = 1 # To make code-coverage see this line
416 continue
417 start = segment['p_paddr']
418 offset = 0
419 if start < location:
420 offset = location - start
421 start = location
422 # A legal ELF file can have a program header with non-zero length
423 # but zero-length file size and a non-zero offset which, added
424 # together, are greater than input->size (i.e. the total file size).
425 # So we need to not even test in the case that p_filesz is zero.
426 # Note: All of this code is commented out since we don't have a test
427 # case for it.
428 size = segment['p_filesz']
429 #if not size:
430 #continue
431 #end = segment['p_offset'] + segment['p_filesz']
432 #if end > file_size:
433 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
434 #file_size, end)
435 output[start - data_start:start - data_start + size] = (
436 segment.data()[offset:])
437 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
438 mem_end - data_start)
Simon Glassadfb8492021-11-03 21:09:18 -0600439
440def UpdateFile(infile, outfile, start_sym, end_sym, insert):
Simon Glass011f1b32022-01-29 14:14:15 -0700441 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
Simon Glassadfb8492021-11-03 21:09:18 -0600442 (outfile, len(insert), len(insert), start_sym, end_sym))
443 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
444 if len(syms) != 2:
445 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
446 (start_sym, end_sym, len(syms),
447 ','.join(syms.keys())))
448
449 size = syms[end_sym].offset - syms[start_sym].offset
450 if len(insert) > size:
451 raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" %
452 (infile, len(insert), len(insert), size, size))
453
Simon Glass80025522022-01-29 14:14:04 -0700454 data = tools.read_file(infile)
Simon Glassadfb8492021-11-03 21:09:18 -0600455 newdata = data[:syms[start_sym].offset]
Simon Glass80025522022-01-29 14:14:04 -0700456 newdata += insert + tools.get_bytes(0, size - len(insert))
Simon Glassadfb8492021-11-03 21:09:18 -0600457 newdata += data[syms[end_sym].offset:]
Simon Glass80025522022-01-29 14:14:04 -0700458 tools.write_file(outfile, newdata)
Simon Glass011f1b32022-01-29 14:14:15 -0700459 tout.info('Written to offset %#x' % syms[start_sym].offset)
Simon Glass571adc82022-02-08 11:49:55 -0700460
Simon Glassacc03752022-03-05 20:18:57 -0700461def read_loadable_segments(data):
Simon Glass571adc82022-02-08 11:49:55 -0700462 """Read segments from an ELF file
463
464 Args:
465 data (bytes): Contents of file
466
467 Returns:
468 tuple:
469 list of segments, each:
470 int: Segment number (0 = first)
471 int: Start address of segment in memory
472 bytes: Contents of segment
473 int: entry address for image
474
475 Raises:
476 ValueError: elftools is not available
477 """
478 if not ELF_TOOLS:
Simon Glassacc03752022-03-05 20:18:57 -0700479 raise ValueError("Python: No module named 'elftools'")
Simon Glass571adc82022-02-08 11:49:55 -0700480 with io.BytesIO(data) as inf:
481 try:
482 elf = ELFFile(inf)
483 except ELFError as err:
484 raise ValueError(err)
485 entry = elf.header['e_entry']
486 segments = []
487 for i in range(elf.num_segments()):
488 segment = elf.get_segment(i)
489 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
490 skipped = 1 # To make code-coverage see this line
491 continue
492 start = segment['p_offset']
493 rend = start + segment['p_filesz']
494 segments.append((i, segment['p_paddr'], data[start:rend]))
495 return segments, entry