Commit 19790632648be6fff7a4898350bd52565bde7c96

Authored by Simon Glass
1 parent 7fe9173be7

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>

Showing 11 changed files with 403 additions and 10 deletions Side-by-side Diff

include/binman_sym.h
  1 +/*
  2 + * Symbol access for symbols set up by binman as part of the build.
  3 + *
  4 + * This allows C code to access the position of a particular part of the image
  5 + * assembled by binman.
  6 + *
  7 + * Copyright (c) 2017 Google, Inc
  8 + *
  9 + * SPDX-License-Identifier: GPL-2.0+
  10 + */
  11 +
  12 +#ifndef __BINMAN_SYM_H
  13 +#define __BINMAN_SYM_H
  14 +
  15 +#define BINMAN_SYM_MISSING (-1UL)
  16 +
  17 +#ifdef CONFIG_BINMAN
  18 +
  19 +/**
  20 + * binman_symname() - Internal fnuction to get a binman symbol name
  21 + *
  22 + * @entry_name: Name of the entry to look for (e.g. 'u_boot_spl')
  23 + * @_prop_name: Property value to get from that entry (e.g. 'pos')
  24 + * @returns name of the symbol for that entry and property
  25 + */
  26 +#define binman_symname(_entry_name, _prop_name) \
  27 + _binman_ ## _entry_name ## _prop_ ## _prop_name
  28 +
  29 +/**
  30 + * binman_sym_declare() - Declare a symbol that will be used at run-time
  31 + *
  32 + * @_type: Type f the symbol (e.g. unsigned long)
  33 + * @entry_name: Name of the entry to look for (e.g. 'u_boot_spl')
  34 + * @_prop_name: Property value to get from that entry (e.g. 'pos')
  35 + */
  36 +#define binman_sym_declare(_type, _entry_name, _prop_name) \
  37 + _type binman_symname(_entry_name, _prop_name) \
  38 + __attribute__((aligned(4), unused, section(".binman_sym")))
  39 +
  40 +/**
  41 + * binman_sym_declare_optional() - Declare an optional symbol
  42 + *
  43 + * If this symbol cannot be provided by binman, an error will not be generated.
  44 + * Instead the image will be assigned the value BINMAN_SYM_MISSING.
  45 + *
  46 + * @_type: Type f the symbol (e.g. unsigned long)
  47 + * @entry_name: Name of the entry to look for (e.g. 'u_boot_spl')
  48 + * @_prop_name: Property value to get from that entry (e.g. 'pos')
  49 + */
  50 +#define binman_sym_declare_optional(_type, _entry_name, _prop_name) \
  51 + _type binman_symname(_entry_name, _prop_name) \
  52 + __attribute__((aligned(4), weak, unused, \
  53 + section(".binman_sym")))
  54 +
  55 +/**
  56 + * binman_sym() - Access a previously declared symbol
  57 + *
  58 + * This is used to get the value of a symbol. E.g.:
  59 + *
  60 + * ulong address = binman_sym(ulong, u_boot_spl, pos);
  61 + *
  62 + * @_type: Type f the symbol (e.g. unsigned long)
  63 + * @entry_name: Name of the entry to look for (e.g. 'u_boot_spl')
  64 + * @_prop_name: Property value to get from that entry (e.g. 'pos')
  65 + * @returns value of that property (filled in by binman)
  66 + */
  67 +#define binman_sym(_type, _entry_name, _prop_name) \
  68 + (*(_type *)&binman_symname(_entry_name, _prop_name))
  69 +
  70 +#else /* !BINMAN */
  71 +
  72 +#define binman_sym_declare(_type, _entry_name, _prop_name)
  73 +
  74 +#define binman_sym_declare_optional(_type, _entry_name, _prop_name)
  75 +
  76 +#define binman_sym(_type, _entry_name, _prop_name) BINMAN_SYM_MISSING
  77 +
  78 +#endif /* BINMAN */
  79 +
  80 +#endif
tools/binman/binman.py
... ... @@ -37,6 +37,7 @@
37 37 import entry_test
38 38 import fdt_test
39 39 import ftest
  40 + import image_test
40 41 import test
41 42 import doctest
42 43  
... ... @@ -53,7 +54,8 @@
53 54 # 'entry' module.
54 55 suite = unittest.TestLoader().loadTestsFromTestCase(entry_test.TestEntry)
55 56 suite.run(result)
56   - for module in (ftest.TestFunctional, fdt_test.TestFdt, elf_test.TestElf):
  57 + for module in (ftest.TestFunctional, fdt_test.TestFdt, elf_test.TestElf,
  58 + image_test.TestImage):
