17ec681f3Smrg#!/usr/bin/env python3 27ec681f3Smrg 37ec681f3Smrgimport argparse 47ec681f3Smrgimport os 57ec681f3Smrgimport platform 67ec681f3Smrgimport subprocess 77ec681f3Smrg 87ec681f3Smrg# This list contains symbols that _might_ be exported for some platforms 97ec681f3SmrgPLATFORM_SYMBOLS = [ 107ec681f3Smrg '__bss_end__', 117ec681f3Smrg '__bss_start__', 127ec681f3Smrg '__bss_start', 137ec681f3Smrg '__cxa_guard_abort', 147ec681f3Smrg '__cxa_guard_acquire', 157ec681f3Smrg '__cxa_guard_release', 167ec681f3Smrg '__end__', 177ec681f3Smrg '__odr_asan._glapi_Context', 187ec681f3Smrg '__odr_asan._glapi_Dispatch', 197ec681f3Smrg '_bss_end__', 207ec681f3Smrg '_edata', 217ec681f3Smrg '_end', 227ec681f3Smrg '_fini', 237ec681f3Smrg '_init', 247ec681f3Smrg '_fbss', 257ec681f3Smrg '_fdata', 267ec681f3Smrg '_ftext', 277ec681f3Smrg] 287ec681f3Smrg 297ec681f3Smrgdef get_symbols_nm(nm, lib): 307ec681f3Smrg ''' 317ec681f3Smrg List all the (non platform-specific) symbols exported by the library 327ec681f3Smrg using `nm` 337ec681f3Smrg ''' 347ec681f3Smrg symbols = [] 357ec681f3Smrg platform_name = platform.system() 367ec681f3Smrg output = subprocess.check_output([nm, '-gP', lib], 377ec681f3Smrg stderr=open(os.devnull, 'w')).decode("ascii") 387ec681f3Smrg for line in output.splitlines(): 397ec681f3Smrg fields = line.split() 407ec681f3Smrg if len(fields) == 2 or fields[1] == 'U': 417ec681f3Smrg continue 427ec681f3Smrg symbol_name = fields[0] 437ec681f3Smrg if platform_name == 'Linux': 447ec681f3Smrg if symbol_name in PLATFORM_SYMBOLS: 457ec681f3Smrg continue 467ec681f3Smrg elif platform_name == 'Darwin': 477ec681f3Smrg assert symbol_name[0] == '_' 487ec681f3Smrg symbol_name = symbol_name[1:] 497ec681f3Smrg symbols.append(symbol_name) 507ec681f3Smrg return symbols 517ec681f3Smrg 527ec681f3Smrg 537ec681f3Smrgdef get_symbols_dumpbin(dumpbin, lib): 547ec681f3Smrg ''' 557ec681f3Smrg List all the (non platform-specific) symbols exported by the library 567ec681f3Smrg using `dumpbin` 577ec681f3Smrg ''' 587ec681f3Smrg symbols = [] 597ec681f3Smrg output = subprocess.check_output([dumpbin, '/exports', lib], 607ec681f3Smrg stderr=open(os.devnull, 'w')).decode("ascii") 617ec681f3Smrg for line in output.splitlines(): 627ec681f3Smrg fields = line.split() 637ec681f3Smrg # The lines with the symbols are made of at least 4 columns; see details below 647ec681f3Smrg if len(fields) < 4: 657ec681f3Smrg continue 667ec681f3Smrg try: 677ec681f3Smrg # Making sure the first 3 columns are a dec counter, a hex counter 687ec681f3Smrg # and a hex address 697ec681f3Smrg _ = int(fields[0], 10) 707ec681f3Smrg _ = int(fields[1], 16) 717ec681f3Smrg _ = int(fields[2], 16) 727ec681f3Smrg except ValueError: 737ec681f3Smrg continue 747ec681f3Smrg symbol_name = fields[3] 757ec681f3Smrg # De-mangle symbols 767ec681f3Smrg if symbol_name[0] == '_' and '@' in symbol_name: 777ec681f3Smrg symbol_name = symbol_name[1:].split('@')[0] 787ec681f3Smrg symbols.append(symbol_name) 797ec681f3Smrg return symbols 807ec681f3Smrg 817ec681f3Smrg 827ec681f3Smrgdef main(): 837ec681f3Smrg parser = argparse.ArgumentParser() 847ec681f3Smrg parser.add_argument('--symbols-file', 857ec681f3Smrg action='store', 867ec681f3Smrg required=True, 877ec681f3Smrg help='path to file containing symbols') 887ec681f3Smrg parser.add_argument('--lib', 897ec681f3Smrg action='store', 907ec681f3Smrg required=True, 917ec681f3Smrg help='path to library') 927ec681f3Smrg parser.add_argument('--nm', 937ec681f3Smrg action='store', 947ec681f3Smrg help='path to binary (or name in $PATH)') 957ec681f3Smrg parser.add_argument('--dumpbin', 967ec681f3Smrg action='store', 977ec681f3Smrg help='path to binary (or name in $PATH)') 987ec681f3Smrg parser.add_argument('--ignore-symbol', 997ec681f3Smrg action='append', 1007ec681f3Smrg help='do not process this symbol') 1017ec681f3Smrg args = parser.parse_args() 1027ec681f3Smrg 1037ec681f3Smrg try: 1047ec681f3Smrg if platform.system() == 'Windows': 1057ec681f3Smrg if not args.dumpbin: 1067ec681f3Smrg parser.error('--dumpbin is mandatory') 1077ec681f3Smrg lib_symbols = get_symbols_dumpbin(args.dumpbin, args.lib) 1087ec681f3Smrg else: 1097ec681f3Smrg if not args.nm: 1107ec681f3Smrg parser.error('--nm is mandatory') 1117ec681f3Smrg lib_symbols = get_symbols_nm(args.nm, args.lib) 1127ec681f3Smrg except: 1137ec681f3Smrg # We can't run this test, but we haven't technically failed it either 1147ec681f3Smrg # Return the GNU "skip" error code 1157ec681f3Smrg exit(77) 1167ec681f3Smrg mandatory_symbols = [] 1177ec681f3Smrg optional_symbols = [] 1187ec681f3Smrg with open(args.symbols_file) as symbols_file: 1197ec681f3Smrg qualifier_optional = '(optional)' 1207ec681f3Smrg for line in symbols_file.readlines(): 1217ec681f3Smrg 1227ec681f3Smrg # Strip comments 1237ec681f3Smrg line = line.split('#')[0] 1247ec681f3Smrg line = line.strip() 1257ec681f3Smrg if not line: 1267ec681f3Smrg continue 1277ec681f3Smrg 1287ec681f3Smrg # Line format: 1297ec681f3Smrg # [qualifier] symbol 1307ec681f3Smrg qualifier = None 1317ec681f3Smrg symbol = None 1327ec681f3Smrg 1337ec681f3Smrg fields = line.split() 1347ec681f3Smrg if len(fields) == 1: 1357ec681f3Smrg symbol = fields[0] 1367ec681f3Smrg elif len(fields) == 2: 1377ec681f3Smrg qualifier = fields[0] 1387ec681f3Smrg symbol = fields[1] 1397ec681f3Smrg else: 1407ec681f3Smrg print(args.symbols_file + ': invalid format: ' + line) 1417ec681f3Smrg exit(1) 1427ec681f3Smrg 1437ec681f3Smrg # The only supported qualifier is 'optional', which means the 1447ec681f3Smrg # symbol doesn't have to be exported by the library 1457ec681f3Smrg if qualifier and not qualifier == qualifier_optional: 1467ec681f3Smrg print(args.symbols_file + ': invalid qualifier: ' + qualifier) 1477ec681f3Smrg exit(1) 1487ec681f3Smrg 1497ec681f3Smrg if qualifier == qualifier_optional: 1507ec681f3Smrg optional_symbols.append(symbol) 1517ec681f3Smrg else: 1527ec681f3Smrg mandatory_symbols.append(symbol) 1537ec681f3Smrg 1547ec681f3Smrg unknown_symbols = [] 1557ec681f3Smrg for symbol in lib_symbols: 1567ec681f3Smrg if symbol in mandatory_symbols: 1577ec681f3Smrg continue 1587ec681f3Smrg if symbol in optional_symbols: 1597ec681f3Smrg continue 1607ec681f3Smrg if args.ignore_symbol and symbol in args.ignore_symbol: 1617ec681f3Smrg continue 1627ec681f3Smrg if symbol[:2] == '_Z': 1637ec681f3Smrg # As ajax found out, the compiler intentionally exports symbols 1647ec681f3Smrg # that we explicitely asked it not to export, and we can't do 1657ec681f3Smrg # anything about it: 1667ec681f3Smrg # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36022#c4 1677ec681f3Smrg continue 1687ec681f3Smrg unknown_symbols.append(symbol) 1697ec681f3Smrg 1707ec681f3Smrg missing_symbols = [ 1717ec681f3Smrg sym for sym in mandatory_symbols if sym not in lib_symbols 1727ec681f3Smrg ] 1737ec681f3Smrg 1747ec681f3Smrg for symbol in unknown_symbols: 1757ec681f3Smrg print(args.lib + ': unknown symbol exported: ' + symbol) 1767ec681f3Smrg 1777ec681f3Smrg for symbol in missing_symbols: 1787ec681f3Smrg print(args.lib + ': missing symbol: ' + symbol) 1797ec681f3Smrg 1807ec681f3Smrg if unknown_symbols or missing_symbols: 1817ec681f3Smrg exit(1) 1827ec681f3Smrg exit(0) 1837ec681f3Smrg 1847ec681f3Smrg 1857ec681f3Smrgif __name__ == '__main__': 1867ec681f3Smrg main() 187