blob: 0d56a19561e3bc1249bbd2f58c9ae08177179b4c [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',
13 '__end__',
14 '_bss_end__',
15 '_edata',
16 '_end',
17 '_fini',
18 '_init',
19]
20
21
Eric Engestrom4d5cde12019-10-29 21:42:16 +000022def get_symbols_nm(nm, lib):
Eric Engestrom176f3502018-10-11 13:08:42 +010023 '''
24 List all the (non platform-specific) symbols exported by the library
Eric Engestrom4d5cde12019-10-29 21:42:16 +000025 using `nm`
Eric Engestrom176f3502018-10-11 13:08:42 +010026 '''
27 symbols = []
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010028 platform_name = platform.system()
29 output = subprocess.check_output([nm, '-gP', lib],
Eric Engestrom176f3502018-10-11 13:08:42 +010030 stderr=open(os.devnull, 'w')).decode("ascii")
31 for line in output.splitlines():
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010032 fields = line.split()
33 if len(fields) == 2 or fields[1] == 'U':
Eric Engestrom59f88092019-08-03 18:21:26 +010034 continue
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010035 symbol_name = fields[0]
36 if platform_name == 'Linux':
37 if symbol_name in PLATFORM_SYMBOLS:
38 continue
39 elif platform_name == 'Darwin':
40 assert symbol_name[0] == '_'
41 symbol_name = symbol_name[1:]
Eric Engestrom176f3502018-10-11 13:08:42 +010042 symbols.append(symbol_name)
Eric Engestrom4d5cde12019-10-29 21:42:16 +000043 return symbols
Eric Engestrom8f1cdac2019-08-03 18:08:38 +010044
Eric Engestrom4d5cde12019-10-29 21:42:16 +000045
46def get_symbols_dumpbin(dumpbin, lib):
47 '''
48 List all the (non platform-specific) symbols exported by the library
49 using `dumpbin`
50 '''
51 symbols = []
52 output = subprocess.check_output([dumpbin, '/exports', lib],
53 stderr=open(os.devnull, 'w')).decode("ascii")
54 for line in output.splitlines():
55 fields = line.split()
56 # The lines with the symbols are made of at least 4 columns; see details below
57 if len(fields) < 4:
58 continue
59 try:
60 # Making sure the first 3 columns are a dec counter, a hex counter
61 # and a hex address
62 _ = int(fields[0], 10)
63 _ = int(fields[1], 16)
64 _ = int(fields[2], 16)
65 except ValueError:
66 continue
67 symbol_name = fields[3]
68 # De-mangle symbols
69 if symbol_name[0] == '_':
70 symbol_name = symbol_name[1:].split('@')[0]
71 symbols.append(symbol_name)
Eric Engestrom176f3502018-10-11 13:08:42 +010072 return symbols
73
74
75def main():
76 parser = argparse.ArgumentParser()
77 parser.add_argument('--symbols-file',
78 action='store',
79 required=True,
80 help='path to file containing symbols')
81 parser.add_argument('--lib',
82 action='store',
83 required=True,
84 help='path to library')
85 parser.add_argument('--nm',
86 action='store',
Eric Engestrom4d5cde12019-10-29 21:42:16 +000087 help='path to binary (or name in $PATH)')
88 parser.add_argument('--dumpbin',
89 action='store',
Eric Engestrom176f3502018-10-11 13:08:42 +010090 help='path to binary (or name in $PATH)')
91 args = parser.parse_args()
92
Eric Engestrom81b3d142019-08-04 00:31:05 +010093 try:
Eric Engestrom4d5cde12019-10-29 21:42:16 +000094 if platform.system() == 'Windows':
95 if not args.dumpbin:
96 parser.error('--dumpbin is mandatory')
97 lib_symbols = get_symbols_dumpbin(args.dumpbin, args.lib)
98 else:
99 if not args.nm:
100 parser.error('--nm is mandatory')
101 lib_symbols = get_symbols_nm(args.nm, args.lib)
Eric Engestrom81b3d142019-08-04 00:31:05 +0100102 except:
103 # We can't run this test, but we haven't technically failed it either
104 # Return the GNU "skip" error code
105 exit(77)
Eric Engestrom176f3502018-10-11 13:08:42 +0100106 mandatory_symbols = []
107 optional_symbols = []
108 with open(args.symbols_file) as symbols_file:
109 qualifier_optional = '(optional)'
110 for line in symbols_file.readlines():
111
112 # Strip comments
113 line = line.split('#')[0]
114 line = line.strip()
115 if not line:
116 continue
117
118 # Line format:
119 # [qualifier] symbol
120 qualifier = None
121 symbol = None
122
123 fields = line.split()
124 if len(fields) == 1:
125 symbol = fields[0]
126 elif len(fields) == 2:
127 qualifier = fields[0]
128 symbol = fields[1]
129 else:
130 print(args.symbols_file + ': invalid format: ' + line)
131 exit(1)
132
133 # The only supported qualifier is 'optional', which means the
134 # symbol doesn't have to be exported by the library
135 if qualifier and not qualifier == qualifier_optional:
136 print(args.symbols_file + ': invalid qualifier: ' + qualifier)
137 exit(1)
138
139 if qualifier == qualifier_optional:
140 optional_symbols.append(symbol)
141 else:
142 mandatory_symbols.append(symbol)
143
144 unknown_symbols = []
145 for symbol in lib_symbols:
146 if symbol in mandatory_symbols:
147 continue
148 if symbol in optional_symbols:
149 continue
Eric Engestromf1c22392019-08-04 00:27:05 +0100150 if symbol[:2] == '_Z':
151 # Ignore random C++ symbols
152 #TODO: figure out if there's any way to avoid exporting them in the first place
153 continue
Eric Engestrom176f3502018-10-11 13:08:42 +0100154 unknown_symbols.append(symbol)
155
156 missing_symbols = [
157 sym for sym in mandatory_symbols if sym not in lib_symbols
158 ]
159
160 for symbol in unknown_symbols:
161 print(args.lib + ': unknown symbol exported: ' + symbol)
162
163 for symbol in missing_symbols:
164 print(args.lib + ': missing symbol: ' + symbol)
165
166 if unknown_symbols or missing_symbols:
167 exit(1)
168 exit(0)
169
170
171if __name__ == '__main__':
172 main()