]>
Commit | Line | Data |
---|---|---|
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 | ||
8 | from collections import namedtuple, OrderedDict | |
d8d40748 | 9 | import io |
b50e5611 SG |
10 | import os |
11 | import re | |
f58558a5 | 12 | import shutil |
b50e5611 | 13 | import struct |
f58558a5 | 14 | import tempfile |
b50e5611 | 15 | |
bf776679 SG |
16 | from patman import command |
17 | from patman import tools | |
18 | from patman import tout | |
b50e5611 | 19 | |
d8d40748 SG |
20 | ELF_TOOLS = True |
21 | try: | |
22 | from elftools.elf.elffile import ELFFile | |
23 | from elftools.elf.sections import SymbolTableSection | |
24 | except: # pragma: no cover | |
25 | ELF_TOOLS = False | |
26 | ||
b50e5611 SG |
27 | Symbol = 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 | |
35 | ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize']) | |
36 | ||
b50e5611 SG |
37 | |
38 | def 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 | |
80 | def 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 | 96 | def 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. | |
870a9ead SG |
135 | value = section.GetImage().LookupImageSymbol(name, sym.weak, msg, |
136 | base.address) | |
15c981cc | 137 | if value is None: |
19790632 SG |
138 | value = -1 |
139 | pack_string = pack_string.lower() | |
140 | value_bytes = struct.pack(pack_string, value) | |
9f297b09 SG |
141 | tout.Debug('%s:\n insert %s, offset %x, value %x, length %d' % |
142 | (msg, name, offset, value, len(value_bytes))) | |
19790632 SG |
143 | entry.data = (entry.data[:offset] + value_bytes + |
144 | entry.data[offset + sym.size:]) | |
f58558a5 SG |
145 | |
146 | def MakeElf(elf_fname, text, data): | |
147 | """Make an elf file with the given data in a single section | |
148 | ||
149 | The output file has a several section including '.text' and '.data', | |
150 | containing the info provided in arguments. | |
151 | ||
152 | Args: | |
153 | elf_fname: Output filename | |
154 | text: Text (code) to put in the file's .text section | |
155 | data: Data to put in the file's .data section | |
156 | """ | |
157 | outdir = tempfile.mkdtemp(prefix='binman.elf.') | |
158 | s_file = os.path.join(outdir, 'elf.S') | |
159 | ||
160 | # Spilt the text into two parts so that we can make the entry point two | |
161 | # bytes after the start of the text section | |
6a4ccad8 SG |
162 | text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]] |
163 | text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]] | |
164 | data_bytes = ['\t.byte\t%#x' % byte for byte in data] | |
f58558a5 SG |
165 | with open(s_file, 'w') as fd: |
166 | print('''/* Auto-generated C program to produce an ELF file for testing */ | |
167 | ||
168 | .section .text | |
169 | .code32 | |
170 | .globl _start | |
171 | .type _start, @function | |
172 | %s | |
173 | _start: | |
174 | %s | |
175 | .ident "comment" | |
176 | ||
177 | .comm fred,8,4 | |
178 | ||
179 | .section .empty | |
180 | .globl _empty | |
181 | _empty: | |
182 | .byte 1 | |
183 | ||
184 | .globl ernie | |
185 | .data | |
186 | .type ernie, @object | |
187 | .size ernie, 4 | |
188 | ernie: | |
189 | %s | |
190 | ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)), | |
191 | file=fd) | |
192 | lds_file = os.path.join(outdir, 'elf.lds') | |
193 | ||
194 | # Use a linker script to set the alignment and text address. | |
195 | with open(lds_file, 'w') as fd: | |
196 | print('''/* Auto-generated linker script to produce an ELF file for testing */ | |
197 | ||
198 | PHDRS | |
199 | { | |
200 | text PT_LOAD ; | |
201 | data PT_LOAD ; | |
202 | empty PT_LOAD FLAGS ( 6 ) ; | |
203 | note PT_NOTE ; | |
204 | } | |
205 | ||
206 | SECTIONS | |
207 | { | |
208 | . = 0xfef20000; | |
209 | ENTRY(_start) | |
210 | .text . : SUBALIGN(0) | |
211 | { | |
212 | *(.text) | |
213 | } :text | |
214 | .data : { | |
215 | *(.data) | |
216 | } :data | |
217 | _bss_start = .; | |
218 | .empty : { | |
219 | *(.empty) | |
220 | } :empty | |
9d44a7e6 SG |
221 | /DISCARD/ : { |
222 | *(.note.gnu.property) | |
223 | } | |
f58558a5 SG |
224 | .note : { |
225 | *(.comment) | |
226 | } :note | |
227 | .bss _bss_start (OVERLAY) : { | |
228 | *(.bss) | |
229 | } | |
230 | } | |
231 | ''', file=fd) | |
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 | |
1e4687aa ANY |
238 | cc, args = tools.GetTargetCompileTool('cc') |
239 | args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T', | |
240 | lds_file, '-o', elf_fname, s_file] | |
241 | stdout = command.Output(cc, *args) | |
f58558a5 | 242 | shutil.rmtree(outdir) |
d8d40748 SG |
243 | |
244 | def DecodeElf(data, location): | |
245 | """Decode an ELF file and return information about it | |
246 | ||
247 | Args: | |
248 | data: Data from ELF file | |
249 | location: Start address of data to return | |
250 | ||
251 | Returns: | |
252 | ElfInfo object containing information about the decoded ELF file | |
253 | """ | |
254 | file_size = len(data) | |
255 | with io.BytesIO(data) as fd: | |
256 | elf = ELFFile(fd) | |
257 | data_start = 0xffffffff; | |
258 | data_end = 0; | |
259 | mem_end = 0; | |
260 | virt_to_phys = 0; | |
261 | ||
262 | for i in range(elf.num_segments()): | |
263 | segment = elf.get_segment(i) | |
264 | if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']: | |
265 | skipped = 1 # To make code-coverage see this line | |
266 | continue | |
267 | start = segment['p_paddr'] | |
268 | mend = start + segment['p_memsz'] | |
269 | rend = start + segment['p_filesz'] | |
270 | data_start = min(data_start, start) | |
271 | data_end = max(data_end, rend) | |
272 | mem_end = max(mem_end, mend) | |
273 | if not virt_to_phys: | |
274 | virt_to_phys = segment['p_paddr'] - segment['p_vaddr'] | |
275 | ||
276 | output = bytearray(data_end - data_start) | |
277 | for i in range(elf.num_segments()): | |
278 | segment = elf.get_segment(i) | |
279 | if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']: | |
280 | skipped = 1 # To make code-coverage see this line | |
281 | continue | |
282 | start = segment['p_paddr'] | |
283 | offset = 0 | |
284 | if start < location: | |
285 | offset = location - start | |
286 | start = location | |
287 | # A legal ELF file can have a program header with non-zero length | |
288 | # but zero-length file size and a non-zero offset which, added | |
289 | # together, are greater than input->size (i.e. the total file size). | |
290 | # So we need to not even test in the case that p_filesz is zero. | |
291 | # Note: All of this code is commented out since we don't have a test | |
292 | # case for it. | |
293 | size = segment['p_filesz'] | |
294 | #if not size: | |
295 | #continue | |
296 | #end = segment['p_offset'] + segment['p_filesz'] | |
297 | #if end > file_size: | |
298 | #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n', | |
299 | #file_size, end) | |
300 | output[start - data_start:start - data_start + size] = ( | |
301 | segment.data()[offset:]) | |
302 | return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys, | |
303 | mem_end - data_start) |