#!/usr/bin/env python
import subprocess
import optparse
import os
import os.path
import re
import sys
def extract_exe_symbol_names(arch, exe_path, match_str):
command = 'dsymutil --arch %s -s "%s" | grep "%s" | colrm 1 69' % (
arch,
exe_path,
match_str,
)
(command_exit_status, command_output) = subprocess.getstatusoutput(command)
if command_exit_status == 0:
if command_output:
return command_output[0:-1].split("'\n")
else:
print("error: command returned no output")
else:
print(
"error: command failed with exit status %i\n command: %s"
% (command_exit_status, command)
)
return list()
def verify_api(all_args):
"""Verify the API in the specified library is valid given one or more binaries."""
usage = "usage: verify_api --library <path> [ --library <path> ...] executable1 [executable2 ...]"
description = """Verify the API in the specified library is valid given one or more binaries.
Example:
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
"""
parser = optparse.OptionParser(
description=description, prog="verify_api", usage=usage
)
parser.add_option(
"-v",
"--verbose",
action="store_true",
dest="verbose",
help="display verbose debug info",
default=False,
)
parser.add_option(
"-a",
"--arch",
type="string",
action="append",
dest="archs",
help="architecture to use when checking the api",
)
parser.add_option(
"-r",
"--api-regex",
type="string",
dest="api_regex_str",
help="Exclude any undefined symbols that do not match this regular expression when searching for missing APIs.",
)
parser.add_option(
"-l",
"--library",
type="string",
action="append",
dest="libraries",
help="Specify one or more libraries that will contain all needed APIs for the executables.",
)
(options, args) = parser.parse_args(all_args)
api_external_symbols = list()
if options.archs:
for arch in options.archs:
for library in options.libraries:
external_symbols = extract_exe_symbol_names(
arch, library, "( SECT EXT)"
)
if external_symbols:
for external_symbol in external_symbols:
api_external_symbols.append(external_symbol)
else:
sys.exit(1)
else:
print("error: must specify one or more architectures with the --arch option")
sys.exit(4)
if options.verbose:
print("API symbols:")
for i, external_symbol in enumerate(api_external_symbols):
print("[%u] %s" % (i, external_symbol))
api_regex = None
if options.api_regex_str:
api_regex = re.compile(options.api_regex_str)
for arch in options.archs:
for exe_path in args:
print('Verifying (%s) "%s"...' % (arch, exe_path))
exe_errors = 0
undefined_symbols = extract_exe_symbol_names(
arch, exe_path, "( UNDF EXT)"
)
for undefined_symbol in undefined_symbols:
if api_regex:
match = api_regex.search(undefined_symbol)
if not match:
if options.verbose:
print("ignoring symbol: %s" % (undefined_symbol))
continue
if undefined_symbol in api_external_symbols:
if options.verbose:
print("verified symbol: %s" % (undefined_symbol))
else:
print("missing symbol: %s" % (undefined_symbol))
exe_errors += 1
if exe_errors:
print(
"error: missing %u API symbols from %s"
% (exe_errors, options.libraries)
)
else:
print("success")
if __name__ == "__main__":
verify_api(sys.argv[1:])