1#!/usr/bin/env python3 2 3from __future__ import print_function 4 5import argparse 6import os 7import re 8import subprocess 9import sys 10import traceback 11 12# Python 2 and 3 compatibility layers. 13if sys.version_info >= (3, 0): 14 from os import makedirs 15 from shutil import which 16 17 def get_byte(buf, idx): 18 return buf[idx] 19 20 def check_silent_call(cmd): 21 subprocess.check_call(cmd, stdout=subprocess.DEVNULL, 22 stderr=subprocess.DEVNULL) 23else: 24 def makedirs(path, exist_ok): 25 if exist_ok and os.path.isdir(path): 26 return 27 return os.makedirs(path) 28 29 def which(cmd, mode=os.F_OK | os.X_OK, path=None): 30 def is_executable(path): 31 return (os.path.exists(file_path) and \ 32 os.access(file_path, mode) and \ 33 not os.path.isdir(file_path)) 34 if path is None: 35 path = os.environ.get('PATH', os.defpath) 36 for path_dir in path.split(os.pathsep): 37 for file_name in os.listdir(path_dir): 38 if file_name != cmd: 39 continue 40 file_path = os.path.join(path_dir, file_name) 41 if is_executable(file_path): 42 return file_path 43 return None 44 45 def get_byte(buf, idx): 46 return ord(buf[idx]) 47 48 def check_silent_call(cmd): 49 with open(os.devnull, 'wb') as devnull: 50 subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 51 52 FileNotFoundError = OSError 53 54 55# Path constants. 56SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) 57AOSP_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, *['..'] * 4)) 58ABI_DUMPER = os.path.join(AOSP_DIR, 'external', 'abi-dumper', 'abi-dumper.pl') 59VTABLE_DUMPER = 'vndk-vtable-dumper' 60BINARY_ABI_DUMP_EXT = '.bdump' 61 62 63# Compilation targets. 64class Target(object): 65 def __init__(self, arch, gcc_arch, gcc_prefix, gcc_version, lib_dir_name): 66 self.arch = arch 67 self.gcc_dir = self._get_prebuilts_gcc(gcc_arch, gcc_prefix, 68 gcc_version) 69 self.gcc_prefix = gcc_prefix 70 self.lib_dir_name = lib_dir_name 71 72 def _get_prebuilts_host(self): 73 """Get the host dir for prebuilts""" 74 if sys.platform.startswith('linux'): 75 return 'linux-x86' 76 if sys.platform.startswith('darwin'): 77 return 'darwin-x86' 78 raise NotImplementedError('unknown platform') 79 80 def _get_prebuilts_gcc(self, gcc_arch, gcc_prefix, gcc_version): 81 """Get the path to gcc for the current platform""" 82 return os.path.join(AOSP_DIR, 'prebuilts', 'gcc', 83 self._get_prebuilts_host(), gcc_arch, 84 gcc_prefix + gcc_version) 85 86 def get_exe(self, name): 87 """Get the path to prebuilt executable""" 88 return os.path.join(self.gcc_dir, 'bin', self.gcc_prefix + name) 89 90class TargetRegistry(object): 91 def __init__(self): 92 self.targets = dict() 93 94 def add(self, arch, gcc_arch, gcc_prefix, gcc_version, lib_dir_name): 95 self.targets[arch] = Target(arch, gcc_arch, gcc_prefix, gcc_version, 96 lib_dir_name) 97 98 def get(self, arch_name, var_name): 99 try: 100 return self.targets[arch_name] 101 except KeyError: 102 print('{}: error: unknown {}: {}' 103 .format(sys.argv[0], var_name, arch_name), file=sys.stderr) 104 sys.exit(1) 105 106 @staticmethod 107 def create(): 108 res = TargetRegistry() 109 res.add('arm', 'arm', 'arm-linux-androideabi-', '4.9', 'lib') 110 res.add('arm64', 'aarch64', 'aarch64-linux-android-', '4.9', 'lib64') 111 res.add('mips', 'mips', 'mips64el-linux-android-', '4.9', 'lib') 112 res.add('mips64', 'mips', 'mips64el-linux-android-', '4.9', 'lib64') 113 res.add('x86', 'x86', 'x86_64-linux-android-', '4.9', 'lib') 114 res.add('x86_64', 'x86', 'x86_64-linux-android-', '4.9', 'lib64') 115 return res 116 117 118# Command tests. 119def test_command(name, options, expected_output): 120 def is_command_valid(): 121 try: 122 if os.path.exists(name) and os.access(name, os.F_OK | os.X_OK): 123 exec_path = name 124 else: 125 exec_path = which(name) 126 if not exec_path: 127 return False 128 output = subprocess.check_output([exec_path] + options) 129 return (expected_output in output) 130 except Exception: 131 traceback.print_exc() 132 return False 133 134 if not is_command_valid(): 135 print('error: failed to run {} command'.format(name), file=sys.stderr) 136 sys.exit(1) 137 138def test_readelf_command(readelf): 139 test_command(readelf, ['-v'], b'GNU readelf') 140 141def test_objdump_command(objdump): 142 test_command(objdump, ['-v'], b'GNU objdump') 143 144def test_vtable_dumper_command(): 145 test_command(VTABLE_DUMPER, ['--version'], b'vndk-vtable-dumper') 146 147def test_abi_dumper_command(): 148 test_command(ABI_DUMPER, ['-v'], b'ABI Dumper') 149 150def test_all_commands(readelf, objdump): 151 test_readelf_command(readelf) 152 test_objdump_command(objdump) 153 test_vtable_dumper_command() 154 test_abi_dumper_command() 155 156 157# ELF file format constants. 158ELF_MAGIC = b'\x7fELF' 159 160EI_CLASS = 4 161EI_DATA = 5 162EI_NIDENT = 8 163 164ELFCLASS32 = 1 165ELFCLASS64 = 2 166 167ELFDATA2LSB = 1 168ELFDATA2MSB = 2 169 170 171# ELF file check utilities. 172def is_elf_ident(buf): 173 # Check the length of ELF ident. 174 if len(buf) != EI_NIDENT: 175 return False 176 177 # Check ELF magic word. 178 if buf[0:4] != ELF_MAGIC: 179 return False 180 181 # Check ELF machine word size. 182 ei_class = get_byte(buf, EI_CLASS) 183 if ei_class != ELFCLASS32 and ei_class != ELFCLASS64: 184 return False 185 186 # Check ELF endianness. 187 ei_data = get_byte(buf, EI_DATA) 188 if ei_data != ELFDATA2LSB and ei_data != ELFDATA2MSB: 189 return False 190 191 return True 192 193def is_elf_file(path): 194 try: 195 with open(path, 'rb') as f: 196 return is_elf_ident(f.read(EI_NIDENT)) 197 except FileNotFoundError: 198 return False 199 200def create_vndk_lib_name_filter(file_list_path): 201 if not file_list_path: 202 def accept_all_filenames(name): 203 return True 204 return accept_all_filenames 205 206 with open(file_list_path, 'r') as f: 207 lines = f.read().splitlines() 208 209 patt = re.compile('^(?:' + 210 '|'.join('(?:' + re.escape(x) + ')' for x in lines) + 211 ')$') 212 def accept_matched_filenames(name): 213 return patt.match(name) 214 return accept_matched_filenames 215 216def create_abi_reference_dump(out_dir, symbols_dir, api_level, show_commands, 217 target, is_vndk_lib_name): 218 # Check command line tools. 219 readelf = target.get_exe('readelf') 220 objdump = target.get_exe('objdump') 221 test_all_commands(readelf, objdump) 222 223 # Check library directory. 224 lib_dir = os.path.join(symbols_dir, 'system', target.lib_dir_name) 225 if not os.path.exists(lib_dir): 226 print('error: failed to find lib directory:', lib_dir, file=sys.stderr) 227 sys.exit(1) 228 229 # Append target architecture to output directory path. 230 out_dir = os.path.join(out_dir, target.arch) 231 232 # Process libraries. 233 cmd_base = [ABI_DUMPER, '-lver', api_level, '-objdump', objdump, 234 '-readelf', readelf, '-vt-dumper', which(VTABLE_DUMPER), 235 '-use-tu-dump', '--quiet'] 236 237 num_processed = 0 238 lib_dir = os.path.abspath(lib_dir) 239 prefix_len = len(lib_dir) + 1 240 for base, dirnames, filenames in os.walk(lib_dir): 241 for filename in filenames: 242 if not is_vndk_lib_name(filename): 243 continue 244 245 path = os.path.join(base, filename) 246 if not is_elf_file(path): 247 continue 248 249 rel_path = path[prefix_len:] 250 out_path = os.path.join(out_dir, rel_path) + BINARY_ABI_DUMP_EXT 251 252 makedirs(os.path.dirname(out_path), exist_ok=True) 253 cmd = cmd_base + [path, '-o', out_path] 254 if show_commands: 255 print('run:', ' '.join(cmd)) 256 else: 257 print('process:', path) 258 check_silent_call(cmd) 259 num_processed += 1 260 261 return num_processed 262 263def get_build_var_from_build_system(name): 264 """Get build system variable for the launched target.""" 265 if 'ANDROID_PRODUCT_OUT' not in os.environ: 266 return None 267 268 cmd = ['make', '--no-print-directory', '-f', 'build/core/config.mk', 269 'dumpvar-' + name] 270 271 environ = dict(os.environ) 272 environ['CALLED_FROM_SETUP'] = 'true' 273 environ['BUILD_SYSTEM'] = 'build/core' 274 275 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, 276 stderr=subprocess.PIPE, env=environ, 277 cwd=AOSP_DIR) 278 out, err = proc.communicate() 279 return out.decode('utf-8').strip() 280 281def get_build_var(name, args): 282 """Get build system variable either from command line option or build 283 system.""" 284 value = getattr(args, name.lower(), None) 285 return value if value else get_build_var_from_build_system(name) 286 287def report_missing_argument(parser, arg_name): 288 parser.print_usage() 289 print('{}: error: the following arguments are required: {}' 290 .format(sys.argv[0], arg_name), file=sys.stderr) 291 sys.exit(1) 292 293def main(): 294 # Parse command line options. 295 parser = argparse.ArgumentParser() 296 parser.add_argument('--output', '-o', metavar='path', 297 help='output directory for abi reference dump') 298 parser.add_argument('--vndk-list', help='VNDK library list') 299 parser.add_argument('--api-level', default='24', help='VNDK API level') 300 parser.add_argument('--target-arch', help='target architecture') 301 parser.add_argument('--target-2nd-arch', help='second target architecture') 302 parser.add_argument('--product-out', help='android product out') 303 parser.add_argument('--target-product', help='target product') 304 parser.add_argument('--target-build-variant', help='target build variant') 305 parser.add_argument('--symbols-dir', help='unstripped symbols directory') 306 parser.add_argument('--show-commands', action='store_true', 307 help='Show the abi-dumper command') 308 args = parser.parse_args() 309 310 # Check the symbols directory. 311 if args.symbols_dir: 312 symbols_dir = args.symbols_dir 313 else: 314 # If the user did not specify the symbols directory, try to create 315 # one from ANDROID_PRODUCT_OUT. 316 product_out = get_build_var('PRODUCT_OUT', args) 317 if not product_out: 318 report_missing_argument(parser, '--symbols-dir') 319 if not os.path.isabs(product_out): 320 product_out = os.path.join(AOSP_DIR, product_out) 321 symbols_dir = os.path.join(product_out, 'symbols') 322 323 # Check the output directory. 324 if args.output: 325 out_dir = args.output 326 else: 327 # If the user did not specify the output directory, try to create one 328 # default output directory from TARGET_PRODUCT and 329 # TARGET_BUILD_VARIANT. 330 331 target_product = get_build_var('TARGET_PRODUCT', args) 332 target_build_variant = get_build_var('TARGET_BUILD_VARIANT', args) 333 if not target_product or not target_build_variant: 334 report_missing_argument(parser, '--output/-o') 335 lunch_name = target_product + '-' + target_build_variant 336 out_dir = os.path.join(AOSP_DIR, 'vndk', 'dumps', lunch_name) 337 338 # Check the targets. 339 target_registry = TargetRegistry.create() 340 targets = [] 341 342 arch_name = get_build_var('TARGET_ARCH', args) 343 if not arch_name: 344 report_missing_argument(parser, '--target-arch') 345 targets.append(target_registry.get(arch_name, 'TARGET_ARCH')) 346 must_have_2nd_arch = (targets[0].lib_dir_name == 'lib64') 347 348 arch_name = get_build_var('TARGET_2ND_ARCH', args) 349 if arch_name: 350 targets.append(target_registry.get(arch_name, 'TARGET_2ND_ARCH')) 351 elif must_have_2nd_arch: 352 report_missing_argument(parser, '--target-2nd-arch') 353 354 # Dump all libraries for the specified architectures. 355 num_processed = 0 356 for target in targets: 357 num_processed += create_abi_reference_dump( 358 out_dir, symbols_dir, args.api_level, args.show_commands, 359 target, create_vndk_lib_name_filter(args.vndk_list)) 360 361 # Print a summary at the end. 362 _TERM_WIDTH = 79 363 print() 364 print('-' * _TERM_WIDTH) 365 print('msg: Reference dump created at directory:', out_dir) 366 print('msg: Processed', num_processed, 'libraries') 367 368if __name__ == '__main__': 369 main() 370