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