| #!/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.md`.\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() |