LCOV - code coverage report
Current view: top level - test/unittests/wasm - trap-handler-x64-unittest.cc (source / functions) Hit Total Coverage
Test: app.info Lines: 182 183 99.5 %
Date: 2019-04-17 Functions: 34 43 79.1 %

          Line data    Source code
       1             : // Copyright 2018 the V8 project authors. All rights reserved.
       2             : // Use of this source code is governed by a BSD-style license that can be
       3             : // found in the LICENSE file.
       4             : 
       5             : #include "include/v8config.h"
       6             : 
       7             : #if V8_OS_LINUX
       8             : #include <signal.h>
       9             : #include <ucontext.h>
      10             : #elif V8_OS_MACOSX
      11             : #include <signal.h>
      12             : #include <sys/ucontext.h>
      13             : #elif V8_OS_WIN
      14             : #include <windows.h>
      15             : #endif
      16             : 
      17             : #include "testing/gtest/include/gtest/gtest.h"
      18             : 
      19             : #if V8_OS_POSIX
      20             : #include "include/v8-wasm-trap-handler-posix.h"
      21             : #elif V8_OS_WIN
      22             : #include "include/v8-wasm-trap-handler-win.h"
      23             : #endif
      24             : #include "src/allocation.h"
      25             : #include "src/assembler-inl.h"
      26             : #include "src/base/page-allocator.h"
      27             : #include "src/macro-assembler-inl.h"
      28             : #include "src/simulator.h"
      29             : #include "src/trap-handler/trap-handler.h"
      30             : #include "src/vector.h"
      31             : #include "src/wasm/wasm-engine.h"
      32             : #include "src/wasm/wasm-memory.h"
      33             : 
      34             : #include "test/common/assembler-tester.h"
      35             : #include "test/unittests/test-utils.h"
      36             : 
      37             : namespace v8 {
      38             : namespace internal {
      39             : namespace wasm {
      40             : 
      41             : namespace {
      42             : constexpr Register scratch = r10;
      43             : bool g_test_handler_executed = false;
      44             : #if V8_OS_LINUX || V8_OS_MACOSX
      45             : struct sigaction g_old_segv_action;
      46             : struct sigaction g_old_fpe_action;
      47             : struct sigaction g_old_bus_action;  // We get SIGBUS on Mac sometimes.
      48             : #elif V8_OS_WIN
      49             : void* g_registered_handler = nullptr;
      50             : #endif
      51             : 
      52             : // The recovery address allows us to recover from an intentional crash.
      53             : Address g_recovery_address;
      54             : // Flag to indicate if the test handler should call the trap handler as a first
      55             : // chance handler.
      56             : bool g_use_as_first_chance_handler = false;
      57             : }  // namespace
      58             : 
      59             : #define __ masm.
      60             : 
      61             : enum TrapHandlerStyle : int {
      62             :   // The test uses the default trap handler of V8.
      63             :   kDefault = 0,
      64             :   // The test installs the trap handler callback in its own test handler.
      65             :   kCallback = 1
      66             : };
      67             : 
      68       37056 : std::string PrintTrapHandlerTestParam(
      69             :     ::testing::TestParamInfo<TrapHandlerStyle> info) {
      70       37056 :   switch (info.param) {
      71             :     case kDefault:
      72       18528 :       return "DefaultTrapHandler";
      73             :     case kCallback:
      74       18528 :       return "Callback";
      75             :   }
      76           0 :   UNREACHABLE();
      77             : }
      78             : 
      79          36 : class TrapHandlerTest : public TestWithIsolate,
      80             :                         public ::testing::WithParamInterface<TrapHandlerStyle> {
      81             :  protected:
      82          12 :   void SetUp() override {
      83          12 :     void* base = nullptr;
      84          12 :     size_t length = 0;
      85             :     accessible_memory_start_ =
      86             :         i_isolate()
      87             :             ->wasm_engine()
      88             :             ->memory_tracker()
      89          12 :             ->TryAllocateBackingStoreForTesting(
      90          12 :                 i_isolate()->heap(), 1 * kWasmPageSize, &base, &length);
      91             :     memory_buffer_ =
      92          12 :         base::AddressRegion(reinterpret_cast<Address>(base), length);
      93             : 
      94             :     // The allocated memory buffer ends with a guard page.
      95          12 :     crash_address_ = memory_buffer_.end() - 32;
      96             :     // Allocate a buffer for the generated code.
      97          24 :     buffer_ = AllocateAssemblerBuffer(AssemblerBase::kMinimalBufferSize,
      98             :                                       GetRandomMmapAddr());
      99             : 
     100          12 :     InitRecoveryCode();
     101             : 
     102             : #if V8_OS_LINUX || V8_OS_MACOSX
     103             :     // Set up a signal handler to recover from the expected crash.
     104             :     struct sigaction action;
     105          12 :     action.sa_sigaction = SignalHandler;
     106          12 :     sigemptyset(&action.sa_mask);
     107          12 :     action.sa_flags = SA_SIGINFO;
     108             :     // SIGSEGV happens for wasm oob memory accesses on Linux.
     109          12 :     CHECK_EQ(0, sigaction(SIGSEGV, &action, &g_old_segv_action));
     110             :     // SIGBUS happens for wasm oob memory accesses on macOS.
     111          12 :     CHECK_EQ(0, sigaction(SIGBUS, &action, &g_old_bus_action));
     112             :     // SIGFPE to simulate crashes which are not handled by the trap handler.
     113          12 :     CHECK_EQ(0, sigaction(SIGFPE, &action, &g_old_fpe_action));
     114             : #elif V8_OS_WIN
     115             :     g_registered_handler =
     116             :         AddVectoredExceptionHandler(/*first=*/0, TestHandler);
     117             : #endif
     118          12 :   }
     119             : 
     120          12 :   void TearDown() override {
     121             :     // We should always have left wasm code.
     122          12 :     CHECK(!GetThreadInWasmFlag());
     123             :     buffer_.reset();
     124             :     recovery_buffer_.reset();
     125             : 
     126             :     // Free the allocated backing store.
     127          12 :     i_isolate()->wasm_engine()->memory_tracker()->FreeBackingStoreForTesting(
     128          12 :         memory_buffer_, accessible_memory_start_);
     129             : 
     130             :     // Clean up the trap handler
     131          12 :     trap_handler::RemoveTrapHandler();
     132          12 :     if (!g_test_handler_executed) {
     133             : #if V8_OS_LINUX || V8_OS_MACOSX
     134             :       // The test handler cleans up the signal handler setup in the test. If the
     135             :       // test handler was not called, we have to do the cleanup ourselves.
     136          12 :       CHECK_EQ(0, sigaction(SIGSEGV, &g_old_segv_action, nullptr));
     137          12 :       CHECK_EQ(0, sigaction(SIGFPE, &g_old_fpe_action, nullptr));
     138          12 :       CHECK_EQ(0, sigaction(SIGBUS, &g_old_bus_action, nullptr));
     139             : #elif V8_OS_WIN
     140             :       RemoveVectoredExceptionHandler(g_registered_handler);
     141             :       g_registered_handler = nullptr;
     142             : #endif
     143             :     }
     144          12 :   }
     145             : 
     146          12 :   void InitRecoveryCode() {
     147             :     // Create a code snippet where we can jump to to recover from a signal or
     148             :     // exception. The code snippet only consists of a return statement.
     149          24 :     recovery_buffer_ = AllocateAssemblerBuffer(
     150             :         AssemblerBase::kMinimalBufferSize, GetRandomMmapAddr());
     151             : 
     152             :     MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
     153          36 :                         recovery_buffer_->CreateView());
     154             :     int recovery_offset = __ pc_offset();
     155          12 :     __ Pop(scratch);
     156          12 :     __ Ret();
     157          12 :     CodeDesc desc;
     158             :     masm.GetCode(nullptr, &desc);
     159          12 :     recovery_buffer_->MakeExecutable();
     160             :     g_recovery_address =
     161          12 :         reinterpret_cast<Address>(desc.buffer + recovery_offset);
     162          12 :   }
     163             : 
     164             : #if V8_OS_LINUX || V8_OS_MACOSX
     165          12 :   static void SignalHandler(int signal, siginfo_t* info, void* context) {
     166          12 :     if (g_use_as_first_chance_handler) {
     167           7 :       if (v8::TryHandleWebAssemblyTrapPosix(signal, info, context)) {
     168             :         return;
     169             :       }
     170             :     }
     171             : 
     172             :     // Reset the signal handler, to avoid that this signal handler is called
     173             :     // repeatedly.
     174          10 :     sigaction(SIGSEGV, &g_old_segv_action, nullptr);
     175          10 :     sigaction(SIGFPE, &g_old_fpe_action, nullptr);
     176          10 :     sigaction(SIGBUS, &g_old_bus_action, nullptr);
     177             : 
     178          10 :     g_test_handler_executed = true;
     179             :     // Set the $rip to the recovery code.
     180             :     ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
     181             : #if V8_OS_LINUX
     182          10 :     uc->uc_mcontext.gregs[REG_RIP] = g_recovery_address;
     183             : #else  // V8_OS_MACOSX
     184             :     uc->uc_mcontext->__ss.__rip = g_recovery_address;
     185             : #endif
     186             :   }
     187             : #endif
     188             : 
     189             : #if V8_OS_WIN
     190             :   static LONG WINAPI TestHandler(EXCEPTION_POINTERS* exception) {
     191             :     if (g_use_as_first_chance_handler) {
     192             :       if (v8::TryHandleWebAssemblyTrapWindows(exception)) {
     193             :         return EXCEPTION_CONTINUE_EXECUTION;
     194             :       }
     195             :     }
     196             :     RemoveVectoredExceptionHandler(g_registered_handler);
     197             :     g_registered_handler = nullptr;
     198             :     g_test_handler_executed = true;
     199             :     exception->ContextRecord->Rip = g_recovery_address;
     200             :     return EXCEPTION_CONTINUE_EXECUTION;
     201             :   }
     202             : #endif
     203             : 
     204             :  public:
     205          12 :   void SetupTrapHandler(TrapHandlerStyle style) {
     206          12 :     bool use_default_handler = style == kDefault;
     207          12 :     g_use_as_first_chance_handler = !use_default_handler;
     208          12 :     CHECK(v8::V8::EnableWebAssemblyTrapHandler(use_default_handler));
     209          12 :   }
     210             : 
     211           8 :   void GenerateSetThreadInWasmFlagCode(MacroAssembler* masm) {
     212           8 :     masm->Move(scratch,
     213             :                i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
     214             :                RelocInfo::NONE);
     215          16 :     masm->movl(MemOperand(scratch, 0), Immediate(1));
     216           8 :   }
     217             : 
     218           8 :   void GenerateResetThreadInWasmFlagCode(MacroAssembler* masm) {
     219           8 :     masm->Move(scratch,
     220             :                i_isolate()->thread_local_top()->thread_in_wasm_flag_address_,
     221             :                RelocInfo::NONE);
     222          16 :     masm->movl(MemOperand(scratch, 0), Immediate(0));
     223           8 :   }
     224             : 
     225             :   bool GetThreadInWasmFlag() {
     226             :     return *reinterpret_cast<int*>(
     227          25 :         trap_handler::GetThreadInWasmThreadLocalAddress());
     228             :   }
     229             : 
     230             :   // Execute the code in buffer.
     231           4 :   void ExecuteBuffer() {
     232           4 :     buffer_->MakeExecutable();
     233             :     GeneratedCode<void>::FromAddress(
     234           4 :         i_isolate(), reinterpret_cast<Address>(buffer_->start()))
     235             :         .Call();
     236           4 :     CHECK(!g_test_handler_executed);
     237           4 :   }
     238             : 
     239             :   // Execute the code in buffer. We expect a crash which we recover from in the
     240             :   // test handler.
     241          10 :   void ExecuteExpectCrash(TestingAssemblerBuffer* buffer,
     242             :                           bool check_wasm_flag = true) {
     243          10 :     CHECK(!g_test_handler_executed);
     244          10 :     buffer->MakeExecutable();
     245             :     GeneratedCode<void>::FromAddress(i_isolate(),
     246          10 :                                      reinterpret_cast<Address>(buffer->start()))
     247             :         .Call();
     248          10 :     CHECK(g_test_handler_executed);
     249          10 :     g_test_handler_executed = false;
     250          19 :     if (check_wasm_flag) CHECK(!GetThreadInWasmFlag());
     251          10 :   }
     252             : 
     253             :   bool test_handler_executed() { return g_test_handler_executed; }
     254             : 
     255             :   // Allocated memory which corresponds to wasm memory with guard regions.
     256             :   base::AddressRegion memory_buffer_;
     257             :   // Address within the guard region of the wasm memory. Accessing this memory
     258             :   // address causes a signal or exception.
     259             :   Address crash_address_;
     260             :   // The start of the accessible region in the allocated memory. This pointer is
     261             :   // needed to de-register the memory from the wasm memory tracker again.
     262             :   void* accessible_memory_start_;
     263             : 
     264             :   // Buffer for generated code.
     265             :   std::unique_ptr<TestingAssemblerBuffer> buffer_;
     266             :   // Buffer for the code for the landing pad of the test handler.
     267             :   std::unique_ptr<TestingAssemblerBuffer> recovery_buffer_;
     268             : };
     269             : 
     270       18534 : TEST_P(TrapHandlerTest, TestTrapHandlerRecovery) {
     271             :   // Test that the wasm trap handler can recover a memory access violation in
     272             :   // wasm code (we fake the wasm code and the access violation).
     273             :   MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
     274           6 :                       buffer_->CreateView());
     275           2 :   __ Push(scratch);
     276           2 :   GenerateSetThreadInWasmFlagCode(&masm);
     277           2 :   __ Move(scratch, crash_address_, RelocInfo::NONE);
     278             :   int crash_offset = __ pc_offset();
     279           4 :   __ testl(MemOperand(scratch, 0), Immediate(1));
     280             :   int recovery_offset = __ pc_offset();
     281           2 :   GenerateResetThreadInWasmFlagCode(&masm);
     282           2 :   __ Pop(scratch);
     283           2 :   __ Ret();
     284           2 :   CodeDesc desc;
     285             :   masm.GetCode(nullptr, &desc);
     286             : 
     287           2 :   SetupTrapHandler(GetParam());
     288             :   trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
     289           2 :                                                                recovery_offset};
     290           2 :   trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
     291           4 :                                     desc.instr_size, 1, &protected_instruction);
     292             : 
     293           2 :   ExecuteBuffer();
     294           2 : }
     295             : 
     296       18534 : TEST_P(TrapHandlerTest, TestReleaseHandlerData) {
     297             :   // Test that after we release handler data in the trap handler, it cannot
     298             :   // recover from the specific memory access violation anymore.
     299             :   MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
     300           6 :                       buffer_->CreateView());
     301           2 :   __ Push(scratch);
     302           2 :   GenerateSetThreadInWasmFlagCode(&masm);
     303           2 :   __ Move(scratch, crash_address_, RelocInfo::NONE);
     304             :   int crash_offset = __ pc_offset();
     305           4 :   __ testl(MemOperand(scratch, 0), Immediate(1));
     306             :   int recovery_offset = __ pc_offset();
     307           2 :   GenerateResetThreadInWasmFlagCode(&masm);
     308           2 :   __ Pop(scratch);
     309           2 :   __ Ret();
     310           2 :   CodeDesc desc;
     311             :   masm.GetCode(nullptr, &desc);
     312             : 
     313             :   trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
     314           2 :                                                                recovery_offset};
     315           4 :   int handler_id = trap_handler::RegisterHandlerData(
     316           4 :       reinterpret_cast<Address>(desc.buffer), desc.instr_size, 1,
     317           2 :       &protected_instruction);
     318             : 
     319           2 :   SetupTrapHandler(GetParam());
     320             : 
     321           2 :   ExecuteBuffer();
     322             : 
     323             :   // Deregister from the trap handler. The trap handler should not do the
     324             :   // recovery now.
     325           2 :   trap_handler::ReleaseHandlerData(handler_id);
     326             : 
     327           2 :   ExecuteExpectCrash(buffer_.get());
     328           2 : }
     329             : 
     330       18534 : TEST_P(TrapHandlerTest, TestNoThreadInWasmFlag) {
     331             :   // That that if the thread_in_wasm flag is not set, the trap handler does not
     332             :   // get active.
     333             :   MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
     334           6 :                       buffer_->CreateView());
     335           2 :   __ Push(scratch);
     336           2 :   __ Move(scratch, crash_address_, RelocInfo::NONE);
     337             :   int crash_offset = __ pc_offset();
     338           4 :   __ testl(MemOperand(scratch, 0), Immediate(1));
     339             :   int recovery_offset = __ pc_offset();
     340           2 :   __ Pop(scratch);
     341           2 :   __ Ret();
     342           2 :   CodeDesc desc;
     343             :   masm.GetCode(nullptr, &desc);
     344             : 
     345             :   trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
     346           2 :                                                                recovery_offset};
     347           2 :   trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
     348           4 :                                     desc.instr_size, 1, &protected_instruction);
     349             : 
     350           2 :   SetupTrapHandler(GetParam());
     351             : 
     352           2 :   ExecuteExpectCrash(buffer_.get());
     353           2 : }
     354             : 
     355       18534 : TEST_P(TrapHandlerTest, TestCrashInWasmNoProtectedInstruction) {
     356             :   // Test that if the crash in wasm happened at an instruction which is not
     357             :   // protected, then the trap handler does not handle it.
     358             :   MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
     359           6 :                       buffer_->CreateView());
     360           2 :   __ Push(scratch);
     361           2 :   GenerateSetThreadInWasmFlagCode(&masm);
     362             :   int no_crash_offset = __ pc_offset();
     363           2 :   __ Move(scratch, crash_address_, RelocInfo::NONE);
     364           4 :   __ testl(MemOperand(scratch, 0), Immediate(1));
     365             :   // Offset where the crash is not happening.
     366             :   int recovery_offset = __ pc_offset();
     367           2 :   GenerateResetThreadInWasmFlagCode(&masm);
     368           2 :   __ Pop(scratch);
     369           2 :   __ Ret();
     370           2 :   CodeDesc desc;
     371             :   masm.GetCode(nullptr, &desc);
     372             : 
     373             :   trap_handler::ProtectedInstructionData protected_instruction{no_crash_offset,
     374           2 :                                                                recovery_offset};
     375           2 :   trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
     376           4 :                                     desc.instr_size, 1, &protected_instruction);
     377             : 
     378           2 :   SetupTrapHandler(GetParam());
     379             : 
     380           2 :   ExecuteExpectCrash(buffer_.get());
     381           2 : }
     382             : 
     383       18534 : TEST_P(TrapHandlerTest, TestCrashInWasmWrongCrashType) {
     384             :   // Test that if the crash reason is not a memory access violation, then the
     385             :   // wasm trap handler does not handle it.
     386             :   MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
     387           6 :                       buffer_->CreateView());
     388           2 :   __ Push(scratch);
     389           2 :   GenerateSetThreadInWasmFlagCode(&masm);
     390             :   __ xorq(scratch, scratch);
     391             :   int crash_offset = __ pc_offset();
     392             :   __ divq(scratch);
     393             :   // Offset where the crash is not happening.
     394             :   int recovery_offset = __ pc_offset();
     395           2 :   GenerateResetThreadInWasmFlagCode(&masm);
     396           2 :   __ Pop(scratch);
     397           2 :   __ Ret();
     398           2 :   CodeDesc desc;
     399             :   masm.GetCode(nullptr, &desc);
     400             : 
     401             :   trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
     402           2 :                                                                recovery_offset};
     403           2 :   trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
     404           4 :                                     desc.instr_size, 1, &protected_instruction);
     405             : 
     406           2 :   SetupTrapHandler(GetParam());
     407             : 
     408             : #if V8_OS_POSIX
     409             :   // On Posix, the V8 default trap handler does not register for SIGFPE,
     410             :   // therefore the thread-in-wasm flag is never reset in this test. We
     411             :   // therefore do not check the value of this flag.
     412           2 :   bool check_wasm_flag = GetParam() != kDefault;
     413             : #elif V8_OS_WIN
     414             :   // On Windows, the trap handler returns immediately if not an exception of
     415             :   // interest.
     416             :   bool check_wasm_flag = false;
     417             : #else
     418             :   bool check_wasm_flag = true;
     419             : #endif
     420           4 :   ExecuteExpectCrash(buffer_.get(), check_wasm_flag);
     421           2 :   if (!check_wasm_flag) {
     422             :     // Reset the thread-in-wasm flag because it was probably not reset in the
     423             :     // trap handler.
     424           1 :     *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
     425             :   }
     426           2 : }
     427             : 
     428           2 : class CodeRunner : public v8::base::Thread {
     429             :  public:
     430             :   CodeRunner(TrapHandlerTest* test, TestingAssemblerBuffer* buffer)
     431           2 :       : Thread(Options("CodeRunner")), test_(test), buffer_(buffer) {}
     432             : 
     433           2 :   void Run() override { test_->ExecuteExpectCrash(buffer_); }
     434             : 
     435             :  private:
     436             :   TrapHandlerTest* test_;
     437             :   TestingAssemblerBuffer* buffer_;
     438             : };
     439             : 
     440       18534 : TEST_P(TrapHandlerTest, TestCrashInOtherThread) {
     441             :   // Test setup:
     442             :   // The current thread enters wasm land (sets the thread_in_wasm flag)
     443             :   // A second thread crashes at a protected instruction without having the flag
     444             :   // set.
     445             :   MacroAssembler masm(nullptr, AssemblerOptions{}, CodeObjectRequired::kNo,
     446           6 :                       buffer_->CreateView());
     447           2 :   __ Push(scratch);
     448           2 :   __ Move(scratch, crash_address_, RelocInfo::NONE);
     449             :   int crash_offset = __ pc_offset();
     450           4 :   __ testl(MemOperand(scratch, 0), Immediate(1));
     451             :   int recovery_offset = __ pc_offset();
     452           2 :   __ Pop(scratch);
     453           2 :   __ Ret();
     454           2 :   CodeDesc desc;
     455             :   masm.GetCode(nullptr, &desc);
     456             : 
     457             :   trap_handler::ProtectedInstructionData protected_instruction{crash_offset,
     458           2 :                                                                recovery_offset};
     459           2 :   trap_handler::RegisterHandlerData(reinterpret_cast<Address>(desc.buffer),
     460           4 :                                     desc.instr_size, 1, &protected_instruction);
     461             : 
     462           2 :   SetupTrapHandler(GetParam());
     463             : 
     464           2 :   CodeRunner runner(this, buffer_.get());
     465           2 :   CHECK(!GetThreadInWasmFlag());
     466             :   // Set the thread-in-wasm flag manually in this thread.
     467           2 :   *trap_handler::GetThreadInWasmThreadLocalAddress() = 1;
     468           2 :   runner.Start();
     469           2 :   runner.Join();
     470           2 :   CHECK(GetThreadInWasmFlag());
     471             :   // Reset the thread-in-wasm flag.
     472           2 :   *trap_handler::GetThreadInWasmThreadLocalAddress() = 0;
     473           2 : }
     474             : 
     475      108080 : INSTANTIATE_TEST_SUITE_P(/* no prefix */, TrapHandlerTest,
     476             :                          ::testing::Values(kDefault, kCallback),
     477             :                          PrintTrapHandlerTestParam);
     478             : 
     479             : #undef __
     480             : }  // namespace wasm
     481             : }  // namespace internal
     482        9264 : }  // namespace v8

Generated by: LCOV version 1.10