from __future__ import print_function
import argparse
import os.path
import re
import sys
import subprocess
def has_duplicates(x):
return len(x) != len(set(x))
def parse_order_file(filename):
"""Parses a file containing symbols like passed to ld64's -order_file.
Doesn't support arch or object file prefixes.
"""
strip_comments = re.compile('#.*$')
symbols = [strip_comments.sub('', line).strip() for line in open(filename)]
symbols = list(filter(None, symbols))
if has_duplicates(symbols):
print('order file "%s" contains duplicate symbols' % filename,
file=sys.stderr)
sys.exit(1)
return symbols
def check_symbol_table(binary, allowed_symbols, nm, symbol_file):
actual_symbols = subprocess.check_output([nm, '-jUng', binary]).decode('utf8')
actual_symbols = [s.rstrip() for s in actual_symbols.splitlines()]
def print_syms(syms):
print('\n'.join([' ' + s for s in syms]), file=sys.stderr)
try:
symbol_indices = [allowed_symbols.index(s) for s in actual_symbols]
if symbol_indices != sorted(symbol_indices):
print('"%s" exports symbols in order different from order file %s' %
(binary, symbol_file),
file=sys.stderr)
print('actual order:', file=sys.stderr)
print_syms(actual_symbols)
print('expected order:', file=sys.stderr)
print_syms(allowed_symbols)
sys.exit(1)
assert not has_duplicates(allowed_symbols)
except ValueError as e:
print('unexpected export: %s' % e, file=sys.stderr)
print('allowed exports from %s:' % symbol_file, file=sys.stderr)
print_syms(allowed_symbols)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description='Check order of exported symbols of a given binary.')
parser.add_argument('--stamp', required=True,
help='Touch this stamp file on success.')
parser.add_argument('--binary', required=True, help='Check this binary.')
parser.add_argument('--nm-path', required=True, help='Path to nm')
parser.add_argument('--symbol-file', required=True,
help='Order file listing expected public symbols.')
args = parser.parse_args()
allowed_symbols = parse_order_file(args.symbol_file)
check_symbol_table(
args.binary, allowed_symbols, args.nm_path, args.symbol_file)
open(args.stamp, 'w').close()
if __name__ == '__main__':
main()