02703a099a
Check if storage for thread_local variables has been allocated before attempting to access such variables, as exceptions may be raised in the thread before this initializion is complete, causing an infinite loop. Bug: v8:8966 Change-Id: Ifc6223b74999a55bfd0ed2d6ebf054bbffd7e809 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1507714 Commit-Queue: Ben Titzer <titzer@chromium.org> Reviewed-by: Ben Titzer <titzer@chromium.org> Cr-Commit-Position: refs/heads/master@{#60852}
483 lines
17 KiB
C++
483 lines
17 KiB
C++
// 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
|
|
// On 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;
|
|
#elif V8_OS_WIN
|
|
// On Windows, the trap handler returns immediately if not an exception of
|
|
// interest.
|
|
bool check_wasm_flag = false;
|
|
#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_SUITE_P(/* no prefix */, TrapHandlerTest,
|
|
::testing::Values(kDefault, kCallback),
|
|
PrintTrapHandlerTestParam);
|
|
|
|
#undef __
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|