summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorSimon Glass <sjg@chromium.org>2017-11-13 18:55:01 -0700
committerSimon Glass <sjg@chromium.org>2017-12-12 19:53:45 -0700
commit19790632648be6fff7a4898350bd52565bde7c96 (patch)
tree9277556deaaaf4e3f6095205de868cef9ecc5156 /tools
parent7fe9173be78f32047bc38f2d68ac86e871dbfcae (diff)
binman: Support accessing binman tables at run time
Binman construct images consisting of multiple binary files. These files sometimes need to know (at run timme) where their peers are located. For example, SPL may want to know where U-Boot is located in the image, so that it can jump to U-Boot correctly on boot. In general the positions where the binaries end up after binman has finished packing them cannot be known at compile time. One reason for this is that binman does not know the size of the binaries until everything is compiled, linked and converted to binaries with objcopy. To make this work, we add a feature to binman which checks each binary for symbol names starting with '_binman'. These are then decoded to figure out which entry and property they refer to. Then binman writes the value of this symbol into the appropriate binary. With this, the symbol will have the correct value at run time. Macros are used to make this easier to use. As an example, this declares a symbol that will access the 'u-boot-spl' entry to find the 'pos' value (i.e. the position of SPL in the image): binman_sym_declare(unsigned long, u_boot_spl, pos); This converts to a symbol called '_binman_u_boot_spl_prop_pos' in any binary that includes it. Binman then updates the value in that binary, ensuring that it can be accessed at runtime with: ulong u_boot_pos = binman_sym(ulong, u_boot_spl, pos); This assigns the variable u_boot_pos to the position of SPL in the image. Signed-off-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'tools')
-rwxr-xr-xtools/binman/binman.py4
-rw-r--r--tools/binman/control.py4
-rw-r--r--tools/binman/elf.py55
-rw-r--r--tools/binman/elf_test.py92
-rw-r--r--tools/binman/etype/entry.py8
-rw-r--r--tools/binman/etype/u_boot_spl.py6
-rw-r--r--tools/binman/ftest.py19
-rw-r--r--tools/binman/image.py79
-rw-r--r--tools/binman/image_test.py46
-rw-r--r--tools/binman/test/53_symbols.dts20
10 files changed, 323 insertions, 10 deletions
diff --git a/tools/binman/binman.py b/tools/binman/binman.py
index aa51396266..1c8e8dbff6 100755
--- a/tools/binman/binman.py
+++ b/tools/binman/binman.py
@@ -37,6 +37,7 @@ def RunTests(debug):
import entry_test
import fdt_test
import ftest
+ import image_test
import test
import doctest
@@ -53,7 +54,8 @@ def RunTests(debug):
# 'entry' module.
suite = unittest.TestLoader().loadTestsFromTestCase(entry_test.TestEntry)
suite.run(result)
- for module in (ftest.TestFunctional, fdt_test.TestFdt, elf_test.TestElf):
+ for module in (ftest.TestFunctional, fdt_test.TestFdt, elf_test.TestElf,
+ image_test.TestImage):
suite = unittest.TestLoader().loadTestsFromTestCase(module)
suite.run(result)
diff --git a/tools/binman/control.py b/tools/binman/control.py
index e175e8d41b..ffa2bbd80f 100644
--- a/tools/binman/control.py
+++ b/tools/binman/control.py
@@ -90,8 +90,7 @@ def Binman(options, args):
try:
tout.Init(options.verbosity)
- if options.debug:
- elf.debug = True
+ elf.debug = options.debug
try:
tools.SetInputDirs(options.indir)
tools.PrepareOutputDir(options.outdir, options.preserve)
@@ -112,6 +111,7 @@ def Binman(options, args):
image.CheckSize()
image.CheckEntries()
image.ProcessEntryContents()
+ image.WriteSymbols()
image.BuildImage()
finally:
tools.FinaliseOutputDir()
diff --git a/tools/binman/elf.py b/tools/binman/elf.py
index 0fb5a4a8ed..80ff2253f0 100644
--- a/tools/binman/elf.py
+++ b/tools/binman/elf.py
@@ -19,9 +19,6 @@ debug = False
Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
-# Used for tests which don't have an ELF file to read
-ignore_missing_files = False
-
def GetSymbols(fname, patterns):
"""Get the symbols from an ELF file
@@ -78,3 +75,55 @@ def GetSymbolAddress(fname, sym_name):
if not sym:
return None
return sym.address
+
+def LookupAndWriteSymbols(elf_fname, entry, image):
+ """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.
+
+ 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
+ """
+ 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.iteritems():
+ if name.startswith('_binman'):
+ msg = ("Image '%s': Symbol '%s'\n in entry '%s'" %
+ (image.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 = image.LookupSymbol(name, sym.weak, msg)
+ if value is not None:
+ value += base.address
+ else:
+ 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)))
+ entry.data = (entry.data[:offset] + value_bytes +
+ entry.data[offset + sym.size:])
diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py
index dca7bc59f9..e5fc28258d 100644
--- a/tools/binman/elf_test.py
+++ b/tools/binman/elf_test.py
@@ -6,21 +6,60 @@
#
# Test for the elf module
+from contextlib import contextmanager
import os
import sys
import unittest
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
import elf
binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
-fname = os.path.join(binman_dir, 'test', 'u_boot_ucode_ptr')
+
+# Use this to suppress stdout/stderr output:
+# with capture_sys_output() as (stdout, stderr)
+# ...do something...
+@contextmanager
+def capture_sys_output():
+ capture_out, capture_err = StringIO(), StringIO()
+ old_out, old_err = sys.stdout, sys.stderr
+ try:
+ sys.stdout, sys.stderr = capture_out, capture_err
+ yield capture_out, capture_err
+ finally:
+ sys.stdout, sys.stderr = old_out, old_err
+
+
+class FakeEntry:
+ def __init__(self, contents_size):
+ self.contents_size = contents_size
+ self.data = 'a' * contents_size
+
+ def GetPath(self):
+ return 'entry_path'
+
+class FakeImage:
+ def __init__(self, sym_value=1):
+ self.sym_value = sym_value
+
+ def GetPath(self):
+ return 'image_path'
+
+ def LookupSymbol(self, name, weak, msg):
+ return self.sym_value
class TestElf(unittest.TestCase):
def testAllSymbols(self):
+ fname = os.path.join(binman_dir, 'test', 'u_boot_ucode_ptr')
syms = elf.GetSymbols(fname, [])
self.assertIn('.ucode', syms)
def testRegexSymbols(self):
+ fname = os.path.join(binman_dir, 'test', 'u_boot_ucode_ptr')
syms = elf.GetSymbols(fname, ['ucode'])
self.assertIn('.ucode', syms)
syms = elf.GetSymbols(fname, ['missing'])
@@ -28,5 +67,56 @@ class TestElf(unittest.TestCase):
syms = elf.GetSymbols(fname, ['missing', 'ucode'])
self.assertIn('.ucode', syms)
+ def testMissingFile(self):
+ entry = FakeEntry(10)
+ image = FakeImage()
+ with self.assertRaises(ValueError) as e:
+ syms = elf.LookupAndWriteSymbols('missing-file', entry, image)
+ self.assertIn("Filename 'missing-file' not found in input path",
+ str(e.exception))
+
+ def testOutsideFile(self):
+ entry = FakeEntry(10)
+ image = FakeImage()
+ elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms')
+ with self.assertRaises(ValueError) as e:
+ syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
+ self.assertIn('entry_path has offset 4 (size 8) but the contents size '
+ 'is a', str(e.exception))
+
+ def testMissingImageStart(self):
+ entry = FakeEntry(10)
+ image = FakeImage()
+ elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms_bad')
+ self.assertEqual(elf.LookupAndWriteSymbols(elf_fname, entry, image),
+ None)
+
+ def testBadSymbolSize(self):
+ entry = FakeEntry(10)
+ image = FakeImage()
+ elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms_size')
+ with self.assertRaises(ValueError) as e:
+ syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
+ self.assertIn('has size 1: only 4 and 8 are supported',
+ str(e.exception))
+
+ def testNoValue(self):
+ entry = FakeEntry(20)
+ image = FakeImage(sym_value=None)
+ elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms')
+ syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
+ self.assertEqual(chr(255) * 16 + 'a' * 4, entry.data)
+
+ def testDebug(self):
+ elf.debug = True
+ entry = FakeEntry(20)
+ image = FakeImage()
+ elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms')
+ with capture_sys_output() as (stdout, stderr):
+ syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
+ elf.debug = False
+ self.assertTrue(len(stdout.getvalue()) > 0)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tools/binman/etype/entry.py b/tools/binman/etype/entry.py
index 67c57341ca..5541887d47 100644
--- a/tools/binman/etype/entry.py
+++ b/tools/binman/etype/entry.py
@@ -198,3 +198,11 @@ class Entry(object):
def ProcessContents(self):
pass
+
+ def WriteSymbols(self, image):
+ """Write symbol values into binary files for access at run time
+
+ Args:
+ image: Image containing the entry
+ """
+ pass
diff --git a/tools/binman/etype/u_boot_spl.py b/tools/binman/etype/u_boot_spl.py
index 68b0148427..3720b47fef 100644
--- a/tools/binman/etype/u_boot_spl.py
+++ b/tools/binman/etype/u_boot_spl.py
@@ -6,12 +6,18 @@
# Entry-type module for spl/u-boot-spl.bin
#
+import elf
+
from entry import Entry
from blob import Entry_blob
class Entry_u_boot_spl(Entry_blob):
def __init__(self, image, etype, node):
Entry_blob.__init__(self, image, etype, node)
+ self.elf_fname = 'spl/u-boot-spl'
def GetDefaultFilename(self):
return 'spl/u-boot-spl.bin'
+
+ def WriteSymbols(self, image):
+ elf.LookupAndWriteSymbols(self.elf_fname, self, image)
diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 2bee6a168f..5812ab397c 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -20,6 +20,7 @@ import binman
import cmdline
import command
import control
+import elf
import fdt
import fdt_util
import tools
@@ -573,6 +574,8 @@ class TestFunctional(unittest.TestCase):
def testImagePadByte(self):
"""Test that the image pad byte can be specified"""
+ with open(self.TestFile('bss_data')) as fd:
+ TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
data = self._DoReadFile('21_image_pad.dts')
self.assertEqual(U_BOOT_SPL_DATA + (chr(0xff) * 1) + U_BOOT_DATA, data)
@@ -889,6 +892,22 @@ class TestFunctional(unittest.TestCase):
data = self._DoReadFile('52_u_boot_spl_nodtb.dts')
self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
+ def testSymbols(self):
+ """Test binman can assign symbols embedded in U-Boot"""
+ elf_fname = self.TestFile('u_boot_binman_syms')
+ syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
+ addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
+ self.assertEqual(syms['_binman_u_boot_spl_prop_pos'].address, addr)
+
+ with open(self.TestFile('u_boot_binman_syms')) as fd:
+ TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
+ data = self._DoReadFile('53_symbols.dts')
+ sym_values = struct.pack('<LQL', 0x24 + 0, 0x24 + 24, 0x24 + 20)
+ expected = (sym_values + U_BOOT_SPL_DATA[16:] + chr(0xff) +
+ U_BOOT_DATA +
+ sym_values + U_BOOT_SPL_DATA[16:])
+ self.assertEqual(expected, data)
+
if __name__ == "__main__":
unittest.main()
diff --git a/tools/binman/image.py b/tools/binman/image.py
index 24c4f6f578..741630f091 100644
--- a/tools/binman/image.py
+++ b/tools/binman/image.py
@@ -6,8 +6,12 @@
# Class for an image, the output of binman
#
+from __future__ import print_function
+
from collections import OrderedDict
from operator import attrgetter
+import re
+import sys
import fdt_util
import tools
@@ -45,7 +49,7 @@ class Image:
address.
_entries: OrderedDict() of entries
"""
- def __init__(self, name, node):
+ def __init__(self, name, node, test=False):
global entry
global Entry
import entry
@@ -64,8 +68,9 @@ class Image:
self._end_4gb = False
self._entries = OrderedDict()
- self._ReadNode()
- self._ReadEntries()
+ if not test:
+ self._ReadNode()
+ self._ReadEntries()
def _ReadNode(self):
"""Read properties from the image node"""
@@ -119,6 +124,14 @@ class Image:
"""
raise ValueError("Image '%s': %s" % (self._node.path, msg))
+ def GetPath(self):
+ """Get the path of an image (in the FDT)
+
+ Returns:
+ Full path of the node for this image
+ """
+ return self._node.path
+
def _ReadEntries(self):
for node in self._node.subnodes:
self._entries[node.name] = Entry.Create(self, node)
@@ -220,6 +233,11 @@ class Image:
for entry in self._entries.values():
entry.ProcessContents()
+ def WriteSymbols(self):
+ """Write symbol values into binary files for access at run time"""
+ for entry in self._entries.values():
+ entry.WriteSymbols(self)
+
def BuildImage(self):
"""Write the image to a file"""
fname = tools.GetOutputFilename(self._filename)
@@ -230,3 +248,58 @@ class Image:
data = entry.GetData()
fd.seek(self._pad_before + entry.pos - self._skip_at_start)
fd.write(data)
+
+ def LookupSymbol(self, sym_name, optional, msg):
+ """Look up a symbol in an ELF file
+
+ Looks up a symbol in an ELF file. Only entry types which come from an
+ ELF image can be used by this function.
+
+ At present the only entry property supported is pos.
+
+ Args:
+ sym_name: Symbol name in the ELF file to look up in the format
+ _binman_<entry>_prop_<property> where <entry> is the name of
+ the entry and <property> is the property to find (e.g.
+ _binman_u_boot_prop_pos). As a special case, you can append
+ _any to <entry> to have it search for any matching entry. E.g.
+ _binman_u_boot_any_prop_pos will match entries called u-boot,
+ u-boot-img and u-boot-nodtb)
+ optional: True if the symbol is optional. If False this function
+ will raise if the symbol is not found
+ msg: Message to display if an error occurs
+
+ Returns:
+ Value that should be assigned to that symbol, or None if it was
+ optional and not found
+
+ Raises:
+ ValueError if the symbol is invalid or not found, or references a
+ property which is not supported
+ """
+ m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
+ if not m:
+ raise ValueError("%s: Symbol '%s' has invalid format" %
+ (msg, sym_name))
+ entry_name, prop_name = m.groups()
+ entry_name = entry_name.replace('_', '-')
+ entry = self._entries.get(entry_name)
+ if not entry:
+ if entry_name.endswith('-any'):
+ root = entry_name[:-4]
+ for name in self._entries:
+ if name.startswith(root):
+ rest = name[len(root):]
+ if rest in ['', '-img', '-nodtb']:
+ entry = self._entries[name]
+ if not entry:
+ err = ("%s: Entry '%s' not found in list (%s)" %
+ (msg, entry_name, ','.join(self._entries.keys())))
+ if optional:
+ print('Warning: %s' % err, file=sys.stderr)
+ return None
+ raise ValueError(err)
+ if prop_name == 'pos':
+ return entry.pos
+ else:
+ raise ValueError("%s: No such property '%s'" % (msg, prop_name))
diff --git a/tools/binman/image_test.py b/tools/binman/image_test.py
new file mode 100644
index 0000000000..1b50dda4dc
--- /dev/null
+++ b/tools/binman/image_test.py
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2017 Google, Inc
+# Written by Simon Glass <sjg@chromium.org>
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Test for the image module
+
+import unittest
+
+from image import Image
+from elf_test import capture_sys_output
+
+class TestImage(unittest.TestCase):
+ def testInvalidFormat(self):
+ image = Image('name', 'node', test=True)
+ with self.assertRaises(ValueError) as e:
+ image.LookupSymbol('_binman_something_prop_', False, 'msg')
+ self.assertIn(
+ "msg: Symbol '_binman_something_prop_' has invalid format",
+ str(e.exception))
+
+ def testMissingSymbol(self):
+ image = Image('name', 'node', test=True)
+ image._entries = {}
+ with self.assertRaises(ValueError) as e:
+ image.LookupSymbol('_binman_type_prop_pname', False, 'msg')
+ self.assertIn("msg: Entry 'type' not found in list ()",
+ str(e.exception))
+
+ def testMissingSymbolOptional(self):
+ image = Image('name', 'node', test=True)
+ image._entries = {}
+ with capture_sys_output() as (stdout, stderr):
+ val = image.LookupSymbol('_binman_type_prop_pname', True, 'msg')
+ self.assertEqual(val, None)
+ self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n",
+ stderr.getvalue())
+ self.assertEqual('', stdout.getvalue())
+
+ def testBadProperty(self):
+ image = Image('name', 'node', test=True)
+ image._entries = {'u-boot': 1}
+ with self.assertRaises(ValueError) as e:
+ image.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg')
+ self.assertIn("msg: No such property 'bad", str(e.exception))
diff --git a/tools/binman/test/53_symbols.dts b/tools/binman/test/53_symbols.dts
new file mode 100644
index 0000000000..980b066eb2
--- /dev/null
+++ b/tools/binman/test/53_symbols.dts
@@ -0,0 +1,20 @@
+/dts-v1/;
+
+/ {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ binman {
+ pad-byte = <0xff>;
+ u-boot-spl {
+ };
+
+ u-boot {
+ pos = <20>;
+ };
+
+ u-boot-spl2 {
+ type = "u-boot-spl";
+ };
+ };
+};