|  | #!/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() |