Compare commits
1 Commits
cherry-e44
...
llista
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f65aac92c1 |
5
Makefile
5
Makefile
@@ -1139,6 +1139,10 @@ define deprecated
|
||||
|
||||
endef
|
||||
|
||||
# Check linker lists are consistent
|
||||
quiet_cmd_llcheck = LLCHK $2
|
||||
cmd_llcheck = $(srctree)/scripts/check_linker_lists.py $2
|
||||
|
||||
# Timestamp file to make sure that binman always runs
|
||||
.binman_stamp: $(INPUTS-y) FORCE
|
||||
ifeq ($(CONFIG_BINMAN),y)
|
||||
@@ -1837,6 +1841,7 @@ ifeq ($(CONFIG_KALLSYMS),y)
|
||||
$(call cmd,smap)
|
||||
$(call cmd,u-boot__) common/system_map.o
|
||||
endif
|
||||
$(call cmd,llcheck,u-boot)
|
||||
|
||||
ifeq ($(CONFIG_RISCV),y)
|
||||
@tools/prelink-riscv $@
|
||||
|
||||
218
scripts/check_linker_lists.py
Executable file
218
scripts/check_linker_lists.py
Executable file
@@ -0,0 +1,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())
|
||||
Reference in New Issue
Block a user