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 = command.Output('objdump', '-t', fname, raise_on_error=False)
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]
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)
138 if value is not None:
139 value += base.address
142 pack_string = pack_string.lower()
143 value_bytes = struct.pack(pack_string, value)
144 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
145 (msg, name, offset, value, len(value_bytes)))
146 entry.data = (entry.data[:offset] + value_bytes +
147 entry.data[offset + sym.size:])
149 def MakeElf(elf_fname, text, data):
150 """Make an elf file with the given data in a single section
152 The output file has a several section including '.text' and '.data',
153 containing the info provided in arguments.
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
160 outdir = tempfile.mkdtemp(prefix='binman.elf.')
161 s_file = os.path.join(outdir, 'elf.S')
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 */
174 .type _start, @function
193 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
195 lds_file = os.path.join(outdir, 'elf.lds')
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 */
205 empty PT_LOAD FLAGS ( 6 ) ;
213 .text . : SUBALIGN(0)
227 .bss _bss_start (OVERLAY) : {
232 # -static: Avoid requiring any shared libraries
233 # -nostdlib: Don't link with C library
234 # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
235 # text section at the start
236 # -m32: Build for 32-bit x86
237 # -T...: Specifies the link script, which sets the start address
238 stdout = command.Output('cc', '-static', '-nostdlib', '-Wl,--build-id=none',
239 '-m32','-T', lds_file, '-o', elf_fname, s_file)
240 shutil.rmtree(outdir)
242 def DecodeElf(data, location):
243 """Decode an ELF file and return information about it
246 data: Data from ELF file
247 location: Start address of data to return
250 ElfInfo object containing information about the decoded ELF file
252 file_size = len(data)
253 with io.BytesIO(data) as fd:
255 data_start = 0xffffffff;
260 for i in range(elf.num_segments()):
261 segment = elf.get_segment(i)
262 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
263 skipped = 1 # To make code-coverage see this line
265 start = segment['p_paddr']
266 mend = start + segment['p_memsz']
267 rend = start + segment['p_filesz']
268 data_start = min(data_start, start)
269 data_end = max(data_end, rend)
270 mem_end = max(mem_end, mend)
272 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
274 output = bytearray(data_end - data_start)
275 for i in range(elf.num_segments()):
276 segment = elf.get_segment(i)
277 if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
278 skipped = 1 # To make code-coverage see this line
280 start = segment['p_paddr']
283 offset = location - start
285 # A legal ELF file can have a program header with non-zero length
286 # but zero-length file size and a non-zero offset which, added
287 # together, are greater than input->size (i.e. the total file size).
288 # So we need to not even test in the case that p_filesz is zero.
289 # Note: All of this code is commented out since we don't have a test
291 size = segment['p_filesz']
294 #end = segment['p_offset'] + segment['p_filesz']
296 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
298 output[start - data_start:start - data_start + size] = (
299 segment.data()[offset:])
300 return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
301 mem_end - data_start)