1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Handle various things related to ELF images
8 from __future__ import print_function
10 from collections import namedtuple, OrderedDict
23 from elftools.elf.elffile import ELFFile
24 from elftools.elf.sections import SymbolTableSection
25 except: # pragma: no cover
28 # This is enabled from control.py
31 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
33 # 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
39 ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
42 def GetSymbols(fname, patterns):
43 """Get the symbols from an ELF file
46 fname: Filename of the ELF file to read
47 patterns: List of regex patterns to search for, each a string
50 None, if the file does not exist, or Dict:
52 value: Hex value of symbol
54 stdout = command.Output('objdump', '-t', fname, raise_on_error=False)
55 lines = stdout.splitlines()
57 re_syms = re.compile('|'.join(patterns))
63 if not line or not syms_started:
64 if 'SYMBOL TABLE' in line:
66 line = None # Otherwise code coverage complains about 'continue'
68 if re_syms and not re_syms.search(line):
71 space_pos = line.find(' ')
72 value, rest = line[:space_pos], line[space_pos + 1:]
74 parts = rest[7:].split()
75 section, size = parts[:2]
78 syms[name] = Symbol(section, int(value, 16), int(size,16),
81 # Sort dict by address
82 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
84 def GetSymbolAddress(fname, sym_name):
85 """Get a value of a symbol from an ELF file
88 fname: Filename of the ELF file to read
89 patterns: List of regex patterns to search for, each a string
92 Symbol value (as an integer) or None if not found
94 syms = GetSymbols(fname, [sym_name])
95 sym = syms.get(sym_name)
100 def LookupAndWriteSymbols(elf_fname, entry, section):
101 """Replace all symbols in an entry with their correct values
103 The entry contents is updated so that values for referenced symbols will be
104 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
109 elf_fname: Filename of ELF image containing the symbol information for
111 entry: Entry to process
112 section: Section which can be used to lookup symbol values
114 fname = tools.GetInputFilename(elf_fname)
115 syms = GetSymbols(fname, ['image', 'binman'])
118 base = syms.get('__image_copy_start')
121 for name, sym in syms.items():
122 if name.startswith('_binman'):
123 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
124 (section.GetPath(), name, entry.GetPath()))
125 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))
135 raise ValueError('%s has size %d: only 4 and 8 are supported' %
138 # Look up the symbol in our entry tables.
139 value = section.LookupSymbol(name, sym.weak, msg)
140 if value is not None:
141 value += base.address
144 pack_string = pack_string.lower()
145 value_bytes = struct.pack(pack_string, value)
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:])
152 def MakeElf(elf_fname, text, data):
153 """Make an elf file with the given data in a single section
155 The output file has a several section including '.text' and '.data',
156 containing the info provided in arguments.
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
163 outdir = tempfile.mkdtemp(prefix='binman.elf.')
164 s_file = os.path.join(outdir, 'elf.S')
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 */
177 .type _start, @function
196 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
198 lds_file = os.path.join(outdir, 'elf.lds')
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 */
208 empty PT_LOAD FLAGS ( 6 ) ;
216 .text . : SUBALIGN(0)
230 .bss _bss_start (OVERLAY) : {
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)
245 def DecodeElf(data, location):
246 """Decode an ELF file and return information about it
249 data: Data from ELF file
250 location: Start address of data to return
253 ElfInfo object containing information about the decoded ELF file
255 file_size = len(data)
256 with io.BytesIO(data) as fd:
258 data_start = 0xffffffff;
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
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)
275 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
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
283 start = segment['p_paddr']
286 offset = location - start
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
294 size = segment['p_filesz']
297 #end = segment['p_offset'] + segment['p_filesz']
299 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
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)