blob: 46f2f6619620ddb00348787bb8f51e98f2abe10b [file] [log] [blame]
#
# 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"{path.resolve()}: LOAD section at {offset:x} has incorrect alignment {alignment:x}. "
f"Expected {expected_alignment:x}",
)
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,
cmake_build_flags=["-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"],
)
# Page-size compat for ndk-build is enabled in project/jni/Application.mk.
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}"
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=16 * 1024 if config.abi == Abi("arm64-v8a") else 4 * 1024,
)