binman: Add a function to decode an ELF file

Add a function which decodes an ELF file, working out where in memory each
part of the data should be written.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/tools/binman/elf.py b/tools/binman/elf.py
index 82ea8c3..8147b34 100644
--- a/tools/binman/elf.py
+++ b/tools/binman/elf.py
@@ -9,6 +9,7 @@
 
 from collections import namedtuple, OrderedDict
 import command
+import io
 import os
 import re
 import shutil
@@ -17,11 +18,26 @@
 
 import tools
 
+ELF_TOOLS = True
+try:
+    from elftools.elf.elffile import ELFFile
+    from elftools.elf.sections import SymbolTableSection
+except:  # pragma: no cover
+    ELF_TOOLS = False
+
 # This is enabled from control.py
 debug = 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
@@ -225,3 +241,64 @@
     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)