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" * (3 - len(version))) 27 return data[0], data[1], priority 28 29 30def invert_mapping(symbol_map): 31 """Transforms a map from Key->Value to Value->Key.""" 32 store = defaultdict(list) 33 for symbol, (version, _) in symbol_map.items(): 34 store[version].append(symbol) 35 result = [] 36 for k, v in store.items(): 37 v.sort() 38 result.append((k, v)) 39 result.sort(key=lambda x: x[0]) 40 return result 41 42 43def intersection(llvm, gcc): 44 """ 45 Finds the intersection between the symbols extracted from compiler-rt.a/libunwind.a 46 and libgcc_s.so.1. 47 """ 48 common_symbols = {} 49 for i in gcc: 50 suffix_triple = split_suffix(i) 51 if not suffix_triple: 52 continue 53 54 symbol, version_name, version_number = suffix_triple 55 if symbol in llvm: 56 if symbol not in common_symbols: 57 common_symbols[symbol] = (version_name, version_number) 58 continue 59 if version_number < common_symbols[symbol][1]: 60 common_symbols[symbol] = (version_name, version_number) 61 return invert_mapping(common_symbols) 62 63 64def find_function_names(path): 65 """ 66 Runs readelf on a binary and reduces to only defined functions. Equivalent to 67 `llvm-readelf --wide ${path} | grep 'FUNC' | grep -v 'UND' | awk '{print $8}'`. 68 """ 69 result = subprocess.run(args=["llvm-readelf", "-su", path], capture_output=True) 70 71 if result.returncode != 0: 72 print(result.stderr.decode("utf-8"), file=sys.stderr) 73 sys.exit(1) 74 75 stdout = result.stdout.decode("utf-8") 76 stdout = filter(lambda x: "FUNC" in x and "UND" not in x, stdout.split("\n")) 77 stdout = chain(map(lambda x: filter(None, x), (i.split(" ") for i in stdout))) 78 79 return [list(i)[7] for i in stdout] 80 81 82def to_file(versioned_symbols): 83 path = f"{os.path.dirname(os.path.realpath(__file__))}/new-gcc_s-symbols" 84 with open(path, "w") as f: 85 f.write( 86 "Do not check this version script in: you should instead work " 87 "out which symbols are missing in `lib/gcc_s.ver` and then " 88 "integrate them into `lib/gcc_s.ver`. For more information, " 89 "please see `doc/LLVMLibgcc.rst`.\n" 90 ) 91 for version, symbols in versioned_symbols: 92 f.write(f"{version} {{\n") 93 for i in symbols: 94 f.write(f" {i};\n") 95 f.write("};\n\n") 96 97 98def read_args(): 99 parser = argparse.ArgumentParser() 100 parser.add_argument( 101 "--compiler_rt", 102 type=str, 103 help="Path to `libclang_rt.builtins-${ARCH}.a`.", 104 required=True, 105 ) 106 parser.add_argument( 107 "--libunwind", type=str, help="Path to `libunwind.a`.", required=True 108 ) 109 parser.add_argument( 110 "--libgcc_s", 111 type=str, 112 help="Path to `libgcc_s.so.1`. Note that unlike the other two arguments, this is a dynamic library.", 113 required=True, 114 ) 115 return parser.parse_args() 116 117 118def main(): 119 args = read_args() 120 llvm = find_function_names(args.compiler_rt) + find_function_names(args.libunwind) 121 gcc = find_function_names(args.libgcc_s) 122 versioned_symbols = intersection(llvm, gcc) 123 # TODO(cjdb): work out a way to integrate new symbols in with the existing 124 # ones 125 to_file(versioned_symbols) 126 127 128if __name__ == "__main__": 129 main() 130