Skip to content

Commit 526e0c8

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
1 parent 4db1878 commit 526e0c8

File tree

3 files changed

+54
-43
lines changed

3 files changed

+54
-43
lines changed

lldb/test/API/lit.cfg.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@
2121
config.test_exec_root = config.test_source_root
2222

2323

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+
2435
def find_sanitizer_runtime(name):
2536
import subprocess
2637
resource_dir = subprocess.check_output(
@@ -38,6 +49,43 @@ def find_shlibpath_var():
3849
yield 'PATH'
3950

4051

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+
4189
if 'Address' in config.llvm_use_sanitizer:
4290
config.environment['ASAN_OPTIONS'] = 'detect_stack_use_after_return=1'
4391
if 'Darwin' in config.host_os and 'x86' in config.host_triple:
@@ -49,6 +97,9 @@ def find_shlibpath_var():
4997
config.environment['DYLD_INSERT_LIBRARIES'] = find_sanitizer_runtime(
5098
'libclang_rt.tsan_osx_dynamic.dylib')
5199

100+
if 'DYLD_INSERT_LIBRARIES' in config.environment and platform.system() == 'Darwin':
101+
config.python_executable = find_python_interpreter()
102+
52103
# Shared library build of LLVM may require LD_LIBRARY_PATH or equivalent.
53104
if config.shared_libs:
54105
for shlibpath_var in find_shlibpath_var():

lldb/test/API/lit.site.cfg.py.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ config.shared_libs = @LLVM_ENABLE_SHARED_LIBS@
1818
config.llvm_use_sanitizer = "@LLVM_USE_SANITIZER@"
1919
config.target_triple = "@TARGET_TRIPLE@"
2020
config.lldb_build_directory = "@LLDB_TEST_BUILD_DIRECTORY@"
21+
config.lldb_reproducer_directory = os.path.join("@LLDB_TEST_BUILD_DIRECTORY@", "reproducers")
2122
config.python_executable = "@PYTHON_EXECUTABLE@"
2223
config.dotest_path = "@LLDB_SOURCE_DIR@/test/API/dotest.py"
2324
config.dotest_args_str = "@LLDB_DOTEST_ARGS@"

lldb/test/API/lldbtest.py

Lines changed: 2 additions & 43 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,33 +55,10 @@ 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-
builddir = getBuildDir(cmd)
77-
mkdir_p(builddir)
78-
79-
# On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim
80-
# python binary as the ASan interceptors get loaded too late. Also,
81-
# when SIP is enabled, we can't inject libraries into system binaries
82-
# at all, so we need a copy of the "real" python to work with.
83-
#
84-
# Find the "real" python binary, copy it, and invoke it.
85-
if 'DYLD_INSERT_LIBRARIES' in test.config.environment and \
86-
platform.system() == 'Darwin':
87-
copied_python = os.path.join(builddir, 'copied-system-python')
88-
if not os.path.isfile(copied_python):
89-
import shutil, subprocess
90-
python = subprocess.check_output([
91-
executable,
92-
os.path.join(os.path.dirname(os.path.realpath(__file__)),
93-
'get_darwin_real_python.py')
94-
]).decode('utf-8').strip()
95-
shutil.copy(python, copied_python)
96-
cmd[0] = copied_python
97-
9858
if 'lldb-repro-capture' in test.config.available_features or \
9959
'lldb-repro-replay' in test.config.available_features:
100-
reproducer_root = os.path.join(builddir, 'reproducers')
101-
mkdir_p(reproducer_root)
102-
reproducer_path = os.path.join(reproducer_root, testFile)
60+
reproducer_path = os.path.join(
61+
test.config.lldb_reproducer_directory, testFile)
10362
if 'lldb-repro-capture' in test.config.available_features:
10463
cmd.extend(['--capture-path', reproducer_path])
10564
else:

0 commit comments

Comments
 (0)