[PATCH v4 14/34] binman: Support reading the offset of an ELF-file symbol

Simon Glass
2021-11-04T03:12:16Z

Binman needs to be able to update the contents of an ELF file after it has
been build. To support this, add a function to locate the position of a
symbol's contents within the file.

Fix the comments on bss_data.c and Symbol while we are here.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

(no changes since v1)

tools/binman/elf.py | 53 ++++++++++++++++++++++++++++++--
tools/binman/elf_test.py | 37 ++++++++++++++++++++++
tools/binman/test/Makefile | 5 ++-
tools/binman/test/bss_data.c | 2 +-
tools/binman/test/embed_data.c | 16 ++++++++++
tools/binman/test/embed_data.lds | 23 ++++++++++++++
6 files changed, 131 insertions(+), 5 deletions(-)
create mode 100644 tools/binman/test/embed_data.c
create mode 100644 tools/binman/test/embed_data.lds

diff --git a/tools/binman/elf.py b/tools/binman/elf.py
index 03b49d7163c..4aca4f847ce 100644
--- a/tools/binman/elf.py
+++ b/tools/binman/elf.py
@@ -24,7 +24,14 @@ try:
except: # pragma: no cover
ELF_TOOLS = False

-Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak'])
+# Information about an EFL symbol:
+# section (str): Name of the section containing this symbol
+# address (int): Address of the symbol (its value)
+# size (int): Size of the symbol in bytes
+# weak (bool): True if the symbol is weak
+# offset (int or None): Offset of the symbol's data in the ELF file, or None if
+# not known
+Symbol = namedtuple('Symbol', ['section', 'address', 'size', 'weak', 'offset'])

# Information about an ELF file:
# data: Extracted program contents of ELF file (this would be loaded by an
@@ -71,8 +78,48 @@ def GetSymbols(fname, patterns):
section, size = parts[:2]
if len(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')
+ syms[name] = Symbol(section, int(value, 16), int(size, 16),
+ flags[1] == 'w', None)
+
+ # Sort dict by address
+ return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
+
+def GetSymbolFileOffset(fname, patterns):
+ """Get the symbols from an ELF file
+
+ Args:
+ fname: Filename of the ELF file to read
+ patterns: List of regex patterns to search for, each a string
+
+ Returns:
+ None, if the file does not exist, or Dict:
+ key: Name of symbol
+ value: Hex value of symbol
+ """
+ def _GetFileOffset(elf, addr):
+ for seg in elf.iter_segments():
+ seg_end = seg['p_vaddr'] + seg['p_filesz']
+ if seg.header['p_type'] == 'PT_LOAD':
+ if addr >= seg['p_vaddr'] and addr < seg_end:
+ return addr - seg['p_vaddr'] + seg['p_offset']
+
+ if not ELF_TOOLS:
+ raise ValueError('Python elftools package is not available')
+
+ syms = {}
+ with open(fname, 'rb') as fd:
+ elf = ELFFile(fd)
+
+ re_syms = re.compile('|'.join(patterns))
+ for section in elf.iter_sections():
+ if isinstance(section, SymbolTableSection):
+ for symbol in section.iter_symbols():
+ if not re_syms or re_syms.search(symbol.name):
+ addr = symbol.entry['st_value']
+ syms[symbol.name] = Symbol(
+ section.name, addr, symbol.entry['st_size'],
+ symbol.entry['st_info']['bind'] == 'STB_WEAK',
+ _GetFileOffset(elf, addr))

# Sort dict by address
return OrderedDict(sorted(syms.items(), key=lambda x: x[1].address))
diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py
index bcccd78c0a1..ac69a95b654 100644
--- a/tools/binman/elf_test.py
+++ b/tools/binman/elf_test.py
@@ -6,6 +6,7 @@

import os
import shutil
+import struct
import sys
import tempfile
import unittest
@@ -221,6 +222,42 @@ class TestElf(unittest.TestCase):
elf.DecodeElf(data, load + 2))
shutil.rmtree(outdir)

