xref: /aosp_15_r20/external/mesa3d/bin/symbols-check.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
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    '_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
58def 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
82def 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
111def 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
214if __name__ == '__main__':
215    main()
216