diff --git a/core/check_elf_file.mk b/core/check_elf_file.mk index b5be81f9d7..ec3c4b0280 100644 --- a/core/check_elf_file.mk +++ b/core/check_elf_file.mk @@ -7,9 +7,12 @@ # # Inputs: # - LOCAL_ALLOW_UNDEFINED_SYMBOLS +# - LOCAL_IGNORE_MAX_PAGE_SIZE # - LOCAL_BUILT_MODULE # - LOCAL_IS_HOST_MODULE # - LOCAL_MODULE_CLASS +# - TARGET_CHECK_PREBUILT_MAX_PAGE_SIZE +# - TARGET_MAX_PAGE_SIZE_SUPPORTED # - intermediates # - my_installed_module_stem # - my_prebuilt_src_file @@ -26,6 +29,21 @@ $(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_CHECK_PREBUILT_MAX_PAGE_SIZE)),true) +ifeq ($(strip $(LOCAL_IGNORE_MAX_PAGE_SIZE)),true) +$(check_elf_files_stamp): PRIVATE_MAX_PAGE_SIZE := +else +$(check_elf_files_stamp): PRIVATE_MAX_PAGE_SIZE := $(TARGET_MAX_PAGE_SIZE_SUPPORTED) +endif +else +$(check_elf_files_stamp): PRIVATE_MAX_PAGE_SIZE := +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 +51,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 \ + $(if $(PRIVATE_MAX_PAGE_SIZE),--max-page-size=$(PRIVATE_MAX_PAGE_SIZE)) \ $(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/core/clear_vars.mk b/core/clear_vars.mk index 2b1e4365b1..61926907c3 100644 --- a/core/clear_vars.mk +++ b/core/clear_vars.mk @@ -106,6 +106,7 @@ LOCAL_GTEST:=true LOCAL_HEADER_LIBRARIES:= LOCAL_HOST_PREFIX:= LOCAL_HOST_REQUIRED_MODULES:= +LOCAL_IGNORE_MAX_PAGE_SIZE:= LOCAL_INIT_RC:= LOCAL_INJECT_BSSL_HASH:= LOCAL_INSTALLED_MODULE:= diff --git a/core/config.mk b/core/config.mk index ce11b1d558..c7567e3b9c 100644 --- a/core/config.mk +++ b/core/config.mk @@ -419,6 +419,13 @@ else endif .KATI_READONLY := TARGET_MAX_PAGE_SIZE_SUPPORTED +ifdef PRODUCT_CHECK_PREBUILT_MAX_PAGE_SIZE + TARGET_CHECK_PREBUILT_MAX_PAGE_SIZE := $(PRODUCT_CHECK_PREBUILT_MAX_PAGE_SIZE) +else + TARGET_CHECK_PREBUILT_MAX_PAGE_SIZE := false +endif +.KATI_READONLY := TARGET_CHECK_PREBUILT_MAX_PAGE_SIZE + # Boolean variable determining if AOSP relies on bionic's PAGE_SIZE macro. ifdef PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO TARGET_NO_BIONIC_PAGE_SIZE_MACRO := $(PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO) diff --git a/core/product.mk b/core/product.mk index 9a49927598..68d772126b 100644 --- a/core/product.mk +++ b/core/product.mk @@ -32,6 +32,7 @@ _product_single_value_vars += PRODUCT_MODEL_FOR_ATTESTATION # PRODUCT_MAX_PAGE_SIZE_SUPPORTED=65536, the possible values for PAGE_SIZE could be # 4096, 16384 and 65536. _product_single_value_vars += PRODUCT_MAX_PAGE_SIZE_SUPPORTED +_product_single_value_vars += PRODUCT_CHECK_PREBUILT_MAX_PAGE_SIZE # Boolean variable determining if AOSP relies on bionic's PAGE_SIZE macro. _product_single_value_vars += PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO diff --git a/tools/check_elf_file.py b/tools/check_elf_file.py index 51ec23b8a6..1fd7950bfe 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,24 @@ 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') + self._note() + self._note('If the fix above doesn\'t work, bypass this check with:') + self._note(' Android.bp: ignore_max_page_size: true,') + self._note(' Android.mk: LOCAL_IGNORE_MAX_PAGE_SIZE := true') + self._note(' Device mk: PRODUCT_CHECK_PREBUILT_MAX_PAGE_SIZE := false') + + # 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 +580,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 +610,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()