Commit 431fff66 authored by Clemens Backes's avatar Clemens Backes Committed by V8 LUCI CQ

[traphandler] Add simulator support

This prepares the trap handler to support being used from simulators.
Modifications to the arm64 simulator will be done in a follow-up CL. For
now, the trap handler will be registered but not used in Wasm (we emit
explicit bounds checks instead, as before).

The implementation uses inline assembly, so it is only available on x64
POSIX systems for now. This is the main platform we use for testing and
for fuzzing, so it should give us the test coverage we need. If needed,
inline assembly for other platforms can be added later.
The new code will be executed by the existing arm64 simulator bots, e.g.
"V8 Linux - arm64 - sim".

R=ahaas@chromium.org, mseaborn@chromium.org

Bug: v8:11955
Change-Id: Idc50291c704d9dea902ae0098e5309f19055816c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3011160
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75780}
parent 09b06657
......@@ -3377,8 +3377,15 @@ v8_header_set("v8_internal_headers") {
if (v8_control_flow_integrity) {
sources += [ "src/execution/arm64/pointer-authentication-arm64.h" ]
}
if (v8_enable_webassembly && current_cpu == "arm64" && is_mac) {
sources += [ "src/trap-handler/handler-inside-posix.h" ]
if (v8_enable_webassembly) {
# Trap handling is enabled on arm64 Mac and in simulators on x64 on Linux.
if ((current_cpu == "arm64" && is_mac) ||
(current_cpu == "x64" && is_linux)) {
sources += [ "src/trap-handler/handler-inside-posix.h" ]
}
if (current_cpu == "x64" && is_linux) {
sources += [ "src/trap-handler/trap-handler-simulator.h" ]
}
}
if (is_win) {
sources += [ "src/diagnostics/unwinding-info-win64.h" ]
......@@ -4278,11 +4285,18 @@ v8_source_set("v8_base_without_compiler") {
"src/execution/arm64/simulator-logic-arm64.cc",
"src/regexp/arm64/regexp-macro-assembler-arm64.cc",
]
if (v8_enable_webassembly && current_cpu == "arm64" && is_mac) {
sources += [
"src/trap-handler/handler-inside-posix.cc",
"src/trap-handler/handler-outside-posix.cc",
]
if (v8_enable_webassembly) {
# Trap handling is enabled on arm64 Mac and in simulators on x64 on Linux.
if ((current_cpu == "arm64" && is_mac) ||
(current_cpu == "x64" && is_linux)) {
sources += [
"src/trap-handler/handler-inside-posix.cc",
"src/trap-handler/handler-outside-posix.cc",
]
}
if (current_cpu == "x64" && is_linux) {
sources += [ "src/trap-handler/handler-outside-simulator.cc" ]
}
}
if (is_win) {
sources += [ "src/diagnostics/unwinding-info-win64.cc" ]
......
......@@ -277,7 +277,13 @@ RUNTIME_FUNCTION(Runtime_IsWasmCode) {
RUNTIME_FUNCTION(Runtime_IsWasmTrapHandlerEnabled) {
DisallowGarbageCollection no_gc;
DCHECK_EQ(0, args.length());
// We currently lie to tests about enabled trap handling in simulator builds.
// TODO(clemensb): Remove this once the arm64 simulator support trap handling.
#ifdef USE_SIMULATOR
return isolate->heap()->ToBoolean(false);
#else
return isolate->heap()->ToBoolean(trap_handler::IsTrapHandlerEnabled());
#endif
}
RUNTIME_FUNCTION(Runtime_IsThreadInWasm) {
......
......@@ -43,6 +43,16 @@ namespace v8 {
namespace internal {
namespace trap_handler {
#if V8_OS_LINUX
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.gregs[REG_##REG]
#elif V8_OS_MACOSX
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext->__ss.__##reg
#elif V8_OS_FREEBSD
#define CONTEXT_REG(reg, REG) &uc->uc_mcontext.mc_##reg
#else
#error "Unsupported platform."
#endif
bool IsKernelGeneratedSignal(siginfo_t* info) {
// On macOS, only `info->si_code > 0` is relevant, because macOS leaves
// si_code at its default of 0 for signals that don’t originate in hardware.
......@@ -72,11 +82,18 @@ class UnmaskOobSignalScope {
sigset_t old_mask_;
};
#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
// These are addresses inside the "ProbeMemory" function, defined in
// "handler-outside-simulators.cc".
extern "C" char v8_probe_memory_address[];
extern "C" char v8_probe_memory_continuation[];
#endif // V8_TRAP_HANDLER_VIA_SIMULATOR
bool TryHandleSignal(int signum, siginfo_t* info, void* context) {
// Ensure the faulting thread was actually running Wasm code. This should be
// the first check in the trap handler to guarantee that the IsThreadInWasm
// flag is only set in wasm code. Otherwise a later signal handler is executed
// with the flag set.
// the first check in the trap handler to guarantee that the
// g_thread_in_wasm_code flag is only set in wasm code. Otherwise a later
// signal handler is executed with the flag set.
if (!g_thread_in_wasm_code) return false;
// Clear g_thread_in_wasm_code, primarily to protect against nested faults.
......@@ -102,23 +119,38 @@ bool TryHandleSignal(int signum, siginfo_t* info, void* context) {
UnmaskOobSignalScope unmask_oob_signal;
ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
#if V8_OS_LINUX && V8_TARGET_ARCH_X64
auto* context_ip = &uc->uc_mcontext.gregs[REG_RIP];
#elif V8_OS_MACOSX && V8_TARGET_ARCH_ARM64
auto* context_ip = &uc->uc_mcontext->__ss.__pc;
#elif V8_OS_MACOSX && V8_TARGET_ARCH_X64
auto* context_ip = &uc->uc_mcontext->__ss.__rip;
#elif V8_OS_FREEBSD && V8_TARGET_ARCH_X64
auto* context_ip = &uc->uc_mcontext.mc_rip;
#if V8_HOST_ARCH_X64
auto* context_ip = CONTEXT_REG(rip, RIP);
#elif V8_HOST_ARCH_ARM64
auto* context_ip = CONTEXT_REG(pc, PC);
#else
#error Unsupported platform
#error "Unsupported architecture."
#endif
uintptr_t fault_addr = *context_ip;
uintptr_t landing_pad = 0;
#ifdef V8_TRAP_HANDLER_VIA_SIMULATOR
// Only handle signals triggered by the load in {ProbeMemory}.
if (fault_addr != reinterpret_cast<uintptr_t>(&v8_probe_memory_address)) {
return false;
}
// The simulated ip will be in the second parameter register (%rsi).
auto* simulated_ip_reg = CONTEXT_REG(rsi, RSI);
if (!TryFindLandingPad(*simulated_ip_reg, &landing_pad)) return false;
TH_DCHECK(landing_pad != 0);
auto* return_reg = CONTEXT_REG(rax, RAX);
*return_reg = landing_pad;
// Continue at the memory probing continuation.
*context_ip = reinterpret_cast<uintptr_t>(&v8_probe_memory_continuation);
#else
if (!TryFindLandingPad(fault_addr, &landing_pad)) return false;
// Tell the caller to return to the landing pad.
*context_ip = landing_pad;
#endif
}
// We will return to wasm code, so restore the g_thread_in_wasm_code flag.
// This should only be done once the signal is blocked again (outside the
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/v8config.h"
#include "src/trap-handler/trap-handler-simulator.h"
#if !V8_OS_LINUX
#error "The inline assembly only works on Linux so far."
#endif
asm(
// Define the ProbeMemory function declared in trap-handler-simulators.h.
".pushsection .text \n"
".globl ProbeMemory \n"
".type ProbeMemory, %function \n"
".globl v8_probe_memory_address \n"
".globl v8_probe_memory_continuation \n"
"ProbeMemory: \n"
// First parameter (address) passed in %rdi.
// The second parameter (pc) is unused here. It is read by the trap handler
// instead.
"v8_probe_memory_address: \n"
" movb (%rdi), %al \n"
// Return 0 on success.
" xorl %eax, %eax \n"
"v8_probe_memory_continuation: \n"
// If the trap handler continues here, it wrote the landing pad in %rax.
" ret \n"
".popsection \n");
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TRAP_HANDLER_TRAP_HANDLER_SIMULATOR_H_
#define V8_TRAP_HANDLER_TRAP_HANDLER_SIMULATOR_H_
#include <cstdint>
// This header defines the ProbeMemory function to be used by simulators to
// trigger a signal at a defined location, before doing an actual memory access.
// This implementation is only usable on an x64 host with non-x64 target (i.e. a
// simulator build on x64).
#if (!defined(_M_X64) && !defined(__x86_64__)) || defined(V8_TARGET_ARCH_X64)
#error "Do only include this file on simulator builds on x64."
#endif
namespace v8 {
namespace internal {
namespace trap_handler {
// Probe a memory address by doing a 1-byte read from the given address. If the
// address is not readable, this will cause a trap as usual, but the trap
// handler will recognise the address of the instruction doing the access and
// treat it specially. It will use the given {pc} to look up the respective
// landing pad and return to this function to return that landing pad. If {pc}
// is not registered as a protected instruction, the signal will be propagated
// as usual.
// If the read at {address} succeeds, this function returns {0} instead.
extern "C" uintptr_t ProbeMemory(uintptr_t address, uintptr_t pc);
} // namespace trap_handler
} // namespace internal
} // namespace v8
#endif // V8_TRAP_HANDLER_TRAP_HANDLER_SIMULATOR_H_
......@@ -17,16 +17,19 @@ namespace v8 {
namespace internal {
namespace trap_handler {
#if V8_TARGET_ARCH_X64 && V8_OS_LINUX && !V8_OS_ANDROID
// X64 on Linux, Windows, MacOS, FreeBSD.
#if V8_HOST_ARCH_X64 && V8_TARGET_ARCH_X64 && \
((V8_OS_LINUX && !V8_OS_ANDROID) || V8_OS_WIN || V8_OS_MACOSX || \
V8_OS_FREEBSD)
#define V8_TRAP_HANDLER_SUPPORTED true
#elif V8_TARGET_ARCH_X64 && V8_OS_WIN
// Arm64 (non-simulator) on Mac.
#elif V8_TARGET_ARCH_ARM64 && V8_HOST_ARCH_ARM64 && V8_OS_MACOSX
#define V8_TRAP_HANDLER_SUPPORTED true
#elif V8_TARGET_ARCH_X64 && V8_OS_MACOSX
#define V8_TRAP_HANDLER_SUPPORTED true
#elif V8_TARGET_ARCH_X64 && V8_OS_FREEBSD
#define V8_TRAP_HANDLER_SUPPORTED true
#elif V8_HOST_ARCH_ARM64 && V8_TARGET_ARCH_ARM64 && V8_OS_MACOSX
// Arm64 simulator on x64 on Linux.
#elif V8_TARGET_ARCH_ARM64 && V8_HOST_ARCH_X64 && V8_OS_LINUX
#define V8_TRAP_HANDLER_VIA_SIMULATOR
#define V8_TRAP_HANDLER_SUPPORTED true
// Everything else is unsupported.
#else
#define V8_TRAP_HANDLER_SUPPORTED false
#endif
......
......@@ -823,7 +823,10 @@ BoundsCheckStrategy GetBoundsChecks(const WasmModule* module) {
if (FLAG_wasm_enforce_bounds_checks) return kExplicitBoundsChecks;
// We do not have trap handler support for memory64 yet.
if (module->is_memory64) return kExplicitBoundsChecks;
// TODO(clemensb): Enable trap handling in the arm64 simulator.
#ifndef USE_SIMULATOR
if (trap_handler::IsTrapHandlerEnabled()) return kTrapHandler;
#endif // USE_SIMULATOR
return kExplicitBoundsChecks;
}
} // namespace
......
......@@ -488,6 +488,12 @@ v8_source_set("unittests_sources") {
sources += [ "wasm/trap-handler-win-unittest.cc" ]
}
# Include this test only on arm64 simulator builds on x64 on Linux.
if (current_cpu == "x64" && v8_current_cpu == "arm64" && is_linux &&
v8_enable_webassembly) {
sources += [ "wasm/trap-handler-simulator-unittest.cc" ]
}
configs = [
"../..:cppgc_base_config",
"../..:external_config",
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/trap-handler/trap-handler-simulator.h"
#include "include/v8.h"
#include "src/codegen/macro-assembler-inl.h"
#include "src/execution/simulator.h"
#include "src/trap-handler/trap-handler.h"
#include "test/common/assembler-tester.h"
#include "test/unittests/test-utils.h"
#if !V8_HOST_ARCH_X64 || !V8_TARGET_ARCH_ARM64
#error "Only include this file on arm64 simulator builds on x64."
#endif
namespace v8 {
namespace internal {
namespace trap_handler {
constexpr uintptr_t kFakePc = 11;
class SimulatorTrapHandlerTest : public TestWithIsolate {
public:
static void SetThreadInWasm() {
EXPECT_EQ(0, g_thread_in_wasm_code);
g_thread_in_wasm_code = 1;
}
static void ResetThreadInWasm() {
EXPECT_EQ(1, g_thread_in_wasm_code);
g_thread_in_wasm_code = 0;
}
};
TEST_F(SimulatorTrapHandlerTest, ProbeMemorySuccess) {
int x = 47;
EXPECT_EQ(0u, ProbeMemory(reinterpret_cast<uintptr_t>(&x), kFakePc));
}
TEST_F(SimulatorTrapHandlerTest, ProbeMemoryFail) {
constexpr uintptr_t kNullAddress = 0;
EXPECT_DEATH_IF_SUPPORTED(ProbeMemory(kNullAddress, kFakePc), "");
}
TEST_F(SimulatorTrapHandlerTest, ProbeMemoryFailWhileInWasm) {
// Test that we still crash if the trap handler is set up and the "thread in
// wasm" flag is set, but the PC is not registered as a protected instruction.
constexpr bool kUseDefaultHandler = true;
CHECK(v8::V8::EnableWebAssemblyTrapHandler(kUseDefaultHandler));
constexpr uintptr_t kNullAddress = 0;
SetThreadInWasm();
EXPECT_DEATH_IF_SUPPORTED(ProbeMemory(kNullAddress, kFakePc), "");
}
TEST_F(SimulatorTrapHandlerTest, ProbeMemoryWithTrapHandled) {
constexpr uintptr_t kNullAddress = 0;
constexpr uintptr_t kFakeLandingPad = 19;
constexpr bool kUseDefaultHandler = true;
CHECK(v8::V8::EnableWebAssemblyTrapHandler(kUseDefaultHandler));
ProtectedInstructionData fake_protected_instruction{kFakePc, kFakeLandingPad};
int handler_data_index =
RegisterHandlerData(0, 128, 1, &fake_protected_instruction);
SetThreadInWasm();
EXPECT_EQ(kFakeLandingPad, ProbeMemory(kNullAddress, kFakePc));
// Reset everything.
ResetThreadInWasm();
ReleaseHandlerData(handler_data_index);
RemoveTrapHandler();
}
TEST_F(SimulatorTrapHandlerTest, ProbeMemoryWithLandingPad) {
EXPECT_EQ(0u, GetRecoveredTrapCount());
// Test that the trap handler can recover a memory access violation in
// wasm code (we fake the wasm code and the access violation).
std::unique_ptr<TestingAssemblerBuffer> buffer = AllocateAssemblerBuffer();
constexpr Register scratch = x0;
MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
buffer->CreateView());
// Generate an illegal memory access.
masm.Mov(scratch, 0);
uint32_t crash_offset = masm.pc_offset();
masm.Str(scratch, MemOperand(scratch, 0)); // nullptr access
uint32_t recovery_offset = masm.pc_offset();
// Return.
masm.Ret();
CodeDesc desc;
masm.GetCode(nullptr, &desc);
constexpr bool kUseDefaultHandler = true;
CHECK(v8::V8::EnableWebAssemblyTrapHandler(kUseDefaultHandler));
ProtectedInstructionData protected_instruction{crash_offset, recovery_offset};
int handler_data_index =
RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
desc.instr_size, 1, &protected_instruction);
// Now execute the code.
buffer->MakeExecutable();
GeneratedCode<void> code = GeneratedCode<void>::FromAddress(
i_isolate(), reinterpret_cast<Address>(desc.buffer));
SetThreadInWasm();
// TODO(clemensb): This should pass after adding memory probing in the
// simulator.
// code.Call();
EXPECT_DEATH_IF_SUPPORTED(code.Call(), "");
ResetThreadInWasm();
ReleaseHandlerData(handler_data_index);
RemoveTrapHandler();
// EXPECT_EQ(1u, GetRecoveredTrapCount());
}
} // namespace trap_handler
} // namespace internal
} // namespace v8
......@@ -33,6 +33,8 @@ AUTO_EXCLUDE = [
'src/flags/flag-definitions.h',
# recorder.h should only be included conditionally.
'src/libplatform/tracing/recorder.h',
# trap-handler-simulator.h can only be included in simulator builds.
'src/trap-handler/trap-handler-simulator.h',
]
AUTO_EXCLUDE_PATTERNS = [
'src/base/atomicops_internals_.*',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment