summaryrefslogtreecommitdiff
path: root/tools/check_elf_file.py
diff options
context:
space:
mode:
author Steven Moreland <smoreland@google.com> 2024-05-24 21:13:15 +0000
committer Steven Moreland <smoreland@google.com> 2024-05-30 17:31:08 +0000
commit432bd660dc354893dfd190d1e9a3bd7b1150d884 (patch)
tree4b13bfaaa76bf4edacaa6d018a21ccc14cfb41b9 /tools/check_elf_file.py
parentbdb6b87837135a69f35f9abe28658ce2cdf179e2 (diff)
check_elf_file: check prebuilts are aligned
WARNING: two things still need to be done 1. this should probably only be enabled when PAGE_SIZE is undefined, but I'm curious what other targets break now. 2. we may need a per-prebuilt way to disable this, like some of the other settings here. For prebuilts added onto a device, we can check the page alignment matches the one declared in the build configuration. Bug: 342466032 Test: manually, by changing the script to require 64 KB alignment, I was able to see its errors on new targets. Change-Id: Ic118245e64d67204bf5fa740a3e1afb7325b34f5
Diffstat (limited to 'tools/check_elf_file.py')
-rwxr-xr-xtools/check_elf_file.py72
1 files changed, 69 insertions, 3 deletions
diff --git a/tools/check_elf_file.py b/tools/check_elf_file.py
index 51ec23b8a6..bed336611c 100755
--- a/tools/check_elf_file.py
+++ b/tools/check_elf_file.py
@@ -67,7 +67,7 @@ ELFHeader = collections.namedtuple(
ELF = collections.namedtuple(
'ELF',
- ('dt_soname', 'dt_needed', 'imported', 'exported', 'header'))
+ ('alignments', 'dt_soname', 'dt_needed', 'imported', 'exported', 'header'))
def _get_os_name():
@@ -195,7 +195,8 @@ class ELFParser(object):
@classmethod
def _read_llvm_readobj(cls, elf_file_path, header, llvm_readobj):
"""Run llvm-readobj and parse the output."""
- cmd = [llvm_readobj, '--dynamic-table', '--dyn-symbols', elf_file_path]
+ cmd = [llvm_readobj, '--program-headers', '--dynamic-table',
+ '--dyn-symbols', elf_file_path]
out = subprocess.check_output(cmd, text=True)
lines = out.splitlines()
return cls._parse_llvm_readobj(elf_file_path, header, lines)
@@ -205,9 +206,56 @@ class ELFParser(object):
def _parse_llvm_readobj(cls, elf_file_path, header, lines):
"""Parse the output of llvm-readobj."""
lines_it = iter(lines)
+ alignments = cls._parse_program_headers(lines_it)
dt_soname, dt_needed = cls._parse_dynamic_table(elf_file_path, lines_it)
imported, exported = cls._parse_dynamic_symbols(lines_it)
- return ELF(dt_soname, dt_needed, imported, exported, header)
+ return ELF(alignments, dt_soname, dt_needed, imported, exported, header)
+
+
+ _PROGRAM_HEADERS_START_PATTERN = 'ProgramHeaders ['
+ _PROGRAM_HEADERS_END_PATTERN = ']'
+ _PROGRAM_HEADER_START_PATTERN = 'ProgramHeader {'
+ _PROGRAM_HEADER_TYPE_PATTERN = re.compile('^\\s+Type:\\s+(.*)$')
+ _PROGRAM_HEADER_ALIGN_PATTERN = re.compile('^\\s+Alignment:\\s+(.*)$')
+ _PROGRAM_HEADER_END_PATTERN = '}'
+
+
+ @classmethod
+ def _parse_program_headers(cls, lines_it):
+ """Parse the dynamic table section."""
+ alignments = []
+
+ if not cls._find_prefix(cls._PROGRAM_HEADERS_START_PATTERN, lines_it):
+ raise ELFError()
+
+ for line in lines_it:
+ # Parse each program header
+ if line.strip() == cls._PROGRAM_HEADER_START_PATTERN:
+ p_align = None
+ p_type = None
+ for line in lines_it:
+ if line.strip() == cls._PROGRAM_HEADER_END_PATTERN:
+ if not p_align:
+ raise ELFError("Could not parse alignment from program header!")
+ if not p_type:
+ raise ELFError("Could not parse type from program header!")
+
+ if p_type.startswith("PT_LOAD "):
+ alignments.append(int(p_align))
+ break
+
+ match = cls._PROGRAM_HEADER_TYPE_PATTERN.match(line)
+ if match:
+ p_type = match.group(1)
+
+ match = cls._PROGRAM_HEADER_ALIGN_PATTERN.match(line)
+ if match:
+ p_align = match.group(1)
+
+ if line == cls._PROGRAM_HEADERS_END_PATTERN:
+ break
+
+ return alignments
_DYNAMIC_SECTION_START_PATTERN = 'DynamicSection ['
@@ -434,6 +482,19 @@ class Checker(object):
sys.exit(2)
+ def check_max_page_size(self, max_page_size):
+ for alignment in self._file_under_test.alignments:
+ if alignment % max_page_size != 0:
+ self._error(f'Load segment has alignment {alignment} but '
+ f'{max_page_size} required.')
+ self._note()
+ self._note('Fix suggestions:')
+ self._note(f' use linker flag "-Wl,-z,max-page-size={max_page_size}" '
+ f'when compiling this lib')
+
+ # TODO: instead of exiting immediately, we may want to collect the
+ # errors from all checks and emit them at once
+ sys.exit(2)
@staticmethod
def _find_symbol(lib, name, version):
@@ -514,6 +575,8 @@ def _parse_args():
help='Ignore the input file with unknown machine ID')
parser.add_argument('--allow-undefined-symbols', action='store_true',
help='Ignore unresolved undefined symbols')
+ parser.add_argument('--max-page-size', action='store', type=int,
+ help='Required page size alignment support')
# Other options
parser.add_argument('--llvm-readobj',
@@ -542,6 +605,9 @@ def main():
checker.check_dt_needed(args.system_shared_lib)
+ if args.max_page_size:
+ checker.check_max_page_size(args.max_page_size)
+
if not args.allow_undefined_symbols:
checker.check_symbols()