• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8from __future__ import print_function
9
10from collections import namedtuple, OrderedDict
11import command
12import io
13import os
14import re
15import shutil
16import struct
17import tempfile
18
19import tools
20import tout
21
22ELF_TOOLS = True
23try:
24    from elftools.elf.elffile import ELFFile
25    from elftools.elf.sections import SymbolTableSection
26except:  # pragma: no cover
27    ELF_TOOLS = False
28
29Symbol = 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
37ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize'])
38
39
40def 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
82def 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
98def 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
147def 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
189ernie:
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
199PHDRS
200{
201    text PT_LOAD ;
202    data PT_LOAD ;
203    empty PT_LOAD FLAGS ( 6 ) ;
204    note PT_NOTE ;
205}
206
207SECTIONS
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
243def 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)
303