1 | #!/usr/bin/env python3
|
---|
2 |
|
---|
3 | import argparse
|
---|
4 | import os
|
---|
5 | import platform
|
---|
6 | import subprocess
|
---|
7 |
|
---|
8 | # This list contains symbols that _might_ be exported for some platforms
|
---|
9 | PLATFORM_SYMBOLS = [
|
---|
10 | '__bss_end__',
|
---|
11 | '__bss_start__',
|
---|
12 | '__bss_start',
|
---|
13 | '__cxa_guard_abort',
|
---|
14 | '__cxa_guard_acquire',
|
---|
15 | '__cxa_guard_release',
|
---|
16 | '__end__',
|
---|
17 | '__odr_asan._glapi_Context',
|
---|
18 | '__odr_asan._glapi_Dispatch',
|
---|
19 | '_bss_end__',
|
---|
20 | '_edata',
|
---|
21 | '_end',
|
---|
22 | '_fini',
|
---|
23 | '_init',
|
---|
24 | '_fbss',
|
---|
25 | '_fdata',
|
---|
26 | '_ftext',
|
---|
27 | ]
|
---|
28 |
|
---|
29 | def get_symbols_nm(nm, lib):
|
---|
30 | '''
|
---|
31 | List all the (non platform-specific) symbols exported by the library
|
---|
32 | using `nm`
|
---|
33 | '''
|
---|
34 | symbols = []
|
---|
35 | platform_name = platform.system()
|
---|
36 | output = subprocess.check_output([nm, '-gP', lib],
|
---|
37 | stderr=open(os.devnull, 'w')).decode("ascii")
|
---|
38 | for line in output.splitlines():
|
---|
39 | fields = line.split()
|
---|
40 | if len(fields) == 2 or fields[1] == 'U':
|
---|
41 | continue
|
---|
42 | symbol_name = fields[0]
|
---|
43 | if platform_name == 'Linux':
|
---|
44 | if symbol_name in PLATFORM_SYMBOLS:
|
---|
45 | continue
|
---|
46 | elif platform_name == 'Darwin':
|
---|
47 | assert symbol_name[0] == '_'
|
---|
48 | symbol_name = symbol_name[1:]
|
---|
49 | symbols.append(symbol_name)
|
---|
50 | return symbols
|
---|
51 |
|
---|
52 |
|
---|
53 | def get_symbols_dumpbin(dumpbin, lib):
|
---|
54 | '''
|
---|
55 | List all the (non platform-specific) symbols exported by the library
|
---|
56 | using `dumpbin`
|
---|
57 | '''
|
---|
58 | symbols = []
|
---|
59 | output = subprocess.check_output([dumpbin, '/exports', lib],
|
---|
60 | stderr=open(os.devnull, 'w')).decode("ascii")
|
---|
61 | for line in output.splitlines():
|
---|
62 | fields = line.split()
|
---|
63 | # The lines with the symbols are made of at least 4 columns; see details below
|
---|
64 | if len(fields) < 4:
|
---|
65 | continue
|
---|
66 | try:
|
---|
67 | # Making sure the first 3 columns are a dec counter, a hex counter
|
---|
68 | # and a hex address
|
---|
69 | _ = int(fields[0], 10)
|
---|
70 | _ = int(fields[1], 16)
|
---|
71 | _ = int(fields[2], 16)
|
---|
72 | except ValueError:
|
---|
73 | continue
|
---|
74 | symbol_name = fields[3]
|
---|
75 | # De-mangle symbols
|
---|
76 | if symbol_name[0] == '_' and '@' in symbol_name:
|
---|
77 | symbol_name = symbol_name[1:].split('@')[0]
|
---|
78 | symbols.append(symbol_name)
|
---|
79 | return symbols
|
---|
80 |
|
---|
81 |
|
---|
82 | def main():
|
---|
83 | parser = argparse.ArgumentParser()
|
---|
84 | parser.add_argument('--symbols-file',
|
---|
85 | action='store',
|
---|
86 | required=True,
|
---|
87 | help='path to file containing symbols')
|
---|
88 | parser.add_argument('--lib',
|
---|
89 | action='store',
|
---|
90 | required=True,
|
---|
91 | help='path to library')
|
---|
92 | parser.add_argument('--nm',
|
---|
93 | action='store',
|
---|
94 | help='path to binary (or name in $PATH)')
|
---|
95 | parser.add_argument('--dumpbin',
|
---|
96 | action='store',
|
---|
97 | help='path to binary (or name in $PATH)')
|
---|
98 | parser.add_argument('--ignore-symbol',
|
---|
99 | action='append',
|
---|
100 | help='do not process this symbol')
|
---|
101 | args = parser.parse_args()
|
---|
102 |
|
---|
103 | try:
|
---|
104 | if platform.system() == 'Windows':
|
---|
105 | if not args.dumpbin:
|
---|
106 | parser.error('--dumpbin is mandatory')
|
---|
107 | lib_symbols = get_symbols_dumpbin(args.dumpbin, args.lib)
|
---|
108 | else:
|
---|
109 | if not args.nm:
|
---|
110 | parser.error('--nm is mandatory')
|
---|
111 | lib_symbols = get_symbols_nm(args.nm, args.lib)
|
---|
112 | except:
|
---|
113 | # We can't run this test, but we haven't technically failed it either
|
---|
114 | # Return the GNU "skip" error code
|
---|
115 | exit(77)
|
---|
116 | mandatory_symbols = []
|
---|
117 | optional_symbols = []
|
---|
118 | with open(args.symbols_file) as symbols_file:
|
---|
119 | qualifier_optional = '(optional)'
|
---|
120 | for line in symbols_file.readlines():
|
---|
121 |
|
---|
122 | # Strip comments
|
---|
123 | line = line.split('#')[0]
|
---|
124 | line = line.strip()
|
---|
125 | if not line:
|
---|
126 | continue
|
---|
127 |
|
---|
128 | # Line format:
|
---|
129 | # [qualifier] symbol
|
---|
130 | qualifier = None
|
---|
131 | symbol = None
|
---|
132 |
|
---|
133 | fields = line.split()
|
---|
134 | if len(fields) == 1:
|
---|
135 | symbol = fields[0]
|
---|
136 | elif len(fields) == 2:
|
---|
137 | qualifier = fields[0]
|
---|
138 | symbol = fields[1]
|
---|
139 | else:
|
---|
140 | print(args.symbols_file + ': invalid format: ' + line)
|
---|
141 | exit(1)
|
---|
142 |
|
---|
143 | # The only supported qualifier is 'optional', which means the
|
---|
144 | # symbol doesn't have to be exported by the library
|
---|
145 | if qualifier and not qualifier == qualifier_optional:
|
---|
146 | print(args.symbols_file + ': invalid qualifier: ' + qualifier)
|
---|
147 | exit(1)
|
---|
148 |
|
---|
149 | if qualifier == qualifier_optional:
|
---|
150 | optional_symbols.append(symbol)
|
---|
151 | else:
|
---|
152 | mandatory_symbols.append(symbol)
|
---|
153 |
|
---|
154 | unknown_symbols = []
|
---|
155 | for symbol in lib_symbols:
|
---|
156 | if symbol in mandatory_symbols:
|
---|
157 | continue
|
---|
158 | if symbol in optional_symbols:
|
---|
159 | continue
|
---|
160 | if args.ignore_symbol and symbol in args.ignore_symbol:
|
---|
161 | continue
|
---|
162 | if symbol[:2] == '_Z':
|
---|
163 | # As ajax found out, the compiler intentionally exports symbols
|
---|
164 | # that we explicitely asked it not to export, and we can't do
|
---|
165 | # anything about it:
|
---|
166 | # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36022#c4
|
---|
167 | continue
|
---|
168 | unknown_symbols.append(symbol)
|
---|
169 |
|
---|
170 | missing_symbols = [
|
---|
171 | sym for sym in mandatory_symbols if sym not in lib_symbols
|
---|
172 | ]
|
---|
173 |
|
---|
174 | for symbol in unknown_symbols:
|
---|
175 | print(args.lib + ': unknown symbol exported: ' + symbol)
|
---|
176 |
|
---|
177 | for symbol in missing_symbols:
|
---|
178 | print(args.lib + ': missing symbol: ' + symbol)
|
---|
179 |
|
---|
180 | if unknown_symbols or missing_symbols:
|
---|
181 | exit(1)
|
---|
182 | exit(0)
|
---|
183 |
|
---|
184 |
|
---|
185 | if __name__ == '__main__':
|
---|
186 | main()
|
---|