1#!/usr/bin/env python 2 3import subprocess 4import optparse 5import os 6import os.path 7import re 8import sys 9 10 11def extract_exe_symbol_names(arch, exe_path, match_str): 12 command = 'dsymutil --arch %s -s "%s" | grep "%s" | colrm 1 69' % ( 13 arch, 14 exe_path, 15 match_str, 16 ) 17 (command_exit_status, command_output) = subprocess.getstatusoutput(command) 18 if command_exit_status == 0: 19 if command_output: 20 return command_output[0:-1].split("'\n") 21 else: 22 print("error: command returned no output") 23 else: 24 print( 25 "error: command failed with exit status %i\n command: %s" 26 % (command_exit_status, command) 27 ) 28 return list() 29 30 31def verify_api(all_args): 32 """Verify the API in the specified library is valid given one or more binaries.""" 33 usage = "usage: verify_api --library <path> [ --library <path> ...] executable1 [executable2 ...]" 34 description = """Verify the API in the specified library is valid given one or more binaries. 35 36 Example: 37 38 verify_api.py --library ~/Documents/src/lldb/build/Debug/LLDB.framework/LLDB --arch x86_64 /Applications/Xcode.app/Contents/PlugIns/DebuggerLLDB.ideplugin/Contents/MacOS/DebuggerLLDB --api-regex lldb 39 """ 40 parser = optparse.OptionParser( 41 description=description, prog="verify_api", usage=usage 42 ) 43 parser.add_option( 44 "-v", 45 "--verbose", 46 action="store_true", 47 dest="verbose", 48 help="display verbose debug info", 49 default=False, 50 ) 51 parser.add_option( 52 "-a", 53 "--arch", 54 type="string", 55 action="append", 56 dest="archs", 57 help="architecture to use when checking the api", 58 ) 59 parser.add_option( 60 "-r", 61 "--api-regex", 62 type="string", 63 dest="api_regex_str", 64 help="Exclude any undefined symbols that do not match this regular expression when searching for missing APIs.", 65 ) 66 parser.add_option( 67 "-l", 68 "--library", 69 type="string", 70 action="append", 71 dest="libraries", 72 help="Specify one or more libraries that will contain all needed APIs for the executables.", 73 ) 74 (options, args) = parser.parse_args(all_args) 75 76 api_external_symbols = list() 77 if options.archs: 78 for arch in options.archs: 79 for library in options.libraries: 80 external_symbols = extract_exe_symbol_names( 81 arch, library, "( SECT EXT)" 82 ) 83 if external_symbols: 84 for external_symbol in external_symbols: 85 api_external_symbols.append(external_symbol) 86 else: 87 sys.exit(1) 88 else: 89 print("error: must specify one or more architectures with the --arch option") 90 sys.exit(4) 91 if options.verbose: 92 print("API symbols:") 93 for i, external_symbol in enumerate(api_external_symbols): 94 print("[%u] %s" % (i, external_symbol)) 95 96 api_regex = None 97 if options.api_regex_str: 98 api_regex = re.compile(options.api_regex_str) 99 100 for arch in options.archs: 101 for exe_path in args: 102 print('Verifying (%s) "%s"...' % (arch, exe_path)) 103 exe_errors = 0 104 undefined_symbols = extract_exe_symbol_names( 105 arch, exe_path, "( UNDF EXT)" 106 ) 107 for undefined_symbol in undefined_symbols: 108 if api_regex: 109 match = api_regex.search(undefined_symbol) 110 if not match: 111 if options.verbose: 112 print("ignoring symbol: %s" % (undefined_symbol)) 113 continue 114 if undefined_symbol in api_external_symbols: 115 if options.verbose: 116 print("verified symbol: %s" % (undefined_symbol)) 117 else: 118 print("missing symbol: %s" % (undefined_symbol)) 119 exe_errors += 1 120 if exe_errors: 121 print( 122 "error: missing %u API symbols from %s" 123 % (exe_errors, options.libraries) 124 ) 125 else: 126 print("success") 127 128 129if __name__ == "__main__": 130 verify_api(sys.argv[1:]) 131