]> Git Repo - J-u-boot.git/blame - tools/binman/elf.py
patman: Move to absolute imports
[J-u-boot.git] / tools / binman / elf.py
CommitLineData
83d290c5 1# SPDX-License-Identifier: GPL-2.0+
b50e5611
SG
2# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <[email protected]>
4#
b50e5611
SG
5# Handle various things related to ELF images
6#
7
8from collections import namedtuple, OrderedDict
d8d40748 9import io
b50e5611
SG
10import os
11import re
f58558a5 12import shutil
b50e5611 13import struct
f58558a5 14import tempfile
b50e5611 15
bf776679
SG
16from patman import command
17from patman import tools
18from patman import tout
b50e5611 19
d8d40748
SG
20ELF_TOOLS = True
21try:
22 from elftools.elf.elffile import ELFFile
23 from elftools.elf.sections import SymbolTableSection
24except: # pragma: no cover
25 ELF_TOOLS = False
26
b50e5611
SG
27Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
28
d8d40748
SG
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
35ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
36
b50e5611
SG
37
38def GetSymbols(fname, patterns):
39 """Get the symbols from an ELF file
40
41 Args:
42 fname: Filename of the ELF file to read
43 patterns: List of regex patterns to search for, each a string
44
45 Returns:
46 None, if the file does not exist, or Dict:
47 key: Name of symbol
48 value: Hex value of symbol
49 """
180f556b 50 stdout = tools.Run('objdump', '-t', fname)
b50e5611
SG
51 lines = stdout.splitlines()
52 if patterns:
53 re_syms = re.compile('|'.join(patterns))
54 else:
55 re_syms = None
56 syms = {}
57 syms_started = False
58 for line in lines:
59 if not line or not syms_started:
60 if 'SYMBOL TABLE' in line:
61 syms_started = True
62 line = None # Otherwise code coverage complains about 'continue'
63 continue
64 if re_syms and not re_syms.search(line):
65 continue
66
67 space_pos = line.find(' ')
68 value, rest = line[:space_pos], line[space_pos + 1:]
69 flags = rest[:7]
70 parts = rest[7:].split()
71 section, size = parts[:2]
72 if len(parts) > 2:
39c8e47d 73 name = parts[2] if parts[2] != '.hidden' else parts[3]
b50e5611
SG
74 syms[name] = Symbol(section, int(value, 16), int(size,16),
75 flags[1] == 'w')
46d61a2f
SG
76
77 # Sort dict by address
50979154 78 return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
b50e5611
SG
79
80def GetSymbolAddress(fname, sym_name):
81 """Get a value of a symbol from an ELF file
82
83 Args:
84 fname: Filename of the ELF file to read
85 patterns: List of regex patterns to search for, each a string
86
87 Returns:
88 Symbol value (as an integer) or None if not found
89 """
90 syms = GetSymbols(fname, [sym_name])
91 sym = syms.get(sym_name)
92 if not sym:
93 return None
94 return sym.address
19790632 95
f55382b5 96def LookupAndWriteSymbols(elf_fname, entry, section):
19790632
SG
97 """Replace all symbols in an entry with their correct values
98
99 The entry contents is updated so that values for referenced symbols will be
3ab9598d
SG
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
102 structures.
19790632
SG
103
104 Args:
105 elf_fname: Filename of ELF image containing the symbol information for
106 entry
107 entry: Entry to process
f55382b5 108 section: Section which can be used to lookup symbol values
19790632
SG
109 """
110 fname = tools.GetInputFilename(elf_fname)
111 syms = GetSymbols(fname, ['image', 'binman'])
112 if not syms:
113 return
114 base = syms.get('__image_copy_start')
115 if not base:
116 return
50979154 117 for name, sym in syms.items():
19790632 118 if name.startswith('_binman'):
f55382b5
SG
119 msg = ("Section '%s': Symbol '%s'\n in entry '%s'" %
120 (section.GetPath(), name, entry.GetPath()))
19790632
SG
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))
126 if sym.size == 4:
127 pack_string = '<I'
128 elif sym.size == 8:
129 pack_string = '<Q'
130 else:
131 raise ValueError('%s has size %d: only 4 and 8 are supported' %
132 (msg, sym.size))
133
134 # Look up the symbol in our entry tables.
7c150136 135 value = section.LookupSymbol(name, sym.weak, msg, base.address)
15c981cc 136 if value is None:
19790632
SG
137 value = -1
138 pack_string = pack_string.lower()
139 value_bytes = struct.pack(pack_string, value)
9f297b09
SG
140 tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' %
141 (msg, name, offset, value, len(value_bytes)))
19790632
SG
142 entry.data = (entry.data[:offset] + value_bytes +
143 entry.data[offset + sym.size:])
f58558a5
SG
144
145def MakeElf(elf_fname, text, data):
146 """Make an elf file with the given data in a single section
147
148 The output file has a several section including '.text' and '.data',
149 containing the info provided in arguments.
150
151 Args:
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
155 """
156 outdir = tempfile.mkdtemp(prefix='binman.elf.')
157 s_file = os.path.join(outdir, 'elf.S')
158
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 */
166
167.section .text
168.code32
169.globl _start
170.type _start, @function
171%s
172_start:
173%s
174.ident "comment"
175
176.comm fred,8,4
177
178.section .empty
179.globl _empty
180_empty:
181.byte 1
182
183.globl ernie
184.data
185.type ernie, @object
186.size ernie, 4
187ernie:
188%s
189''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
190 file=fd)
191 lds_file = os.path.join(outdir, 'elf.lds')
192
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 */
196
197PHDRS
198{
199 text PT_LOAD ;
200 data PT_LOAD ;
201 empty PT_LOAD FLAGS ( 6 ) ;
202 note PT_NOTE ;
203}
204
205SECTIONS
206{
207 . = 0xfef20000;
208 ENTRY(_start)
209 .text . : SUBALIGN(0)
210 {
211 *(.text)
212 } :text
213 .data : {
214 *(.data)
215 } :data
216 _bss_start = .;
217 .empty : {
218 *(.empty)
219 } :empty
9d44a7e6
SG
220 /DISCARD/ : {
221 *(.note.gnu.property)
222 }
f58558a5
SG
223 .note : {
224 *(.comment)
225 } :note
226 .bss _bss_start (OVERLAY) : {
227 *(.bss)
228 }
229}
230''', file=fd)
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)
d8d40748
SG
240
241def DecodeElf(data, location):
242 """Decode an ELF file and return information about it
243
244 Args:
245 data: Data from ELF file
246 location: Start address of data to return
247
248 Returns:
249 ElfInfo object containing information about the decoded ELF file
250 """
251 file_size = len(data)
252 with io.BytesIO(data) as fd:
253 elf = ELFFile(fd)
254 data_start = 0xffffffff;
255 data_end = 0;
256 mem_end = 0;
257 virt_to_phys = 0;
258
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
263 continue
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)
270 if not virt_to_phys:
271 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
272
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
278 continue
279 start = segment['p_paddr']
280 offset = 0
281 if start < location:
282 offset = location - start
283 start = location
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
289 # case for it.
290 size = segment['p_filesz']
291 #if not size:
292 #continue
293 #end = segment['p_offset'] + segment['p_filesz']
294 #if end > file_size:
295 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
296 #file_size, end)
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)
This page took 0.127513 seconds and 4 git commands to generate.