| # |
| # Copyright (C) 2024 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| """Tests that binaries are built with the correct max-page-size.""" |
| from pathlib import Path |
| from subprocess import CalledProcessError |
| import subprocess |
| import re |
| from collections.abc import Iterator |
| |
| from ndk.abis import Abi |
| from ndk.hosts import Host |
| from ndk.test.spec import BuildConfiguration |
| from ndk.testing.builders import CMakeBuilder, NdkBuildBuilder |
| |
| |
| PROJECT_PATH = Path("project") |
| |
| |
| def iter_load_alignments(readelf_output: str) -> Iterator[tuple[int, int]]: |
| """Iterates over the offset and alignment of each LOAD section.""" |
| # Example output: |
| # |
| # Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align |
| # PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0002a0 0x0002a0 R 0x8 |
| # LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x099604 0x099604 R 0x1000 |
| pattern = re.compile(r"^\s+LOAD\s+(0x[0-9a-fA-F]+).+(0x[0-9a-fA-F]+)$") |
| for line in readelf_output.splitlines(): |
| if "LOAD" not in line: |
| continue |
| if (match := pattern.search(line)) is not None: |
| yield int(match.group(1), base=16), int(match.group(2), base=16) |
| else: |
| raise ValueError(f"Could not parse LOAD line {line}") |
| |
| |
| def verify_load_section_alignment( |
| path: Path, ndk: Path, expected_alignment: int |
| ) -> tuple[bool, str | None]: |
| """Verifies that each LOAD section in the given file has the correct alignment.""" |
| readelf = ( |
| ndk / "toolchains/llvm/prebuilt" / Host.current().tag / "bin" / "llvm-readelf" |
| ) |
| readelf = readelf.with_suffix(Host.current().exe_suffix) |
| output = subprocess.run( |
| [readelf, "-lW", path], check=True, capture_output=True, text=True |
| ).stdout |
| for offset, alignment in iter_load_alignments(output): |
| if alignment != expected_alignment: |
| return ( |
| False, |
| f"LOAD section at {offset:x} has incorrect alignment {alignment:x}. " |
| f"Expected {expected_alignment:x} ({path})", |
| ) |
| return True, None |
| |
| |
| def verify_load_section_alignment_each_file( |
| paths: list[Path], ndk: Path, expected_alignment: int |
| ) -> tuple[bool, str | None]: |
| """Verifies that the LOAD section alignment is correct for each given file.""" |
| for path in paths: |
| result, text = verify_load_section_alignment(path, ndk, expected_alignment) |
| if not result: |
| return result, text |
| return True, None |
| |
| |
| def run_test(ndk_path: str, config: BuildConfiguration) -> tuple[bool, str | None]: |
| """Checks that the binary's LOAD sections have the correct alignment.""" |
| cmake_builder = CMakeBuilder.from_build_config(PROJECT_PATH, Path(ndk_path), config) |
| ndk_build_builder = NdkBuildBuilder.from_build_config( |
| PROJECT_PATH, Path(ndk_path), config |
| ) |
| try: |
| cmake_builder.build() |
| ndk_build_builder.build() |
| except CalledProcessError as ex: |
| return False, f"Build failed:\n{ex.stdout}" |
| |
| if config.abi in (Abi("arm64-v8a"), Abi("x86_64")): |
| expected_alignment = 16 * 1024 |
| else: |
| expected_alignment = 4 * 1024 |
| |
| return verify_load_section_alignment_each_file( |
| [cmake_builder.out_dir / "libfoo.so", ndk_build_builder.out_dir / "libfoo.so"], |
| Path(ndk_path), |
| expected_alignment, |
| ) |