19bd392adSmrg#!/usr/bin/env python3
29bd392adSmrg
39bd392adSmrgimport argparse
49bd392adSmrgimport os
59bd392adSmrgimport platform
69bd392adSmrgimport subprocess
79bd392adSmrg
89bd392adSmrg# This list contains symbols that _might_ be exported for some platforms
99bd392adSmrgPLATFORM_SYMBOLS = [
10bbff01ceSmrg    '_GLOBAL_OFFSET_TABLE_',
119bd392adSmrg    '__bss_end__',
129bd392adSmrg    '__bss_start__',
139bd392adSmrg    '__bss_start',
149bd392adSmrg    '__end__',
159bd392adSmrg    '_bss_end__',
169bd392adSmrg    '_edata',
179bd392adSmrg    '_end',
189bd392adSmrg    '_fini',
199bd392adSmrg    '_init',
20bbff01ceSmrg    '_fbss',
21bbff01ceSmrg    '_fdata',
22bbff01ceSmrg    '_ftext',
239bd392adSmrg]
249bd392adSmrg
259bd392adSmrg
269bd392adSmrgdef get_symbols(nm, lib):
279bd392adSmrg    '''
289bd392adSmrg    List all the (non platform-specific) symbols exported by the library
299bd392adSmrg    '''
309bd392adSmrg    symbols = []
319bd392adSmrg    platform_name = platform.system()
329bd392adSmrg    output = subprocess.check_output([nm, '-gP', lib],
339bd392adSmrg                                     stderr=open(os.devnull, 'w')).decode("ascii")
349bd392adSmrg    for line in output.splitlines():
359bd392adSmrg        fields = line.split()
369bd392adSmrg        if len(fields) == 2 or fields[1] == 'U':
379bd392adSmrg            continue
389bd392adSmrg        symbol_name = fields[0]
399bd392adSmrg        if platform_name == 'Linux':
409bd392adSmrg            if symbol_name in PLATFORM_SYMBOLS:
419bd392adSmrg                continue
429bd392adSmrg        elif platform_name == 'Darwin':
439bd392adSmrg            assert symbol_name[0] == '_'
449bd392adSmrg            symbol_name = symbol_name[1:]
459bd392adSmrg        symbols.append(symbol_name)
469bd392adSmrg
479bd392adSmrg    return symbols
489bd392adSmrg
499bd392adSmrg
509bd392adSmrgdef main():
519bd392adSmrg    parser = argparse.ArgumentParser()
529bd392adSmrg    parser.add_argument('--symbols-file',
539bd392adSmrg                        action='store',
549bd392adSmrg                        required=True,
559bd392adSmrg                        help='path to file containing symbols')
569bd392adSmrg    parser.add_argument('--lib',
579bd392adSmrg                        action='store',
589bd392adSmrg                        required=True,
599bd392adSmrg                        help='path to library')
609bd392adSmrg    parser.add_argument('--nm',
619bd392adSmrg                        action='store',
629bd392adSmrg                        required=True,
639bd392adSmrg                        help='path to binary (or name in $PATH)')
649bd392adSmrg    args = parser.parse_args()
659bd392adSmrg
669bd392adSmrg    try:
679bd392adSmrg        lib_symbols = get_symbols(args.nm, args.lib)
689bd392adSmrg    except:
699bd392adSmrg        # We can't run this test, but we haven't technically failed it either
709bd392adSmrg        # Return the GNU "skip" error code
719bd392adSmrg        exit(77)
729bd392adSmrg    mandatory_symbols = []
739bd392adSmrg    optional_symbols = []
749bd392adSmrg    with open(args.symbols_file) as symbols_file:
759bd392adSmrg        qualifier_optional = '(optional)'
769bd392adSmrg        for line in symbols_file.readlines():
779bd392adSmrg
789bd392adSmrg            # Strip comments
799bd392adSmrg            line = line.split('#')[0]
809bd392adSmrg            line = line.strip()
819bd392adSmrg            if not line:
829bd392adSmrg                continue
839bd392adSmrg
849bd392adSmrg            # Line format:
859bd392adSmrg            # [qualifier] symbol
869bd392adSmrg            qualifier = None
879bd392adSmrg            symbol = None
889bd392adSmrg
899bd392adSmrg            fields = line.split()
909bd392adSmrg            if len(fields) == 1:
919bd392adSmrg                symbol = fields[0]
929bd392adSmrg            elif len(fields) == 2:
939bd392adSmrg                qualifier = fields[0]
949bd392adSmrg                symbol = fields[1]
959bd392adSmrg            else:
969bd392adSmrg                print(args.symbols_file + ': invalid format: ' + line)
979bd392adSmrg                exit(1)
989bd392adSmrg
999bd392adSmrg            # The only supported qualifier is 'optional', which means the
1009bd392adSmrg            # symbol doesn't have to be exported by the library
1019bd392adSmrg            if qualifier and not qualifier == qualifier_optional:
1029bd392adSmrg                print(args.symbols_file + ': invalid qualifier: ' + qualifier)
1039bd392adSmrg                exit(1)
1049bd392adSmrg
1059bd392adSmrg            if qualifier == qualifier_optional:
1069bd392adSmrg                optional_symbols.append(symbol)
1079bd392adSmrg            else:
1089bd392adSmrg                mandatory_symbols.append(symbol)
1099bd392adSmrg
1109bd392adSmrg    unknown_symbols = []
1119bd392adSmrg    for symbol in lib_symbols:
1129bd392adSmrg        if symbol in mandatory_symbols:
1139bd392adSmrg            continue
1149bd392adSmrg        if symbol in optional_symbols:
1159bd392adSmrg            continue
1169bd392adSmrg        unknown_symbols.append(symbol)
1179bd392adSmrg
1189bd392adSmrg    missing_symbols = [
1199bd392adSmrg        sym for sym in mandatory_symbols if sym not in lib_symbols
1209bd392adSmrg    ]
1219bd392adSmrg
1229bd392adSmrg    for symbol in unknown_symbols:
1239bd392adSmrg        print(args.lib + ': unknown symbol exported: ' + symbol)
1249bd392adSmrg
1259bd392adSmrg    for symbol in missing_symbols:
1269bd392adSmrg        print(args.lib + ': missing symbol: ' + symbol)
1279bd392adSmrg
1289bd392adSmrg    if unknown_symbols or missing_symbols:
1299bd392adSmrg        exit(1)
1309bd392adSmrg    exit(0)
1319bd392adSmrg
1329bd392adSmrg
1339bd392adSmrgif __name__ == '__main__':
1349bd392adSmrg    main()
135