diff options
Diffstat (limited to 'build/unix/rewrite_asan_dylib.py')
-rw-r--r-- | build/unix/rewrite_asan_dylib.py | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/build/unix/rewrite_asan_dylib.py b/build/unix/rewrite_asan_dylib.py new file mode 100644 index 0000000000..0bcf31b559 --- /dev/null +++ b/build/unix/rewrite_asan_dylib.py @@ -0,0 +1,131 @@ +# 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/. + +from __future__ import absolute_import, print_function, unicode_literals + +import re +import sys +import os +import subprocess +import shutil +from buildconfig import substs + +""" +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" + + +def resolve_rpath(filename): + otoolOut = subprocess.check_output( + [substs["OTOOL"], "-l", filename], universal_newlines=True + ) + 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) + sys.exit(1) + + +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: + otoolOut = subprocess.check_output( + [substs["OTOOL"], "-L", filename], universal_newlines=True + ) + except Exception: + # Errors are expected on non-mach executables, ignore them and continue + continue + + for line in otoolOut.splitlines(): + if DYLIB_NAME in line: + absDylibPath = line.split()[0] + + # Don't try to rewrite binaries twice + if absDylibPath.startswith("@executable_path/"): + continue + + if not dylibCopied: + if absDylibPath.startswith("@rpath/"): + rpath = resolve_rpath(filename) + copyDylibPath = absDylibPath.replace("@rpath", rpath) + else: + copyDylibPath = absDylibPath + + if os.path.isfile(copyDylibPath): + # Copy the runtime once to the main directory, which is passed + # as the argument to this function. + shutil.copy(copyDylibPath, path) + + # Now rewrite the library itself + subprocess.check_call( + [ + substs["INSTALL_NAME_TOOL"], + "-id", + "@executable_path/" + DYLIB_NAME, + os.path.join(path, DYLIB_NAME), + ] + ) + dylibCopied = True + else: + sys.stderr.write( + "dylib path in %s was not found at: %s\n" + % (filename, copyDylibPath) + ) + + # Now use install_name_tool to rewrite the path in our binary + relpath = "" if path == root else os.path.relpath(path, root) + "/" + subprocess.check_call( + [ + substs["INSTALL_NAME_TOOL"], + "-change", + absDylibPath, + "@executable_path/" + relpath + DYLIB_NAME, + filename, + ] + ) + break + + if not dylibCopied: + sys.stderr.write("%s could not be found\n" % DYLIB_NAME) + sys.exit(1) + + +if __name__ == "__main__": + for d in sys.argv[1:]: + scan_directory(d) |