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 <[email protected]>
---
 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

Reply via email to