forked from OSchip/llvm-project
132 lines
4.4 KiB
Python
132 lines
4.4 KiB
Python
|
#!/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()
|