patman: Drop references to __future__
[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 collections import namedtuple, OrderedDict
9 import command
10 import io
11 import os
12 import re
13 import shutil
14 import struct
15 import tempfile
16
17 import tools
18 import tout
19
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
27 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
28
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
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     """
50     stdout = tools.Run('objdump', '-t', fname)
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:
73             name = parts[2] if parts[2] != '.hidden' else parts[3]
74             syms[name] = Symbol(section, int(value, 16), int(size,16),
75                                 flags[1] == 'w')
76
77     # Sort dict by address
78     return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
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
95
96 def LookupAndWriteSymbols(elf_fname, entry, section):
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
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.
103
104     Args:
105         elf_fname: Filename of ELF image containing the symbol information for
106             entry
107         entry: Entry to process
108         section: Section which can be used to lookup symbol values
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
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))
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.
135             value = section.LookupSymbol(name, sym.weak, msg, base.address)
136             if value is None:
137                 value = -1
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:])
144
145 def 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
187 ernie:
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
197 PHDRS
198 {
199     text PT_LOAD ;
200     data PT_LOAD ;
201     empty PT_LOAD FLAGS ( 6 ) ;
202     note PT_NOTE ;
203 }
204
205 SECTIONS
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
220     /DISCARD/ : {
221         *(.note.gnu.property)
222     }
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)
240
241 def 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)