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