From 432bd660dc354893dfd190d1e9a3bd7b1150d884 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 24 May 2024 21:13:15 +0000 Subject: [PATCH] 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 --- core/check_elf_file.mk | 12 +++++++ tools/check_elf_file.py | 72 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/core/check_elf_file.mk b/core/check_elf_file.mk index b5be81f9d7..584facc5b2 100644 --- a/core/check_elf_file.mk +++ b/core/check_elf_file.mk @@ -10,6 +10,8 @@ # - LOCAL_BUILT_MODULE # - LOCAL_IS_HOST_MODULE # - LOCAL_MODULE_CLASS +# - TARGET_MAX_PAGE_SIZE_SUPPORTED +# - TARGET_NO_BIONIC_PAGE_SIZE_MACRO # - intermediates # - my_installed_module_stem # - my_prebuilt_src_file @@ -26,6 +28,15 @@ $(check_elf_files_stamp): PRIVATE_SYSTEM_SHARED_LIBRARIES := $(my_system_shared_ # In addition to $(my_check_elf_file_shared_lib_files), some file paths are # added by `resolve-shared-libs-for-elf-file-check` from `core/main.mk`. $(check_elf_files_stamp): PRIVATE_SHARED_LIBRARY_FILES := $(my_check_elf_file_shared_lib_files) +# For different page sizes to work, we must support a larger max page size +# as well as properly reflect page size at runtime. Limit this check, since many +# devices set the max page size (for future proof) than actually use the +# larger page size. +ifeq ($(strip $(TARGET_NO_BIONIC_PAGE_SIZE_MACRO)),true) +$(check_elf_files_stamp): PRIVATE_MAX_PAGE_SIZE_OPT := --max-page-size=$(TARGET_MAX_PAGE_SIZE_SUPPORTED) +else +$(check_elf_files_stamp): PRIVATE_MAX_PAGE_SIZE_OPT := +endif $(check_elf_files_stamp): $(my_prebuilt_src_file) $(my_check_elf_file_shared_lib_files) $(CHECK_ELF_FILE) $(LLVM_READOBJ) @echo Check prebuilt ELF binary: $< $(hide) mkdir -p $(dir $@) @@ -33,6 +44,7 @@ $(check_elf_files_stamp): $(my_prebuilt_src_file) $(my_check_elf_file_shared_lib $(hide) $(CHECK_ELF_FILE) \ --skip-bad-elf-magic \ --skip-unknown-elf-machine \ + $(PRIVATE_MAX_PAGE_SIZE_OPT) \ $(if $(PRIVATE_SONAME),--soname $(PRIVATE_SONAME)) \ $(foreach l,$(PRIVATE_SHARED_LIBRARY_FILES),--shared-lib $(l)) \ $(foreach l,$(PRIVATE_SYSTEM_SHARED_LIBRARIES),--system-shared-lib $(l)) \ 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()