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