Skip to content

Commit 711673d

Browse files
committed
[lldb/Test] Fix ASan/TSan workaround for Xcode Python 3
The Python 3 interpreter in Xcode has a relative RPATH and dyld fails to load it when we copy it into the build directory. This patch adds an additional check that the copied binary can be executed. If it doesn't, we assume we're dealing with the Xcode python interpreter and return the path to the real executable. That is sufficient for the sanitizers because only system binaries need to be copied to work around SIP. This patch also moves all that logic out of LLDBTest and into the lit configuration so that it's executed only once per test run, instead of once for every test. Although I didn't benchmark the difference this should result in a mild speedup. Differential revision: https://reviews.llvm.org/D81696 (cherry picked from commit 526e0c8)
1 parent 9a8dec8 commit 711673d

File tree

2 files changed

+71
-53
lines changed

2 files changed

+71
-53
lines changed

lldb/test/API/lit.cfg.py

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,25 @@
2020
config.test_source_root = os.path.dirname(__file__)
2121
config.test_exec_root = config.test_source_root
2222

23-
if 'Address' in config.llvm_use_sanitizer:
24-
config.environment['ASAN_OPTIONS'] = 'detect_stack_use_after_return=1'
25-
# Swift's libReflection builds without ASAN, which causes a known
26-
# false positive in std::vector.
27-
config.environment['ASAN_OPTIONS'] += ':detect_container_overflow=0'
28-
# macOS flags needed for LLDB built with address sanitizer.
29-
if 'Darwin' in config.host_os and 'x86' in config.host_triple:
30-
import subprocess
31-
resource_dir = subprocess.check_output(
32-
[config.cmake_cxx_compiler,
33-
'-print-resource-dir']).decode('utf-8').strip()
34-
runtime = os.path.join(resource_dir, 'lib', 'darwin',
35-
'libclang_rt.asan_osx_dynamic.dylib')
36-
config.environment['DYLD_INSERT_LIBRARIES'] = runtime
23+
24+
def mkdir_p(path):
25+
import errno
26+
try:
27+
os.makedirs(path)
28+
except OSError as e:
29+
if e.errno != errno.EEXIST:
30+
raise
31+
if not os.path.isdir(path):
32+
raise OSError(errno.ENOTDIR, "%s is not a directory"%path)
33+
34+
35+
def find_sanitizer_runtime(name):
36+
import subprocess
37+
resource_dir = subprocess.check_output(
38+
[config.cmake_cxx_compiler,
39+
'-print-resource-dir']).decode('utf-8').strip()
40+
return os.path.join(resource_dir, 'lib', 'darwin', name)
41+
3742

3843
def find_shlibpath_var():
3944
if platform.system() in ['Linux', 'FreeBSD', 'NetBSD', 'SunOS']:
@@ -43,6 +48,58 @@ def find_shlibpath_var():
4348
elif platform.system() == 'Windows':
4449
yield 'PATH'
4550

51+
52+
# On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim python
53+
# binary as the ASan interceptors get loaded too late. Also, when SIP is
54+
# enabled, we can't inject libraries into system binaries at all, so we need a
55+
# copy of the "real" python to work with.
56+
def find_python_interpreter():
57+
# Avoid doing any work if we already copied the binary.
58+
copied_python = os.path.join(config.lldb_build_directory, 'copied-python')
59+
if os.path.isfile(copied_python):
60+
return copied_python
61+
62+
# Find the "real" python binary.
63+
import shutil, subprocess
64+
real_python = subprocess.check_output([
65+
config.python_executable,
66+
os.path.join(os.path.dirname(os.path.realpath(__file__)),
67+
'get_darwin_real_python.py')
68+
]).decode('utf-8').strip()
69+
70+
shutil.copy(real_python, copied_python)
71+
72+
# Now make sure the copied Python works. The Python in Xcode has a relative
73+
# RPATH and cannot be copied.
74+
try:
75+
# We don't care about the output, just make sure it runs.
76+
subprocess.check_output([copied_python, '-V'], stderr=subprocess.STDOUT)
77+
except subprocess.CalledProcessError:
78+
# The copied Python didn't work. Assume we're dealing with the Python
79+
# interpreter in Xcode. Given that this is not a system binary SIP
80+
# won't prevent us form injecting the interceptors so we get away with
81+
# not copying the executable.
82+
os.remove(copied_python)
83+
return real_python
84+
85+
# The copied Python works.
86+
return copied_python
87+
88+
89+
if 'Address' in config.llvm_use_sanitizer:
90+
config.environment['ASAN_OPTIONS'] = 'detect_stack_use_after_return=1'
91+
if 'Darwin' in config.host_os and 'x86' in config.host_triple:
92+
config.environment['DYLD_INSERT_LIBRARIES'] = find_sanitizer_runtime(
93+
'libclang_rt.asan_osx_dynamic.dylib')
94+
95+
if 'Thread' in config.llvm_use_sanitizer:
96+
if 'Darwin' in config.host_os and 'x86' in config.host_triple:
97+
config.environment['DYLD_INSERT_LIBRARIES'] = find_sanitizer_runtime(
98+
'libclang_rt.tsan_osx_dynamic.dylib')
99+
100+
if 'DYLD_INSERT_LIBRARIES' in config.environment and platform.system() == 'Darwin':
101+
config.python_executable = find_python_interpreter()
102+
46103
# Shared library build of LLVM may require LD_LIBRARY_PATH or equivalent.
47104
if config.shared_libs:
48105
for shlibpath_var in find_shlibpath_var():

lldb/test/API/lldbtest.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,6 @@
1010
import lit.util
1111
from lit.formats.base import TestFormat
1212

13-
def getBuildDir(cmd):
14-
found = False
15-
for arg in cmd:
16-
if found:
17-
return arg
18-
if arg == '--build-dir':
19-
found = True
20-
return None
21-
22-
def mkdir_p(path):
23-
import errno
24-
try:
25-
os.makedirs(path)
26-
except OSError as e:
27-
if e.errno != errno.EEXIST:
28-
raise
29-
if not os.path.isdir(path):
30-
raise OSError(errno.ENOTDIR, "%s is not a directory"%path)
3113

3214
class LLDBTest(TestFormat):
3315
def __init__(self, dotest_cmd):
@@ -73,27 +55,6 @@ def execute(self, test, litConfig):
7355
# python exe as the first parameter of the command.
7456
cmd = [executable] + self.dotest_cmd + [testPath, '-p', testFile]
7557

76-
# On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim
77-
# python binary as the ASan interceptors get loaded too late. Also,
78-
# when SIP is enabled, we can't inject libraries into system binaries
79-
# at all, so we need a copy of the "real" python to work with.
80-
#
81-
# Find the "real" python binary, copy it, and invoke it.
82-
if 'DYLD_INSERT_LIBRARIES' in test.config.environment and \
83-
platform.system() == 'Darwin':
84-
builddir = getBuildDir(cmd)
85-
mkdir_p(builddir)
86-
copied_python = os.path.join(builddir, 'copied-system-python')
87-
if not os.path.isfile(copied_python):
88-
import shutil, subprocess
89-
python = subprocess.check_output([
90-
executable,
91-
os.path.join(os.path.dirname(os.path.realpath(__file__)),
92-
'get_darwin_real_python.py')
93-
]).decode('utf-8').strip()
94-
shutil.copy(python, copied_python)
95-
cmd[0] = copied_python
96-
9758
timeoutInfo = None
9859
try:
9960
out, err, exitCode = lit.util.executeCommand(

0 commit comments

Comments
 (0)