diff options
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.cc | 478 |
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 |