From 836b621fc04d16c2e8e56902fd9a657ed73525af Mon Sep 17 00:00:00 2001 From: Kasper Nielsen Date: Fri, 8 Nov 2024 18:56:10 +0100 Subject: [PATCH 1/4] [MLIR, Python] Make it easy to run tests with ASan on mac --- mlir/test/get_darwin_real_python.py | 16 +++++ mlir/test/lit.cfg.py | 100 +++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 mlir/test/get_darwin_real_python.py diff --git a/mlir/test/get_darwin_real_python.py b/mlir/test/get_darwin_real_python.py new file mode 100644 index 0000000000000..63bd08bcff89e --- /dev/null +++ b/mlir/test/get_darwin_real_python.py @@ -0,0 +1,16 @@ +# On macOS, system python binaries like /usr/bin/python and $(xcrun -f python3) +# are shims. They do some light validation work and then spawn the "real" python +# binary. Find the "real" python by asking dyld -- sys.executable reports the +# wrong thing more often than not. This is also useful when we're running under +# a Homebrew python3 binary, which also appears to be some kind of shim. +def getDarwinRealPythonExecutable(): + import ctypes + + dyld = ctypes.cdll.LoadLibrary("/usr/lib/system/libdyld.dylib") + namelen = ctypes.c_ulong(1024) + name = ctypes.create_string_buffer(b"\000", namelen.value) + dyld._NSGetExecutablePath(ctypes.byref(name), ctypes.byref(namelen)) + return name.value.decode("utf-8").strip() + + +print(getDarwinRealPythonExecutable()) diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py index 9b429b424d357..0daa9f3a151fe 100644 --- a/mlir/test/lit.cfg.py +++ b/mlir/test/lit.cfg.py @@ -3,6 +3,7 @@ import os import platform import re +import shutil import subprocess import tempfile @@ -77,6 +78,85 @@ def add_runtime(name): return ToolSubst(f"%{name}", find_runtime(name)) +# Provide the path to asan runtime lib 'libclang_rt.asan_osx_dynamic.dylib' if +# available. This is darwin specific since it's currently only needed on darwin. +# Stolen from llvm/test/lit.cfg.py with a few modifications +def get_asan_rtlib(): + if ( + not "asan" in config.available_features + or not "Darwin" in config.host_os + ): + return "" + try: + import glob + except: + print("glob module not found, skipping get_asan_rtlib() lookup") + return "" + # The libclang_rt.asan_osx_dynamic.dylib path is obtained using the relative + # path from the host cc. + host_lib_dir = os.path.join(os.path.dirname(config.host_cc), "../lib") + asan_dylib_dir_pattern = ( + host_lib_dir + "/clang/*/lib/darwin/libclang_rt.asan_osx_dynamic.dylib" + ) + found_dylibs = glob.glob(asan_dylib_dir_pattern) + found_dylibs = set([os.path.realpath(dylib_file) for dylib_file in found_dylibs]) + if len(found_dylibs) != 1: + return "" + return next(iter(found_dylibs)) + + +# 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. +# Stolen from lldb/test/API/lit.cfg.py with a few modifications +def find_real_python_interpreter(): + # If we're running in a virtual environment, we have to copy Python into + # the virtual environment for it to work. + if sys.prefix != sys.base_prefix: + copied_python = os.path.join(sys.prefix, "bin", "copied-python") + else: + copied_python = os.path.join(config.lldb_build_directory, "copied-python") + + # Avoid doing any work if we already copied the binary. + if os.path.isfile(copied_python): + return copied_python + + # Find the "real" python binary. + 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_call([copied_python, "-V"]) + 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, but when running in + # a virtual environment, we can't use it directly. Create a symlink + # instead. + os.remove(copied_python) + os.symlink(real_python, copied_python) + + # The copied Python works. + return copied_python + + llvm_config.with_system_environment(["HOME", "INCLUDE", "LIB", "TMP", "TEMP"]) llvm_config.use_default_substitutions() @@ -91,6 +171,7 @@ def add_runtime(name): "LICENSE.txt", "lit.cfg.py", "lit.site.cfg.py", + "get_darwin_real_python.py", ] # Tweak the PATH to include the tools dir. @@ -174,8 +255,23 @@ def add_runtime(name): python_executable = config.python_executable # Python configuration with sanitizer requires some magic preloading. This will only work on clang/linux. # TODO: detect Darwin/Windows situation (or mark these tests as unsupported on these platforms). -if "asan" in config.available_features and "Linux" in config.host_os: - python_executable = f"LD_PRELOAD=$({config.host_cxx} -print-file-name=libclang_rt.asan-{config.host_arch}.so) {config.python_executable}" +if "asan" in config.available_features: + if "Linux" in config.host_os: + python_executable = f"LD_PRELOAD=$({config.host_cxx} -print-file-name=libclang_rt.asan-{config.host_arch}.so) {config.python_executable}" + if "Darwin" in config.host_os: + real_python_executable = find_real_python_interpreter() + if real_python_executable: + python_executable = real_python_executable + # Ensure Python is not wrapped, for DYLD_INSERT_LIBRARIES to take effect + lit_config.note( + "Using {} instead of {}".format(python_executable, config.python_executable) + ) + + asan_rtlib = get_asan_rtlib() + lit_config.note("Using ASan rtlib {}".format(asan_rtlib)) + config.environment["DYLD_INSERT_LIBRARIES"] = asan_rtlib + + # On Windows the path to python could contains spaces in which case it needs to be provided in quotes. # This is the equivalent of how %python is setup in llvm/utils/lit/lit/llvm/config.py. elif "Windows" in config.host_os: From ab2c39178bb905358076cdfe0bd485cbf6d1419d Mon Sep 17 00:00:00 2001 From: Kasper Nielsen Date: Fri, 8 Nov 2024 19:18:09 +0100 Subject: [PATCH 2/4] Fix Darker --- mlir/test/lit.cfg.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py index 0daa9f3a151fe..538b907d7a801 100644 --- a/mlir/test/lit.cfg.py +++ b/mlir/test/lit.cfg.py @@ -82,10 +82,7 @@ def add_runtime(name): # available. This is darwin specific since it's currently only needed on darwin. # Stolen from llvm/test/lit.cfg.py with a few modifications def get_asan_rtlib(): - if ( - not "asan" in config.available_features - or not "Darwin" in config.host_os - ): + if not "asan" in config.available_features or not "Darwin" in config.host_os: return "" try: import glob @@ -264,7 +261,9 @@ def find_real_python_interpreter(): python_executable = real_python_executable # Ensure Python is not wrapped, for DYLD_INSERT_LIBRARIES to take effect lit_config.note( - "Using {} instead of {}".format(python_executable, config.python_executable) + "Using {} instead of {}".format( + python_executable, config.python_executable + ) ) asan_rtlib = get_asan_rtlib() From f7e3f1c686a490924b17fa07582c02de853e23a1 Mon Sep 17 00:00:00 2001 From: Kasper Nielsen Date: Wed, 13 Nov 2024 15:48:15 -0800 Subject: [PATCH 3/4] Address comment and improve ASan RT lib finding --- mlir/test/lit.cfg.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py index 538b907d7a801..bba0b76257f7c 100644 --- a/mlir/test/lit.cfg.py +++ b/mlir/test/lit.cfg.py @@ -89,17 +89,15 @@ def get_asan_rtlib(): except: print("glob module not found, skipping get_asan_rtlib() lookup") return "" - # The libclang_rt.asan_osx_dynamic.dylib path is obtained using the relative - # path from the host cc. - host_lib_dir = os.path.join(os.path.dirname(config.host_cc), "../lib") - asan_dylib_dir_pattern = ( - host_lib_dir + "/clang/*/lib/darwin/libclang_rt.asan_osx_dynamic.dylib" + # Find the asan rt lib + resource_dir = ( + subprocess.check_output([config.host_cc.strip(), "-print-resource-dir"]) + .decode("utf-8") + .strip() + ) + return os.path.join( + resource_dir, "lib", "darwin", "libclang_rt.asan_osx_dynamic.dylib" ) - found_dylibs = glob.glob(asan_dylib_dir_pattern) - found_dylibs = set([os.path.realpath(dylib_file) for dylib_file in found_dylibs]) - if len(found_dylibs) != 1: - return "" - return next(iter(found_dylibs)) # On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim python @@ -250,16 +248,17 @@ def find_real_python_interpreter(): ) python_executable = config.python_executable -# Python configuration with sanitizer requires some magic preloading. This will only work on clang/linux. -# TODO: detect Darwin/Windows situation (or mark these tests as unsupported on these platforms). +# Python configuration with sanitizer requires some magic preloading. This will only work on clang/linux/darwin. +# TODO: detect Windows situation (or mark these tests as unsupported on these platforms). if "asan" in config.available_features: if "Linux" in config.host_os: python_executable = f"LD_PRELOAD=$({config.host_cxx} -print-file-name=libclang_rt.asan-{config.host_arch}.so) {config.python_executable}" if "Darwin" in config.host_os: + # Ensure we use a non-shim Python executable, for the `DYLD_INSERT_LIBRARIES` + # env variable to take effect real_python_executable = find_real_python_interpreter() if real_python_executable: python_executable = real_python_executable - # Ensure Python is not wrapped, for DYLD_INSERT_LIBRARIES to take effect lit_config.note( "Using {} instead of {}".format( python_executable, config.python_executable @@ -268,6 +267,8 @@ def find_real_python_interpreter(): asan_rtlib = get_asan_rtlib() lit_config.note("Using ASan rtlib {}".format(asan_rtlib)) + config.environment["MallocNanoZone"] = "0" + config.environment["ASAN_OPTIONS"] = "detect_stack_use_after_return=1" config.environment["DYLD_INSERT_LIBRARIES"] = asan_rtlib From 3bbe9c2e343b6a2692626b6b32e073d38a27f3e3 Mon Sep 17 00:00:00 2001 From: Kasper Nielsen Date: Wed, 13 Nov 2024 15:50:32 -0800 Subject: [PATCH 4/4] No need to import glob anymore --- mlir/test/lit.cfg.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mlir/test/lit.cfg.py b/mlir/test/lit.cfg.py index bba0b76257f7c..f162f9a00efa7 100644 --- a/mlir/test/lit.cfg.py +++ b/mlir/test/lit.cfg.py @@ -84,11 +84,6 @@ def add_runtime(name): def get_asan_rtlib(): if not "asan" in config.available_features or not "Darwin" in config.host_os: return "" - try: - import glob - except: - print("glob module not found, skipping get_asan_rtlib() lookup") - return "" # Find the asan rt lib resource_dir = ( subprocess.check_output([config.host_cc.strip(), "-print-resource-dir"])