1#!/usr/bin/env python3 2 3import argparse 4import os 5import platform 6import subprocess 7 8# This list contains symbols that _might_ be exported for some platforms 9PLATFORM_SYMBOLS = [ 10 '__bss_end__', 11 '__bss_start__', 12 '__bss_start', 13 '__end__', 14 '_bss_end__', 15 '_edata', 16 '_end', 17 '_fini', 18 '_init', 19] 20 21 22def get_symbols(nm, lib): 23 ''' 24 List all the (non platform-specific) symbols exported by the library 25 ''' 26 symbols = [] 27 platform_name = platform.system() 28 output = subprocess.check_output([nm, '-gP', lib], 29 stderr=open(os.devnull, 'w')).decode("ascii") 30 for line in output.splitlines(): 31 fields = line.split() 32 if len(fields) == 2 or fields[1] == 'U': 33 continue 34 symbol_name = fields[0] 35 if platform_name == 'Linux': 36 if symbol_name in PLATFORM_SYMBOLS: 37 continue 38 elif platform_name == 'Darwin': 39 assert symbol_name[0] == '_' 40 symbol_name = symbol_name[1:] 41 symbols.append(symbol_name) 42 43 return symbols 44 45 46def main(): 47 parser = argparse.ArgumentParser() 48 parser.add_argument('--symbols-file', 49 action='store', 50 required=True, 51 help='path to file containing symbols') 52 parser.add_argument('--lib', 53 action='store', 54 required=True, 55 help='path to library') 56 parser.add_argument('--nm', 57 action='store', 58 required=True, 59 help='path to binary (or name in $PATH)') 60 args = parser.parse_args() 61 62 try: 63 lib_symbols = get_symbols(args.nm, args.lib) 64 except: 65 # We can't run this test, but we haven't technically failed it either 66 # Return the GNU "skip" error code 67 exit(77) 68 mandatory_symbols = [] 69 optional_symbols = [] 70 with open(args.symbols_file) as symbols_file: 71 qualifier_optional = '(optional)' 72 for line in symbols_file.readlines(): 73 74 # Strip comments 75 line = line.split('#')[0] 76 line = line.strip() 77 if not line: 78 continue 79 80 # Line format: 81 # [qualifier] symbol 82 qualifier = None 83 symbol = None 84 85 fields = line.split() 86 if len(fields) == 1: 87 symbol = fields[0] 88 elif len(fields) == 2: 89 qualifier = fields[0] 90 symbol = fields[1] 91 else: 92 print(args.symbols_file + ': invalid format: ' + line) 93 exit(1) 94 95 # The only supported qualifier is 'optional', which means the 96 # symbol doesn't have to be exported by the library 97 if qualifier and not qualifier == qualifier_optional: 98 print(args.symbols_file + ': invalid qualifier: ' + qualifier) 99 exit(1) 100 101 if qualifier == qualifier_optional: 102 optional_symbols.append(symbol) 103 else: 104 mandatory_symbols.append(symbol) 105 106 unknown_symbols = [] 107 for symbol in lib_symbols: 108 if symbol in mandatory_symbols: 109 continue 110 if symbol in optional_symbols: 111 continue 112 unknown_symbols.append(symbol) 113 114 missing_symbols = [ 115 sym for sym in mandatory_symbols if sym not in lib_symbols 116 ] 117 118 for symbol in unknown_symbols: 119 print(args.lib + ': unknown symbol exported: ' + symbol) 120 121 for symbol in missing_symbols: 122 print(args.lib + ': missing symbol: ' + symbol) 123 124 if unknown_symbols or missing_symbols: 125 exit(1) 126 exit(0) 127 128 129if __name__ == '__main__': 130 main() 131