[RFC PATCH 9/9] tools: add linker-lists.py parser/checker script
Rasmus Villemoes
ravi at prevas.dk
Fri May 22 23:27:56 CEST 2026
Add a python script which will make use of the special symbols emitted
by the linker list macros, and perform various sanity checks. By
default, it ends with printing a list of all the defined linker lists,
including their start/end addresses, the size and aligment of
individual items and the number of items.
With --check, it only does the sanity checking and its exit code
reflects whether any problems were found. That is eventually intended
to be done as part of the build.
With --dump, it not only prints the lists and their overall
properties, but also the names/addresses of each item belong to the
list.
Signed-off-by: Rasmus Villemoes <ravi at prevas.dk>
---
tools/linker-lists.py | 234 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 234 insertions(+)
create mode 100755 tools/linker-lists.py
diff --git a/tools/linker-lists.py b/tools/linker-lists.py
new file mode 100755
index 00000000000..419879ee31a
--- /dev/null
+++ b/tools/linker-lists.py
@@ -0,0 +1,234 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+import sys
+from argparse import ArgumentParser
+import subprocess
+
+# Parse the output of
+#
+# readelf --wide --symbols u-boot
+#
+# or
+#
+# nm --print-size u-boot
+#
+# do sanity checks, and optionally print information on all the
+# defined lists.
+
+bad_lists = set()
+def warn(list_name, msg):
+ print(msg, file=sys.stderr)
+ bad_lists.add(list_name)
+
+class unique_dict(dict):
+ def __init__(self, name, format_spec, *arg, **kw):
+ super(unique_dict, self).__init__(*arg, **kw)
+ self.name = name
+ self.format_spec = format_spec
+
+ def __setitem__(self, key, value):
+ if key in self:
+ old = self[key]
+ if value != old:
+ warn(key,
+ f"Inconsistent {self.name} for list '{key}': Old value {self.format_spec}, new value {self.format_spec}" %
+ (old, value))
+ return
+ super(unique_dict, self).__setitem__(key, value)
+
+# <list name> -> <value>
+item_size = unique_dict("size", "%d")
+item_alignment = unique_dict("alignment", "%d")
+start_address = unique_dict("start address", "0x%08x")
+end_address = unique_dict("end address", "0x%08x")
+
+# <list name> -> list of (name, address, size) triples
+entries = dict()
+
+def handle_symbol(symbol, address, size):
+ if not symbol.startswith("_u_boot_list_"):
+ return
+
+ symbol = symbol[13:]
+ if symbol.endswith("_1_start"):
+ assert(size == 0)
+ start_address[symbol[0:-8]] = address
+ return
+
+ if symbol.endswith("_3_end"):
+ assert(size == 0)
+ end_address[symbol[0:-6]] = address
+ return
+
+ if symbol.endswith("_0_item_align"):
+ item_alignment[symbol[0:-13]] = size
+ return
+
+ if symbol.endswith("_0_item_size"):
+ item_size[symbol[0:-12]] = size
+ return
+
+ # Deal with lists/sublists. ut_2_bootm_2_bootm_test_silent
+ # - An entry called "bootm_2_bootm_test_silent" in the outer "ut" list, and
+ # - An entry called "bootm_test_silent" in the "ut_2_bootm" list.
+
+ atoms = symbol.split("_2_")
+ if len(atoms) < 2:
+ return
+
+ for i in range(1, len(atoms)):
+ list_name = "_2_".join(atoms[0:i])
+ entry_name = "_2_".join(atoms[i:])
+ if list_name not in entries:
+ entries[list_name] = []
+ entries[list_name].append((entry_name, address, size))
+
+def parse_readelf(line):
+ # Fields are
+ #
+ # Num: Value Size Type Bind Vis Ndx Name
+ #
+ # where Value (i.e. address) is in hex and Size is in
+ # decimal. There are lines (such as that header line) that we just
+ # need to ignore.
+ fields = line.split()
+ if len(fields) != 8:
+ return
+
+ (_, address, size, _, _, _, _, symbol) = fields
+
+ address = int(address, 16)
+ size = int(size, 10)
+
+ handle_symbol(symbol, address, size)
+
+def parse_nm(line):
+ # Fields are
+ #
+ # Address [Size] Type Name
+ #
+ # but [Size] is not present when it is 0. Both Address and Size
+ # are in hex.
+ fields = line.split()
+ if len(fields) == 4:
+ (address, size, _, symbol) = fields
+ size = int(size, 16)
+ elif len(fields) == 3:
+ (address, _, symbol) = fields
+ size = 0
+ else:
+ return
+
+ address = int(address, 16)
+
+ handle_symbol(symbol, address, size)
+
+
+ap = ArgumentParser(description='Linker lists sanity checker')
+
+ap.add_argument('--parser', '-p', default='nm', choices=['nm', 'readelf'],
+ help='Program to use to parse the ELF file (nm or readelf)')
+ap.add_argument('--check', '-c', action='store_true',
+ help='Only do sanity checks and exit non-zero if any problems are found')
+ap.add_argument('--dump', '-d', action='store_true',
+ help='Print all individual list entries')
+
+ap.add_argument('elf_file', metavar='ELF_FILE', nargs='?', default="u-boot", help='ELF file to parse (default u-boot)')
+
+args = ap.parse_args()
+
+if args.parser == 'nm':
+ parser = parse_nm
+ cmd = ['nm', '--print-size', args.elf_file]
+else:
+ parser = parse_readelf
+ cmd = ['readelf', '--symbols', '--wide', args.elf_file]
+
+subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
+
+for line in subp.stdout:
+ if "_u_boot_list_" not in line:
+ continue
+ parser(line)
+
+# These should all be the same, except perhaps that there might be lists without entries.
+list_names = set(item_size.keys())
+list_names.update(item_alignment.keys())
+list_names.update(start_address.keys())
+list_names.update(end_address.keys())
+list_names.update(entries.keys())
+
+list_names = list(list_names)
+list_names.sort(key=lambda x: (start_address.get(x, 0), x))
+
+for name in list_names:
+ # There really should be item_size and item_alignment values for
+ # all lists. Otherwise, we've emitted list entries to a list that
+ # is never referred to via the start/end macros.
+ size = item_size.get(name)
+ align = item_alignment.get(name)
+ if size is None:
+ warn(name, f"No known entry size for list '{name}'")
+ # Let the below sanity checks pass.
+ size = 1
+ if align is None:
+ warn(name, f"No known entry alignment for list '{name}'")
+ # Let the below sanity checks pass.
+ align = 1
+
+ if size % align != 0:
+ warn(name, f"Item size {size} for list '{name}' is not a multiple of the alignment {align}")
+
+ start = start_address.get(name)
+ end = end_address.get(name)
+
+ if start is None:
+ warn(name, f"No known start address for list '{name}'")
+ elif start % align != 0:
+ warn(name, f"Start address 0x{start:08x} for list '{name}' is not {align}-byte aligned")
+
+ if end is None:
+ warn(name, f"No known end address for list '{name}'")
+ elif end % align != 0:
+ warn(name, f"End address 0x{end:08x} for list '{name}' is not {align}-byte aligned")
+
+ if start is not None and end is not None and (end - start) % size != 0:
+ warn(name, f"Difference {end - start} between start 0x{start:08x} and end 0x{end:08x} addresses for list '{name}' is not a multiple of the item size {size}")
+
+ for (symbol, address, entry_size) in entries.get(name, []):
+ if start is not None and address < start:
+ warn(name, f"Entry {symbol} in list {name} has address 0x{address:08x} before start address 0x{start:08x}")
+ if end is not None and address > end:
+ warn(name, f"Entry {symbol} in list {name} has address 0x{address:08x} after end address 0x{end:08x}")
+ if entry_size % size != 0:
+ warn(name, f"Size {entry_size} of entry {symbol} in list {name} is not a multiple item size {size}")
+ if address % align != 0:
+ warn(name, f"Address 0x{address:08x} of entry {symbol} in list {name} is not {align}-byte aligned")
+
+if args.check:
+ if len(bad_lists) == 0:
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
+
+print(f"{'List':36s}\t{'Start':10s}\t{'End':10s}\tSize\tAlign\tCount")
+
+for name in list_names:
+ size = item_size.get(name, 1)
+ align = item_alignment.get(name, 1)
+ start = start_address.get(name, 0)
+ end = end_address.get(name, 0)
+ count = (end - start) // size
+ if name in bad_lists:
+ bang = "\t!!!"
+ else:
+ bang = ""
+
+ print(f"{name:36s}\t0x{start:08x}\t0x{end:08x}\t{size}\t{align}\t{count}{bang}")
+ if not args.dump:
+ continue
+
+ for (symbol, address, entry_size) in entries.get(name, []):
+ print(f" {symbol:32s}\t0x{address:08x}\t{'':10s}\t{entry_size}\t\t{entry_size // size}")
--
2.54.0
More information about the U-Boot
mailing list