Merge https://gitlab.denx.de/u-boot/custodians/u-boot-sh
[oweals/u-boot.git] / tools / binman / elf.py
index 80ff2253f039f8e566cc9854bf608ddf11b5b9e2..f88031c2bf97c19874eb2a7ae596c7ba3cfe04ba 100644 (file)
@@ -1,24 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0+
 # Copyright (c) 2016 Google, Inc
 # Written by Simon Glass <sjg@chromium.org>
 #
-# SPDX-License-Identifier:      GPL-2.0+
-#
 # Handle various things related to ELF images
 #
 
 from collections import namedtuple, OrderedDict
-import command
+import io
 import os
 import re
+import shutil
 import struct
+import tempfile
 
-import tools
+from patman import command
+from patman import tools
+from patman import tout
 
-# This is enabled from control.py
-debug = False
+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
@@ -32,7 +47,7 @@ def GetSymbols(fname, patterns):
           key: Name of symbol
           value: Hex value of symbol
     """
-    stdout = command.Output('objdump', '-t', fname, raise_on_error=False)
+    stdout = tools.Run('objdump', '-t', fname)
     lines = stdout.splitlines()
     if patterns:
         re_syms = re.compile('|'.join(patterns))
@@ -55,10 +70,12 @@ def GetSymbols(fname, patterns):
         parts = rest[7:].split()
         section, size =  parts[:2]
         if len(parts) > 2:
-            name = 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')
-    return syms
+
+    # 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
@@ -76,19 +93,19 @@ def GetSymbolAddress(fname, sym_name):
         return None
     return sym.address
 
-def LookupAndWriteSymbols(elf_fname, entry, image):
+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 positions in
-    the entry (using the ELF file) and replacing them with values from binman's
-    data structures.
+    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
-        image: Image which can be used to lookup symbol values
+        section: Section which can be used to lookup symbol values
     """
     fname = tools.GetInputFilename(elf_fname)
     syms = GetSymbols(fname, ['image', 'binman'])
@@ -97,10 +114,10 @@ def LookupAndWriteSymbols(elf_fname, entry, image):
     base = syms.get('__image_copy_start')
     if not base:
         return
-    for name, sym in syms.iteritems():
+    for name, sym in syms.items():
         if name.startswith('_binman'):
-            msg = ("Image '%s': Symbol '%s'\n   in entry '%s'" %
-                   (image.GetPath(), name, entry.GetPath()))
+            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 '
@@ -115,15 +132,169 @@ def LookupAndWriteSymbols(elf_fname, entry, image):
                                  (msg, sym.size))
 
             # Look up the symbol in our entry tables.
-            value = image.LookupSymbol(name, sym.weak, msg)
-            if value is not None:
-                value += base.address
-            else:
+            value = section.LookupSymbol(name, sym.weak, msg, base.address)
+            if value is None:
                 value = -1
                 pack_string = pack_string.lower()
             value_bytes = struct.pack(pack_string, value)
-            if debug:
-                print('%s:\n   insert %s, offset %x, value %x, length %d' %
-                      (msg, name, offset, value, len(value_bytes)))
+            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' % tools.ToByte(byte) for byte in text[:2]]
+    text_bytes2 = ['\t.byte\t%#x' % tools.ToByte(byte) for byte in text[2:]]
+    data_bytes = ['\t.byte\t%#x' % tools.ToByte(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
+    stdout = command.Output('cc', '-static', '-nostdlib', '-Wl,--build-id=none',
+                            '-m32','-T', lds_file, '-o', elf_fname, s_file)
+    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)