#!/usr/bin/env python3 # # Copyright (C) 2022 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. """Audit a prebuilt directory to scan for various build issues""" import argparse import inspect from pathlib import Path import re import subprocess import sys import context from android_rust.paths import READELF_PATH, TOOLCHAIN_RESOURCE_PATH from android_rust.utils import ResolvedPath, print_colored, TERM_GREEN, TERM_RED ALLOW_LIST_PATH = TOOLCHAIN_RESOURCE_PATH / "shared_library_allow_list.txt" NEEDED_LIBRARY_PATTERN = re.compile(r"\(NEEDED\)\s+Shared library: \[([\w\-]+\.so(?:\.\d+)?)\]") def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=inspect.getdoc(sys.modules[__name__])) parser.add_argument("scandir", type=ResolvedPath, help="Directory to audit") parser.add_argument( "--map", "-m", action="store_true", help="Produce a map of libs to requirements") return parser.parse_args() def get_allow_list() -> list[str]: with open(ALLOW_LIST_PATH, "r") as f: return sorted(f.read().splitlines()) def get_required_libs(scandir: Path) -> list[str]: requirements_map = get_required_libs_map(scandir) required_libs = set() for libs in requirements_map.values(): for lib in libs: required_libs.add(lib) return sorted(required_libs) def get_required_libs_map(scandir: Path) -> dict[str, list[str]]: lib_paths = list(scandir.glob("**/*.so")) + list(scandir.glob("**/*.so.*")) local_libs: list[str] = [p.name for p in lib_paths] required_libs: dict[str, list[str]] = {} for lib_path in lib_paths: local_libs.append(lib_path.name) result = subprocess.run([READELF_PATH, "--dynamic", lib_path], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) if result.returncode != 0: sys.exit(f"Failed to run readelf on {lib_path}") required_libs[str(lib_path)] = [] for line in result.stdout.splitlines(): search_result = NEEDED_LIBRARY_PATTERN.search(line) if search_result != None: assert search_result is not None required_lib = search_result[1] if required_lib not in local_libs: required_libs[str(lib_path)].append(required_lib) return required_libs def main() -> None: args = parse_args() allowed_libs = get_allow_list() print("Required shared libraries:") if args.map: for (local_lib, requirements) in get_required_libs_map(args.scandir).items(): print(f"{local_lib}:") for req in requirements: print_colored(f"\t{req}", TERM_GREEN if req in allowed_libs else TERM_RED) print("") else: for lib in get_required_libs(args.scandir): print_colored(f"\t{lib}", TERM_GREEN if lib in allowed_libs else TERM_RED) if __name__ == "__main__": main()