Source code

Revision control

Other Tools

1
# This Source Code Form is subject to the terms of the Mozilla Public
2
# License, v. 2.0. If a copy of the MPL was not distributed with this
3
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5
from __future__ import absolute_import, print_function, unicode_literals
6
7
import re
8
import sys
9
import os
10
import subprocess
11
import shutil
12
from buildconfig import substs
13
14
'''
15
Scans the given directories for binaries referencing the AddressSanitizer
16
runtime library, copies it to the main directory and rewrites binaries to not
17
reference it with absolute paths but with @executable_path instead.
18
'''
19
20
# This is the dylib we're looking for
21
DYLIB_NAME = 'libclang_rt.asan_osx_dynamic.dylib'
22
23
24
def resolve_rpath(filename):
25
otoolOut = subprocess.check_output([substs['OTOOL'], '-l', filename],
26
universal_newlines=True)
27
currentCmd = None
28
29
# The lines we need to find look like this:
30
# ...
31
# Load command 22
32
# cmd LC_RPATH
33
# cmdsize 80
34
# path /home/build/src/clang/bin/../lib/clang/3.8.0/lib/darwin (offset 12)
35
# Load command 23
36
# ...
37
# Other load command types have a varying number of fields.
38
for line in otoolOut.splitlines():
39
cmdMatch = re.match(r'^\s+cmd ([A-Z_]+)', line)
40
if cmdMatch is not None:
41
currentCmd = cmdMatch.group(1)
42
continue
43
44
if currentCmd == 'LC_RPATH':
45
pathMatch = re.match(r'^\s+path (.*) \(offset \d+\)', line)
46
if pathMatch is not None:
47
path = pathMatch.group(1)
48
if os.path.isdir(path):
49
return path
50
51
sys.stderr.write('@rpath could not be resolved from %s\n' % filename)
52
sys.exit(1)
53
54
55
def scan_directory(path):
56
dylibCopied = False
57
58
for root, subdirs, files in os.walk(path):
59
for filename in files:
60
filename = os.path.join(root, filename)
61
62
# Skip all files that aren't either dylibs or executable
63
if not (filename.endswith('.dylib') or os.access(filename, os.X_OK)):
64
continue
65
66
try:
67
otoolOut = subprocess.check_output(
68
[substs['OTOOL'], '-L', filename], universal_newlines=True)
69
except Exception:
70
# Errors are expected on non-mach executables, ignore them and continue
71
continue
72
73
for line in otoolOut.splitlines():
74
if DYLIB_NAME in line:
75
absDylibPath = line.split()[0]
76
77
# Don't try to rewrite binaries twice
78
if absDylibPath.startswith('@executable_path/'):
79
continue
80
81
if not dylibCopied:
82
if absDylibPath.startswith('@rpath/'):
83
rpath = resolve_rpath(filename)
84
copyDylibPath = absDylibPath.replace(
85
'@rpath', rpath)
86
else:
87
copyDylibPath = absDylibPath
88
89
if os.path.isfile(copyDylibPath):
90
# Copy the runtime once to the main directory, which is passed
91
# as the argument to this function.
92
shutil.copy(copyDylibPath, path)
93
94
# Now rewrite the library itself
95
subprocess.check_call(
96
[substs['INSTALL_NAME_TOOL'], '-id',
97
'@executable_path/' + DYLIB_NAME,
98
os.path.join(path, DYLIB_NAME)])
99
dylibCopied = True
100
else:
101
sys.stderr.write('dylib path in %s was not found at: %s\n' % (
102
filename, copyDylibPath))
103
104
# Now use install_name_tool to rewrite the path in our binary
105
relpath = '' if path == root else os.path.relpath(
106
path, root) + '/'
107
subprocess.check_call([substs['INSTALL_NAME_TOOL'], '-change',
108
absDylibPath,
109
'@executable_path/' + relpath + DYLIB_NAME,
110
filename])
111
break
112
113
if not dylibCopied:
114
sys.stderr.write('%s could not be found\n' % DYLIB_NAME)
115
sys.exit(1)
116
117
118
if __name__ == '__main__':
119
for d in sys.argv[1:]:
120
scan_directory(d)