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/README b/tools/binman/README
index 28624fa..0ff30ef 100644
--- a/tools/binman/README
+++ b/tools/binman/README
@@ -181,6 +181,10 @@
Running binman
--------------
+First install prerequisites, e.g.
+
+ sudo apt-get install python-pyelftools python3-pyelftools
+
Type:
binman -b <board_name>
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)
diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py
index 3172982..e250637 100644
--- a/tools/binman/elf_test.py
+++ b/tools/binman/elf_test.py
@@ -156,6 +156,27 @@
self.assertEqual(expected_text + expected_data, data)
shutil.rmtree(outdir)
+ def testDecodeElf(self):
+ """Test for the MakeElf function"""
+ if not elf.ELF_TOOLS:
+ self.skipTest('Python elftools not available')
+ outdir = tempfile.mkdtemp(prefix='elf.')
+ expected_text = b'1234'
+ expected_data = b'wxyz'
+ elf_fname = os.path.join(outdir, 'elf')
+ elf.MakeElf(elf_fname, expected_text, expected_data)
+ data = tools.ReadFile(elf_fname)
+
+ load = 0xfef20000
+ entry = load + 2
+ expected = expected_text + expected_data
+ self.assertEqual(elf.ElfInfo(expected, load, entry, len(expected)),
+ elf.DecodeElf(data, 0))
+ self.assertEqual(elf.ElfInfo(b'\0\0' + expected[2:],
+ load, entry, len(expected)),
+ elf.DecodeElf(data, load + 2))
+ #shutil.rmtree(outdir)
+
if __name__ == '__main__':
unittest.main()