57 59 suite = unittest.TestLoader().loadTestsFromTestCase(module)
58 60 suite.run(result)
59 61  
tools/binman/control.py
... ... @@ -90,8 +90,7 @@
90 90  
91 91 try:
92 92 tout.Init(options.verbosity)
93   - if options.debug:
94   - elf.debug = True
  93 + elf.debug = options.debug
95 94 try:
96 95 tools.SetInputDirs(options.indir)
97 96 tools.PrepareOutputDir(options.outdir, options.preserve)
... ... @@ -112,6 +111,7 @@
112 111 image.CheckSize()
113 112 image.CheckEntries()
114 113 image.ProcessEntryContents()
  114 + image.WriteSymbols()
115 115 image.BuildImage()
116 116 finally:
117 117 tools.FinaliseOutputDir()
... ... @@ -19,10 +19,7 @@
19 19  
20 20 Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
21 21  
22   -# Used for tests which don't have an ELF file to read
23   -ignore_missing_files = False
24 22  
25   -
26 23 def GetSymbols(fname, patterns):
27 24 """Get the symbols from an ELF file
28 25  
... ... @@ -78,4 +75,56 @@
78 75 if not sym:
79 76 return None
80 77 return sym.address
  78 +
  79 +def LookupAndWriteSymbols(elf_fname, entry, image):
  80 + """Replace all symbols in an entry with their correct values
  81 +
  82 + The entry contents is updated so that values for referenced symbols will be
  83 + visible at run time. This is done by finding out the symbols positions in
  84 + the entry (using the ELF file) and replacing them with values from binman's
  85 + data structures.
  86 +
  87 + Args:
  88 + elf_fname: Filename of ELF image containing the symbol information for
  89 + entry
  90 + entry: Entry to process
  91 + image: Image which can be used to lookup symbol values
  92 + """
  93 + fname = tools.GetInputFilename(elf_fname)
  94 + syms = GetSymbols(fname, ['image', 'binman'])
  95 + if not syms:
  96 + return
  97 + base = syms.get('__image_copy_start')
  98 + if not base:
  99 + return
  100 + for name, sym in syms.iteritems():
  101 + if name.startswith('_binman'):
  102 + msg = ("Image '%s': Symbol '%s'\n in entry '%s'" %
  103 + (image.GetPath(), name, entry.GetPath()))
  104 + offset = sym.address - base.address
  105 + if offset < 0 or offset + sym.size > entry.contents_size:
  106 + raise ValueError('%s has offset %x (size %x) but the contents '
  107 + 'size is %x' % (entry.GetPath(), offset,
  108 + sym.size, entry.contents_size))
  109 + if sym.size == 4:
  110 + pack_string = '<I'
  111 + elif sym.size == 8:
  112 + pack_string = '<Q'
  113 + else:
  114 + raise ValueError('%s has size %d: only 4 and 8 are supported' %
  115 + (msg, sym.size))
  116 +
  117 + # Look up the symbol in our entry tables.
  118 + value = image.LookupSymbol(name, sym.weak, msg)
  119 + if value is not None:
  120 + value += base.address
  121 + else:
  122 + value = -1
  123 + pack_string = pack_string.lower()
  124 + value_bytes = struct.pack(pack_string, value)
  125 + if debug:
  126 + print('%s:\n insert %s, offset %x, value %x, length %d' %
  127 + (msg, name, offset, value, len(value_bytes)))
  128 + entry.data = (entry.data[:offset] + value_bytes +
  129 + entry.data[offset + sym.size:])
tools/binman/elf_test.py
... ... @@ -6,27 +6,117 @@
6 6 #
7 7 # Test for the elf module
8 8  
  9 +from contextlib import contextmanager
9 10 import os
10 11 import sys
11 12 import unittest
12 13  
  14 +try:
  15 + from StringIO import StringIO
  16 +except ImportError:
  17 + from io import StringIO
  18 +
13 19 import elf
14 20  
15 21 binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
16   -fname = os.path.join(binman_dir, 'test', 'u_boot_ucode_ptr')
17 22  
  23 +# Use this to suppress stdout/stderr output:
  24 +# with capture_sys_output() as (stdout, stderr)
  25 +# ...do something...
  26 +@contextmanager
  27 +def capture_sys_output():
  28 + capture_out, capture_err = StringIO(), StringIO()
  29 + old_out, old_err = sys.stdout, sys.stderr
  30 + try:
  31 + sys.stdout, sys.stderr = capture_out, capture_err
  32 + yield capture_out, capture_err
  33 + finally:
  34 + sys.stdout, sys.stderr = old_out, old_err
  35 +
  36 +
  37 +class FakeEntry:
  38 + def __init__(self, contents_size):
  39 + self.contents_size = contents_size
  40 + self.data = 'a' * contents_size
  41 +
  42 + def GetPath(self):
  43 + return 'entry_path'
  44 +
  45 +class FakeImage:
  46 + def __init__(self, sym_value=1):
  47 + self.sym_value = sym_value
  48 +
  49 + def GetPath(self):
  50 + return 'image_path'
  51 +
  52 + def LookupSymbol(self, name, weak, msg):
  53 + return self.sym_value
  54 +
18 55 class TestElf(unittest.TestCase):
19 56 def testAllSymbols(self):
  57 + fname = os.path.join(binman_dir, 'test', 'u_boot_ucode_ptr')
20 58 syms = elf.GetSymbols(fname, [])
21 59 self.assertIn('.ucode', syms)
22 60  
23 61 def testRegexSymbols(self):
  62 + fname = os.path.join(binman_dir, 'test', 'u_boot_ucode_ptr')
24 63 syms = elf.GetSymbols(fname, ['ucode'])
25 64 self.assertIn('.ucode', syms)
26 65 syms = elf.GetSymbols(fname, ['missing'])
27 66 self.assertNotIn('.ucode', syms)
28 67 syms = elf.GetSymbols(fname, ['missing', 'ucode'])
29 68 self.assertIn('.ucode', syms)
  69 +
  70 + def testMissingFile(self):
  71 + entry = FakeEntry(10)
  72 + image = FakeImage()
  73 + with self.assertRaises(ValueError) as e:
  74 + syms = elf.LookupAndWriteSymbols('missing-file', entry, image)
  75 + self.assertIn("Filename 'missing-file' not found in input path",
  76 + str(e.exception))
  77 +
  78 + def testOutsideFile(self):
  79 + entry = FakeEntry(10)
  80 + image = FakeImage()
  81 + elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms')
  82 + with self.assertRaises(ValueError) as e:
  83 + syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
  84 + self.assertIn('entry_path has offset 4 (size 8) but the contents size '
  85 + 'is a', str(e.exception))
  86 +
  87 + def testMissingImageStart(self):
  88 + entry = FakeEntry(10)
  89 + image = FakeImage()
  90 + elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms_bad')
  91 + self.assertEqual(elf.LookupAndWriteSymbols(elf_fname, entry, image),
  92 + None)
  93 +
  94 + def testBadSymbolSize(self):
  95 + entry = FakeEntry(10)
  96 + image = FakeImage()
  97 + elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms_size')
  98 + with self.assertRaises(ValueError) as e:
  99 + syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
  100 + self.assertIn('has size 1: only 4 and 8 are supported',
  101 + str(e.exception))
  102 +
  103 + def testNoValue(self):
  104 + entry = FakeEntry(20)
  105 + image = FakeImage(sym_value=None)
  106 + elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms')
  107 + syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
  108 + self.assertEqual(chr(255) * 16 + 'a' * 4, entry.data)
  109 +
  110 + def testDebug(self):
  111 + elf.debug = True
  112 + entry = FakeEntry(20)
  113 + image = FakeImage()
  114 + elf_fname = os.path.join(binman_dir, 'test', 'u_boot_binman_syms')
  115 + with capture_sys_output() as (stdout, stderr):
  116 + syms = elf.LookupAndWriteSymbols(elf_fname, entry, image)
  117 + elf.debug = False
  118 + self.assertTrue(len(stdout.getvalue()) > 0)
  119 +
30 120  
31 121 if __name__ == '__main__':
32 122 unittest.main()
tools/binman/etype/entry.py
... ... @@ -198,4 +198,12 @@
198 198  
199 199 def ProcessContents(self):
200 200 pass
  201 +
  202 + def WriteSymbols(self, image):
  203 + """Write symbol values into binary files for access at run time
  204 +
  205 + Args:
  206 + image: Image containing the entry
  207 + """
  208 + pass
tools/binman/etype/u_boot_spl.py
... ... @@ -6,13 +6,19 @@
6 6 # Entry-type module for spl/u-boot-spl.bin
7 7 #
8 8  
  9 +import elf
  10 +
9 11 from entry import Entry
10 12 from blob import Entry_blob
11 13  
12 14 class Entry_u_boot_spl(Entry_blob):
13 15 def __init__(self, image, etype, node):
14 16 Entry_blob.__init__(self, image, etype, node)
  17 + self.elf_fname = 'spl/u-boot-spl'
15 18  
16 19 def GetDefaultFilename(self):
17 20 return 'spl/u-boot-spl.bin'
  21 +
  22 + def WriteSymbols(self, image):
  23 + elf.LookupAndWriteSymbols(self.elf_fname, self, image)
tools/binman/ftest.py
... ... @@ -20,6 +20,7 @@
20 20 import cmdline
21 21 import command
22 22 import control
  23 +import elf
23 24 import fdt
24 25 import fdt_util
25 26 import tools
... ... @@ -573,6 +574,8 @@
573 574  
574 575 def testImagePadByte(self):
575 576 """Test that the image pad byte can be specified"""
  577 + with open(self.TestFile('bss_data')) as fd:
  578 + TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
576 579 data = self._DoReadFile('21_image_pad.dts')
577 580 self.assertEqual(U_BOOT_SPL_DATA + (chr(0xff) * 1) + U_BOOT_DATA, data)
578 581  
... ... @@ -888,6 +891,22 @@
888 891 """Test that an image with spl/u-boot-spl-nodtb.bin can be created"""
889 892 data = self._DoReadFile('52_u_boot_spl_nodtb.dts')
890 893 self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
  894 +
  895 + def testSymbols(self):
  896 + """Test binman can assign symbols embedded in U-Boot"""
  897 + elf_fname = self.TestFile('u_boot_binman_syms')
  898 + syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
  899 + addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
  900 + self.assertEqual(syms['_binman_u_boot_spl_prop_pos'].address, addr)
  901 +
  902 + with open(self.TestFile('u_boot_binman_syms')) as fd:
  903 + TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
  904 + data = self._DoReadFile('53_symbols.dts')
  905 + sym_values = struct.pack('<LQL', 0x24 + 0, 0x24 + 24, 0x24 + 20)
  906 + expected = (sym_values + U_BOOT_SPL_DATA[16:] + chr(0xff) +
  907 + U_BOOT_DATA +
  908 + sym_values + U_BOOT_SPL_DATA[16:])
  909 + self.assertEqual(expected, data)
891 910  
892 911  
893 912 if __name__ == "__main__":
tools/binman/image.py
... ... @@ -6,8 +6,12 @@
6 6 # Class for an image, the output of binman
7 7 #
8 8  
  9 +from __future__ import print_function
  10 +
9 11 from collections import OrderedDict
10 12 from operator import attrgetter
  13 +import re
  14 +import sys
11 15  
12 16 import fdt_util
13 17 import tools
... ... @@ -45,7 +49,7 @@
45 49 address.
46 50 _entries: OrderedDict() of entries
47 51 """
48   - def __init__(self, name, node):
  52 + def __init__(self, name, node, test=False):
