1#!/usr/bin/env python 2# 3# Copyright (C) 2023 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""apex_elf_checker checks if ELF files in the APEX 17 18Usage: apex_elf_checker [--unwanted <names>] <apex> 19 20 --unwanted <names> 21 22 Fail if any of ELF files in APEX has any of unwanted names in NEEDED ` 23""" 24 25import argparse 26import os 27import re 28import subprocess 29import sys 30import tempfile 31 32 33_DYNAMIC_SECTION_NEEDED_PATTERN = re.compile( 34 '^ 0x[0-9a-fA-F]+\\s+NEEDED\\s+Shared library: \\[(.*)\\]$' 35) 36 37 38_ELF_MAGIC = b'\x7fELF' 39 40 41def ParseArgs(): 42 parser = argparse.ArgumentParser() 43 parser.add_argument('apex', help='Path to the APEX') 44 parser.add_argument( 45 '--tool_path', 46 help='Tools are searched in TOOL_PATH/bin. Colon-separated list of paths', 47 ) 48 parser.add_argument( 49 '--unwanted', 50 help='Names not allowed in DT_NEEDED. Colon-separated list of names', 51 ) 52 return parser.parse_args() 53 54 55def InitTools(tool_path): 56 if tool_path is None: 57 exec_path = os.path.realpath(sys.argv[0]) 58 if exec_path.endswith('.py'): 59 script_name = os.path.basename(exec_path)[:-3] 60 sys.exit( 61 f'Do not invoke {exec_path} directly. Instead, use {script_name}' 62 ) 63 tool_path = os.environ['PATH'] 64 65 def ToolPath(name): 66 for p in tool_path.split(':'): 67 path = os.path.join(p, name) 68 if os.path.exists(path): 69 return path 70 sys.exit(f'Required tool({name}) not found in {tool_path}') 71 72 return { 73 tool: ToolPath(tool) 74 for tool in [ 75 'deapexer', 76 'debugfs_static', 77 'fsck.erofs', 78 'llvm-readelf', 79 ] 80 } 81 82 83def IsElfFile(path): 84 with open(path, 'rb') as f: 85 buf = bytearray(len(_ELF_MAGIC)) 86 f.readinto(buf) 87 return buf == _ELF_MAGIC 88 89 90def ParseElfNeeded(path, tools): 91 output = subprocess.check_output( 92 [tools['llvm-readelf'], '-d', '--elf-output-style', 'LLVM', path], 93 text=True, 94 stderr=subprocess.PIPE, 95 ) 96 97 needed = [] 98 for line in output.splitlines(): 99 match = _DYNAMIC_SECTION_NEEDED_PATTERN.match(line) 100 if match: 101 needed.append(match.group(1)) 102 return needed 103 104 105def ScanElfFiles(work_dir): 106 for parent, _, files in os.walk(work_dir): 107 for file in files: 108 path = os.path.join(parent, file) 109 # Skip symlinks for APEXes with symlink optimization 110 if os.path.islink(path): 111 continue 112 if IsElfFile(path): 113 yield path 114 115 116def CheckElfFiles(args, tools): 117 with tempfile.TemporaryDirectory() as work_dir: 118 subprocess.check_output( 119 [ 120 tools['deapexer'], 121 '--debugfs_path', 122 tools['debugfs_static'], 123 '--fsckerofs_path', 124 tools['fsck.erofs'], 125 'extract', 126 args.apex, 127 work_dir, 128 ], 129 text=True, 130 stderr=subprocess.PIPE, 131 ) 132 133 if args.unwanted: 134 unwanted = set(args.unwanted.split(':')) 135 for file in ScanElfFiles(work_dir): 136 needed = set(ParseElfNeeded(file, tools)) 137 if unwanted & needed: 138 sys.exit( 139 f'{os.path.relpath(file, work_dir)} has unwanted NEEDED:' 140 f' {",".join(unwanted & needed)}' 141 ) 142 143 144def main(): 145 args = ParseArgs() 146 tools = InitTools(args.tool_path) 147 try: 148 CheckElfFiles(args, tools) 149 except subprocess.CalledProcessError as e: 150 sys.exit('Result:' + str(e.stderr)) 151 152 153if __name__ == '__main__': 154 main() 155