1#!/usr/bin/env python3 2 3# Generates a version script for an architecture so that it can be incorporated 4# into gcc_s.ver. 5 6from collections import defaultdict 7from itertools import chain 8import argparse, subprocess, sys, os 9 10 11def split_suffix(symbol): 12 """ 13 Splits a symbol such as `__gttf2@GCC_3.0` into a triple representing its 14 function name (__gttf2), version name (GCC_3.0), and version number (300). 15 16 The version number acts as a priority. Since earlier versions are more 17 accessible and are likely to be used more, the lower the number is, the higher 18 its priortiy. A symbol that has a '@@' instead of '@' has been designated by 19 the linker as the default symbol, and is awarded a priority of -1. 20 """ 21 if '@' not in symbol: 22 return None 23 data = [i for i in filter(lambda s: s, symbol.split('@'))] 24 _, version = data[-1].split('_') 25 version = version.replace('.', '') 26 priority = -1 if '@@' in symbol else int(version + '0' * 27 (3 - len(version))) 28 return data[0], data[1], priority 29 30 31def invert_mapping(symbol_map): 32 """Transforms a map from Key->Value to Value->Key.""" 33 store = defaultdict(list) 34 for symbol, (version, _) in symbol_map.items(): 35 store[version].append(symbol) 36 result = [] 37 for k, v in store.items(): 38 v.sort() 39 result.append((k, v)) 40 result.sort(key=lambda x: x[0]) 41 return result 42 43 44def intersection(llvm, gcc): 45 """ 46 Finds the intersection between the symbols extracted from compiler-rt.a/libunwind.a 47 and libgcc_s.so.1. 48 """ 49 common_symbols = {} 50 for i in gcc: 51 suffix_triple = split_suffix(i) 52 if not suffix_triple: 53 continue 54 55 symbol, version_name, version_number = suffix_triple 56 if symbol in llvm: 57 if symbol not in common_symbols: 58 common_symbols[symbol] = (version_name, version_number) 59 continue 60 if version_number < common_symbols[symbol][1]: 61 common_symbols[symbol] = (version_name, version_number) 62 return invert_mapping(common_symbols) 63 64 65def find_function_names(path): 66 """ 67 Runs readelf on a binary and reduces to only defined functions. Equivalent to 68 `llvm-readelf --wide ${path} | grep 'FUNC' | grep -v 'UND' | awk '{print $8}'`. 69 """ 70 result = subprocess.run(args=['llvm-readelf', '-su', path], 71 capture_output=True) 72 73 if result.returncode != 0: 74 print(result.stderr.decode('utf-8'), file=sys.stderr) 75 sys.exit(1) 76 77 stdout = result.stdout.decode('utf-8') 78 stdout = filter(lambda x: 'FUNC' in x and 'UND' not in x, 79 stdout.split('\n')) 80 stdout = chain( 81 map(lambda x: filter(None, x), (i.split(' ') for i in stdout))) 82 83 return [list(i)[7] for i in stdout] 84 85 86def to_file(versioned_symbols): 87 path = f'{os.path.dirname(os.path.realpath(__file__))}/new-gcc_s-symbols' 88 with open(path, 'w') as f: 89 f.write('Do not check this version script in: you should instead work ' 90 'out which symbols are missing in `lib/gcc_s.ver` and then ' 91 'integrate them into `lib/gcc_s.ver`. For more information, ' 92 'please see `doc/LLVMLibgcc.rst`.\n') 93 for version, symbols in versioned_symbols: 94 f.write(f'{version} {{\n') 95 for i in symbols: 96 f.write(f' {i};\n') 97 f.write('};\n\n') 98 99 100def read_args(): 101 parser = argparse.ArgumentParser() 102 parser.add_argument('--compiler_rt', 103 type=str, 104 help='Path to `libclang_rt.builtins-${ARCH}.a`.', 105 required=True) 106 parser.add_argument('--libunwind', 107 type=str, 108 help='Path to `libunwind.a`.', 109 required=True) 110 parser.add_argument( 111 '--libgcc_s', 112 type=str, 113 help= 114 'Path to `libgcc_s.so.1`. Note that unlike the other two arguments, this is a dynamic library.', 115 required=True) 116 return parser.parse_args() 117 118 119def main(): 120 args = read_args() 121 llvm = find_function_names(args.compiler_rt) + find_function_names( 122 args.libunwind) 123 gcc = find_function_names(args.libgcc_s) 124 versioned_symbols = intersection(llvm, gcc) 125 # TODO(cjdb): work out a way to integrate new symbols in with the existing 126 # ones 127 to_file(versioned_symbols) 128 129 130if __name__ == '__main__': 131 main() 132