• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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