Jonas Devlieghere 643969e780
[lldb] Run the LLDB test suite under MTE on capable Apple HW (#185780)
This PR adds support for running the LLDB test suite under MTE. It's
enabled by default on capable hardware when asserts are enabled. It
relies on a launcher (#185921) which launches the process with the
appropriate posix_spawn attribute. One thing worth noting here is that
child processes inherit the MTE property, so binaries launched by the
test suite in this mode also run under MTE.

Besides the logic to detect the default and thread through the launcher,
I also had to make a small change to LLVM LIT's `ToolSubst` class to
support an optional launcher for the shell tests.
2026-03-12 09:24:15 -07:00

317 lines
11 KiB
Python

import os
import itertools
import platform
import re
import subprocess
import sys
import lit.util
from lit.formats import ShTest
from lit.llvm import llvm_config
from lit.llvm.subst import FindTool
from lit.llvm.subst import ToolSubst
import posixpath
def _get_lldb_init_path(config):
return os.path.join(config.test_exec_root, "lit-lldb-init-quiet")
def _disallow(config, execName):
warning = """
echo '*** Do not use \'{0}\' in tests; use \'%''{0}\'. ***' &&
exit 1 && echo
"""
config.substitutions.append((" {0} ".format(execName), warning.format(execName)))
def get_lldb_args(config, suffix=""):
lldb_args = []
if "remote-linux" in config.available_features:
lldb_args += [
"-O",
'"platform select remote-linux"',
"-O",
f'"platform connect {config.lldb_platform_url}"',
]
if config.lldb_platform_working_dir:
dir = posixpath.join(f"{config.lldb_platform_working_dir}", "shell")
if suffix:
dir += posixpath.join(dir, f"{suffix}")
lldb_args += [
"-O",
f'"platform shell mkdir -p {dir}"',
"-O",
f'"platform settings -w {dir}"',
]
lldb_args += ["--no-lldbinit", "-S", _get_lldb_init_path(config)]
return lldb_args
class ShTestLldb(ShTest):
def __init__(
self, execute_external=False, extra_substitutions=[], preamble_commands=[]
):
super().__init__(execute_external, extra_substitutions, preamble_commands)
def execute(self, test, litConfig):
# Run each Shell test in a separate directory (on remote).
# Find directory change command in %lldb substitution.
for i, t in enumerate(test.config.substitutions):
if re.match(t[0], "%lldb"):
cmd = t[1]
if '-O "platform settings -w ' in cmd:
# If command is present, it is added by get_lldb_args.
# Replace the path with the tests' path in suite.
# Example:
# bin/lldb
# -O "platform shell mkdir -p /home/user/shell"
# -O "platform settings -w /home/user/shell" ...
# =>
# bin/lldb
# -O "platform shell mkdir -p /home/user/shell/SymbolFile/Breakpad/inline-record.test"
# -O "platform settings -w /home/user/shell/SymbolFile/Breakpad/inline-record.test" ...
args_def = " ".join(get_lldb_args(test.config))
args_unique = " ".join(
get_lldb_args(
test.config,
posixpath.join(*test.path_in_suite),
)
)
test.config.substitutions[i] = (
t[0],
cmd.replace(args_def, args_unique),
)
break
return super().execute(test, litConfig)
def use_lldb_substitutions(config):
# Set up substitutions for primary tools. These tools must come from config.lldb_tools_dir
# which is basically the build output directory. We do not want to find these in path or
# anywhere else, since they are specifically the programs which are actually being tested.
dsname = "debugserver" if platform.system() in ["Darwin"] else "lldb-server"
dsargs = [] if platform.system() in ["Darwin"] else ["gdbserver"]
build_script = os.path.dirname(__file__)
build_script = os.path.join(build_script, "build.py")
build_script_args = [
build_script,
(
"--compiler=clang" if config.enable_remote else "--compiler=any"
), # Default to best compiler
"--arch=" + str(config.lldb_bitness),
]
if config.lldb_lit_tools_dir:
build_script_args.append("--tools-dir={0}".format(config.lldb_lit_tools_dir))
if config.lldb_tools_dir:
build_script_args.append("--tools-dir={0}".format(config.lldb_tools_dir))
if config.llvm_libs_dir:
build_script_args.append("--libs-dir={0}".format(config.llvm_libs_dir))
if config.objc_gnustep_dir:
build_script_args.append(
'--objc-gnustep-dir="{0}"'.format(config.objc_gnustep_dir)
)
if config.cmake_sysroot:
build_script_args.append("--sysroot={0}".format(config.cmake_sysroot))
lldb_init = _get_lldb_init_path(config)
launcher = getattr(config, "lldb_launcher", None)
primary_tools = [
ToolSubst(
"%lldb",
command=FindTool("lldb"),
extra_args=get_lldb_args(config),
launcher=launcher,
unresolved="fatal",
),
ToolSubst(
"%lldb-init",
command=FindTool("lldb"),
extra_args=["-S", lldb_init],
launcher=launcher,
unresolved="fatal",
),
ToolSubst(
"%lldb-noinit",
command=FindTool("lldb"),
extra_args=["--no-lldbinit"],
launcher=launcher,
unresolved="fatal",
),
ToolSubst(
"%lldb-server",
command=FindTool("lldb-server"),
extra_args=[],
unresolved="ignore",
),
ToolSubst(
"%debugserver",
command=FindTool(dsname),
extra_args=dsargs,
unresolved="ignore",
),
ToolSubst(
"%platformserver",
command=FindTool("lldb-server"),
extra_args=["platform"],
unresolved="ignore",
),
ToolSubst(
"%lldb-rpc-gen",
command=FindTool("lldb-rpc-gen"),
# We need the LLDB build directory root to pass into the tool, not the test build root.
extra_args=[
"-p " + config.lldb_build_directory + "/..",
'--extra-arg="-resource-dir=' + config.clang_resource_dir + '"',
],
unresolved="ignore",
),
"lldb-test",
"lldb-dap",
ToolSubst(
"%build", command="'" + sys.executable + "'", extra_args=build_script_args
),
]
_disallow(config, "lldb")
_disallow(config, "lldb-server")
_disallow(config, "debugserver")
_disallow(config, "platformserver")
llvm_config.add_tool_substitutions(primary_tools, [config.lldb_tools_dir])
def _use_msvc_substitutions(config):
# If running from a Visual Studio Command prompt (e.g. vcvars), this will
# detect the include and lib paths, and find cl.exe and link.exe and create
# substitutions for each of them that explicitly specify /I and /L paths
cl = lit.util.which("cl")
if not cl:
return
# Don't use lit.util.which() for link.exe: In `git bash`, it will pick
# up /usr/bin/link (another name for ln).
link = os.path.join(os.path.dirname(cl), "link.exe")
cl = '"' + cl + '"'
link = '"' + link + '"'
includes = os.getenv("INCLUDE", "").split(";")
libs = os.getenv("LIB", "").split(";")
config.available_features.add("msvc")
compiler_flags = ['"/I{}"'.format(x) for x in includes if os.path.exists(x)]
linker_flags = ['"/LIBPATH:{}"'.format(x) for x in libs if os.path.exists(x)]
tools = [
ToolSubst("%msvc_cl", command=cl, extra_args=compiler_flags),
ToolSubst("%msvc_link", command=link, extra_args=linker_flags),
]
llvm_config.add_tool_substitutions(tools)
return
def use_support_substitutions(config):
# Set up substitutions for support tools. These tools can be overridden at the CMake
# level (by specifying -DLLDB_LIT_TOOLS_DIR), installed, or as a last resort, we can use
# the just-built version.
if config.enable_remote:
host_flags = ["--target=" + config.target_triple]
else:
host_flags = ["--target=" + config.host_triple]
if platform.system() in ["Darwin"]:
try:
out = subprocess.check_output(["xcrun", "--show-sdk-path"]).strip()
res = 0
except OSError:
res = -1
if res == 0 and out:
sdk_path = out.decode("utf-8")
llvm_config.lit_config.note("using SDKROOT: %r" % sdk_path)
host_flags += ["-isysroot", sdk_path]
elif sys.platform != "win32":
host_flags += ["-pthread"]
if sys.platform.startswith("netbsd"):
# needed e.g. to use freshly built libc++
host_flags += [
"-L" + config.llvm_libs_dir,
"-Wl,-rpath," + config.llvm_libs_dir,
]
# The clang module cache is used for building inferiors.
host_flags += ["-fmodules-cache-path={}".format(config.clang_module_cache)]
# Our files use x86 AT&T assembly throughout.
# Enable it explicitly so any local Clang preference for Intel syntax gets overriden.
if "x86-registered-target" in config.available_features:
host_flags += ["-mllvm", "-x86-asm-syntax=att"]
if config.cmake_sysroot:
host_flags += ["--sysroot={}".format(config.cmake_sysroot)]
if config.enable_remote and config.has_libcxx:
host_flags += [
"-L{}".format(config.libcxx_libs_dir),
"-lc++",
]
# By default, macOS doesn't allow injecting the ASAN runtime into system processes.
if platform.system() in ["Darwin"] and config.llvm_use_sanitizer:
system_clang = (
subprocess.check_output(["xcrun", "-find", "clang"]).strip().decode("utf-8")
)
system_liblto = os.path.join(
os.path.dirname(os.path.dirname(system_clang)), "lib", "libLTO.dylib"
)
host_flags += ["-Wl,-lto_library", "-Wl," + system_liblto]
host_flags = " ".join(host_flags)
config.substitutions.append(("%clang_host", "%clang " + host_flags))
config.substitutions.append(("%clangxx_host", "%clangxx " + host_flags))
config.substitutions.append(
("%clang_cl_host", "%clang_cl --target=" + config.host_triple)
)
additional_tool_dirs = []
if config.lldb_lit_tools_dir:
additional_tool_dirs.append(config.lldb_lit_tools_dir)
llvm_config.use_clang(
additional_flags=["--target=specify-a-target-or-use-a-_host-substitution"],
additional_tool_dirs=additional_tool_dirs,
required=True,
use_installed=True,
)
if llvm_config.clang_has_bounds_safety():
llvm_config.lit_config.note("clang has -fbounds-safety support")
config.available_features.add("clang-bounds-safety")
if sys.platform == "win32":
_use_msvc_substitutions(config)
have_lld = llvm_config.use_lld(
additional_tool_dirs=additional_tool_dirs, required=False, use_installed=True
)
if have_lld:
config.available_features.add("lld")
support_tools = [
"yaml2obj",
"obj2yaml",
"llvm-dwp",
"llvm-pdbutil",
"llvm-mc",
"llvm-readobj",
"llvm-objdump",
"llvm-objcopy",
"lli",
]
additional_tool_dirs += [config.lldb_tools_dir, config.llvm_tools_dir]
llvm_config.add_tool_substitutions(support_tools, additional_tool_dirs)
_disallow(config, "clang")