diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index e2a5ffc6a1a..e34fa2a2687 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -40,6 +40,7 @@ from servo.platform.build_target import BuildTarget SUPPORTED_ASAN_TARGETS = [ "aarch64-apple-darwin", "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-ohos", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", ] @@ -109,6 +110,8 @@ class MachCommands(CommandBase): opts += ["-v"] if very_verbose: opts += ["-vv"] + if with_asan: + self.config["build"]["with_asan"] = True env = self.build_env() self.ensure_bootstrapped() @@ -136,6 +139,10 @@ class MachCommands(CommandBase): opts += ["-Zbuild-std"] kwargs["target_override"] = target_triple + # With asan we also want frame pointers + if "force-frame-pointers" not in env["RUSTFLAGS"]: + env["RUSTFLAGS"] += " -C force-frame-pointers=yes" + # Note: We want to use the same clang/LLVM version as rustc. rustc_llvm_version = get_rustc_llvm_version() if rustc_llvm_version is None: @@ -144,11 +151,14 @@ 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: - raise RuntimeError(f"--with-asan requires `{target_clang}` and `{target_cxx}` to be in PATH") - env.setdefault("TARGET_CC", target_clang) - env.setdefault("TARGET_CXX", target_cxx) - # TODO: We should also parse the LLVM version from the clang compiler we chose. - # It's unclear if the major version being the same is sufficient. + env.setdefault("TARGET_CC", target_clang) + env.setdefault("TARGET_CXX", target_cxx) + 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++") # 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. @@ -156,7 +166,6 @@ class MachCommands(CommandBase): env.setdefault("TARGET_CXXFLAGS", "") env["TARGET_CFLAGS"] += " -fsanitize=address" env["TARGET_CXXFLAGS"] += " -fsanitize=address" - env["TARGET_LDFLAGS"] = "-static-libasan" # By default build mozjs from source to enable ASAN with mozjs. env.setdefault("MOZJS_FROM_SOURCE", "1") @@ -190,7 +199,9 @@ class MachCommands(CommandBase): built_binary = self.get_binary_path(build_type, asan=with_asan) if not no_package and self.target.needs_packaging(): - rv = Registrar.dispatch("package", context=self.context, build_type=build_type, flavor=flavor) + rv = Registrar.dispatch( + "package", context=self.context, build_type=build_type, flavor=flavor, with_asan=with_asan + ) if rv: return rv diff --git a/python/servo/command_base.py b/python/servo/command_base.py index abd193eda49..f0f28e5488e 100644 --- a/python/servo/command_base.py +++ b/python/servo/command_base.py @@ -305,6 +305,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("with_asan", False) self.config.setdefault("android", {}) self.config["android"].setdefault("sdk", "") @@ -803,15 +804,16 @@ class CommandBase(object): "--manifest-path", path.join(self.context.topdir, "ports", "servoshell", "Cargo.toml"), ] - if target_override: - args += ["--target", target_override] - elif self.target.is_cross_build(): + + if self.target.is_cross_build(): args += ["--target", self.target.triple()] if type(self.target) in [AndroidTarget, OpenHarmonyTarget]: # Note: in practice `cargo rustc` should just be used unconditionally. assert command != "build", "For Android / OpenHarmony `cargo rustc` must be used instead of cargo build" if command == "rustc": args += ["--lib", "--crate-type=cdylib"] + elif target_override: + args += ["--target", target_override] features = [] diff --git a/python/servo/package_commands.py b/python/servo/package_commands.py index 5e63e6549c0..9bc4d70232d 100644 --- a/python/servo/package_commands.py +++ b/python/servo/package_commands.py @@ -184,6 +184,9 @@ class PackageCommands(CommandBase): "-p", f"buildMode={build_mode}", ] + if with_asan: + hvigor_command.extend(["-p", "ohos-debug-asan=true"]) + # Detect if PATH already has hvigor, or else fallback to npm installation # provided via HVIGOR_PATH if "HVIGOR_PATH" not in env: diff --git a/python/servo/platform/build_target.py b/python/servo/platform/build_target.py index 5f0f500ad58..536b10a8d3d 100644 --- a/python/servo/platform/build_target.py +++ b/python/servo/platform/build_target.py @@ -350,9 +350,8 @@ class OpenHarmonyTarget(CrossBuildTarget): env[f"CXX_{clang_target_triple_underscore}"] = ndk_clangxx # rustc linker env[f"CARGO_TARGET_{rust_target_triple.upper()}_LINKER"] = ndk_clang - # We could also use a cross-compile wrapper - env["RUSTFLAGS"] += f" -Clink-arg=--target={clang_target_triple}" - env["RUSTFLAGS"] += f" -Clink-arg=--sysroot={ohos_sysroot_posix}" + + link_args = ["-fuse-ld=lld", f"--target={clang_target_triple}", f"--sysroot={ohos_sysroot_posix}"] env["HOST_CFLAGS"] = "" env["HOST_CXXFLAGS"] = "" @@ -398,6 +397,49 @@ 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"]["with_asan"]: + # Lookup `/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()] + if len(children) != 1: + 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() + 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(): + raise RuntimeError(f"Couldn't find ASAN runtime library at {libasan_so_path}") + link_args.extend( + [ + "-fsanitize=address", + "--rtlib=compiler-rt", + "-shared-libasan", + str(libasan_so_path), + "-Wl,--whole-archive", + "-Wl," + str(libasan_preinit_path), + "-Wl,--no-whole-archive", + ] + ) + + # 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" + ) + + 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) + 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 + + link_args = [f"-Clink-arg={arg}" for arg in link_args] + env["RUSTFLAGS"] += " " + " ".join(link_args) + def binary_name(self) -> str: return "libservoshell.so"