1 #!/usr/bin/env python 2 # 3 # Copyright (C) 2017 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 # 17 18 import argparse 19 import importlib 20 import os 21 import subprocess 22 import sys 23 24 25 class ExternalModules(object): 26 """This class imports modules dynamically and keeps them as attributes. 27 28 Assume the user runs this script in the source directory. The VTS modules 29 are outside the search path and thus have to be imported dynamically. 30 31 Attribtues: 32 elf_parser: The elf_parser module. 33 vtable_parser: The vtable_parser module. 34 """ 35 @classmethod 36 def ImportParsers(cls, import_dir): 37 """Imports elf_parser and vtable_parser. 38 39 Args: 40 import_dir: The directory containing vts.utils.python.library.*. 41 """ 42 sys.path.append(import_dir) 43 cls.elf_parser = importlib.import_module( 44 "vts.utils.python.library.elf_parser") 45 cls.vtable_parser = importlib.import_module( 46 "vts.utils.python.library.vtable_parser") 47 48 49 def GetBuildVariable(build_top_dir, var): 50 """Gets value of a variable from build config. 51 52 Args: 53 build_top_dir: The path to root directory of Android source. 54 var: The name of the variable. 55 56 Returns: 57 A string which is the value of the variable. 58 """ 59 build_core = os.path.join(build_top_dir, "build", "core") 60 env = dict(os.environ) 61 env["CALLED_FROM_SETUP"] = "true" 62 env["BUILD_SYSTEM"] = build_core 63 cmd = ["make", "--no-print-directory", 64 "-f", os.path.join(build_core, "config.mk"), 65 "dumpvar-" + var] 66 proc = subprocess.Popen(cmd, env=env, cwd=build_top_dir, 67 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 68 stdout, stderr = proc.communicate() 69 if stderr: 70 sys.exit("Cannot get variable: cmd=%s\nstdout=%s\nstderr=%s" % ( 71 cmd, stdout, stderr)) 72 return stdout.strip() 73 74 75 def FindBinary(file_name): 76 """Finds an executable binary in environment variable PATH. 77 78 Args: 79 file_name: The file name to find. 80 81 Returns: 82 A string which is the path to the binary. 83 """ 84 cmd = ["which", file_name] 85 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 86 stdout, stderr = proc.communicate() 87 if proc.returncode: 88 sys.exit("Cannot find file: cmd=%s\nstdout=%s\nstderr=%s" % ( 89 cmd, stdout, stderr)) 90 return stdout 91 92 93 def DumpSymbols(lib_path, dump_path): 94 """Dump symbols from a library to a dump file. 95 96 The dump file is a sorted list of symbols. Each line contains one symbol. 97 98 Args: 99 lib_path: The path to the library. 100 dump_path: The path to the dump file. 101 102 Returns: 103 A string which is the description about the result. 104 105 Raises: 106 elf_parser.ElfError if fails to load the library. 107 IOError if fails to write to the dump. 108 """ 109 elf_parser = ExternalModules.elf_parser 110 parser = None 111 try: 112 parser = elf_parser.ElfParser(lib_path) 113 symbols = parser.ListGlobalDynamicSymbols() 114 finally: 115 if parser: 116 parser.Close() 117 if not symbols: 118 return "No symbols" 119 symbols.sort() 120 with open(dump_path, "w") as dump_file: 121 dump_file.write("\n".join(symbols) + "\n") 122 return "Output: " + dump_path 123 124 125 def DumpVtables(lib_path, dump_path, dumper_dir): 126 """Dump vtables from a library to a dump file. 127 128 The dump file is the raw output of vndk-vtable-dumper. 129 130 Args: 131 lib_path: The path to the library. 132 dump_path: The path to the text file. 133 dumper_dir: The path to the directory containing the dumper executable 134 and library. 135 136 Returns: 137 A string which is the description about the result. 138 139 Raises: 140 vtable_parser.VtableError if fails to load the library. 141 IOError if fails to write to the dump. 142 """ 143 vtable_parser = ExternalModules.vtable_parser 144 parser = vtable_parser.VtableParser(dumper_dir) 145 vtables = parser.CallVtableDumper(lib_path) 146 if not vtables: 147 return "No vtables" 148 with open(dump_path, "w+") as dump_file: 149 dump_file.write(vtables) 150 return "Output: " + dump_path 151 152 153 def GetSystemLibDirByArch(product_dir, arch_name): 154 """Returns the directory containing libraries for specific architecture. 155 156 Args: 157 product_dir: The path to the product output directory in Android source. 158 arch_name: The name of the CPU architecture. 159 160 Returns: 161 The path to the directory containing the libraries. 162 """ 163 if arch_name in ("arm", "x86", "mips"): 164 src_dir = os.path.join(product_dir, "system", "lib") 165 elif arch_name in ("arm64", "x86_64", "mips64"): 166 src_dir = os.path.join(product_dir, "system", "lib64") 167 else: 168 sys.exit("Unknown target arch " + str(target_arch)) 169 return src_dir 170 171 172 def DumpAbi(output_dir, input_files, product_dir, archs, dumper_dir): 173 """Generates dump from libraries. 174 175 Args: 176 output_dir: The output directory of dump files. 177 input_files: A list of strings. Each element can be .so file or a text 178 file which contains list of libraries. 179 product_dir: The path to the product output directory in Android source. 180 archs: A list of strings which are the CPU architectures of the 181 libraries. 182 dumper_dir: The path to the directory containing the vtable dumper 183 executable and library. 184 """ 185 # Get names of the libraries to dump 186 lib_names = [] 187 for input_file in input_files: 188 if input_file.endswith(".so"): 189 lib_names.append(input_file) 190 else: 191 with open(input_file, "r") as lib_list: 192 lib_names.extend(line.strip() for line in lib_list 193 if line.strip()) 194 # Create the dumps 195 for arch in archs: 196 lib_dir = GetSystemLibDirByArch(product_dir, arch) 197 dump_dir = os.path.join(output_dir, arch) 198 if not os.path.exists(dump_dir): 199 os.makedirs(dump_dir) 200 for lib_name in lib_names: 201 lib_path = os.path.join(lib_dir, lib_name) 202 symbol_dump_path = os.path.join(dump_dir, lib_name + "_symbol.dump") 203 vtable_dump_path = os.path.join(dump_dir, lib_name + "_vtable.dump") 204 print(lib_path) 205 print(DumpSymbols(lib_path, symbol_dump_path)) 206 print(DumpVtables(lib_path, vtable_dump_path, dumper_dir)) 207 print("") 208 209 210 def main(): 211 # Parse arguments 212 arg_parser = argparse.ArgumentParser() 213 arg_parser.add_argument("file", nargs="*", 214 help="the library to dump. Can be .so file or a" 215 "text file containing list of libraries.") 216 arg_parser.add_argument("--dumper-dir", "-d", action="store", 217 help="the path to the directory containing " 218 "bin/vndk-vtable-dumper.") 219 arg_parser.add_argument("--import-path", "-i", action="store", 220 help="the directory for VTS python modules. " 221 "Default value is $ANDROID_BUILD_TOP/test") 222 arg_parser.add_argument("--output", "-o", action="store", required=True, 223 help="output directory for ABI reference dump.") 224 args = arg_parser.parse_args() 225 226 # Get product directory 227 product_dir = os.getenv("ANDROID_PRODUCT_OUT") 228 if not product_dir: 229 sys.exit("env var ANDROID_PRODUCT_OUT is not set") 230 print("ANDROID_PRODUCT_OUT=" + product_dir) 231 232 # Get target architectures 233 build_top_dir = os.getenv("ANDROID_BUILD_TOP") 234 if not build_top_dir: 235 sys.exit("env var ANDROID_BUILD_TOP is not set") 236 target_arch = GetBuildVariable(build_top_dir, "TARGET_ARCH") 237 target_2nd_arch = GetBuildVariable(build_top_dir, "TARGET_2ND_ARCH") 238 print("TARGET_ARCH=" + target_arch) 239 print("TARGET_2ND_ARCH=" + target_2nd_arch) 240 archs = [target_arch] 241 if target_2nd_arch: 242 archs.append(target_2nd_arch) 243 244 # Import elf_parser and vtable_parser 245 ExternalModules.ImportParsers(args.import_path if args.import_path else 246 os.path.join(build_top_dir, "test")) 247 248 # Find vtable dumper 249 if args.dumper_dir: 250 dumper_dir = args.dumper_dir 251 else: 252 dumper_path = FindBinary(vtable_parser.VtableParser.VNDK_VTABLE_DUMPER) 253 dumper_dir = os.path.dirname(os.path.dirname(dumper_path)) 254 print("DUMPER_DIR=" + dumper_dir) 255 256 DumpAbi(args.output, args.file, product_dir, archs, dumper_dir) 257 258 259 if __name__ == "__main__": 260 main() 261