49 53 global entry
50 54 global Entry
51 55 import entry
... ... @@ -64,8 +68,9 @@
64 68 self._end_4gb = False
65 69 self._entries = OrderedDict()
66 70  
67   - self._ReadNode()
68   - self._ReadEntries()
  71 + if not test:
  72 + self._ReadNode()
  73 + self._ReadEntries()
69 74  
70 75 def _ReadNode(self):
71 76 """Read properties from the image node"""
... ... @@ -119,6 +124,14 @@
119 124 """
120 125 raise ValueError("Image '%s': %s" % (self._node.path, msg))
121 126  
  127 + def GetPath(self):
  128 + """Get the path of an image (in the FDT)
  129 +
  130 + Returns:
  131 + Full path of the node for this image
  132 + """
  133 + return self._node.path
  134 +
122 135 def _ReadEntries(self):
123 136 for node in self._node.subnodes:
124 137 self._entries[node.name] = Entry.Create(self, node)
... ... @@ -220,6 +233,11 @@
220 233 for entry in self._entries.values():
221 234 entry.ProcessContents()
222 235  
  236 + def WriteSymbols(self):
  237 + """Write symbol values into binary files for access at run time"""
  238 + for entry in self._entries.values():
  239 + entry.WriteSymbols(self)
  240 +
223 241 def BuildImage(self):
224 242 """Write the image to a file"""
225 243 fname = tools.GetOutputFilename(self._filename)
... ... @@ -230,4 +248,59 @@
230 248 data = entry.GetData()
231 249 fd.seek(self._pad_before + entry.pos - self._skip_at_start)
232 250 fd.write(data)
  251 +
  252 + def LookupSymbol(self, sym_name, optional, msg):
  253 + """Look up a symbol in an ELF file
  254 +
  255 + Looks up a symbol in an ELF file. Only entry types which come from an
  256 + ELF image can be used by this function.
  257 +
  258 + At present the only entry property supported is pos.
  259 +
  260 + Args:
  261 + sym_name: Symbol name in the ELF file to look up in the format
  262 + _binman_<entry>_prop_<property> where <entry> is the name of
  263 + the entry and <property> is the property to find (e.g.
  264 + _binman_u_boot_prop_pos). As a special case, you can append
  265 + _any to <entry> to have it search for any matching entry. E.g.
  266 + _binman_u_boot_any_prop_pos will match entries called u-boot,
  267 + u-boot-img and u-boot-nodtb)
  268 + optional: True if the symbol is optional. If False this function
  269 + will raise if the symbol is not found
  270 + msg: Message to display if an error occurs
  271 +
  272 + Returns:
  273 + Value that should be assigned to that symbol, or None if it was
  274 + optional and not found
  275 +
  276 + Raises:
  277 + ValueError if the symbol is invalid or not found, or references a
  278 + property which is not supported
  279 + """
  280 + m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
  281 + if not m:
  282 + raise ValueError("%s: Symbol '%s' has invalid format" %
  283 + (msg, sym_name))
  284 + entry_name, prop_name = m.groups()
  285 + entry_name = entry_name.replace('_', '-')
  286 + entry = self._entries.get(entry_name)
  287 + if not entry:
  288 + if entry_name.endswith('-any'):
  289 + root = entry_name[:-4]
  290 + for name in self._entries:
  291 + if name.startswith(root):
  292 + rest = name[len(root):]
  293 + if rest in ['', '-img', '-nodtb']:
  294 + entry = self._entries[name]
  295 + if not entry:
  296 + err = ("%s: Entry '%s' not found in list (%s)" %
  297 + (msg, entry_name, ','.join(self._entries.keys())))
  298 + if optional:
  299 + print('Warning: %s' % err, file=sys.stderr)
  300 + return None
  301 + raise ValueError(err)
  302 + if prop_name == 'pos':
  303 + return entry.pos
  304 + else:
  305 + raise ValueError("%s: No such property '%s'" % (msg, prop_name))
tools/binman/image_test.py
  1 +#
  2 +# Copyright (c) 2017 Google, Inc
  3 +# Written by Simon Glass <sjg@chromium.org>
  4 +#
  5 +# SPDX-License-Identifier: GPL-2.0+
  6 +#
  7 +# Test for the image module
  8 +
  9 +import unittest
  10 +
  11 +from image import Image
  12 +from elf_test import capture_sys_output
  13 +
  14 +class TestImage(unittest.TestCase):
  15 + def testInvalidFormat(self):
  16 + image = Image('name', 'node', test=True)
  17 + with self.assertRaises(ValueError) as e:
  18 + image.LookupSymbol('_binman_something_prop_', False, 'msg')
  19 + self.assertIn(
  20 + "msg: Symbol '_binman_something_prop_' has invalid format",
  21 + str(e.exception))
  22 +
  23 + def testMissingSymbol(self):
  24 + image = Image('name', 'node', test=True)
  25 + image._entries = {}
  26 + with self.assertRaises(ValueError) as e:
  27 + image.LookupSymbol('_binman_type_prop_pname', False, 'msg')
  28 + self.assertIn("msg: Entry 'type' not found in list ()",
  29 + str(e.exception))
  30 +
  31 + def testMissingSymbolOptional(self):
  32 + image = Image('name', 'node', test=True)
  33 + image._entries = {}
  34 + with capture_sys_output() as (stdout, stderr):
  35 + val = image.LookupSymbol('_binman_type_prop_pname', True, 'msg')
  36 + self.assertEqual(val, None)
  37 + self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n",
  38 + stderr.getvalue())
  39 + self.assertEqual('', stdout.getvalue())
  40 +
  41 + def testBadProperty(self):
  42 + image = Image('name', 'node', test=True)
  43 + image._entries = {'u-boot': 1}
  44 + with self.assertRaises(ValueError) as e:
  45 + image.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg')
  46 + self.assertIn("msg: No such property 'bad", str(e.exception))
tools/binman/test/53_symbols.dts
  1 +/dts-v1/;
  2 +
  3 +/ {
  4 + #address-cells = <1>;
  5 + #size-cells = <1>;
  6 +
  7 + binman {
  8 + pad-byte = <0xff>;
  9 + u-boot-spl {
  10 + };
  11 +
  12 + u-boot {
  13 + pos = <20>;
  14 + };
  15 +
  16 + u-boot-spl2 {
  17 + type = "u-boot-spl";
  18 + };
  19 + };
  20 +};