304 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
| # SPDX-License-Identifier: GPL-2.0+
 | |
| # Copyright (c) 2016 Google, Inc
 | |
| # Written by Simon Glass <sjg@chromium.org>
 | |
| #
 | |
| # 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
 | |
| 
 | |
| Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
 | |
| 
 | |
| # 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')
 | |
| 
 | |
|     # 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 = '<I'
 | |
|             elif sym.size == 8:
 | |
|                 pack_string = '<Q'
 | |
|             else:
 | |
|                 raise ValueError('%s has size %d: only 4 and 8 are supported' %
 | |
|                                  (msg, sym.size))
 | |
| 
 | |
|             # Look up the symbol in our entry tables.
 | |
|             value = section.GetImage().LookupImageSymbol(name, sym.weak, msg,
 | |
|                                                          base.address)
 | |
|             if value is None:
 | |
|                 value = -1
 | |
|                 pack_string = pack_string.lower()
 | |
|             value_bytes = struct.pack(pack_string, value)
 | |
|             tout.Debug('%s:\n   insert %s, offset %x, value %x, length %d' %
 | |
|                        (msg, name, offset, value, len(value_bytes)))
 | |
|             entry.data = (entry.data[:offset] + value_bytes +
 | |
|                         entry.data[offset + sym.size:])
 | |
| 
 | |
| def MakeElf(elf_fname, text, data):
 | |
|     """Make an elf file with the given data in a single section
 | |
| 
 | |
|     The output file has a several section including '.text' and '.data',
 | |
|     containing the info provided in arguments.
 | |
| 
 | |
|     Args:
 | |
|         elf_fname: Output filename
 | |
|         text: Text (code) to put in the file's .text section
 | |
|         data: Data to put in the file's .data section
 | |
|     """
 | |
|     outdir = tempfile.mkdtemp(prefix='binman.elf.')
 | |
|     s_file = os.path.join(outdir, 'elf.S')
 | |
| 
 | |
|     # Spilt the text into two parts so that we can make the entry point two
 | |
|     # bytes after the start of the text section
 | |
|     text_bytes1 = ['\t.byte\t%#x' % byte for byte in text[:2]]
 | |
|     text_bytes2 = ['\t.byte\t%#x' % byte for byte in text[2:]]
 | |
|     data_bytes = ['\t.byte\t%#x' % byte for byte in data]
 | |
|     with open(s_file, 'w') as fd:
 | |
|         print('''/* Auto-generated C program to produce an ELF file for testing */
 | |
| 
 | |
| .section .text
 | |
| .code32
 | |
| .globl _start
 | |
| .type _start, @function
 | |
| %s
 | |
| _start:
 | |
| %s
 | |
| .ident "comment"
 | |
| 
 | |
| .comm fred,8,4
 | |
| 
 | |
| .section .empty
 | |
| .globl _empty
 | |
| _empty:
 | |
| .byte 1
 | |
| 
 | |
| .globl ernie
 | |
| .data
 | |
| .type ernie, @object
 | |
| .size ernie, 4
 | |
| ernie:
 | |
| %s
 | |
| ''' % ('\n'.join(text_bytes1), '\n'.join(text_bytes2), '\n'.join(data_bytes)),
 | |
|         file=fd)
 | |
|     lds_file = os.path.join(outdir, 'elf.lds')
 | |
| 
 | |
|     # Use a linker script to set the alignment and text address.
 | |
|     with open(lds_file, 'w') as fd:
 | |
|         print('''/* Auto-generated linker script to produce an ELF file for testing */
 | |
| 
 | |
| PHDRS
 | |
| {
 | |
|     text PT_LOAD ;
 | |
|     data PT_LOAD ;
 | |
|     empty PT_LOAD FLAGS ( 6 ) ;
 | |
|     note PT_NOTE ;
 | |
| }
 | |
| 
 | |
| SECTIONS
 | |
| {
 | |
|     . = 0xfef20000;
 | |
|     ENTRY(_start)
 | |
|     .text . : SUBALIGN(0)
 | |
|     {
 | |
|         *(.text)
 | |
|     } :text
 | |
|     .data : {
 | |
|         *(.data)
 | |
|     } :data
 | |
|     _bss_start = .;
 | |
|     .empty : {
 | |
|         *(.empty)
 | |
|     } :empty
 | |
|     /DISCARD/ : {
 | |
|         *(.note.gnu.property)
 | |
|     }
 | |
|     .note : {
 | |
|         *(.comment)
 | |
|     } :note
 | |
|     .bss _bss_start  (OVERLAY) : {
 | |
|         *(.bss)
 | |
|     }
 | |
| }
 | |
| ''', file=fd)
 | |
|     # -static: Avoid requiring any shared libraries
 | |
|     # -nostdlib: Don't link with C library
 | |
|     # -Wl,--build-id=none: Don't generate a build ID, so that we just get the
 | |
|     #   text section at the start
 | |
|     # -m32: Build for 32-bit x86
 | |
|     # -T...: Specifies the link script, which sets the start address
 | |
|     cc, args = tools.GetTargetCompileTool('cc')
 | |
|     args += ['-static', '-nostdlib', '-Wl,--build-id=none', '-m32', '-T',
 | |
|             lds_file, '-o', elf_fname, s_file]
 | |
|     stdout = command.Output(cc, *args)
 | |
|     shutil.rmtree(outdir)
 | |
| 
 | |
| def DecodeElf(data, location):
 | |
|     """Decode an ELF file and return information about it
 | |
| 
 | |
|     Args:
 | |
|         data: Data from ELF file
 | |
|         location: Start address of data to return
 | |
| 
 | |
|     Returns:
 | |
|         ElfInfo object containing information about the decoded ELF file
 | |
|     """
 | |
|     file_size = len(data)
 | |
|     with io.BytesIO(data) as fd:
 | |
|         elf = ELFFile(fd)
 | |
|         data_start = 0xffffffff;
 | |
|         data_end = 0;
 | |
|         mem_end = 0;
 | |
|         virt_to_phys = 0;
 | |
| 
 | |
|         for i in range(elf.num_segments()):
 | |
|             segment = elf.get_segment(i)
 | |
|             if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
 | |
|                 skipped = 1  # To make code-coverage see this line
 | |
|                 continue
 | |
|             start = segment['p_paddr']
 | |
|             mend = start + segment['p_memsz']
 | |
|             rend = start + segment['p_filesz']
 | |
|             data_start = min(data_start, start)
 | |
|             data_end = max(data_end, rend)
 | |
|             mem_end = max(mem_end, mend)
 | |
|             if not virt_to_phys:
 | |
|                 virt_to_phys = segment['p_paddr'] - segment['p_vaddr']
 | |
| 
 | |
|         output = bytearray(data_end - data_start)
 | |
|         for i in range(elf.num_segments()):
 | |
|             segment = elf.get_segment(i)
 | |
|             if segment['p_type'] != 'PT_LOAD' or not segment['p_memsz']:
 | |
|                 skipped = 1  # To make code-coverage see this line
 | |
|                 continue
 | |
|             start = segment['p_paddr']
 | |
|             offset = 0
 | |
|             if start < location:
 | |
|                 offset = location - start
 | |
|                 start = location
 | |
|             # A legal ELF file can have a program header with non-zero length
 | |
|             # but zero-length file size and a non-zero offset which, added
 | |
|             # together, are greater than input->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)
 |