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