summaryrefslogtreecommitdiff
path: root/deps/v8/test/unittests/wasm/trap-handler-x64-unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/test/unittests/wasm/trap-handler-x64-unittest.cc')
-rw-r--r--deps/v8/test/unittests/wasm/trap-handler-x64-unittest.cc478
1 files changed, 478 insertions, 0 deletions
diff --git a/deps/v8/test/unittests/wasm/trap-handler-x64-unittest.cc b/deps/v8/test/unittests/wasm/trap-handler-x64-unittest.cc
new file mode 100644
index 0000000000..8c42b1735c
--- /dev/null
+++ b/deps/v8/test/unittests/wasm/trap-handler-x64-unittest.cc
@@ -0,0 +1,478 @@
+// Copyright 2018 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"
+
+#if V8_OS_LINUX
+#include <signal.h>
+#include <ucontext.h>
+#elif V8_OS_MACOSX
+#include <signal.h>
+#include <sys/ucontext.h>
+#elif V8_OS_WIN
+#include <windows.h>
+#endif
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if V8_OS_POSIX
+#include "include/v8-wasm-trap-handler-posix.h"
+#elif V8_OS_WIN
+#include "include/v8-wasm-trap-handler-win.h"
+#endif
+#include "src/allocation.h"
+#include "src/assembler-inl.h"
+#include "src/base/page-allocator.h"
+#include "src/macro-assembler-inl.h"
+#include "src/simulator.h"
+#include "src/trap-handler/trap-handler.h"
+#include "src/vector.h"
+#include "src/wasm/wasm-engine.h"
+#include "src/wasm/wasm-memory.h"
+
+#include "test/common/assembler-tester.h"
+#include "test/unittests/test-utils.h"
+
+namespace v8 {
+namespace internal {
+namespace wasm {
+
+namespace {
+constexpr Register scratch = r10;
+bool g_test_handler_executed = false;
+#if V8_OS_LINUX || V8_OS_MACOSX
+struct sigaction g_old_segv_action;
+struct sigaction g_old_fpe_action;
+struct sigaction g_old_bus_action; // We get SIGBUS on Mac sometimes.
+#elif V8_OS_WIN
+void* g_registered_handler = nullptr;
+#endif
+
+// The recovery address allows us to recover from an intentional crash.
+Address g_recovery_address;
+// Flag to indicate if the test handler should call the trap handler as a first
+// chance handler.
+bool g_use_as_first_chance_handler = false;
+} // namespace
+
+#define __ masm.
+
+enum TrapHandlerStyle : int {
+ // The test uses the default trap handler of V8.
+ kDefault = 0,
+ // The test installs the trap handler callback in its own test handler.
+ kCallback = 1
+};
+
+std::string PrintTrapHandlerTestParam(
+ ::testing::TestParamInfo<TrapHandlerStyle> info) {
+ switch (info.param) {
+ case kDefault:
+ return "DefaultTrapHandler";
+ case kCallback:
+ return "Callback";
+ }
+ UNREACHABLE();
+}
+
+class TrapHandlerTest : public TestWithIsolate,
+ public ::testing::WithParamInterface<TrapHandlerStyle> {
+ protected:
+ void SetUp() override {
+ void* base = nullptr;
+ size_t length = 0;
+ accessible_memory_start_ =
+ i_isolate()
+ ->wasm_engine()
+ ->memory_tracker()
+ ->TryAllocateBackingStoreForTesting(
+ i_isolate()->heap(), 1 * kWasmPageSize, &base, &length);
+ memory_buffer_ =
+ base::AddressRegion(reinterpret_cast<Address>(base), length);
+
+ // The allocated memory buffer ends with a guard page.
+ crash_address_ = memory_buffer_.end() - 32;
+ // Allocate a buffer for the generated code.
+ buffer_ = AllocateAssemblerBuffer(AssemblerBase::kMinimalBufferSize,
+ GetRandomMmapAddr());
+
+ InitRecoveryCode();
+
+#if V8_OS_LINUX || V8_OS_MACOSX
+ // Set up a signal handler to recover from the expected crash.
+ struct sigaction action;
+ action.sa_sigaction = SignalHandler;
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = SA_SIGINFO;
+ // SIGSEGV happens for wasm oob memory accesses on Linux.
+ CHECK_EQ(0, sigaction(SIGSEGV, &action, &g_old_segv_action));
+ // SIGBUS happens for wasm oob memory accesses on macOS.
+ CHECK_EQ(0, sigaction(SIGBUS, &action, &g_old_bus_action));
+ // SIGFPE to simulate crashes which are not handled by the trap handler.
+ CHECK_EQ(0, sigaction(SIGFPE, &action, &g_old_fpe_action));
+#elif V8_OS_WIN
+ g_registered_handler =
+ AddVectoredExceptionHandler(/*first=*/0, TestHandler);
+#endif
+ }
+
+ void TearDown() override {
+ // We should always have left wasm code.
+ CHECK(!GetThreadInWasmFlag());
+ buffer_.reset();
+ recovery_buffer_.reset();
+
+ // Free the allocated backing store.
+ i_isolate()->wasm_engine()->memory_tracker()->FreeBackingStoreForTesting(
+ memory_buffer_, accessible_memory_start_);
+
+ // Clean up the trap handler
+ trap_handler::RemoveTrapHandler();
+ if (!g_test_handler_executed) {
+#if V8_OS_LINUX || V8_OS_MACOSX
+ // The test handler cleans up the signal handler setup in the test. If the
+ // test handler was not called, we have to do the cleanup ourselves.
+ CHECK_EQ(0, sigaction(SIGSEGV, &g_old_segv_action, nullptr));
+ CHECK_EQ(0, sigaction(SIGFPE, &g_old_fpe_action, nullptr));
+ CHECK_EQ(0, sigaction(SIGBUS, &g_old_bus_action, nullptr));
+#elif V8_OS_WIN
+ RemoveVectoredExceptionHandler(g_registered_handler);
+ g_registered_handler = nullptr;
+#endif
+ }
+ }
+
+ void InitRecoveryCode() {
+ // Create a code snippet where we can jump to to recover from a signal or
+ // exception. The code snippet only consists of a return statement.
+ recovery_buffer_ = AllocateAssemblerBuffer(
+ AssemblerBase::kMinimalBufferSize, GetRandomMmapAddr());
+
+ MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
+ recovery_buffer_->CreateView());
+ int recovery_offset = __ pc_offset();
+ __ Pop(scratch);
+ __ Ret();
+ CodeDesc desc;
+ masm.GetCode(nullptr, &desc);
+ recovery_buffer_->MakeExecutable();
+ g_recovery_address =
+ reinterpret_cast<Address>(desc.buffer + recovery_offset);
+ }
+
+#if V8_OS_LINUX || V8_OS_MACOSX
+ static void SignalHandler(int signal, siginfo_t* info, void* context) {
+ if (g_use_as_first_chance_handler) {
+ if (v8::TryHandleWebAssemblyTrapPosix(signal, info, context)) {
+ return;
+ }
+ }
+
+ // Reset the signal handler, to avoid that this signal handler is called
+ // repeatedly.
+ sigaction(SIGSEGV, &g_old_segv_action, nullptr);
+ sigaction(SIGFPE, &g_old_fpe_action, nullptr);
+ sigaction(SIGBUS, &g_old_bus_action, nullptr);
+
+ g_test_handler_executed = true;
+ // Set the $rip to the recovery code.
+ ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
+#if V8_OS_LINUX
+ uc->uc_mcontext.gregs[REG_RIP] = g_recovery_address;
+#else // V8_OS_MACOSX
+ uc->uc_mcontext->__ss.__rip = g_recovery_address;
+#endif
+ }
+#endif
+
+#if V8_OS_WIN
+ static LONG WINAPI TestHandler(EXCEPTION_POINTERS* exception) {
+ if (g_use_as_first_chance_handler) {
+ if (v8::TryHandleWebAssemblyTrapWindows(exception)) {
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+ }
+ RemoveVectoredExceptionHandler(g_registered_handler);
+ g_registered_handler = nullptr;
+ g_test_handler_executed = true;
+ exception->ContextRecord->Rip = g_recovery_address;
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+#endif
+
+ public:
+ void SetupTrapHandler(TrapHandlerStyle style) {
+ bool use_default_handler = style == kDefault;
+ g_use_as_first_chance_handler = !use_default_handler;
+ CHECK(v8::V8::EnableWebAssemblyTrapHandler(use_default_handler));
+ }
+
+ void GenerateSetThreadInWasmFlagCode(MacroAssembler* masm) {
+ masm->Move(scratch,
+ i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
+ RelocInfo::NONE);
+ masm->movl(MemOperand(scratch, 0), Immediate(1));
+ }
+
+ void GenerateResetThreadInWasmFlagCode(MacroAssembler* masm) {
+ masm->Move(scratch,
+ i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
+ RelocInfo::NONE);
+ masm->movl(MemOperand(scratch, 0), Immediate(0));
+ }
+
+ bool GetThreadInWasmFlag() {
+ return *reinterpret_cast<int*>(
+ trap_handler::GetThreadInWasmThreadLocalAddress());
+ }
+
+ // Execute the code in buffer.
+ void ExecuteBuffer() {
+ buffer_->MakeExecutable();
+ GeneratedCode<void>::FromAddress(
+ i_isolate(), reinterpret_cast<Address>(buffer_->start()))
+ .Call();
+ CHECK(!g_test_handler_executed);
+ }
+
+ // Execute the code in buffer. We expect a crash which we recover from in the
+ // test handler.
+ void ExecuteExpectCrash(TestingAssemblerBuffer* buffer,
+ bool check_wasm_flag = true) {
+ CHECK(!g_test_handler_executed);
+ buffer->MakeExecutable();
+ GeneratedCode<void>::FromAddress(i_isolate(),
+ reinterpret_cast<Address>(buffer->start()))
+ .Call();
+ CHECK(g_test_handler_executed);
+ g_test_handler_executed = false;
+ if (check_wasm_flag) CHECK(!GetThreadInWasmFlag());
+ }
+
+ bool test_handler_executed() { return g_test_handler_executed; }
+
+ // Allocated memory which corresponds to wasm memory with guard regions.
+ base::AddressRegion memory_buffer_;
+ // Address within the guard region of the wasm memory. Accessing this memory
+ // address causes a signal or exception.
+ Address crash_address_;
+ // The start of the accessible region in the allocated memory. This pointer is
+ // needed to de-register the memory from the wasm memory tracker again.
+ void* accessible_memory_start_;
+
+ // Buffer for generated code.
+ std::unique_ptr<TestingAssemblerBuffer> buffer_;
+ // Buffer for the code for the landing pad of the test handler.
+ std::unique_ptr<TestingAssemblerBuffer> recovery_buffer_;
+};
+
+TEST_P(TrapHandlerTest, TestTrapHandlerRecovery) {
+ // Test that the wasm trap handler can recover a memory access violation in
+ // wasm code (we fake the wasm code and the access violation).
+ MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
+ buffer_->CreateView());
+ __ Push(scratch);
+ GenerateSetThreadInWasmFlagCode(&masm);
+ __ Move(scratch, crash_address_, RelocInfo::NONE);
+ int crash_offset = __ pc_offset();
+ __ testl(MemOperand(scratch, 0), Immediate(1));
+ int recovery_offset = __ pc_offset();
+ GenerateResetThreadInWasmFlagCode(&masm);
+ __ Pop(scratch);
+ __ Ret();
+ CodeDesc desc;
+ masm.GetCode(nullptr, &desc);
+
+ SetupTrapHandler(GetParam());
+ trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
+ recovery_offset};
+ trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
+ desc.instr_size, 1, &protected_instruction);
+
+ ExecuteBuffer();
+}
+
+TEST_P(TrapHandlerTest, TestReleaseHandlerData) {
+ // Test that after we release handler data in the trap handler, it cannot
+ // recover from the specific memory access violation anymore.
+ MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
+ buffer_->CreateView());
+ __ Push(scratch);
+ GenerateSetThreadInWasmFlagCode(&masm);
+ __ Move(scratch, crash_address_, RelocInfo::NONE);
+ int crash_offset = __ pc_offset();
+ __ testl(MemOperand(scratch, 0), Immediate(1));
+ int recovery_offset = __ pc_offset();
+ GenerateResetThreadInWasmFlagCode(&masm);
+ __ Pop(scratch);
+ __ Ret();
+ CodeDesc desc;
+ masm.GetCode(nullptr, &desc);
+
+ trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
+ recovery_offset};
+ int handler_id = trap_handler::RegisterHandlerData(
+ reinterpret_cast<Address>(desc.buffer), desc.instr_size, 1,
+ &protected_instruction);
+
+ SetupTrapHandler(GetParam());
+
+ ExecuteBuffer();
+
+ // Deregister from the trap handler. The trap handler should not do the
+ // recovery now.
+ trap_handler::ReleaseHandlerData(handler_id);
+
+ ExecuteExpectCrash(buffer_.get());
+}
+
+TEST_P(TrapHandlerTest, TestNoThreadInWasmFlag) {
+ // That that if the thread_in_wasm flag is not set, the trap handler does not
+ // get active.
+ MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
+ buffer_->CreateView());
+ __ Push(scratch);
+ __ Move(scratch, crash_address_, RelocInfo::NONE);
+ int crash_offset = __ pc_offset();
+ __ testl(MemOperand(scratch, 0), Immediate(1));
+ int recovery_offset = __ pc_offset();
+ __ Pop(scratch);
+ __ Ret();
+ CodeDesc desc;
+ masm.GetCode(nullptr, &desc);
+
+ trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
+ recovery_offset};
+ trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
+ desc.instr_size, 1, &protected_instruction);
+
+ SetupTrapHandler(GetParam());
+
+ ExecuteExpectCrash(buffer_.get());
+}
+
+TEST_P(TrapHandlerTest, TestCrashInWasmNoProtectedInstruction) {
+ // Test that if the crash in wasm happened at an instruction which is not
+ // protected, then the trap handler does not handle it.
+ MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
+ buffer_->CreateView());
+ __ Push(scratch);
+ GenerateSetThreadInWasmFlagCode(&masm);
+ int no_crash_offset = __ pc_offset();
+ __ Move(scratch, crash_address_, RelocInfo::NONE);
+ __ testl(MemOperand(scratch, 0), Immediate(1));
+ // Offset where the crash is not happening.
+ int recovery_offset = __ pc_offset();
+ GenerateResetThreadInWasmFlagCode(&masm);
+ __ Pop(scratch);
+ __ Ret();
+ CodeDesc desc;
+ masm.GetCode(nullptr, &desc);
+
+ trap_handler::ProtectedInstructionData protected_instruction{no_crash_offset,
+ recovery_offset};
+ trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
+ desc.instr_size, 1, &protected_instruction);
+
+ SetupTrapHandler(GetParam());
+
+ ExecuteExpectCrash(buffer_.get());
+}
+
+TEST_P(TrapHandlerTest, TestCrashInWasmWrongCrashType) {
+ // Test that if the crash reason is not a memory access violation, then the
+ // wasm trap handler does not handle it.
+ MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
+ buffer_->CreateView());
+ __ Push(scratch);
+ GenerateSetThreadInWasmFlagCode(&masm);
+ __ xorq(scratch, scratch);
+ int crash_offset = __ pc_offset();
+ __ divq(scratch);
+ // Offset where the crash is not happening.
+ int recovery_offset = __ pc_offset();
+ GenerateResetThreadInWasmFlagCode(&masm);
+ __ Pop(scratch);
+ __ Ret();
+ CodeDesc desc;
+ masm.GetCode(nullptr, &desc);
+
+ trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
+ recovery_offset};
+ trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
+ desc.instr_size, 1, &protected_instruction);
+
+ SetupTrapHandler(GetParam());
+
+#if V8_OS_POSIX
+ // The V8 default trap handler does not register for SIGFPE, therefore the
+ // thread-in-wasm flag is never reset in this test. We therefore do not check
+ // the value of this flag.
+ bool check_wasm_flag = GetParam() != kDefault;
+#else
+ bool check_wasm_flag = true;
+#endif
+ ExecuteExpectCrash(buffer_.get(), check_wasm_flag);
+ if (!check_wasm_flag) {
+ // Reset the thread-in-wasm flag because it was probably not reset in the
+ // trap handler.
+ *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
+ }
+}
+
+class CodeRunner : public v8::base::Thread {
+ public:
+ CodeRunner(TrapHandlerTest* test, TestingAssemblerBuffer* buffer)
+ : Thread(Options("CodeRunner")), test_(test), buffer_(buffer) {}
+
+ void Run() override { test_->ExecuteExpectCrash(buffer_); }
+
+ private:
+ TrapHandlerTest* test_;
+ TestingAssemblerBuffer* buffer_;
+};
+
+TEST_P(TrapHandlerTest, TestCrashInOtherThread) {
+ // Test setup:
+ // The current thread enters wasm land (sets the thread_in_wasm flag)
+ // A second thread crashes at a protected instruction without having the flag
+ // set.
+ MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
+ buffer_->CreateView());
+ __ Push(scratch);
+ __ Move(scratch, crash_address_, RelocInfo::NONE);
+ int crash_offset = __ pc_offset();
+ __ testl(MemOperand(scratch, 0), Immediate(1));
+ int recovery_offset = __ pc_offset();
+ __ Pop(scratch);
+ __ Ret();
+ CodeDesc desc;
+ masm.GetCode(nullptr, &desc);
+
+ trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
+ recovery_offset};
+ trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
+ desc.instr_size, 1, &protected_instruction);
+
+ SetupTrapHandler(GetParam());
+
+ CodeRunner runner(this, buffer_.get());
+ CHECK(!GetThreadInWasmFlag());
+ // Set the thread-in-wasm flag manually in this thread.
+ *trap_handler::GetThreadInWasmThreadLocalAddress() = 1;
+ runner.Start();
+ runner.Join();
+ CHECK(GetThreadInWasmFlag());
+ // Reset the thread-in-wasm flag.
+ *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
+}
+
+INSTANTIATE_TEST_CASE_P(/* no prefix */, TrapHandlerTest,
+ ::testing::Values(kDefault, kCallback),
+ PrintTrapHandlerTestParam);
+
+#undef __
+} // namespace wasm
+} // namespace internal
+} // namespace v8