Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | #!/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<base_name>_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()) |