summaryrefslogtreecommitdiff
path: root/llvm-libgcc/generate_version_script.py
blob: 98850d4f4a2de1f19cfbbd415c62df01b869a66c (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
#!/usr/bin/env python3

# Generates a version script for an architecture so that it can be incorporated
# into gcc_s.ver.

from collections import defaultdict
from itertools import chain
import argparse, subprocess, sys, os


def split_suffix(symbol):
    """
    Splits a symbol such as `__gttf2@GCC_3.0` into a triple representing its
    function name (__gttf2), version name (GCC_3.0), and version number (300).

    The version number acts as a priority. Since earlier versions are more
    accessible and are likely to be used more, the lower the number is, the higher
    its priortiy. A symbol that has a '@@' instead of '@' has been designated by
    the linker as the default symbol, and is awarded a priority of -1.
    """
    if '@' not in symbol:
        return None
    data = [i for i in filter(lambda s: s, symbol.split('@'))]
    _, version = data[-1].split('_')
    version = version.replace('.', '')
    priority = -1 if '@@' in symbol else int(version + '0' *
                                             (3 - len(version)))
    return data[0], data[1], priority


def invert_mapping(symbol_map):
    """Transforms a map from Key->Value to Value->Key."""
    store = defaultdict(list)
    for symbol, (version, _) in symbol_map.items():
        store[version].append(symbol)
    result = []
    for k, v in store.items():
        v.sort()
        result.append((k, v))
    result.sort(key=lambda x: x[0])
    return result


def intersection(llvm, gcc):
    """
  Finds the intersection between the symbols extracted from compiler-rt.a/libunwind.a
  and libgcc_s.so.1.
  """
    common_symbols = {}
    for i in gcc:
        suffix_triple = split_suffix(i)
        if not suffix_triple:
            continue

        symbol, version_name, version_number = suffix_triple
        if symbol in llvm:
            if symbol not in common_symbols:
                common_symbols[symbol] = (version_name, version_number)
                continue
            if version_number < common_symbols[symbol][1]:
                common_symbols[symbol] = (version_name, version_number)
    return invert_mapping(common_symbols)


def find_function_names(path):
    """
    Runs readelf on a binary and reduces to only defined functions. Equivalent to
    `llvm-readelf --wide ${path} | grep 'FUNC' | grep -v 'UND' | awk '{print $8}'`.
    """
    result = subprocess.run(args=['llvm-readelf', '-su', path],
                            capture_output=True)

    if result.returncode != 0:
        print(result.stderr.decode('utf-8'), file=sys.stderr)
        sys.exit(1)

    stdout = result.stdout.decode('utf-8')
    stdout = filter(lambda x: 'FUNC' in x and 'UND' not in x,
                    stdout.split('\n'))
    stdout = chain(
        map(lambda x: filter(None, x), (i.split(' ') for i in stdout)))

    return [list(i)[7] for i in stdout]


def to_file(versioned_symbols):
    path = f'{os.path.dirname(os.path.realpath(__file__))}/new-gcc_s-symbols'
    with open(path, 'w') as f:
        f.write('Do not check this version script in: you should instead work '
                'out which symbols are missing in `lib/gcc_s.ver` and then '
                'integrate them into `lib/gcc_s.ver`. For more information, '
                'please see `doc/LLVMLibgcc.rst`.\n')
        for version, symbols in versioned_symbols:
            f.write(f'{version} {{\n')
            for i in symbols:
                f.write(f'  {i};\n')
            f.write('};\n\n')


def read_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--compiler_rt',
                        type=str,
                        help='Path to `libclang_rt.builtins-${ARCH}.a`.',
                        required=True)
    parser.add_argument('--libunwind',
                        type=str,
                        help='Path to `libunwind.a`.',
                        required=True)
    parser.add_argument(
        '--libgcc_s',
        type=str,
        help=
        'Path to `libgcc_s.so.1`. Note that unlike the other two arguments, this is a dynamic library.',
        required=True)
    return parser.parse_args()


def main():
    args = read_args()
    llvm = find_function_names(args.compiler_rt) + find_function_names(
        args.libunwind)
    gcc = find_function_names(args.libgcc_s)
    versioned_symbols = intersection(llvm, gcc)
    # TODO(cjdb): work out a way to integrate new symbols in with the existing
    #             ones
    to_file(versioned_symbols)


if __name__ == '__main__':
    main()