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 collections import namedtuple, OrderedDict
22 from elftools.elf.elffile import ELFFile
23 from elftools.elf.sections import SymbolTableSection
24 except: # pragma: no cover
27 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
29 # Information about an ELF file:
30 # data: Extracted program contents of ELF file (this would be loaded by an
31 # ELF loader when reading this file
32 # load: Load address of code
33 # entry: Entry address of code
34 # memsize: Number of bytes in memory occupied by loading this ELF file
35 ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
38 def GetSymbols(fname, patterns):
39 """Get the symbols from an ELF file
42 fname: Filename of the ELF file to read
43 patterns: List of regex patterns to search for, each a string
46 None, if the file does not exist, or Dict:
48 value: Hex value of symbol
50 stdout = tools.Run('objdump', '-t', fname)
51 lines = stdout.splitlines()
53 re_syms = re.compile('|'.join(patterns))
59 if not line or not syms_started:
60 if 'SYMBOL TABLE' in line:
62 line = None # Otherwise code coverage complains about 'continue'
64 if re_syms and not re_syms.search(line):
67 space_pos = line.find(' ')
68 value, rest = line[:space_pos], line[space_pos + 1:]
70 parts = rest[7:].split()
71 section, size = parts[:2]
73 name = parts[2] if parts[2] != '.hidden' else parts[3]
74 syms[name] = Symbol(section, int(value, 16), int(size,16),
77 # Sort dict by address
78 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
80 def GetSymbolAddress(fname, sym_name):
81 """Get a value of a symbol from an ELF file
84 fname: Filename of the ELF file to read
85 patterns: List of regex patterns to search for, each a string
88 Symbol value (as an integer) or None if not found
90 syms = GetSymbols(fname, [sym_name])
91 sym = syms.get(sym_name)
96 def LookupAndWriteSymbols(elf_fname, entry, section):
97 """Replace all symbols in an entry with their correct values
99 The entry contents is updated so that values for referenced symbols will be
100 visible at run time. This is done by finding out the symbols offsets in the
101 entry (using the ELF file) and replacing them with values from binman's data
105 elf_fname: Filename of ELF image containing the symbol information for
107 entry: Entry to process
108 section: Section which can be used to lookup symbol values
110 fname = tools.GetInputFilename(elf_fname)
111 syms = GetSymbols(fname, ['image', 'binman'])
114 base = syms.get('__image_copy_start')
117 for name, sym in syms.items():
118 if name.startswith('_binman'):
119 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
120 (section.GetPath(), name, entry.GetPath()))
121 offset = sym.address - base.address
122 if offset < 0 or offset + sym.size > entry.contents_size:
123 raise ValueError('%s has offset %x (size %x) but the contents '
124 'size is %x' % (entry.GetPath(), offset,
125 sym.size, entry.contents_size))
131 raise ValueError('%s has size %d: only 4 and 8 are supported' %
134 # Look up the symbol in our entry tables.
135 value = section.LookupSymbol(name, sym.weak, msg, base.address)
138 pack_string = pack_string.lower()
139 value_bytes = struct.pack(pack_string, value)
140 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
141 (msg, name, offset, value, len(value_bytes)))
142 entry.data = (entry.data[:offset] + value_bytes +
143 entry.data[offset + sym.size:])
145 def MakeElf(elf_fname, text, data):
146 """Make an elf file with the given data in a single section
148 The output file has a several section including '.text' and '.data',
149 containing the info provided in arguments.
152 elf_fname: Output filename
153 text: Text (code) to put in the file's .text section
154 data: Data to put in the file's .data section
156 outdir = tempfile.mkdtemp(prefix='binman.elf.')
157 s_file = os.path.join(outdir, 'elf.S')
159 # Spilt the text into two parts so that we can make the entry point two
160 # bytes after the start of the text section
161 text_bytes1 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[:2]]
162 text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]]
163 data_bytes = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in data]
164 with open(s_file, 'w') as fd:
165 print('''/* Auto-generated C program to produce an ELF file for testing */
170 .type _start, @function
189 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
191 lds_file = os.path.join(outdir, 'elf.lds')
193 # Use a linker script to set the alignment and text address.
194 with open(lds_file, 'w') as fd:
195 print('''/* Auto-generated linker script to produce an ELF file for testing */
201 empty PT_LOAD FLAGS ( 6 ) ;
209 .text . : SUBALIGN(0)
221 *(.note.gnu.property)
226 .bss _bss_start (OVERLAY) : {
231 # -static: Avoid requiring any shared libraries
232 # -nostdlib: Don't link with C library
233 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
234 # text section at the start
235 # -m32: Build for 32-bit x86
236 # -T...: Specifies the link script, which sets the start address
237 stdout = command.Output('cc', '-static', '-nostdlib', '-Wl,--build-id=none',
238 '-m32','-T', lds_file, '-o', elf_fname, s_file)
239 shutil.rmtree(outdir)
241 def DecodeElf(data, location):
242 """Decode an ELF file and return information about it
245 data: Data from ELF file
246 location: Start address of data to return
249 ElfInfo object containing information about the decoded ELF file
251 file_size = len(data)
252 with io.BytesIO(data) as fd:
254 data_start = 0xffffffff;
259 for i in range(elf.num_segments()):
260 segment = elf.get_segment(i)
261 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
262 skipped = 1 # To make code-coverage see this line
264 start = segment['p_paddr']
265 mend = start + segment['p_memsz']
266 rend = start + segment['p_filesz']
267 data_start = min(data_start, start)
268 data_end = max(data_end, rend)
269 mem_end = max(mem_end, mend)
271 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
273 output = bytearray(data_end - data_start)
274 for i in range(elf.num_segments()):
275 segment = elf.get_segment(i)
276 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
277 skipped = 1 # To make code-coverage see this line
279 start = segment['p_paddr']
282 offset = location - start
284 # A legal ELF file can have a program header with non-zero length
285 # but zero-length file size and a non-zero offset which, added
286 # together, are greater than input->size (i.e. the total file size).
287 # So we need to not even test in the case that p_filesz is zero.
288 # Note: All of this code is commented out since we don't have a test
290 size = segment['p_filesz']
293 #end = segment['p_offset'] + segment['p_filesz']
295 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
297 output[start - data_start:start - data_start + size] = (
298 segment.data()[offset:])
299 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
300 mem_end - data_start)