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
24 from elftools.elf.elffile import ELFFile
25 from elftools.elf.sections import SymbolTableSection
26 except: # pragma: no cover
29 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
31 # 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
37 ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
40 def GetSymbols(fname, patterns):
41 """Get the symbols from an ELF file
44 fname: Filename of the ELF file to read
45 patterns: List of regex patterns to search for, each a string
48 None, if the file does not exist, or Dict:
50 value: Hex value of symbol
52 stdout = tools.Run('objdump', '-t', fname)
53 lines = stdout.splitlines()
55 re_syms = re.compile('|'.join(patterns))
61 if not line or not syms_started:
62 if 'SYMBOL TABLE' in line:
64 line = None # Otherwise code coverage complains about 'continue'
66 if re_syms and not re_syms.search(line):
69 space_pos = line.find(' ')
70 value, rest = line[:space_pos], line[space_pos + 1:]
72 parts = rest[7:].split()
73 section, size = parts[:2]
75 name = parts[2] if parts[2] != '.hidden' else parts[3]
76 syms[name] = Symbol(section, int(value, 16), int(size,16),
79 # Sort dict by address
80 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
82 def GetSymbolAddress(fname, sym_name):
83 """Get a value of a symbol from an ELF file
86 fname: Filename of the ELF file to read
87 patterns: List of regex patterns to search for, each a string
90 Symbol value (as an integer) or None if not found
92 syms = GetSymbols(fname, [sym_name])
93 sym = syms.get(sym_name)
98 def LookupAndWriteSymbols(elf_fname, entry, section):
99 """Replace all symbols in an entry with their correct values
101 The entry contents is updated so that values for referenced symbols will be
102 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
107 elf_fname: Filename of ELF image containing the symbol information for
109 entry: Entry to process
110 section: Section which can be used to lookup symbol values
112 fname = tools.GetInputFilename(elf_fname)
113 syms = GetSymbols(fname, ['image', 'binman'])
116 base = syms.get('__image_copy_start')
119 for name, sym in syms.items():
120 if name.startswith('_binman'):
121 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
122 (section.GetPath(), name, entry.GetPath()))
123 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))
133 raise ValueError('%s has size %d: only 4 and 8 are supported' %
136 # Look up the symbol in our entry tables.
137 value = section.LookupSymbol(name, sym.weak, msg, base.address)
140 pack_string = pack_string.lower()
141 value_bytes = struct.pack(pack_string, value)
142 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
143 (msg, name, offset, value, len(value_bytes)))
144 entry.data = (entry.data[:offset] + value_bytes +
145 entry.data[offset + sym.size:])
147 def MakeElf(elf_fname, text, data):
148 """Make an elf file with the given data in a single section
150 The output file has a several section including '.text' and '.data',
151 containing the info provided in arguments.
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
158 outdir = tempfile.mkdtemp(prefix='binman.elf.')
159 s_file = os.path.join(outdir, 'elf.S')
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 */
172 .type _start, @function
191 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
193 lds_file = os.path.join(outdir, 'elf.lds')
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 */
203 empty PT_LOAD FLAGS ( 6 ) ;
211 .text . : SUBALIGN(0)
223 *(.note.gnu.property)
228 .bss _bss_start (OVERLAY) : {
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)
243 def DecodeElf(data, location):
244 """Decode an ELF file and return information about it
247 data: Data from ELF file
248 location: Start address of data to return
251 ElfInfo object containing information about the decoded ELF file
253 file_size = len(data)
254 with io.BytesIO(data) as fd:
256 data_start = 0xffffffff;
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
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)
273 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
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
281 start = segment['p_paddr']
284 offset = location - start
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
292 size = segment['p_filesz']
295 #end = segment['p_offset'] + segment['p_filesz']
297 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
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)