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