[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
This commit is contained in:
Jonas Devlieghere 2020-06-11 19:29:26 -07:00
parent 4db1878158
commit 526e0c8d15
3 changed files with 54 additions and 43 deletions

View File

@ -21,6 +21,17 @@ config.test_source_root = os.path.dirname(__file__)
config.test_exec_root = config.test_source_root
def mkdir_p(path):
import errno
try:
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST:
raise
if not os.path.isdir(path):
raise OSError(errno.ENOTDIR, "%s is not a directory"%path)
def find_sanitizer_runtime(name):
import subprocess
resource_dir = subprocess.check_output(
@ -38,6 +49,43 @@ def find_shlibpath_var():
yield 'PATH'
# On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim python
# binary as the ASan interceptors get loaded too late. Also, when SIP is
# enabled, we can't inject libraries into system binaries at all, so we need a
# copy of the "real" python to work with.
def find_python_interpreter():
# Avoid doing any work if we already copied the binary.
copied_python = os.path.join(config.lldb_build_directory, 'copied-python')
if os.path.isfile(copied_python):
return copied_python
# Find the "real" python binary.
import shutil, subprocess
real_python = subprocess.check_output([
config.python_executable,
os.path.join(os.path.dirname(os.path.realpath(__file__)),
'get_darwin_real_python.py')
]).decode('utf-8').strip()
shutil.copy(real_python, copied_python)
# Now make sure the copied Python works. The Python in Xcode has a relative
# RPATH and cannot be copied.
try:
# We don't care about the output, just make sure it runs.
subprocess.check_output([copied_python, '-V'], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
# The copied Python didn't work. Assume we're dealing with the Python
# interpreter in Xcode. Given that this is not a system binary SIP
# won't prevent us form injecting the interceptors so we get away with
# not copying the executable.
os.remove(copied_python)
return real_python
# The copied Python works.
return copied_python
if 'Address' in config.llvm_use_sanitizer:
config.environment['ASAN_OPTIONS'] = 'detect_stack_use_after_return=1'
if 'Darwin' in config.host_os and 'x86' in config.host_triple:
@ -49,6 +97,9 @@ if 'Thread' in config.llvm_use_sanitizer:
config.environment['DYLD_INSERT_LIBRARIES'] = find_sanitizer_runtime(
'libclang_rt.tsan_osx_dynamic.dylib')
if 'DYLD_INSERT_LIBRARIES' in config.environment and platform.system() == 'Darwin':
config.python_executable = find_python_interpreter()
# Shared library build of LLVM may require LD_LIBRARY_PATH or equivalent.
if config.shared_libs:
for shlibpath_var in find_shlibpath_var():

View File

@ -18,6 +18,7 @@ config.shared_libs = @LLVM_ENABLE_SHARED_LIBS@
config.llvm_use_sanitizer = "@LLVM_USE_SANITIZER@"
config.target_triple = "@TARGET_TRIPLE@"
config.lldb_build_directory = "@LLDB_TEST_BUILD_DIRECTORY@"
config.lldb_reproducer_directory = os.path.join("@LLDB_TEST_BUILD_DIRECTORY@", "reproducers")
config.python_executable = "@PYTHON_EXECUTABLE@"
config.dotest_path = "@LLDB_SOURCE_DIR@/test/API/dotest.py"
config.dotest_args_str = "@LLDB_DOTEST_ARGS@"

View File

@ -10,24 +10,6 @@ import lit.TestRunner
import lit.util
from lit.formats.base import TestFormat
def getBuildDir(cmd):
found = False
for arg in cmd:
if found:
return arg
if arg == '--build-dir':
found = True
return None
def mkdir_p(path):
import errno
try:
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST:
raise
if not os.path.isdir(path):
raise OSError(errno.ENOTDIR, "%s is not a directory"%path)
class LLDBTest(TestFormat):
def __init__(self, dotest_cmd):
@ -73,33 +55,10 @@ class LLDBTest(TestFormat):
# python exe as the first parameter of the command.
cmd = [executable] + self.dotest_cmd + [testPath, '-p', testFile]
builddir = getBuildDir(cmd)
mkdir_p(builddir)
# On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim
# python binary as the ASan interceptors get loaded too late. Also,
# when SIP is enabled, we can't inject libraries into system binaries
# at all, so we need a copy of the "real" python to work with.
#
# Find the "real" python binary, copy it, and invoke it.
if 'DYLD_INSERT_LIBRARIES' in test.config.environment and \
platform.system() == 'Darwin':
copied_python = os.path.join(builddir, 'copied-system-python')
if not os.path.isfile(copied_python):
import shutil, subprocess
python = subprocess.check_output([
executable,
os.path.join(os.path.dirname(os.path.realpath(__file__)),
'get_darwin_real_python.py')
]).decode('utf-8').strip()
shutil.copy(python, copied_python)
cmd[0] = copied_python
if 'lldb-repro-capture' in test.config.available_features or \
'lldb-repro-replay' in test.config.available_features:
reproducer_root = os.path.join(builddir, 'reproducers')
mkdir_p(reproducer_root)
reproducer_path = os.path.join(reproducer_root, testFile)
reproducer_path = os.path.join(
test.config.lldb_reproducer_directory, testFile)
if 'lldb-repro-capture' in test.config.available_features:
cmd.extend(['--capture-path', reproducer_path])
else: