blob: afa05e58fdd85e947e621fbd3b9d18b78d452f04 [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
Simon Glass056f0ef2021-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 Glassb50e5612017-11-13 18:54:54 -070036
Simon Glassd8d40742019-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 Glassb50e5612017-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 Glassc1aa66e2022-01-29 14:14:04 -070058 stdout = tools.run('objdump', '-t', fname)
Simon Glassb50e5612017-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 Glass39c8e472019-08-24 07:22:46 -060081 name = parts[2] if parts[2] != '.hidden' else parts[3]
Simon Glass056f0ef2021-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 Glass64defba2022-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 Glass40def8a2022-03-18 19:19:49 -0600115 raise ValueError("Python: No module named 'elftools'")
Simon Glass64defba2022-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 Glass40def8a2022-03-18 19:19:49 -0600131 raise ValueError("Python: No module named 'elftools'")
Simon Glass64defba2022-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 Glass056f0ef2021-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 Glass056f0ef2021-11-03 21:09:16 -0600151 if not ELF_TOOLS:
Simon Glass17b4ffc2022-03-05 20:18:57 -0700152 raise ValueError("Python: No module named 'elftools'")
Simon Glass056f0ef2021-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 Glass46d61a22018-07-17 13:25:24 -0600168
169 # Sort dict by address
Simon Glass50979152019-05-14 15:53:41 -0600170 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
Simon Glassb50e5612017-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 Glass19790632017-11-13 18:55:01 -0700187
Simon Glassf55382b2018-06-01 09:38:13 -0600188def LookupAndWriteSymbols(elf_fname, entry, section):
Simon Glass19790632017-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 Glass3ab95982018-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 Glass19790632017-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 Glassf55382b2018-06-01 09:38:13 -0600200 section: Section which can be used to lookup symbol values
Simon Glass19790632017-11-13 18:55:01 -0700201 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700202 fname = tools.get_input_filename(elf_fname)
Simon Glass19790632017-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 Glass50979152019-05-14 15:53:41 -0600209 for name, sym in syms.items():
Simon Glass19790632017-11-13 18:55:01 -0700210 if name.startswith('_binman'):
Simon Glassf55382b2018-06-01 09:38:13 -0600211 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
212 (section.GetPath(), name, entry.GetPath()))
Simon Glass19790632017-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 Glass870a9ea2021-01-06 21:35:15 -0700227 value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
228 base.address)
Simon Glass15c981c2019-10-20 21:31:34 -0600229 if value is None:
Simon Glass19790632017-11-13 18:55:01 -0700230 value = -1
231 pack_string = pack_string.lower()
232 value_bytes = struct.pack(pack_string, value)
Simon Glassf3385a52022-01-29 14:14:15 -0700233 tout.debug('%s:\n insert %s, offset %x, value %x, length %d' %
Simon Glass9f297b02019-07-20 12:23:36 -0600234 (msg, name, offset, value, len(value_bytes)))
Simon Glass19790632017-11-13 18:55:01 -0700235 entry.data = (entry.data[:offset] + value_bytes +
236 entry.data[offset + sym.size:])
Simon Glassf58558a2019-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 Glass6a4ccad2020-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 Glassf58558a2019-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 Glass9d44a7e2019-08-24 07:22:45 -0600313 /DISCARD/ : {
314 *(.note.gnu.property)
315 }
Simon Glassf58558a2019-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 Glassc1aa66e2022-01-29 14:14:04 -0700330 cc, args = tools.get_target_compile_tool('cc')
Alper Nebi Yasak1e4687a2020-09-06 14:46:05 +0300331 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
332 lds_file, '-o', elf_fname, s_file]
Simon Glassd9800692022-01-29 14:14:05 -0700333 stdout = command.output(cc, *args)
Simon Glassf58558a2019-07-08 13:18:34 -0600334 shutil.rmtree(outdir)
Simon Glassd8d40742019-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 Glass0427bed2021-11-03 21:09:18 -0600396
397def UpdateFile(infile, outfile, start_sym, end_sym, insert):
Simon Glassf3385a52022-01-29 14:14:15 -0700398 tout.notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" %
Simon Glass0427bed2021-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 Glassc1aa66e2022-01-29 14:14:04 -0700411 data = tools.read_file(infile)
Simon Glass0427bed2021-11-03 21:09:18 -0600412 newdata = data[:syms[start_sym].offset]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700413 newdata += insert + tools.get_bytes(0, size - len(insert))
Simon Glass0427bed2021-11-03 21:09:18 -0600414 newdata += data[syms[end_sym].offset:]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700415 tools.write_file(outfile, newdata)
Simon Glassf3385a52022-01-29 14:14:15 -0700416 tout.info('Written to offset %#x' % syms[start_sym].offset)
Simon Glass4d38dd72022-02-08 11:49:55 -0700417
Simon Glass17b4ffc2022-03-05 20:18:57 -0700418def read_loadable_segments(data):
Simon Glass4d38dd72022-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 Glass17b4ffc2022-03-05 20:18:57 -0700436 raise ValueError("Python: No module named 'elftools'")
Simon Glass4d38dd72022-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