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

