WIP: TSAN

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender 2025-05-30 18:58:10 +08:00
parent fc217b85ce
commit 1f3e3af432
No known key found for this signature in database
4 changed files with 73 additions and 26 deletions

View file

@ -93,7 +93,7 @@ class MachCommands(CommandBase):
no_package=False,
verbose=False,
very_verbose=False,
sanitizer: Optional[SanitizerKind] = None,
sanitizer: SanitizerKind = SanitizerKind.NONE,
flavor=None,
**kwargs,
):
@ -113,6 +113,7 @@ class MachCommands(CommandBase):
if very_verbose:
opts += ["-vv"]
self.config["build"]["sanitizer"] = sanitizer
assert sanitizer.is_tsan()
env = self.build_env()
self.ensure_bootstrapped()
@ -234,18 +235,25 @@ class MachCommands(CommandBase):
target_clang = f"clang-{llvm_major}"
target_cxx = f"clang++-{llvm_major}"
if shutil.which(target_clang) is None or shutil.which(target_cxx) is None:
env.setdefault("TARGET_CC", target_clang)
env.setdefault("TARGET_CXX", target_cxx)
env.setdefault("TARGET_CC", 'clang')
env.setdefault("TARGET_CXX", 'clang++')
else:
# libasan can be compatible across multiple compiler versions and has a
# runtime check, which would fail if we used incompatible compilers, so
# we can try and fallback to the default clang.
env.setdefault("TARGET_CC", "clang")
env.setdefault("TARGET_CXX", "clang++")
env.setdefault("TARGET_CC", target_clang)
env.setdefault("TARGET_CXX", target_cxx)
# By default, build mozjs from source to enable Sanitizers in mozjs.
env.setdefault("MOZJS_FROM_SOURCE", "1")
if sanitizer == sanitizer.ASAN:
# We need to use `TARGET_CFLAGS`, since we don't want to compile host dependencies with ASAN,
# since that causes issues when building build-scripts / proc macros.
# The actual flags will be appended below depending on the sanitizer kind.
env.setdefault("TARGET_CFLAGS", "")
env.setdefault("TARGET_CXXFLAGS", "")
env.setdefault("RUSTFLAGS", "")
if sanitizer is sanitizer.ASAN:
if target_triple not in SUPPORTED_ASAN_TARGETS:
print(
"AddressSanitizer is currently not supported on this platform\n",
@ -254,18 +262,18 @@ class MachCommands(CommandBase):
sys.exit(1)
# Enable asan
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -Zsanitizer=address"
# We need to use `TARGET_CFLAGS`, since we don't want to compile host dependencies with ASAN,
# since that causes issues when building build-scripts / proc macros.
env.setdefault("TARGET_CFLAGS", "")
env.setdefault("TARGET_CXXFLAGS", "")
env["RUSTFLAGS"] += " -Zsanitizer=address"
env["TARGET_CFLAGS"] += " -fsanitize=address"
env["TARGET_CXXFLAGS"] += " -fsanitize=address"
# asan replaces system allocator with asan allocator
# we need to make sure that we do not replace it with jemalloc
self.features.append("servo_allocator/use-system-allocator")
elif sanitizer is SanitizerKind.TSAN:
env["RUSTFLAGS"] += " -Zsanitizer=thread"
env["TARGET_CFLAGS"] += " -fsanitize=thread"
env["TARGET_CXXFLAGS"] += " -fsanitize=thread"
return None
def notify(self, title: str, message: str):

View file

@ -307,7 +307,7 @@ class CommandBase(object):
self.config["build"].setdefault("incremental", None)
self.config["build"].setdefault("webgl-backtrace", False)
self.config["build"].setdefault("dom-backtrace", False)
self.config["build"].setdefault("sanitizer", None)
self.config["build"].setdefault("sanitizer", SanitizerKind.NONE)
self.config.setdefault("android", {})
self.config["android"].setdefault("sdk", "")
@ -548,6 +548,14 @@ class CommandBase(object):
const=SanitizerKind.ASAN,
help="Build with AddressSanitizer",
),
CommandArgument(
"--with-tsan",
group="Sanitizer",
dest="sanitizer",
action="store_const",
const=SanitizerKind.TSAN,
help="Build with ThreadSanitizer",
),
]
if build_configuration:

View file

