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