# SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2016 Google, Inc # Written by Simon Glass # # Handle various things related to ELF images # from collections import namedtuple, OrderedDict import io import os import re import shutil import struct import tempfile from patman import command from patman import tools from patman import tout ELF_TOOLS = True try: from elftools.elf.elffile import ELFFile from elftools.elf.sections import SymbolTableSection except: # pragma: no cover ELF_TOOLS = False # Information about an EFL symbol: # section (str): Name of the section containing this symbol # address (int): Address of the symbol (its value) # size (int): Size of the symbol in bytes # weak (bool): True if the symbol is weak # offset (int or None): Offset of the symbol's data in the ELF file, or None if # not known Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset']) # Information about an ELF file: # data: Extracted program contents of ELF file (this would be loaded by an # ELF loader when reading this file # load: Load address of code # entry: Entry address of code # memsize: Number of bytes in memory occupied by loading this ELF file ElfInfo = namedtuple('ElfInfo', ['data', 'load', 'entry', 'memsize']) def GetSymbols(fname, patterns): """Get the symbols from an ELF file Args: fname: Filename of the ELF file to read patterns: List of regex patterns to search for, each a string Returns: None, if the file does not exist, or Dict: key: Name of symbol value: Hex value of symbol """ stdout = tools.Run('objdump', '-t', fname) lines = stdout.splitlines() if patterns: re_syms = re.compile('|'.join(patterns)) else: re_syms = None syms = {} syms_started = False for line in lines: if not line or not syms_started: if 'SYMBOL TABLE' in line: syms_started = True line = None # Otherwise code coverage complains about 'continue' continue if re_syms and not re_syms.search(line): continue space_pos = line.find(' ') value, rest = line[:space_pos], line[space_pos + 1:] flags = rest[:7] parts = rest[7:].split() section, size = parts[:2] if len(parts) > 2: name = parts[2] if parts[2] != '.hidden' else parts[3] syms[name] = Symbol(section, int(value, 16), int(size, 16), flags[1] == 'w', None) # Sort dict by address return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address)) def GetSymbolFileOffset(fname, patterns): """Get the symbols from an ELF file Args: fname: Filename of the ELF file to read patterns: List of regex patterns to search for, each a string Returns: None, if the file does not exist, or Dict: key: Name of symbol value: Hex value of symbol """ def _GetFileOffset(elf, addr): for seg in elf.iter_segments(): seg_end = seg['p_vaddr'] + seg['p_filesz'] if seg.header['p_type'] == 'PT_LOAD': if addr >= seg['p_vaddr'] and addr < seg_end: return addr - seg['p_vaddr'] + seg['p_offset'] if not ELF_TOOLS: raise ValueError('Python elftools package is not available') syms = {} with open(fname, 'rb') as fd: elf = ELFFile(fd) re_syms = re.compile('|'.join(patterns)) for section in elf.iter_sections(): if isinstance(section, SymbolTableSection): for symbol in section.iter_symbols(): if not re_syms or re_syms.search(symbol.name): addr = symbol.entry['st_value'] syms[symbol.name] = Symbol( section.name, addr, symbol.entry['st_size'], symbol.entry['st_info']['bind'] == 'STB_WEAK', _GetFileOffset(elf, addr)) # Sort dict by address return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address)) def GetSymbolAddress(fname, sym_name): """Get a value of a symbol from an ELF file Args: fname: Filename of the ELF file to read patterns: List of regex patterns to search for, each a string Returns: Symbol value (as an integer) or None if not found """ syms = GetSymbols(fname, [sym_name]) sym = syms.get(sym_name) if not sym: return None return sym.address def LookupAndWriteSymbols(elf_fname, entry, section): """Replace all symbols in an entry with their correct values The entry contents is updated so that values for referenced symbols will be visible at run time. This is done by finding out the symbols offsets in the entry (using the ELF file) and replacing them with values from binman's data structures. Args: elf_fname: Filename of ELF image containing the symbol information for entry entry: Entry to process section: Section which can be used to lookup symbol values """ fname = tools.GetInputFilename(elf_fname) syms = GetSymbols(fname, ['image', 'binman']) if not syms: return base = syms.get('__image_copy_start') if not base: return for name, sym in syms.items(): if name.startswith('_binman'): msg = ("Section '%s': Symbol '%s'\n in entry '%s'" % (section.GetPath(), name, entry.GetPath())) offset = sym.address - base.address if offset < 0 or offset + sym.size > entry.contents_size: raise ValueError('%s has offset %x (size %x) but the contents ' 'size is %x' % (entry.GetPath(), offset, sym.size, entry.contents_size)) if sym.size == 4: pack_string = 'size (i.e. the total file size). # So we need to not even test in the case that p_filesz is zero. # Note: All of this code is commented out since we don't have a test # case for it. size = segment['p_filesz'] #if not size: #continue #end = segment['p_offset'] + segment['p_filesz'] #if end > file_size: #raise ValueError('Underflow copying out the segment. File has %#x bytes left, segment end is %#x\n', #file_size, end) output[start - data_start:start - data_start + size] = ( segment.data()[offset:]) return ElfInfo(output, data_start, elf.header['e_entry'] + virt_to_phys, mem_end - data_start) def UpdateFile(infile, outfile, start_sym, end_sym, insert): tout.Notice("Creating file '%s' with data length %#x (%d) between symbols '%s' and '%s'" % (outfile, len(insert), len(insert), start_sym, end_sym)) syms = GetSymbolFileOffset(infile, [start_sym, end_sym]) if len(syms) != 2: raise ValueError("Expected two symbols '%s' and '%s': got %d: %s" % (start_sym, end_sym, len(syms), ','.join(syms.keys()))) size = syms[end_sym].offset - syms[start_sym].offset if len(insert) > size: raise ValueError("Not enough space in '%s' for data length %#x (%d); size is %#x (%d)" % (infile, len(insert), len(insert), size, size)) data = tools.ReadFile(infile) newdata = data[:syms[start_sym].offset] newdata += insert + tools.GetBytes(0, size - len(insert)) newdata += data[syms[end_sym].offset:] tools.WriteFile(outfile, newdata) tout.Info('Written to offset %#x' % syms[start_sym].offset)