#!/usr/bin/env python3 # # check_list_alignment.py: Auto-discover and verify the uniform # spacing of all U-Boot linker list symbols # # Analyze the symbol table of a U-Boot ELF file to ensure that # all entries in all linker-generated lists are separated by a consistent # number of bytes. Detect problems caused by linker-inserted # alignment padding. # # By default, produce no output if no problems are found # Use the -v flag to force output even on success # # Exit Codes: # 0: Success. No alignment problems were found # 1: Usage Error. The script was not called with the correct arguments # 2: Execution Error. Failed to run `nm` or the ELF file was not found # 3: Problem Found. An inconsistent gap was detected in at least one list # import sys import subprocess import re import argparse from statistics import mode, StatisticsError from collections import defaultdict, namedtuple # Information about the gap between two consecutive symbols Gap = namedtuple('Gap', ['gap', 'prev_sym', 'next_sym']) # Holds all the analysis results from checking the lists Results = namedtuple('Results', [ 'total_problems', 'total_symbols', 'all_lines', 'max_name_len', 'list_count']) def eprint(*args, **kwargs): '''Print to stderr''' print(*args, file=sys.stderr, **kwargs) def check_single_list(name, symbols, max_name_len): '''Check alignment for a single list and return its findings Args: name (str): The cleaned-up name of the list for display symbols (list): A list of (address, name) tuples, sorted by address max_name_len (int): The max length of list names for column formatting Returns: tuple: (problem_count, list_of_output_lines) ''' lines = [] if len(symbols) < 2: return 0, [] gaps = [] for i in range(len(symbols) - 1): addr1, name1 = symbols[i] addr2, name2 = symbols[i+1] gaps.append(Gap(gap=addr2 - addr1, prev_sym=name1, next_sym=name2)) expected_gap = mode(g.gap for g in gaps) lines.append( f"{name:<{max_name_len + 2}} {len(symbols):>12} " f"{f'0x{expected_gap:x}':>17}") problem_count = 0 for g in gaps: if g.gap != expected_gap: problem_count += 1 lines.append( f" - Bad gap (0x{g.gap:x}) before symbol: {g.next_sym}") return problem_count, lines def run_nm_and_get_lists(elf_path): '''Run `nm` and parse the output to discover all linker lists Args: elf_path (str): The path to the ELF file to process Returns: dict or None: A dictionary of discovered lists, or None on error ''' cmd = ['nm', '-n', elf_path] try: proc = subprocess.run(cmd, capture_output=True, text=True, check=True) except FileNotFoundError: eprint( 'Error: The "nm" command was not found. ' 'Please ensure binutils is installed') return None except subprocess.CalledProcessError as e: eprint( f"Error: Failed to execute 'nm' on '{elf_path}'.\n" f" Return Code: {e.returncode}\n Stderr:\n{e.stderr}") return None list_name_pattern = re.compile( r'^(?P_u_boot_list_\d+_\w+)(?:_info)?_2_') lists = defaultdict(list) for line in proc.stdout.splitlines(): if ' D _u_boot_list_' not in line: continue try: parts = line.strip().split() address, name = int(parts[0], 16), parts[-1] match = list_name_pattern.match(name) if match: base_name = match.group('base_name') lists[base_name].append((address, name)) except (ValueError, IndexError): eprint(f'Warning: Could not parse line: {line}') return lists def collect_data(lists): '''Collect alignment check data for all lists Args: lists (dict): A dictionary of lists and their symbols Returns: Results: A namedtuple containing the analysis results ''' names = {} prefix_to_strip = '_u_boot_list_2_' for list_name in lists.keys(): name = list_name[len(prefix_to_strip):] if list_name.startswith( prefix_to_strip) else list_name names[list_name] = name max_name_len = max(len(name) for name in names.values()) if names else 0 total_problems = 0 total_symbols = 0 all_lines = [] for list_name in sorted(lists.keys()): symbols = lists[list_name] total_symbols += len(symbols) name = names[list_name] problem_count, lines = check_single_list(name, symbols, max_name_len) total_problems += problem_count all_lines.extend(lines) return Results( total_problems=total_problems, total_symbols=total_symbols, all_lines=all_lines, max_name_len=max_name_len, list_count=len(lists)) def show_output(results, verbose): '''Print the collected results to stderr based on verbosity Args: results (Results): The analysis results from collect_data() verbose (bool): True to print output even on success ''' if results.total_problems == 0 and not verbose: return header = (f"{'List Name':<{results.max_name_len + 2}} {'# Symbols':>12} " f"{'Struct Size (hex)':>17}") eprint(header) eprint(f"{'-' * (results.max_name_len + 2)} {'-' * 12} {'-' * 17}") for line in results.all_lines: eprint(line) # Print footer eprint(f"{'-' * (results.max_name_len + 2)} {'-' * 12}") eprint(f"{f'{results.list_count} lists':<{results.max_name_len + 2}} " f"{results.total_symbols:>12}") if results.total_problems > 0: eprint(f'\nFAILURE: Found {results.total_problems} alignment problems') elif verbose: eprint('\nSUCCESS: All discovered lists have consistent alignment') def main(): '''Main entry point of the script, returns an exit code''' epilog_text = ''' Auto-discover all linker-generated lists in a U-Boot ELF file (e.g., for drivers, commands, etc.) and verify their integrity. Check that all elements in a given list are separated by a consistent number of bytes. Problems typically indicate that the linker has inserted alignment padding between two elements in a list, which can break U-Boot's assumption that the list is a simple, contiguous array of same-sized structs. ''' parser = argparse.ArgumentParser( description='Check alignment of all U-Boot linker lists in an ELF file.', epilog=epilog_text, formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument('elf_path', metavar='path_to_elf_file', help='Path to the U-Boot ELF file to check') parser.add_argument('-v', '--verbose', action='store_true', help='Print detailed output even on success') args = parser.parse_args() lists = run_nm_and_get_lists(args.elf_path) if lists is None: return 2 # Error running nm if not lists: if args.verbose: eprint('Success: No U-Boot linker lists found to check') return 0 results = collect_data(lists) show_output(results, args.verbose) return 3 if results.total_problems > 0 else 0 if __name__ == '__main__': sys.exit(main())