blob: 7bc7cf61b5d83721cbd73e093fd40d6619889520 [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
Simon Glass4f379ea2019-07-08 13:18:34 -06008from __future__ import print_function
9
Simon Glass24ad3652017-11-13 18:54:54 -070010from collections import namedtuple, OrderedDict
11import command
Simon Glass567b6822019-07-08 13:18:35 -060012import io
Simon Glass24ad3652017-11-13 18:54:54 -070013import os
14import re
Simon Glass4f379ea2019-07-08 13:18:34 -060015import shutil
Simon Glass24ad3652017-11-13 18:54:54 -070016import struct
Simon Glass4f379ea2019-07-08 13:18:34 -060017import tempfile
Simon Glass24ad3652017-11-13 18:54:54 -070018
19import tools
Simon Glassb6dff4c2019-07-20 12:23:36 -060020import tout
Simon Glass24ad3652017-11-13 18:54:54 -070021
Simon Glass567b6822019-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 Glass24ad3652017-11-13 18:54:54 -070029Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
30
Simon Glass567b6822019-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 Glass24ad3652017-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 Glass08cc4cc2019-08-24 07:22:52 -060052 stdout = tools.Run('objdump', '-t', fname)
Simon Glass24ad3652017-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 Glassc29a85d2019-08-24 07:22:46 -060075 name = parts[2] if parts[2] != '.hidden' else parts[3]
Simon Glass24ad3652017-11-13 18:54:54 -070076 syms[name] = Symbol(section, int(value, 16), int(size,16),
77 flags[1] == 'w')
Simon Glasse6854aa2018-07-17 13:25:24 -060078
79 # Sort dict by address
Simon Glass5f3645b2019-05-14 15:53:41 -060080 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
Simon Glass24ad3652017-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 Glass4ca8e042017-11-13 18:55:01 -070097
Simon Glass8a6f56e2018-06-01 09:38:13 -060098def LookupAndWriteSymbols(elf_fname, entry, section):
Simon Glass4ca8e042017-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 Glasse8561af2018-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 Glass4ca8e042017-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 Glass8a6f56e2018-06-01 09:38:13 -0600110 section: Section which can be used to lookup symbol values
Simon Glass4ca8e042017-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 Glass5f3645b2019-05-14 15:53:41 -0600119 for name, sym in syms.items():
Simon Glass4ca8e042017-11-13 18:55:01 -0700120 if name.startswith('_binman'):
Simon Glass8a6f56e2018-06-01 09:38:13 -0600121 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
122 (section.GetPath(), name, entry.GetPath()))
Simon Glass4ca8e042017-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 Glass8a6f56e2018-06-01 09:38:13 -0600137 value = section.LookupSymbol(name, sym.weak, msg)
Simon Glass4ca8e042017-11-13 18:55:01 -0700138 if value is not None:
139 value += base.address
140 else:
141 value = -1
142 pack_string = pack_string.lower()
143 value_bytes = struct.pack(pack_string, value)
Simon Glassb6dff4c2019-07-20 12:23:36 -0600144 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
145 (msg, name, offset, value, len(value_bytes)))
Simon Glass4ca8e042017-11-13 18:55:01 -0700146 entry.data = (entry.data[:offset] + value_bytes +
147 entry.data[offset + sym.size:])
Simon Glass4f379ea2019-07-08 13:18:34 -0600148
149def MakeElf(elf_fname, text, data):
150 """Make an elf file with the given data in a single section
151
152 The output file has a several section including '.text' and '.data',
153 containing the info provided in arguments.
154
155 Args:
156 elf_fname: Output filename
157 text: Text (code) to put in the file's .text section
158 data: Data to put in the file's .data section
159 """
160 outdir = tempfile.mkdtemp(prefix='binman.elf.')
161 s_file = os.path.join(outdir, 'elf.S')
162
163 # Spilt the text into two parts so that we can make the entry point two
164 # bytes after the start of the text section
165 text_bytes1 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[:2]]
166 text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]]
167 data_bytes = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in data]
168 with open(s_file, 'w') as fd:
169 print('''/* Auto-generated C program to produce an ELF file for testing */
170
171.section .text
172.code32
173.globl _start
174.type _start, @function
175%s
176_start:
177%s
178.ident "comment"
179
180.comm fred,8,4
181
182.section .empty
183.globl _empty
184_empty:
185.byte 1
186
187.globl ernie
188.data
189.type ernie, @object
190.size ernie, 4
191ernie:
192%s
193''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
194 file=fd)
195 lds_file = os.path.join(outdir, 'elf.lds')
196
197 # Use a linker script to set the alignment and text address.
198 with open(lds_file, 'w') as fd:
199 print('''/* Auto-generated linker script to produce an ELF file for testing */
200
201PHDRS
202{
203 text PT_LOAD ;
204 data PT_LOAD ;
205 empty PT_LOAD FLAGS ( 6 ) ;
206 note PT_NOTE ;
207}
208
209SECTIONS
210{
211 . = 0xfef20000;
212 ENTRY(_start)
213 .text . : SUBALIGN(0)
214 {
215 *(.text)
216 } :text
217 .data : {
218 *(.data)
219 } :data
220 _bss_start = .;
221 .empty : {
222 *(.empty)
223 } :empty
Simon Glassd349ada2019-08-24 07:22:45 -0600224 /DISCARD/ : {
225 *(.note.gnu.property)
226 }
Simon Glass4f379ea2019-07-08 13:18:34 -0600227 .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 Glass567b6822019-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)