+ def testEmbedData(self):
+ """Test for the GetSymbolFileOffset() function"""
+ if not elf.ELF_TOOLS:
+ self.skipTest('Python elftools not available')
+
+ fname = self.ElfTestFile('embed_data')
+ offset = elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end'])
+ start = offset['embed_start'].offset
+ end = offset['embed_end'].offset
+ data = tools.ReadFile(fname)
+ embed_data = data[start:end]
+ expect = struct.pack('<III', 0x1234, 0x5678, 0)
+ self.assertEqual(expect, embed_data)
+
+ def testEmbedFail(self):
+ """Test calling GetSymbolFileOffset() without elftools"""
+ try:
+ old_val = elf.ELF_TOOLS
+ elf.ELF_TOOLS = False
+ fname = self.ElfTestFile('embed_data')
+ with self.assertRaises(ValueError) as e:
+ elf.GetSymbolFileOffset(fname, ['embed_start', 'embed_end'])
+ self.assertIn('Python elftools package is not available',
+ str(e.exception))
+ finally:
+ elf.ELF_TOOLS = old_val
+
+ def testEmbedDataNoSym(self):
+ """Test for GetSymbolFileOffset() getting no symbols"""
+ if not elf.ELF_TOOLS:
+ self.skipTest('Python elftools not available')
+
+ fname = self.ElfTestFile('embed_data')
+ offset = elf.GetSymbolFileOffset(fname, ['missing_sym'])
+ self.assertEqual({}, offset)
+

if __name__ == '__main__':
unittest.main()
diff --git a/tools/binman/test/Makefile b/tools/binman/test/Makefile
index 0b19b7d9935..6e6cc6de491 100644
--- a/tools/binman/test/Makefile
+++ b/tools/binman/test/Makefile
@@ -31,7 +31,7 @@ LDS_BINMAN_X86 := -T $(SRC)u_boot_binman_syms_x86.lds

TARGETS = u_boot_ucode_ptr u_boot_no_ucode_ptr bss_data \
u_boot_binman_syms u_boot_binman_syms.bin u_boot_binman_syms_bad \
- u_boot_binman_syms_size u_boot_binman_syms_x86
+ u_boot_binman_syms_size u_boot_binman_syms_x86 embed_data

all: $(TARGETS)

@@ -44,6 +44,9 @@ u_boot_ucode_ptr: u_boot_ucode_ptr.c
bss_data: CFLAGS += $(SRC)bss_data.lds
bss_data: bss_data.c

+embed_data: CFLAGS += $(SRC)embed_data.lds
+embed_data: embed_data.c
+
u_boot_binman_syms.bin: u_boot_binman_syms
$(OBJCOPY) -O binary $< -R .note.gnu.build-id $@

diff --git a/tools/binman/test/bss_data.c b/tools/binman/test/bss_data.c
index 79537c31b65..4f9b64cef9e 100644
--- a/tools/binman/test/bss_data.c
+++ b/tools/binman/test/bss_data.c
@@ -2,7 +2,7 @@
/*
* Copyright (c) 2016 Google, Inc
*
- * Simple program to create a _dt_ucode_base_size symbol which can be read
+ * Simple program to create a bss_data region so the symbol can be read
* by binutils. This is used by binman tests.
*/

diff --git a/tools/binman/test/embed_data.c b/tools/binman/test/embed_data.c
new file mode 100644
index 00000000000..47d8c38248c
--- /dev/null
+++ b/tools/binman/test/embed_data.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Simple program including some embedded data that can be accessed by binman.
+ * This is used by binman tests.
+ */
+
+int first[10] = {1};
+int embed[3] __attribute__((section(".embed"))) = {0x1234, 0x5678};
+int second[10] = {1};
+
+int main(void)
+{
+ return 0;
+}
diff --git a/tools/binman/test/embed_data.lds b/tools/binman/test/embed_data.lds
new file mode 100644
index 00000000000..908bf66c294
--- /dev/null
+++ b/tools/binman/test/embed_data.lds
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Google LLC
+ */
+
+OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
+OUTPUT_ARCH(i386)
+ENTRY(_start)
+
+SECTIONS
+{
+ _start = .;
+ __data_start = .;
+ .data :
+ {
+ . = ALIGN(32);
+ embed_start = .;
+ *(.embed*)
+ embed_end = .;
+ . = ALIGN(32);
+ *(.data*)
+ }
+}
--
2.34.0.rc0.344.g81b53c2807-goog