blob: 03b49d7163c553d733878a6b070ef3697160b2d0 [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
23 from elftools.elf.sections import SymbolTableSection
24except: # pragma: no cover
25 ELF_TOOLS = False
26
Simon Glassb50e5612017-11-13 18:54:54 -070027Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
28
Simon Glassd8d40742019-07-08 13:18:35 -060029# Information about an ELF file:
30# data: Extracted program contents of ELF file (this would be loaded by an
31# ELF loader when reading this file
32# load: Load address of code
33# entry: Entry address of code
34# memsize: Number of bytes in memory occupied by loading this ELF file
35ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
36
Simon Glassb50e5612017-11-13 18:54:54 -070037
38def GetSymbols(fname, patterns):
39 """Get the symbols from an ELF file
40
41 Args:
42 fname: Filename of the ELF file to read
43 patterns: List of regex patterns to search for, each a string
44
45 Returns:
46 None, if the file does not exist, or Dict:
47 key: Name of symbol
48 value: Hex value of symbol
49 """
Simon Glass180f5562019-08-24 07:22:52 -060050 stdout = tools.Run('objdump', '-t', fname)
Simon Glassb50e5612017-11-13 18:54:54 -070051 lines = stdout.splitlines()
52 if patterns:
53 re_syms = re.compile('|'.join(patterns))
54 else:
55 re_syms = None
56 syms = {}
57 syms_started = False
58 for line in lines:
59 if not line or not syms_started:
60 if 'SYMBOL TABLE' in line:
61 syms_started = True
62 line = None # Otherwise code coverage complains about 'continue'
63 continue
64 if re_syms and not re_syms.search(line):
65 continue
66
67 space_pos = line.find(' ')
68 value, rest = line[:space_pos], line[space_pos + 1:]
69 flags = rest[:7]
70 parts = rest[7:].split()
71 section, size = parts[:2]
72 if len(parts) > 2:
Simon Glass39c8e472019-08-24 07:22:46 -060073 name = parts[2] if parts[2] != '.hidden' else parts[3]
Simon Glassb50e5612017-11-13 18:54:54 -070074 syms[name] = Symbol(section, int(value, 16), int(size,16),
75 flags[1] == 'w')
Simon Glass46d61a22018-07-17 13:25:24 -060076
77 # Sort dict by address
Simon Glass50979152019-05-14 15:53:41 -060078 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
Simon Glassb50e5612017-11-13 18:54:54 -070079
80def GetSymbolAddress(fname, sym_name):
81 """Get a value of a symbol from an ELF file
82
83 Args:
84 fname: Filename of the ELF file to read
85 patterns: List of regex patterns to search for, each a string
86
87 Returns:
88 Symbol value (as an integer) or None if not found
89 """
90 syms = GetSymbols(fname, [sym_name])
91 sym = syms.get(sym_name)
92 if not sym:
93 return None
94 return sym.address
Simon Glass19790632017-11-13 18:55:01 -070095
Simon Glassf55382b2018-06-01 09:38:13 -060096def LookupAndWriteSymbols(elf_fname, entry, section):
Simon Glass19790632017-11-13 18:55:01 -070097 """Replace all symbols in an entry with their correct values
98
99 The entry contents is updated so that values for referenced symbols will be
Simon Glass3ab95982018-08-01 15:22:37 -0600100 visible at run time. This is done by finding out the symbols offsets in the
101 entry (using the ELF file) and replacing them with values from binman's data
102 structures.
Simon Glass19790632017-11-13 18:55:01 -0700103
104 Args:
105 elf_fname: Filename of ELF image containing the symbol information for
106 entry
107 entry: Entry to process
Simon Glassf55382b2018-06-01 09:38:13 -0600108 section: Section which can be used to lookup symbol values
Simon Glass19790632017-11-13 18:55:01 -0700109 """
110 fname = tools.GetInputFilename(elf_fname)
111 syms = GetSymbols(fname, ['image', 'binman'])
112 if not syms:
113 return
114 base = syms.get('__image_copy_start')
115 if not base:
116 return
Simon Glass50979152019-05-14 15:53:41 -0600117 for name, sym in syms.items():
Simon Glass19790632017-11-13 18:55:01 -0700118 if name.startswith('_binman'):
Simon Glassf55382b2018-06-01 09:38:13 -0600119 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
120 (section.GetPath(), name, entry.GetPath()))
Simon Glass19790632017-11-13 18:55:01 -0700121 offset = sym.address - base.address
122 if offset < 0 or offset + sym.size > entry.contents_size:
123 raise ValueError('%s has offset %x (size %x) but the contents '
124 'size is %x' % (entry.GetPath(), offset,
125 sym.size, entry.contents_size))
126 if sym.size == 4:
127 pack_string = '<I'
128 elif sym.size == 8:
129 pack_string = '<Q'
130 else:
131 raise ValueError('%s has size %d: only 4 and 8 are supported' %
132 (msg, sym.size))
133
134 # Look up the symbol in our entry tables.
Simon Glass870a9ea2021-01-06 21:35:15 -0700135 value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
136 base.address)
Simon Glass15c981c2019-10-20 21:31:34 -0600137 if value is None:
Simon Glass19790632017-11-13 18:55:01 -0700138 value = -1
139 pack_string = pack_string.lower()
140 value_bytes = struct.pack(pack_string, value)
Simon Glass9f297b02019-07-20 12:23:36 -0600141 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
142 (msg, name, offset, value, len(value_bytes)))
Simon Glass19790632017-11-13 18:55:01 -0700143 entry.data = (entry.data[:offset] + value_bytes +
144 entry.data[offset + sym.size:])
Simon Glassf58558a2019-07-08 13:18:34 -0600145
146def MakeElf(elf_fname, text, data):
147 """Make an elf file with the given data in a single section
148
149 The output file has a several section including '.text' and '.data',
150 containing the info provided in arguments.
151
152 Args:
153 elf_fname: Output filename
154 text: Text (code) to put in the file's .text section
155 data: Data to put in the file's .data section
156 """
157 outdir = tempfile.mkdtemp(prefix='binman.elf.')
158 s_file = os.path.join(outdir, 'elf.S')
159
160 # Spilt the text into two parts so that we can make the entry point two
161 # bytes after the start of the text section
Simon Glass6a4ccad2020-11-08 20:36:19 -0700162 text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
163 text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
164 data_bytes = ['\t.byte\t%#x' % byte for byte in data]
Simon Glassf58558a2019-07-08 13:18:34 -0600165 with open(s_file, 'w') as fd:
166 print('''/* Auto-generated C program to produce an ELF file for testing */
167
168.section .text
169.code32
170.globl _start
171.type _start, @function
172%s
173_start:
174%s
175.ident "comment"
176
177.comm fred,8,4
178
179.section .empty
180.globl _empty
181_empty:
182.byte 1
183
184.globl ernie
185.data
186.type ernie, @object
187.size ernie, 4
188ernie:
189%s
190''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
191 file=fd)
192 lds_file = os.path.join(outdir, 'elf.lds')
193
194 # Use a linker script to set the alignment and text address.
195 with open(lds_file, 'w') as fd:
196 print('''/* Auto-generated linker script to produce an ELF file for testing */
197
198PHDRS
199{
200 text PT_LOAD ;
201 data PT_LOAD ;
202 empty PT_LOAD FLAGS ( 6 ) ;
203 note PT_NOTE ;
204}
205
206SECTIONS
207{
208 . = 0xfef20000;
209 ENTRY(_start)
210 .text . : SUBALIGN(0)
211 {
212 *(.text)
213 } :text
214 .data : {
215 *(.data)
216 } :data
217 _bss_start = .;
218 .empty : {
219 *(.empty)
220 } :empty
Simon Glass9d44a7e2019-08-24 07:22:45 -0600221 /DISCARD/ : {
222 *(.note.gnu.property)
223 }
Simon Glassf58558a2019-07-08 13:18:34 -0600224 .note : {
225 *(.comment)
226 } :note
227 .bss _bss_start (OVERLAY) : {
228 *(.bss)
229 }
230}
231''', file=fd)
232 # -static: Avoid requiring any shared libraries
233 # -nostdlib: Don't link with C library
234 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
235 # text section at the start
236 # -m32: Build for 32-bit x86
237 # -T...: Specifies the link script, which sets the start address
Alper Nebi Yasak1e4687a2020-09-06 14:46:05 +0300238 cc, args = tools.GetTargetCompileTool('cc')
239 args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
240 lds_file, '-o', elf_fname, s_file]
241 stdout = command.Output(cc, *args)
Simon Glassf58558a2019-07-08 13:18:34 -0600242 shutil.rmtree(outdir)
Simon Glassd8d40742019-07-08 13:18:35 -0600243
244def DecodeElf(data, location):
245 """Decode an ELF file and return information about it
246
247 Args:
248 data: Data from ELF file
249 location: Start address of data to return
250
251 Returns:
252 ElfInfo object containing information about the decoded ELF file
253 """
254 file_size = len(data)
255 with io.BytesIO(data) as fd:
256 elf = ELFFile(fd)
257 data_start = 0xffffffff;
258 data_end = 0;
259 mem_end = 0;
260 virt_to_phys = 0;
261
262 for i in range(elf.num_segments()):
263 segment = elf.get_segment(i)
264 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
265 skipped = 1 # To make code-coverage see this line
266 continue
267 start = segment['p_paddr']
268 mend = start + segment['p_memsz']
269 rend = start + segment['p_filesz']
270 data_start = min(data_start, start)
271 data_end = max(data_end, rend)
272 mem_end = max(mem_end, mend)
273 if not virt_to_phys:
274 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
275
276 output = bytearray(data_end - data_start)
277 for i in range(elf.num_segments()):
278 segment = elf.get_segment(i)
279 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
280 skipped = 1 # To make code-coverage see this line
281 continue
282 start = segment['p_paddr']
283 offset = 0
284 if start < location:
285 offset = location - start
286 start = location
287 # A legal ELF file can have a program header with non-zero length
288 # but zero-length file size and a non-zero offset which, added
289 # together, are greater than input->size (i.e. the total file size).
290 # So we need to not even test in the case that p_filesz is zero.
291 # Note: All of this code is commented out since we don't have a test
292 # case for it.
293 size = segment['p_filesz']
294 #if not size:
295 #continue
296 #end = segment['p_offset'] + segment['p_filesz']
297 #if end > file_size:
298 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
299 #file_size, end)
300 output[start - data_start:start - data_start + size] = (
301 segment.data()[offset:])
302 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
303 mem_end - data_start)