Merge tag 'dm-pull-24jul19-take3' of https://gitlab.denx.de/u-boot/custodians/u-boot-dm
[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
21 ELF_TOOLS = True
22 try:
23     from elftools.elf.elffile import ELFFile
24     from elftools.elf.sections import SymbolTableSection
25 except:  # pragma: no cover
26     ELF_TOOLS = False
27
28 # This is enabled from control.py
29 debug = False
30
31 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
32
33 # Information about an ELF file:
34 #    data: Extracted program contents of ELF file (this would be loaded by an
35 #           ELF loader when reading this file
36 #    load: Load address of code
37 #    entry: Entry address of code
38 #    memsize: Number of bytes in memory occupied by loading this ELF file
39 ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
40
41
42 def GetSymbols(fname, patterns):
43     """Get the symbols from an ELF file
44
45     Args:
46         fname: Filename of the ELF file to read
47         patterns: List of regex patterns to search for, each a string
48
49     Returns:
50         None, if the file does not exist, or Dict:
51           key: Name of symbol
52           value: Hex value of symbol
53     """
54     stdout = command.Output('objdump', '-t', fname, raise_on_error=False)
55     lines = stdout.splitlines()
56     if patterns:
57         re_syms = re.compile('|'.join(patterns))
58     else:
59         re_syms = None
60     syms = {}
61     syms_started = False
62     for line in lines:
63         if not line or not syms_started:
64             if 'SYMBOL TABLE' in line:
65                 syms_started = True
66             line = None  # Otherwise code coverage complains about 'continue'
67             continue
68         if re_syms and not re_syms.search(line):
69             continue
70
71         space_pos = line.find(' ')
72         value, rest = line[:space_pos], line[space_pos + 1:]
73         flags = rest[:7]
74         parts = rest[7:].split()
75         section, size =  parts[:2]
76         if len(parts) > 2:
77             name = parts[2]
78             syms[name] = Symbol(section, int(value, 16), int(size,16),
79                                 flags[1] == 'w')
80
81     # Sort dict by address
82     return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
83
84 def GetSymbolAddress(fname, sym_name):
85     """Get a value of a symbol from an ELF file
86
87     Args:
88         fname: Filename of the ELF file to read
89         patterns: List of regex patterns to search for, each a string
90
91     Returns:
92         Symbol value (as an integer) or None if not found
93     """
94     syms = GetSymbols(fname, [sym_name])
95     sym = syms.get(sym_name)
96     if not sym:
97         return None
98     return sym.address
99
100 def LookupAndWriteSymbols(elf_fname, entry, section):
101     """Replace all symbols in an entry with their correct values
102
103     The entry contents is updated so that values for referenced symbols will be
104     visible at run time. This is done by finding out the symbols offsets in the
105     entry (using the ELF file) and replacing them with values from binman's data
106     structures.
107
108     Args:
109         elf_fname: Filename of ELF image containing the symbol information for
110             entry
111         entry: Entry to process
112         section: Section which can be used to lookup symbol values
113     """
114     fname = tools.GetInputFilename(elf_fname)
115     syms = GetSymbols(fname, ['image', 'binman'])
116     if not syms:
117         return
118     base = syms.get('__image_copy_start')
119     if not base:
120         return
121     for name, sym in syms.items():
122         if name.startswith('_binman'):
123             msg = ("Section '%s': Symbol '%s'\n   in entry '%s'" %
124                    (section.GetPath(), name, entry.GetPath()))
125             offset = sym.address - base.address
126             if offset < 0 or offset + sym.size > entry.contents_size:
127                 raise ValueError('%s has offset %x (size %x) but the contents '
128                                  'size is %x' % (entry.GetPath(), offset,
129                                                  sym.size, entry.contents_size))
130             if sym.size == 4:
131                 pack_string = '<I'
132             elif sym.size == 8:
133                 pack_string = '<Q'
134             else:
135                 raise ValueError('%s has size %d: only 4 and 8 are supported' %
136                                  (msg, sym.size))
137
138             # Look up the symbol in our entry tables.
139             value = section.LookupSymbol(name, sym.weak, msg)
140             if value is not None:
141                 value += base.address
142             else:
143                 value = -1
144                 pack_string = pack_string.lower()
145             value_bytes = struct.pack(pack_string, value)
146             if debug:
147                 print('%s:\n   insert %s, offset %x, value %x, length %d' %
148                       (msg, name, offset, value, len(value_bytes)))
149             entry.data = (entry.data[:offset] + value_bytes +
150                         entry.data[offset + sym.size:])
151
152 def MakeElf(elf_fname, text, data):
153     """Make an elf file with the given data in a single section
154
155     The output file has a several section including '.text' and '.data',
156     containing the info provided in arguments.
157
158     Args:
159         elf_fname: Output filename
160         text: Text (code) to put in the file's .text section
161         data: Data to put in the file's .data section
162     """
163     outdir = tempfile.mkdtemp(prefix='binman.elf.')
164     s_file = os.path.join(outdir, 'elf.S')
165
166     # Spilt the text into two parts so that we can make the entry point two
167     # bytes after the start of the text section
168     text_bytes1 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[:2]]
169     text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]]
170     data_bytes = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in data]
171     with open(s_file, 'w') as fd:
172         print('''/* Auto-generated C program to produce an ELF file for testing */
173
174 .section .text
175 .code32
176 .globl _start
177 .type _start, @function
178 %s
179 _start:
180 %s
181 .ident "comment"
182
183 .comm fred,8,4
184
185 .section .empty
186 .globl _empty
187 _empty:
188 .byte 1
189
190 .globl ernie
191 .data
192 .type ernie, @object
193 .size ernie, 4
194 ernie:
195 %s
196 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
197         file=fd)
198     lds_file = os.path.join(outdir, 'elf.lds')
199
200     # Use a linker script to set the alignment and text address.
201     with open(lds_file, 'w') as fd:
202         print('''/* Auto-generated linker script to produce an ELF file for testing */
203
204 PHDRS
205 {
206     text PT_LOAD ;
207     data PT_LOAD ;
208     empty PT_LOAD FLAGS ( 6 ) ;
209     note PT_NOTE ;
210 }
211
212 SECTIONS
213 {
214     . = 0xfef20000;
215     ENTRY(_start)
216     .text . : SUBALIGN(0)
217     {
218         *(.text)
219     } :text
220     .data : {
221         *(.data)
222     } :data
223     _bss_start = .;
224     .empty : {
225         *(.empty)
226     } :empty
227     .note : {
228         *(.comment)
229     } :note
230     .bss _bss_start  (OVERLAY) : {
231         *(.bss)
232     }
233 }
234 ''', file=fd)
235     # -static: Avoid requiring any shared libraries
236     # -nostdlib: Don't link with C library
237     # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
238     #   text section at the start
239     # -m32: Build for 32-bit x86
240     # -T...: Specifies the link script, which sets the start address
241     stdout = command.Output('cc', '-static', '-nostdlib', '-Wl,--build-id=none',
242                             '-m32','-T', lds_file, '-o', elf_fname, s_file)
243     shutil.rmtree(outdir)
244
245 def DecodeElf(data, location):
246     """Decode an ELF file and return information about it
247
248     Args:
249         data: Data from ELF file
250         location: Start address of data to return
251
252     Returns:
253         ElfInfo object containing information about the decoded ELF file
254     """
255     file_size = len(data)
256     with io.BytesIO(data) as fd:
257         elf = ELFFile(fd)
258         data_start = 0xffffffff;
259         data_end = 0;
260         mem_end = 0;
261         virt_to_phys = 0;
262
263         for i in range(elf.num_segments()):
264             segment = elf.get_segment(i)
265             if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
266                 skipped = 1  # To make code-coverage see this line
267                 continue
268             start = segment['p_paddr']
269             mend = start + segment['p_memsz']
270             rend = start + segment['p_filesz']
271             data_start = min(data_start, start)
272             data_end = max(data_end, rend)
273             mem_end = max(mem_end, mend)
274             if not virt_to_phys:
275                 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
276
277         output = bytearray(data_end - data_start)
278         for i in range(elf.num_segments()):
279             segment = elf.get_segment(i)
280             if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
281                 skipped = 1  # To make code-coverage see this line
282                 continue
283             start = segment['p_paddr']
284             offset = 0
285             if start < location:
286                 offset = location - start
287                 start = location
288             # A legal ELF file can have a program header with non-zero length
289             # but zero-length file size and a non-zero offset which, added
290             # together, are greater than input->size (i.e. the total file size).
291             #  So we need to not even test in the case that p_filesz is zero.
292             # Note: All of this code is commented out since we don't have a test
293             # case for it.
294             size = segment['p_filesz']
295             #if not size:
296                 #continue
297             #end = segment['p_offset'] + segment['p_filesz']
298             #if end > file_size:
299                 #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n',
300                                  #file_size, end)
301             output[start - data_start:start - data_start + size] = (
302                 segment.data()[offset:])
303     return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys,
304                    mem_end - data_start)