blob: e56b49df10a11a607db1d76ed6a231b67a4d939b [file] [log] [blame]
Eric Engestrom176f3502018-10-11 13:08:42 +01001#!/usr/bin/env python
2
3import argparse
Eric Engestrom176f3502018-10-11 13:08:42 +01004import os
Eric Engestrom8f1cdac2019-08-03 18:08:38 +01005import platform
6import subprocess
Eric Engestrom176f3502018-10-11 13:08:42 +01007
8# This list contains symbols that _might_ be exported for some platforms
9PLATFORM_SYMBOLS = [
10 '__bss_end__',
11 '__bss_start__',
12 '__bss_start',
Eric Anholtad618992020-10-06 09:17:32 -070013 '__cxa_guard_abort',
14 '__cxa_guard_acquire',
15 '__cxa_guard_release',
Eric Engestrom176f3502018-10-11 13:08:42 +010016 '__end__',
17 '_bss_end__',
18 '_edata',
19 '_end',
20 '_fini',
21 '_init',
22]
23
Eric Engestrom4d5cde12019-10-29 21:42:16 +000024def get_symbols_nm(nm, lib):
Eric Engestrom176f3502018-10-11 13:08:42 +010025 '''
26 List all the (non platform-specific) symbols exported by the library
Eric Engestrom4d5cde12019-10-29 21:42:16 +000027 using `nm`
Eric Engestrom176f3502018-10-11 13:08:42 +010028 '''
29 symbols = []
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010030 platform_name = platform.system()
31 output = subprocess.check_output([nm, '-gP', lib],
Eric Engestrom176f3502018-10-11 13:08:42 +010032 stderr=open(os.devnull, 'w')).decode("ascii")
33 for line in output.splitlines():
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010034 fields = line.split()
35 if len(fields) == 2 or fields[1] == 'U':
Eric Engestrom59f88092019-08-03 18:21:26 +010036 continue
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010037 symbol_name = fields[0]
38 if platform_name == 'Linux':
39 if symbol_name in PLATFORM_SYMBOLS:
40 continue
41 elif platform_name == 'Darwin':
42 assert symbol_name[0] == '_'
43 symbol_name = symbol_name[1:]
Eric Engestrom176f3502018-10-11 13:08:42 +010044 symbols.append(symbol_name)
Eric Engestrom4d5cde12019-10-29 21:42:16 +000045 return symbols
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010046
Eric Engestrom4d5cde12019-10-29 21:42:16 +000047
48def get_symbols_dumpbin(dumpbin, lib):
49 '''
50 List all the (non platform-specific) symbols exported by the library
51 using `dumpbin`
52 '''
53 symbols = []
54 output = subprocess.check_output([dumpbin, '/exports', lib],
55 stderr=open(os.devnull, 'w')).decode("ascii")
56 for line in output.splitlines():
57 fields = line.split()
58 # The lines with the symbols are made of at least 4 columns; see details below
59 if len(fields) < 4:
60 continue
61 try:
62 # Making sure the first 3 columns are a dec counter, a hex counter
63 # and a hex address
64 _ = int(fields[0], 10)
65 _ = int(fields[1], 16)
66 _ = int(fields[2], 16)
67 except ValueError:
68 continue
69 symbol_name = fields[3]
70 # De-mangle symbols
71 if symbol_name[0] == '_':
72 symbol_name = symbol_name[1:].split('@')[0]
73 symbols.append(symbol_name)
Eric Engestrom176f3502018-10-11 13:08:42 +010074 return symbols
75
76
77def main():
78 parser = argparse.ArgumentParser()
79 parser.add_argument('--symbols-file',
80 action='store',
81 required=True,
82 help='path to file containing symbols')
83 parser.add_argument('--lib',
84 action='store',
85 required=True,
86 help='path to library')
87 parser.add_argument('--nm',
88 action='store',
Eric Engestrom4d5cde12019-10-29 21:42:16 +000089 help='path to binary (or name in $PATH)')
90 parser.add_argument('--dumpbin',
91 action='store',
Eric Engestrom176f3502018-10-11 13:08:42 +010092 help='path to binary (or name in $PATH)')
Pierre-Eric Pelloux-Prayer8da23742020-07-08 13:55:24 +020093 parser.add_argument('--ignore-symbol',
94 action='append',
95 help='do not process this symbol')
Eric Engestrom176f3502018-10-11 13:08:42 +010096 args = parser.parse_args()
97
Eric Engestrom81b3d142019-08-04 00:31:05 +010098 try:
Eric Engestrom4d5cde12019-10-29 21:42:16 +000099 if platform.system() == 'Windows':
100 if not args.dumpbin:
101 parser.error('--dumpbin is mandatory')
102 lib_symbols = get_symbols_dumpbin(args.dumpbin, args.lib)
103 else:
104 if not args.nm:
105 parser.error('--nm is mandatory')
106 lib_symbols = get_symbols_nm(args.nm, args.lib)
Eric Engestrom81b3d142019-08-04 00:31:05 +0100107 except:
108 # We can't run this test, but we haven't technically failed it either
109 # Return the GNU "skip" error code
110 exit(77)
Eric Engestrom176f3502018-10-11 13:08:42 +0100111 mandatory_symbols = []
112 optional_symbols = []
113 with open(args.symbols_file) as symbols_file:
114 qualifier_optional = '(optional)'
115 for line in symbols_file.readlines():
116
117 # Strip comments
118 line = line.split('#')[0]
119 line = line.strip()
120 if not line:
121 continue
122
123 # Line format:
124 # [qualifier] symbol
125 qualifier = None
126 symbol = None
127
128 fields = line.split()
129 if len(fields) == 1:
130 symbol = fields[0]
131 elif len(fields) == 2:
132 qualifier = fields[0]
133 symbol = fields[1]
134 else:
135 print(args.symbols_file + ': invalid format: ' + line)
136 exit(1)
137
138 # The only supported qualifier is 'optional', which means the
139 # symbol doesn't have to be exported by the library
140 if qualifier and not qualifier == qualifier_optional:
141 print(args.symbols_file + ': invalid qualifier: ' + qualifier)
142 exit(1)
143
144 if qualifier == qualifier_optional:
145 optional_symbols.append(symbol)
146 else:
147 mandatory_symbols.append(symbol)
148
149 unknown_symbols = []
150 for symbol in lib_symbols:
151 if symbol in mandatory_symbols:
152 continue
153 if symbol in optional_symbols:
154 continue
Pierre-Eric Pelloux-Prayer8da23742020-07-08 13:55:24 +0200155 if args.ignore_symbol and symbol in args.ignore_symbol:
156 continue
Eric Engestromf1c22392019-08-04 00:27:05 +0100157 if symbol[:2] == '_Z':
Eric Engestrom2a61a8d2020-06-19 12:44:41 +0200158 # As ajax found out, the compiler intentionally exports symbols
159 # that we explicitely asked it not to export, and we can't do
160 # anything about it:
161 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36022#c4
Eric Engestromf1c22392019-08-04 00:27:05 +0100162 continue
Eric Engestrom176f3502018-10-11 13:08:42 +0100163 unknown_symbols.append(symbol)
164
165 missing_symbols = [
166 sym for sym in mandatory_symbols if sym not in lib_symbols
167 ]
168
169 for symbol in unknown_symbols:
170 print(args.lib + ': unknown symbol exported: ' + symbol)
171
172 for symbol in missing_symbols:
173 print(args.lib + ': missing symbol: ' + symbol)
174
175 if unknown_symbols or missing_symbols:
176 exit(1)
177 exit(0)
178
179
180if __name__ == '__main__':
181 main()