#!/usr/bin/env python # # Copyright (C) 2023 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. """apex_elf_checker checks if ELF files in the APEX Usage: apex_elf_checker [--unwanted ] --unwanted Fail if any of ELF files in APEX has any of unwanted names in NEEDED ` """ import argparse import os import re import subprocess import sys import tempfile _DYNAMIC_SECTION_NEEDED_PATTERN = re.compile( '^ 0x[0-9a-fA-F]+\\s+NEEDED\\s+Shared library: \\[(.*)\\]$' ) _ELF_MAGIC = b'\x7fELF' def ParseArgs(): parser = argparse.ArgumentParser() parser.add_argument('apex', help='Path to the APEX') parser.add_argument( '--tool_path', help='Tools are searched in TOOL_PATH/bin. Colon-separated list of paths', ) parser.add_argument( '--unwanted', help='Names not allowed in DT_NEEDED. Colon-separated list of names', ) return parser.parse_args() def InitTools(tool_path): if tool_path is None: exec_path = os.path.realpath(sys.argv[0]) if exec_path.endswith('.py'): script_name = os.path.basename(exec_path)[:-3] sys.exit( f'Do not invoke {exec_path} directly. Instead, use {script_name}' ) tool_path = os.environ['PATH'] def ToolPath(name): for p in tool_path.split(':'): path = os.path.join(p, name) if os.path.exists(path): return path sys.exit(f'Required tool({name}) not found in {tool_path}') return { tool: ToolPath(tool) for tool in [ 'deapexer', 'debugfs_static', 'fsck.erofs', 'llvm-readelf', ] } def IsElfFile(path): with open(path, 'rb') as f: buf = bytearray(len(_ELF_MAGIC)) f.readinto(buf) return buf == _ELF_MAGIC def ParseElfNeeded(path, tools): output = subprocess.check_output( [tools['llvm-readelf'], '-d', '--elf-output-style', 'LLVM', path], text=True, stderr=subprocess.PIPE, ) needed = [] for line in output.splitlines(): match = _DYNAMIC_SECTION_NEEDED_PATTERN.match(line) if match: needed.append(match.group(1)) return needed def ScanElfFiles(work_dir): for parent, _, files in os.walk(work_dir): for file in files: path = os.path.join(parent, file) # Skip symlinks for APEXes with symlink optimization if os.path.islink(path): continue if IsElfFile(path): yield path def CheckElfFiles(args, tools): with tempfile.TemporaryDirectory() as work_dir: subprocess.check_output( [ tools['deapexer'], '--debugfs_path', tools['debugfs_static'], '--fsckerofs_path', tools['fsck.erofs'], 'extract', args.apex, work_dir, ], text=True, stderr=subprocess.PIPE, ) if args.unwanted: unwanted = set(args.unwanted.split(':')) for file in ScanElfFiles(work_dir): needed = set(ParseElfNeeded(file, tools)) if unwanted & needed: sys.exit( f'{os.path.relpath(file, work_dir)} has unwanted NEEDED:' f' {",".join(unwanted & needed)}' ) def main(): args = ParseArgs() tools = InitTools(args.tool_path) try: CheckElfFiles(args, tools) except subprocess.CalledProcessError as e: sys.exit('Result:' + str(e.stderr)) if __name__ == '__main__': main()