@ -186,8 +186,10 @@ class PackageCommands(CommandBase):
"-p",
f"buildMode={build_mode}",
]
if sanitizer == SanitizerKind.ASAN:
if sanitizer.is_asan():
hvigor_command.extend(["-p", "ohos-debug-asan=true"])
elif sanitizer.is_tsan():
hvigor_command.extend(["-p", "ohos-enable-tsan=true"])
# Detect if PATH already has hvigor, or else fallback to npm installation
# provided via HVIGOR_PATH

View file

@ -26,8 +26,21 @@ import servo.util as util
class SanitizerKind(Enum):
UNKNOWN = 0
NONE = 0
ASAN = 1
TSAN = 2
# Apparently enums don't always compare across modules, so we define
# helper methods.
def is_asan(self) -> bool:
return self is self.ASAN
def is_tsan(self) -> bool:
return self is self.TSAN
# Returns true if a sanitizer is enabled.
def is_some(self) -> bool:
return self is not self.NONE
class BuildTarget(object):
@ -403,8 +416,9 @@ class OpenHarmonyTarget(CrossBuildTarget):
bindgen_extra_clangs_args = bindgen_extra_clangs_args + " " + ohos_cflags_str
env[bindgen_extra_clangs_args_var] = bindgen_extra_clangs_args
# On OpenHarmony we add some additional flags when asan is enabled
if config["build"]["sanitizer"] == SanitizerKind.ASAN:
sanitizer: SanitizerKind = config["build"]["sanitizer"]
san_compile_flags = []
if sanitizer.is_some():
# Lookup `<sdk>/native/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos/libclang_rt.asan.so`
lib_clang = llvm_toolchain.joinpath("lib", "clang")
children = [f.path for f in os.scandir(lib_clang) if f.is_dir()]
@ -412,6 +426,12 @@ class OpenHarmonyTarget(CrossBuildTarget):
raise RuntimeError(f"Expected exactly 1 libclang version: `{children}`")
lib_clang_version_dir = pathlib.Path(children[0])
libclang_arch = lib_clang_version_dir.joinpath("lib", clang_target_triple).resolve()
# Use the clangrt from the NDK to use the same library for both C++ and Rust.
env["RUSTFLAGS"] += " -Zexternal-clangrt"
san_compile_flags.append("-fno-omit-frame-pointer")
# On OpenHarmony we add some additional flags when asan is enabled
if sanitizer.is_asan():
libasan_so_path = libclang_arch.joinpath("libclang_rt.asan.so")
libasan_preinit_path = libclang_arch.joinpath("libclang_rt.asan-preinit.a")
if not libasan_so_path.exists():
@ -428,23 +448,32 @@ class OpenHarmonyTarget(CrossBuildTarget):
]
)
# Use the clangrt from the NDK to use the same library for both C++ and Rust.
env["RUSTFLAGS"] += " -Zexternal-clangrt"
asan_compile_flags = (
" -fsanitize=address -shared-libasan -fno-omit-frame-pointer -fsanitize-recover=address"
)
san_compile_flags.extend([ "-fsanitize=address", "-shared-libasan", "-fsanitize-recover=address"])
arch_asan_ignore_list = lib_clang_version_dir.joinpath("share", "asan_ignorelist.txt")
if arch_asan_ignore_list.exists():
asan_compile_flags += " -fsanitize-system-ignorelist=" + str(arch_asan_ignore_list)
san_compile_flags.append("-fsanitize-system-ignorelist=" + str(arch_asan_ignore_list))
else:
print(f"Warning: Couldn't find system ASAN ignorelist at `{arch_asan_ignore_list}`")
env["TARGET_CFLAGS"] += asan_compile_flags
env["TARGET_CXXFLAGS"] += asan_compile_flags
elif sanitizer.is_tsan():
libtsan_so_path = libclang_arch.joinpath("libclang_rt.tsan.so")
builtins_path = libclang_arch.joinpath("libclang_rt.builtins.a")
link_args.extend(
[
"-fsanitize=thread",
"--rtlib=compiler-rt",
"-shared-libsan",
str(libtsan_so_path),
str(builtins_path)
]
)
san_compile_flags.append("-shared-libsan")
link_args = [f"-Clink-arg={arg}" for arg in link_args]
env["RUSTFLAGS"] += " " + " ".join(link_args)
env["TARGET_CFLAGS"] += " " + " ".join(san_compile_flags)
env["TARGET_CXXFLAGS"] += " " + " ".join(san_compile_flags)
def binary_name(self) -> str:
return "libservoshell.so"