aboutsummaryrefslogtreecommitdiffstats
path: root/bin/symbols-check.py
blob: 47305919634c68751c6a38e5c91e663b4769d5d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python

import argparse
import os
import platform
import subprocess

# This list contains symbols that _might_ be exported for some platforms
PLATFORM_SYMBOLS = [
    '__bss_end__',
    '__bss_start__',
    '__bss_start',
    '__end__',
    '_bss_end__',
    '_edata',
    '_end',
    '_fini',
    '_init',
]


def get_symbols(nm, lib):
    '''
    List all the (non platform-specific) symbols exported by the library
    '''
    symbols = []
    platform_name = platform.system()
    output = subprocess.check_output([nm, '-gP', lib],
                                     stderr=open(os.devnull, 'w')).decode("ascii")
    for line in output.splitlines():
        fields = line.split()
        if len(fields) == 2 or fields[1] == 'U':
            continue
        symbol_name = fields[0]
        if platform_name == 'Linux':
            if symbol_name in PLATFORM_SYMBOLS:
                continue
        elif platform_name == 'Darwin':
            assert symbol_name[0] == '_'
            symbol_name = symbol_name[1:]
        symbols.append(symbol_name)

    return symbols


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--symbols-file',
                        action='store',
                        required=True,
                        help='path to file containing symbols')
    parser.add_argument('--lib',
                        action='store',
                        required=True,
                        help='path to library')
    parser.add_argument('--nm',
                        action='store',
                        required=True,
                        help='path to binary (or name in $PATH)')
    args = parser.parse_args()

    try:
        lib_symbols = get_symbols(args.nm, args.lib)
    except:
        # We can't run this test, but we haven't technically failed it either
        # Return the GNU "skip" error code
        exit(77)
    mandatory_symbols = []
    optional_symbols = []
    with open(args.symbols_file) as symbols_file:
        qualifier_optional = '(optional)'
        for line in symbols_file.readlines():

            # Strip comments
            line = line.split('#')[0]
            line = line.strip()
            if not line:
                continue

            # Line format:
            # [qualifier] symbol
            qualifier = None
            symbol = None

            fields = line.split()
            if len(fields) == 1:
                symbol = fields[0]
            elif len(fields) == 2:
                qualifier = fields[0]
                symbol = fields[1]
            else:
                print(args.symbols_file + ': invalid format: ' + line)
                exit(1)

            # The only supported qualifier is 'optional', which means the
            # symbol doesn't have to be exported by the library
            if qualifier and not qualifier == qualifier_optional:
                print(args.symbols_file + ': invalid qualifier: ' + qualifier)
                exit(1)

            if qualifier == qualifier_optional:
                optional_symbols.append(symbol)
            else:
                mandatory_symbols.append(symbol)

    unknown_symbols = []
    for symbol in lib_symbols:
        if symbol in mandatory_symbols:
            continue
        if symbol in optional_symbols:
            continue
        if symbol[:2] == '_Z':
            # Ignore random C++ symbols
            #TODO: figure out if there's any way to avoid exporting them in the first place
            continue
        unknown_symbols.append(symbol)

    missing_symbols = [
        sym for sym in mandatory_symbols if sym not in lib_symbols
    ]

    for symbol in unknown_symbols:
        print(args.lib + ': unknown symbol exported: ' + symbol)

    for symbol in missing_symbols:
        print(args.lib + ': missing symbol: ' + symbol)

    if unknown_symbols or missing_symbols:
        exit(1)
    exit(0)


if __name__ == '__main__':
    main()