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