binman: Add a function to create a sample ELF file
[platform/kernel/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 os
13 import re
14 import shutil
15 import struct
16 import tempfile
17
18 import tools
19
20 # This is enabled from control.py
21 debug = False
22
23 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
24
25
26 def GetSymbols(fname, patterns):
27     """Get the symbols from an ELF file
28
29     Args:
30         fname: Filename of the ELF file to read
31         patterns: List of regex patterns to search for, each a string
32
33     Returns:
34         None, if the file does not exist, or Dict:
35           key: Name of symbol
36           value: Hex value of symbol
37     """
38     stdout = command.Output('objdump', '-t', fname, raise_on_error=False)
39     lines = stdout.splitlines()
40     if patterns:
41         re_syms = re.compile('|'.join(patterns))
42     else:
43         re_syms = None
44     syms = {}
45     syms_started = False
46     for line in lines:
47         if not line or not syms_started:
48             if 'SYMBOL TABLE' in line:
49                 syms_started = True
50             line = None  # Otherwise code coverage complains about 'continue'
51             continue
52         if re_syms and not re_syms.search(line):
53             continue
54
55         space_pos = line.find(' ')
56         value, rest = line[:space_pos], line[space_pos + 1:]
57         flags = rest[:7]
58         parts = rest[7:].split()
59         section, size =  parts[:2]
60         if len(parts) > 2:
61             name = parts[2]
62             syms[name] = Symbol(section, int(value, 16), int(size,16),
63                                 flags[1] == 'w')
64
65     # Sort dict by address
66     return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
67
68 def GetSymbolAddress(fname, sym_name):
69     """Get a value of a symbol from an ELF file
70
71     Args:
72         fname: Filename of the ELF file to read
73         patterns: List of regex patterns to search for, each a string
74
75     Returns:
76         Symbol value (as an integer) or None if not found
77     """
78     syms = GetSymbols(fname, [sym_name])
79     sym = syms.get(sym_name)
80     if not sym:
81         return None
82     return sym.address
83
84 def LookupAndWriteSymbols(elf_fname, entry, section):
85     """Replace all symbols in an entry with their correct values
86
87     The entry contents is updated so that values for referenced symbols will be
88     visible at run time. This is done by finding out the symbols offsets in the
89     entry (using the ELF file) and replacing them with values from binman's data
90     structures.
91
92     Args:
93         elf_fname: Filename of ELF image containing the symbol information for
94             entry
95         entry: Entry to process
96         section: Section which can be used to lookup symbol values
97     """
98     fname = tools.GetInputFilename(elf_fname)
99     syms = GetSymbols(fname, ['image', 'binman'])
100     if not syms:
101         return
102     base = syms.get('__image_copy_start')
103     if not base:
104         return
105     for name, sym in syms.items():
106         if name.startswith('_binman'):
107             msg = ("Section '%s': Symbol '%s'\n   in entry '%s'" %
108                    (section.GetPath(), name, entry.GetPath()))
109             offset = sym.address - base.address
110             if offset < 0 or offset + sym.size > entry.contents_size:
111                 raise ValueError('%s has offset %x (size %x) but the contents '
112                                  'size is %x' % (entry.GetPath(), offset,
113                                                  sym.size, entry.contents_size))
114             if sym.size == 4:
115                 pack_string = '<I'
116             elif sym.size == 8:
117                 pack_string = '<Q'
118             else:
119                 raise ValueError('%s has size %d: only 4 and 8 are supported' %
120                                  (msg, sym.size))
121
122             # Look up the symbol in our entry tables.
123             value = section.LookupSymbol(name, sym.weak, msg)
124             if value is not None:
125                 value += base.address
126             else:
127                 value = -1
128                 pack_string = pack_string.lower()
129             value_bytes = struct.pack(pack_string, value)
130             if debug:
131                 print('%s:\n   insert %s, offset %x, value %x, length %d' %
132                       (msg, name, offset, value, len(value_bytes)))
133             entry.data = (entry.data[:offset] + value_bytes +
134                         entry.data[offset + sym.size:])
135
136 def MakeElf(elf_fname, text, data):
137     """Make an elf file with the given data in a single section
138
139     The output file has a several section including '.text' and '.data',
140     containing the info provided in arguments.
141
142     Args:
143         elf_fname: Output filename
144         text: Text (code) to put in the file's .text section
145         data: Data to put in the file's .data section
146     """
147     outdir = tempfile.mkdtemp(prefix='binman.elf.')
148     s_file = os.path.join(outdir, 'elf.S')
149
150     # Spilt the text into two parts so that we can make the entry point two
151     # bytes after the start of the text section
152     text_bytes1 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[:2]]
153     text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]]
154     data_bytes = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in data]
155     with open(s_file, 'w') as fd:
156         print('''/* Auto-generated C program to produce an ELF file for testing */
157
158 .section .text
159 .code32
160 .globl _start
161 .type _start, @function
162 %s
163 _start:
164 %s
165 .ident "comment"
166
167 .comm fred,8,4
168
169 .section .empty
170 .globl _empty
171 _empty:
172 .byte 1
173
174 .globl ernie
175 .data
176 .type ernie, @object
177 .size ernie, 4
178 ernie:
179 %s
180 ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
181         file=fd)
182     lds_file = os.path.join(outdir, 'elf.lds')
183
184     # Use a linker script to set the alignment and text address.
185     with open(lds_file, 'w') as fd:
186         print('''/* Auto-generated linker script to produce an ELF file for testing */
187
188 PHDRS
189 {
190     text PT_LOAD ;
191     data PT_LOAD ;
192     empty PT_LOAD FLAGS ( 6 ) ;
193     note PT_NOTE ;
194 }
195
196 SECTIONS
197 {
198     . = 0xfef20000;
199     ENTRY(_start)
200     .text . : SUBALIGN(0)
201     {
202         *(.text)
203     } :text
204     .data : {
205         *(.data)
206     } :data
207     _bss_start = .;
208     .empty : {
209         *(.empty)
210     } :empty
211     .note : {
212         *(.comment)
213     } :note
214     .bss _bss_start  (OVERLAY) : {
215         *(.bss)
216     }
217 }
218 ''', file=fd)
219     # -static: Avoid requiring any shared libraries
220     # -nostdlib: Don't link with C library
221     # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
222     #   text section at the start
223     # -m32: Build for 32-bit x86
224     # -T...: Specifies the link script, which sets the start address
225     stdout = command.Output('cc', '-static', '-nostdlib', '-Wl,--build-id=none',
226                             '-m32','-T', lds_file, '-o', elf_fname, s_file)
227     shutil.rmtree(outdir)