2014-09-01 16:34:23 +04:00
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
2017-02-10 19:10:23 +03:00
|
|
|
import re
|
2014-09-01 16:34:23 +04:00
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import shutil
|
2014-11-25 13:12:00 +03:00
|
|
|
from buildconfig import substs
|
2014-09-01 16:34:23 +04:00
|
|
|
|
|
|
|
'''
|
|
|
|
Scans the given directories for binaries referencing the AddressSanitizer
|
|
|
|
runtime library, copies it to the main directory and rewrites binaries to not
|
|
|
|
reference it with absolute paths but with @executable_path instead.
|
|
|
|
'''
|
|
|
|
|
|
|
|
# This is the dylib we're looking for
|
|
|
|
DYLIB_NAME='libclang_rt.asan_osx_dynamic.dylib'
|
|
|
|
|
2017-02-10 19:10:23 +03:00
|
|
|
def resolve_rpath(filename):
|
|
|
|
otoolOut = subprocess.check_output([substs['OTOOL'], '-l', filename])
|
|
|
|
currentCmd = None
|
|
|
|
|
|
|
|
# The lines we need to find look like this:
|
|
|
|
# ...
|
|
|
|
# Load command 22
|
|
|
|
# cmd LC_RPATH
|
|
|
|
# cmdsize 80
|
|
|
|
# path /home/build/src/clang/bin/../lib/clang/3.8.0/lib/darwin (offset 12)
|
|
|
|
# Load command 23
|
|
|
|
# ...
|
|
|
|
# Other load command types have a varying number of fields.
|
|
|
|
for line in otoolOut.splitlines():
|
|
|
|
cmdMatch = re.match(r'^\s+cmd ([A-Z_]+)', line)
|
|
|
|
if cmdMatch is not None:
|
|
|
|
currentCmd = cmdMatch.group(1)
|
|
|
|
continue
|
|
|
|
|
|
|
|
if currentCmd == 'LC_RPATH':
|
|
|
|
pathMatch = re.match(r'^\s+path (.*) \(offset \d+\)', line)
|
|
|
|
if pathMatch is not None:
|
|
|
|
path = pathMatch.group(1)
|
|
|
|
if os.path.isdir(path):
|
|
|
|
return path
|
|
|
|
|
|
|
|
sys.stderr.write('@rpath could not be resolved from %s\n' % filename)
|
|
|
|
exit(1)
|
|
|
|
|
2014-09-01 16:34:23 +04:00
|
|
|
def scan_directory(path):
|
|
|
|
dylibCopied = False
|
|
|
|
|
|
|
|
for root, subdirs, files in os.walk(path):
|
|
|
|
for filename in files:
|
|
|
|
filename = os.path.join(root, filename)
|
|
|
|
|
|
|
|
# Skip all files that aren't either dylibs or executable
|
|
|
|
if not (filename.endswith('.dylib') or os.access(filename, os.X_OK)):
|
|
|
|
continue
|
|
|
|
|
|
|
|
try:
|
2014-11-25 13:12:00 +03:00
|
|
|
otoolOut = subprocess.check_output([substs['OTOOL'], '-L', filename])
|
2014-09-01 16:34:23 +04:00
|
|
|
except:
|
|
|
|
# Errors are expected on non-mach executables, ignore them and continue
|
|
|
|
continue
|
|
|
|
|
|
|
|
for line in otoolOut.splitlines():
|
|
|
|
if line.find(DYLIB_NAME) != -1:
|
|
|
|
absDylibPath = line.split()[0]
|
|
|
|
|
|
|
|
# Don't try to rewrite binaries twice
|
|
|
|
if absDylibPath.find('@executable_path/') == 0:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not dylibCopied:
|
2017-02-10 19:10:23 +03:00
|
|
|
if absDylibPath.find('@rpath/') == 0:
|
|
|
|
rpath = resolve_rpath(filename)
|
|
|
|
copyDylibPath = absDylibPath.replace('@rpath', rpath)
|
|
|
|
else:
|
|
|
|
copyDylibPath = absDylibPath
|
|
|
|
|
2014-09-01 16:34:23 +04:00
|
|
|
# Copy the runtime once to the main directory, which is passed
|
|
|
|
# as the argument to this function.
|
2017-02-10 19:10:23 +03:00
|
|
|
shutil.copy(copyDylibPath, path)
|
2014-09-01 16:34:23 +04:00
|
|
|
|
|
|
|
# Now rewrite the library itself
|
2017-02-10 19:10:23 +03:00
|
|
|
subprocess.check_call([substs['INSTALL_NAME_TOOL'], '-id', '@executable_path/' + DYLIB_NAME, os.path.join(path, DYLIB_NAME)])
|
2014-09-01 16:34:23 +04:00
|
|
|
dylibCopied = True
|
|
|
|
|
|
|
|
# Now use install_name_tool to rewrite the path in our binary
|
2017-02-10 19:10:23 +03:00
|
|
|
subprocess.check_call([substs['INSTALL_NAME_TOOL'], '-change', absDylibPath, '@executable_path/' + DYLIB_NAME, filename])
|
2014-09-01 16:34:23 +04:00
|
|
|
break
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
for d in sys.argv[1:]:
|
|
|
|
scan_directory(d)
|