blob: afa05e58fdd85e947e621fbd3b9d18b78d452f04 [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
Simon Glassa4e259e2021-11-03 21:09:16 -060028# 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
34# not known
35Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset'])
Simon Glass24ad3652017-11-13 18:54:54 -070036
Simon Glass567b6822019-07-08 13:18:35 -060037# 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
43ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
44
Simon Glass24ad3652017-11-13 18:54:54 -070045
46def GetSymbols(fname, patterns):
47 """Get the symbols from an ELF file
48
49 Args:
50 fname: Filename of the ELF file to read
51 patterns: List of regex patterns to search for, each a string
52
53 Returns:
54 None, if the file does not exist, or Dict:
55 key: Name of symbol
56 value: Hex value of symbol
57 """
Simon Glass80025522022-01-29 14:14:04 -070058 stdout = tools.run('objdump', '-t', fname)
Simon Glass24ad3652017-11-13 18:54:54 -070059 lines = stdout.splitlines()
60 if patterns:
61 re_syms = re.compile('|'.join(patterns))
62 else:
63 re_syms = None
64 syms = {}
65 syms_started = False
66 for line in lines:
67 if not line or not syms_started:
68 if 'SYMBOL TABLE' in line:
69 syms_started = True
70 line = None # Otherwise code coverage complains about 'continue'
71 continue
72 if re_syms and not re_syms.search(line):
73 continue
74
75 space_pos = line.find(' ')
76 value, rest = line[:space_pos], line[space_pos + 1:]
77 flags = rest[:7]
78 parts = rest[7:].split()
79 section, size = parts[:2]
80 if len(parts) > 2:
Simon Glassc29a85d2019-08-24 07:22:46 -060081 name = parts[2] if parts[2] != '.hidden' else parts[3]
Simon Glassa4e259e2021-11-03 21:09:16 -060082 syms[name] = Symbol(section, int(value, 16), int(size, 16),
83 flags[1] == 'w', None)
84
85 # Sort dict by address
86 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
87
Simon Glassbea8ef12022-03-04 08:42:59 -070088def _GetFileOffset(elf, addr):
89 """Get the file offset for an address
90
91 Args:
92 elf (ELFFile): ELF file to check
93 addr (int): Address to search for
94
95 Returns
96 int: Offset of that address in the ELF file, or None if not valid
97 """
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']
103
104def GetFileOffset(fname, addr):
105 """Get the file offset for an address
106
107 Args:
108 fname (str): Filename of ELF file to check
109 addr (int): Address to search for
110
111 Returns
112 int: Offset of that address in the ELF file, or None if not valid
113 """
114 if not ELF_TOOLS:
Simon Glassea64c022022-03-18 19:19:49 -0600115 raise ValueError("Python: No module named 'elftools'")
Simon Glassbea8ef12022-03-04 08:42:59 -0700116 with open(fname, 'rb') as fd:
117 elf = ELFFile(fd)
118 return _GetFileOffset(elf, addr)
119
120def GetSymbolFromAddress(fname, addr):
121 """Get the symbol at a particular address
122
123 Args:
124 fname (str): Filename of ELF file to check
125 addr (int): Address to search for
126
127 Returns:
128 str: Symbol name, or None if no symbol at that address
129 """
130 if not ELF_TOOLS:
Simon Glassea64c022022-03-18 19:19:49 -0600131 raise ValueError("Python: No module named 'elftools'")
Simon Glassbea8ef12022-03-04 08:42:59 -0700132 with open(fname, 'rb') as fd:
133 elf = ELFFile(fd)
134 syms = GetSymbols(fname, None)
135 for name, sym in syms.items():
136 if sym.address == addr:
137 return name
138
Simon Glassa4e259e2021-11-03 21:09:16 -0600139def GetSymbolFileOffset(fname, patterns):
140 """Get the symbols from an ELF file
141
142 Args:
143 fname: Filename of the ELF file to read
144 patterns: List of regex patterns to search for, each a string
145
146 Returns:
147 None, if the file does not exist, or Dict:
148 key: Name of symbol
149 value: Hex value of symbol
150 """
Simon Glassa4e259e2021-11-03 21:09:16 -0600151 if not ELF_TOOLS:
Simon Glassacc03752022-03-05 20:18:57 -0700152 raise ValueError("Python: No module named 'elftools'")
Simon Glassa4e259e2021-11-03 21:09:16 -0600153
154 syms = {}
155 with open(fname, 'rb') as fd:
156 elf = ELFFile(fd)
157
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))
Simon Glasse6854aa2018-07-17 13:25:24 -0600168
169 # Sort dict by address
Simon Glass5f3645b2019-05-14 15:53:41 -0600170 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
Simon Glass24ad3652017-11-13 18:54:54 -0700171
172def GetSymbolAddress(fname, sym_name):
173 """Get a value of a symbol from an ELF file
174
175 Args:
176 fname: Filename of the ELF file to read
177 patterns: List of regex patterns to search for, each a string
178
179 Returns:
180 Symbol value (as an integer) or None if not found
181 """
182 syms = GetSymbols(fname, [sym_name])
183 sym = syms.get(sym_name)
184 if not sym:
185 return None
186 return sym.address
Simon Glass4ca8e042017-11-13 18:55:01 -0700187
Simon Glass8a6f56e2018-06-01 09:38:13 -0600188def LookupAndWriteSymbols(elf_fname, entry, section):
Simon Glass4ca8e042017-11-13 18:55:01 -0700189 """Replace all symbols in an entry with their correct values
190
191 The entry contents is updated so that values for referenced symbols will be
Simon Glasse8561af2018-08-01 15:22:37 -0600192 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
194 structures.
Simon Glass4ca8e042017-11-13 18:55:01 -0700195
196 Args:
197 elf_fname: Filename of ELF image containing the symbol information for
198 entry
199 entry: Entry to process
Simon Glass8a6f56e2018-06-01 09:38:13 -0600200 section: Section which can be used to lookup symbol values
Simon Glass4ca8e042017-11-13 18:55:01 -0700201 """
Simon Glass80025522022-01-29 14:14:04 -0700202 fname = tools.get_input_filename(elf_fname)
Simon Glass4ca8e042017-11-13 18:55:01 -0700203 syms = GetSymbols(fname, ['image', 'binman'])
204 if not syms:
205 return
206 base = syms.get('__image_copy_start')
207 if not base:
208 return
Simon Glass5f3645b2019-05-14 15:53:41 -0600209 for name, sym in syms.items():
Simon Glass4ca8e042017-11-13 18:55:01 -0700210 if name.startswith('_binman'):
Simon Glass8a6f56e2018-06-01 09:38:13 -0600211 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
212 (section.GetPath(), name, entry.GetPath()))
Simon Glass4ca8e042017-11-13 18:55:01 -0700213 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))
218 if sym.size == 4:
219 pack_string = '<I'
220 elif sym.size == 8:
221 pack_string = '<Q'
222 else:
223 raise ValueError('%s has size %d: only 4 and 8 are supported' %
224 (msg, sym.size))
225
226 # Look up the symbol in our entry tables.
Simon Glassecbe4732021-01-06 21:35:15 -0700227 value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
228 base.address)
Simon Glass33778202019-10-20 21:31:34 -0600229 if value is None:
Simon Glass4ca8e042017-11-13 18:55:01 -0700230 value = -1
231 pack_string = pack_string.lower()
232 value_bytes = struct.pack(pack_string, value)
Simon Glass011f1b32022-01-29 14:14:15 -0700233 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
Simon Glassb6dff4c2019-07-20 12:23:36 -0600234 (msg, name, offset, value, len(value_bytes)))
Simon Glass4ca8e042017-11-13 18:55:01 -0700235 entry.data = (entry.data[:offset] + value_bytes +
236 entry.data[offset + sym.size:])
Simon Glass4f379ea2019-07-08 13:18:34 -0600237
238def MakeElf(elf_fname, text, data):
239 """Make an elf file with the given data in a single section
240
241 The output file has a several section including '.text' and '.data',
242 containing the info provided in arguments.
243
244 Args:
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
248 """
249 outdir = tempfile.mkdtemp(prefix='binman.elf.')
250 s_file = os.path.join(outdir, 'elf.S')
251
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
Simon Glassc27ee7c2020-11-08 20:36:19 -0700254 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]
Simon Glass4f379ea2019-07-08 13:18:34 -0600257 with open(s_file, 'w') as fd:
258 print('''/* Auto-generated C program to produce an ELF file for testing */
259
260.section .text
261.code32
262.globl _start
263.type _start, @function
264%s
265_start:
266%s
267.ident "comment"
268
269.comm fred,8,4
270
271.section .empty
272.globl _empty
273_empty:
274.byte 1
275
276.globl ernie
277.data
278.type ernie, @object
279.size ernie, 4
280ernie:
281%s
282''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
283 file=fd)
284 lds_file = os.path.join(outdir, 'elf.lds')
285
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 */
289
290PHDRS
291{
292 text PT_LOAD ;
293 data PT_LOAD ;
294 empty PT_LOAD FLAGS ( 6 ) ;
295 note PT_NOTE ;
296}
297
298SECTIONS
299{
300 . = 0xfef20000;
301 ENTRY(_start)
302 .text . : SUBALIGN(0)
303 {
304 *(.text)
305 } :text
306 .data : {
307 *(.data)
308 } :data
309 _bss_start = .;
310 .empty : {
311 *(.empty)
312 } :empty
Simon Glassd349ada2019-08-24 07:22:45 -0600313 /DISCARD/ : {
314 *(.note.gnu.property)
315 }
Simon Glass4f379ea2019-07-08 13:18:34 -0600316 .note : {
317 *(.comment)
318 } :note
319 .bss _bss_start (OVERLAY) : {
320 *(.bss)
321 }
322}
323''', file=fd)
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
Simon Glass80025522022-01-29 14:14:04 -0700330 cc, args = tools.get_target_compile_tool('cc')
Alper Nebi Yasak5cd321d2020-09-06 14:46:05 +0300331 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
332 lds_file, '-o', elf_fname, s_file]
Simon Glass840be732022-01-29 14:14:05 -0700333 stdout = command.output(cc, *args)
Simon Glass4f379ea2019-07-08 13:18:34 -0600334 shutil.rmtree(outdir)
Simon Glass567b6822019-07-08 13:18:35 -0600335
336def DecodeElf(data, location):
337 """Decode an ELF file and return information about it
338
339 Args:
340 data: Data from ELF file
341 location: Start address of data to return
342
343 Returns:
344 ElfInfo object containing information about the decoded ELF file
345 """
346 file_size = len(data)
347 with io.BytesIO(data) as fd:
348 elf = ELFFile(fd)
349 data_start = 0xffffffff;
350 data_end = 0;
351 mem_end = 0;
352 virt_to_phys = 0;
353
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
358 continue
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)
365 if not virt_to_phys:
366 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
367
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
373 continue
374 start = segment['p_paddr']
375 offset = 0
376 if start < location:
377 offset = location - start
378 start = location
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
384 # case for it.
385 size = segment['p_filesz']
386 #if not size:
387 #continue
388 #end = segment['p_offset'] + segment['p_filesz']
389 #if end > file_size:
390 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
391 #file_size, end)
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)
Simon Glassadfb8492021-11-03 21:09:18 -0600396
397def UpdateFile(infile, outfile, start_sym, end_sym, insert):
Simon Glass011f1b32022-01-29 14:14:15 -0700398 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
Simon Glassadfb8492021-11-03 21:09:18 -0600399 (outfile, len(insert), len(insert), start_sym, end_sym))
400 syms = GetSymbolFileOffset(infile, [start_sym, end_sym])
401 if len(syms) != 2:
402 raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" %
403 (start_sym, end_sym, len(syms),
404 ','.join(syms.keys())))
405
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))
410
Simon Glass80025522022-01-29 14:14:04 -0700411 data = tools.read_file(infile)
Simon Glassadfb8492021-11-03 21:09:18 -0600412 newdata = data[:syms[start_sym].offset]
Simon Glass80025522022-01-29 14:14:04 -0700413 newdata += insert + tools.get_bytes(0, size - len(insert))
Simon Glassadfb8492021-11-03 21:09:18 -0600414 newdata += data[syms[end_sym].offset:]
Simon Glass80025522022-01-29 14:14:04 -0700415 tools.write_file(outfile, newdata)
Simon Glass011f1b32022-01-29 14:14:15 -0700416 tout.info('Written to offset %#x' % syms[start_sym].offset)
Simon Glass571adc82022-02-08 11:49:55 -0700417
Simon Glassacc03752022-03-05 20:18:57 -0700418def read_loadable_segments(data):
Simon Glass571adc82022-02-08 11:49:55 -0700419 """Read segments from an ELF file
420
421 Args:
422 data (bytes): Contents of file
423
424 Returns:
425 tuple:
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
431
432 Raises:
433 ValueError: elftools is not available
434 """
435 if not ELF_TOOLS:
Simon Glassacc03752022-03-05 20:18:57 -0700436 raise ValueError("Python: No module named 'elftools'")
Simon Glass571adc82022-02-08 11:49:55 -0700437 with io.BytesIO(data) as inf:
438 try:
439 elf = ELFFile(inf)
440 except ELFError as err:
441 raise ValueError(err)
442 entry = elf.header['e_entry']
443 segments = []
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
448 continue
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