/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright 2014 Mozilla Foundation * Copyright 2023 Moonchild Productions * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "wasm/WasmSignalHandlers.h" #include "mozilla/DebugOnly.h" #include "mozilla/PodOperations.h" #include "jit/AtomicOperations.h" #include "jit/Disassembler.h" #include "vm/Runtime.h" #include "wasm/WasmInstance.h" using namespace js; using namespace js::jit; using namespace js::wasm; using JS::GenericNaN; using mozilla::DebugOnly; using mozilla::PodArrayZero; // For platforms where the signal/exception handler runs on the same // thread/stack as the victim (Unix and Windows), we can use TLS to find any // currently executing wasm code. static JSRuntime* RuntimeForCurrentThread() { PerThreadData* threadData = TlsPerThreadData.get(); if (!threadData) return nullptr; return threadData->runtimeIfOnOwnerThread(); } // Crashing inside the signal handler can cause the handler to be recursively // invoked, eventually blowing the stack without actually showing a crash // report dialog via Breakpad. To guard against this we watch for such // recursion and fall through to the next handler immediately rather than // trying to handle it. class AutoSetHandlingSegFault { JSRuntime* rt; public: explicit AutoSetHandlingSegFault(JSRuntime* rt) : rt(rt) { MOZ_ASSERT(!rt->handlingSegFault); rt->handlingSegFault = true; } ~AutoSetHandlingSegFault() { MOZ_ASSERT(rt->handlingSegFault); rt->handlingSegFault = false; } }; #if defined(XP_WIN) # define XMM_sig(p,i) ((p)->Xmm##i) # define EIP_sig(p) ((p)->Eip) # define RIP_sig(p) ((p)->Rip) # define RAX_sig(p) ((p)->Rax) # define RCX_sig(p) ((p)->Rcx) # define RDX_sig(p) ((p)->Rdx) # define RBX_sig(p) ((p)->Rbx) # define RSP_sig(p) ((p)->Rsp) # define RBP_sig(p) ((p)->Rbp) # define RSI_sig(p) ((p)->Rsi) # define RDI_sig(p) ((p)->Rdi) # define R8_sig(p) ((p)->R8) # define R9_sig(p) ((p)->R9) # define R10_sig(p) ((p)->R10) # define R11_sig(p) ((p)->R11) # define R12_sig(p) ((p)->R12) # define R13_sig(p) ((p)->R13) # define R14_sig(p) ((p)->R14) # define R15_sig(p) ((p)->R15) #elif defined(__OpenBSD__) # define XMM_sig(p,i) ((p)->sc_fpstate->fx_xmm[i]) # define EIP_sig(p) ((p)->sc_eip) # define RIP_sig(p) ((p)->sc_rip) # define RAX_sig(p) ((p)->sc_rax) # define RCX_sig(p) ((p)->sc_rcx) # define RDX_sig(p) ((p)->sc_rdx) # define RBX_sig(p) ((p)->sc_rbx) # define RSP_sig(p) ((p)->sc_rsp) # define RBP_sig(p) ((p)->sc_rbp) # define RSI_sig(p) ((p)->sc_rsi) # define RDI_sig(p) ((p)->sc_rdi) # define R8_sig(p) ((p)->sc_r8) # define R9_sig(p) ((p)->sc_r9) # define R10_sig(p) ((p)->sc_r10) # define R11_sig(p) ((p)->sc_r11) # define R12_sig(p) ((p)->sc_r12) # define R13_sig(p) ((p)->sc_r13) # define R14_sig(p) ((p)->sc_r14) # if defined(__arm__) # define R15_sig(p) ((p)->sc_pc) # else # define R15_sig(p) ((p)->sc_r15) # endif # if defined(__aarch64__) # define EPC_sig(p) ((p)->sc_elr) # define RFP_sig(p) ((p)->sc_x[29]) # endif # if defined(__mips__) # define EPC_sig(p) ((p)->sc_pc) # define RFP_sig(p) ((p)->sc_regs[30]) # endif #elif defined(__linux__) || defined(__sun) # if defined(__linux__) # define XMM_sig(p,i) ((p)->uc_mcontext.fpregs->_xmm[i]) # define EIP_sig(p) ((p)->uc_mcontext.gregs[REG_EIP]) # else // defined(__sun) /* See https://www.illumos.org/issues/5876. They keep arguing over whether * should provide the register index defines in regset.h or * require applications to request them specifically, and we need them here. */ #include #include # define XMM_sig(p,i) ((p)->uc_mcontext.fpregs.fp_reg_set.fpchip_state.xmm[i]) # define EIP_sig(p) ((p)->uc_mcontext.gregs[REG_PC]) # endif # define RIP_sig(p) ((p)->uc_mcontext.gregs[REG_RIP]) # define RAX_sig(p) ((p)->uc_mcontext.gregs[REG_RAX]) # define RCX_sig(p) ((p)->uc_mcontext.gregs[REG_RCX]) # define RDX_sig(p) ((p)->uc_mcontext.gregs[REG_RDX]) # define RBX_sig(p) ((p)->uc_mcontext.gregs[REG_RBX]) # define RSP_sig(p) ((p)->uc_mcontext.gregs[REG_RSP]) # define RBP_sig(p) ((p)->uc_mcontext.gregs[REG_RBP]) # define RSI_sig(p) ((p)->uc_mcontext.gregs[REG_RSI]) # define RDI_sig(p) ((p)->uc_mcontext.gregs[REG_RDI]) # define R8_sig(p) ((p)->uc_mcontext.gregs[REG_R8]) # define R9_sig(p) ((p)->uc_mcontext.gregs[REG_R9]) # define R10_sig(p) ((p)->uc_mcontext.gregs[REG_R10]) # define R11_sig(p) ((p)->uc_mcontext.gregs[REG_R11]) # define R12_sig(p) ((p)->uc_mcontext.gregs[REG_R12]) # define R13_sig(p) ((p)->uc_mcontext.gregs[REG_R13]) # define R14_sig(p) ((p)->uc_mcontext.gregs[REG_R14]) # if defined(__linux__) && defined(__arm__) # define R15_sig(p) ((p)->uc_mcontext.arm_pc) # else # define R15_sig(p) ((p)->uc_mcontext.gregs[REG_R15]) # endif # if defined(__linux__) && defined(__aarch64__) # define EPC_sig(p) ((p)->uc_mcontext.pc) # endif # if defined(__linux__) && defined(__mips__) # define EPC_sig(p) ((p)->uc_mcontext.pc) # define RSP_sig(p) ((p)->uc_mcontext.gregs[29]) # define RFP_sig(p) ((p)->uc_mcontext.gregs[30]) # endif #elif defined(__NetBSD__) # define XMM_sig(p,i) (((struct fxsave64*)(p)->uc_mcontext.__fpregs)->fx_xmm[i]) # define EIP_sig(p) ((p)->uc_mcontext.__gregs[_REG_EIP]) # define RIP_sig(p) ((p)->uc_mcontext.__gregs[_REG_RIP]) # define RAX_sig(p) ((p)->uc_mcontext.__gregs[_REG_RAX]) # define RCX_sig(p) ((p)->uc_mcontext.__gregs[_REG_RCX]) # define RDX_sig(p) ((p)->uc_mcontext.__gregs[_REG_RDX]) # define RBX_sig(p) ((p)->uc_mcontext.__gregs[_REG_RBX]) # define RSP_sig(p) ((p)->uc_mcontext.__gregs[_REG_RSP]) # define RBP_sig(p) ((p)->uc_mcontext.__gregs[_REG_RBP]) # define RSI_sig(p) ((p)->uc_mcontext.__gregs[_REG_RSI]) # define RDI_sig(p) ((p)->uc_mcontext.__gregs[_REG_RDI]) # define R8_sig(p) ((p)->uc_mcontext.__gregs[_REG_R8]) # define R9_sig(p) ((p)->uc_mcontext.__gregs[_REG_R9]) # define R10_sig(p) ((p)->uc_mcontext.__gregs[_REG_R10]) # define R11_sig(p) ((p)->uc_mcontext.__gregs[_REG_R11]) # define R12_sig(p) ((p)->uc_mcontext.__gregs[_REG_R12]) # define R13_sig(p) ((p)->uc_mcontext.__gregs[_REG_R13]) # define R14_sig(p) ((p)->uc_mcontext.__gregs[_REG_R14]) # define R15_sig(p) ((p)->uc_mcontext.__gregs[_REG_R15]) # if defined(__aarch64__) # define EPC_sig(p) ((p)->uc_mcontext.__gregs[_REG_PC]) # define RFP_sig(p) ((p)->uc_mcontext.__gregs[_REG_X29]) # endif # if defined(__mips__) # define EPC_sig(p) ((p)->uc_mcontext.__gregs[_REG_EPC]) # define RFP_sig(p) ((p)->uc_mcontext.__gregs[_REG_S8]) # endif #elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) # if defined(__DragonFly__) # define XMM_sig(p,i) (((union savefpu*)(p)->uc_mcontext.mc_fpregs)->sv_xmm.sv_xmm[i]) # else # define XMM_sig(p,i) (((struct savefpu*)(p)->uc_mcontext.mc_fpstate)->sv_xmm[i]) # endif # define EIP_sig(p) ((p)->uc_mcontext.mc_eip) # define RIP_sig(p) ((p)->uc_mcontext.mc_rip) # define RAX_sig(p) ((p)->uc_mcontext.mc_rax) # define RCX_sig(p) ((p)->uc_mcontext.mc_rcx) # define RDX_sig(p) ((p)->uc_mcontext.mc_rdx) # define RBX_sig(p) ((p)->uc_mcontext.mc_rbx) # define RSP_sig(p) ((p)->uc_mcontext.mc_rsp) # define RBP_sig(p) ((p)->uc_mcontext.mc_rbp) # define RSI_sig(p) ((p)->uc_mcontext.mc_rsi) # define RDI_sig(p) ((p)->uc_mcontext.mc_rdi) # define R8_sig(p) ((p)->uc_mcontext.mc_r8) # define R9_sig(p) ((p)->uc_mcontext.mc_r9) # define R10_sig(p) ((p)->uc_mcontext.mc_r10) # define R11_sig(p) ((p)->uc_mcontext.mc_r11) # define R12_sig(p) ((p)->uc_mcontext.mc_r12) # define R13_sig(p) ((p)->uc_mcontext.mc_r13) # define R14_sig(p) ((p)->uc_mcontext.mc_r14) # if defined(__FreeBSD__) && defined(__arm__) # define R15_sig(p) ((p)->uc_mcontext.__gregs[_REG_R15]) # else # define R15_sig(p) ((p)->uc_mcontext.mc_r15) # endif # if defined(__FreeBSD__) && defined(__aarch64__) # define EPC_sig(p) ((p)->uc_mcontext.mc_gpregs.gp_elr) # define RFP_sig(p) ((p)->uc_mcontext.mc_gpregs.gp_x[29]) # endif # if defined(__FreeBSD__) && defined(__mips__) # define EPC_sig(p) ((p)->uc_mcontext.mc_pc) # define RFP_sig(p) ((p)->uc_mcontext.mc_regs[30]) # endif #elif defined(XP_DARWIN) # define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip) # define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip) # define R15_sig(p) ((p)->uc_mcontext->__ss.__pc) #else # error "Don't know how to read/write to the thread state via the mcontext_t." #endif #if defined(XP_WIN) # include "jswin.h" #else # include # include #endif #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) # include // for ucontext_t, mcontext_t #endif #if defined(JS_CPU_X64) # if defined(__DragonFly__) # include // for union savefpu # elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ defined(__NetBSD__) || defined(__OpenBSD__) # include // for struct savefpu/fxsave64 # endif #endif #if !defined(XP_WIN) # define CONTEXT ucontext_t #endif // Define a context type for use in the emulator code. This is usually just // the same as CONTEXT, but on Mac we use a different structure since we call // into the emulator code from a Mach exception handler rather than a // sigaction-style signal handler. #if defined(XP_DARWIN) # if defined(JS_CPU_X64) struct macos_x64_context { x86_thread_state64_t thread; x86_float_state64_t float_; }; # define EMULATOR_CONTEXT macos_x64_context # elif defined(JS_CPU_X86) struct macos_x86_context { x86_thread_state_t thread; x86_float_state_t float_; }; # define EMULATOR_CONTEXT macos_x86_context # elif defined(JS_CPU_ARM) struct macos_arm_context { arm_thread_state_t thread; arm_neon_state_t float_; }; # define EMULATOR_CONTEXT macos_arm_context # elif defined(__aarch64__) struct macos_aarch64_context { arm_thread_state64_t thread; arm_neon_state64_t float_; }; # define EMULATOR_CONTEXT macos_aarch64_context # else # error Unsupported architecture # endif #else # define EMULATOR_CONTEXT CONTEXT #endif #if defined(JS_CPU_X64) # define PC_sig(p) RIP_sig(p) #elif defined(JS_CPU_X86) # define PC_sig(p) EIP_sig(p) #elif defined(JS_CPU_ARM) # define PC_sig(p) R15_sig(p) #elif defined(__aarch64__) # define PC_sig(p) EPC_sig(p) #elif defined(JS_CPU_MIPS) # define PC_sig(p) EPC_sig(p) #endif static uint8_t** ContextToPC(CONTEXT* context) { #ifdef JS_CODEGEN_NONE MOZ_CRASH(); #else return reinterpret_cast(&PC_sig(context)); #endif } #if defined(WASM_HUGE_MEMORY) MOZ_COLD static void SetFPRegToNaN(size_t size, void* fp_reg) { MOZ_RELEASE_ASSERT(size <= Simd128DataSize); memset(fp_reg, 0, Simd128DataSize); switch (size) { case 4: *static_cast(fp_reg) = GenericNaN(); break; case 8: *static_cast(fp_reg) = GenericNaN(); break; default: // All SIMD accesses throw on OOB. MOZ_CRASH("unexpected size in SetFPRegToNaN"); } } MOZ_COLD static void SetGPRegToZero(void* gp_reg) { memset(gp_reg, 0, sizeof(intptr_t)); } MOZ_COLD static void SetFPRegToLoadedValue(SharedMem addr, size_t size, void* fp_reg) { MOZ_RELEASE_ASSERT(size <= Simd128DataSize); memset(fp_reg, 0, Simd128DataSize); AtomicOperations::memcpySafeWhenRacy(fp_reg, addr, size); } MOZ_COLD static void SetGPRegToLoadedValue(SharedMem addr, size_t size, void* gp_reg) { MOZ_RELEASE_ASSERT(size <= sizeof(void*)); memset(gp_reg, 0, sizeof(void*)); AtomicOperations::memcpySafeWhenRacy(gp_reg, addr, size); } MOZ_COLD static void SetGPRegToLoadedValueSext32(SharedMem addr, size_t size, void* gp_reg) { MOZ_RELEASE_ASSERT(size <= sizeof(int32_t)); int8_t msb = AtomicOperations::loadSafeWhenRacy(addr.cast() + (size - 1)); memset(gp_reg, 0, sizeof(void*)); memset(gp_reg, msb >> 7, sizeof(int32_t)); AtomicOperations::memcpySafeWhenRacy(gp_reg, addr, size); } MOZ_COLD static void StoreValueFromFPReg(SharedMem addr, size_t size, const void* fp_reg) { MOZ_RELEASE_ASSERT(size <= Simd128DataSize); AtomicOperations::memcpySafeWhenRacy(addr, const_cast(fp_reg), size); } MOZ_COLD static void StoreValueFromGPReg(SharedMem addr, size_t size, const void* gp_reg) { MOZ_RELEASE_ASSERT(size <= sizeof(void*)); AtomicOperations::memcpySafeWhenRacy(addr, const_cast(gp_reg), size); } MOZ_COLD static void StoreValueFromGPImm(SharedMem addr, size_t size, int32_t imm) { MOZ_RELEASE_ASSERT(size <= sizeof(imm)); AtomicOperations::memcpySafeWhenRacy(addr, static_cast(&imm), size); } # if !defined(XP_DARWIN) MOZ_COLD static void* AddressOfFPRegisterSlot(CONTEXT* context, FloatRegisters::Encoding encoding) { switch (encoding) { case X86Encoding::xmm0: return &XMM_sig(context, 0); case X86Encoding::xmm1: return &XMM_sig(context, 1); case X86Encoding::xmm2: return &XMM_sig(context, 2); case X86Encoding::xmm3: return &XMM_sig(context, 3); case X86Encoding::xmm4: return &XMM_sig(context, 4); case X86Encoding::xmm5: return &XMM_sig(context, 5); case X86Encoding::xmm6: return &XMM_sig(context, 6); case X86Encoding::xmm7: return &XMM_sig(context, 7); case X86Encoding::xmm8: return &XMM_sig(context, 8); case X86Encoding::xmm9: return &XMM_sig(context, 9); case X86Encoding::xmm10: return &XMM_sig(context, 10); case X86Encoding::xmm11: return &XMM_sig(context, 11); case X86Encoding::xmm12: return &XMM_sig(context, 12); case X86Encoding::xmm13: return &XMM_sig(context, 13); case X86Encoding::xmm14: return &XMM_sig(context, 14); case X86Encoding::xmm15: return &XMM_sig(context, 15); default: break; } MOZ_CRASH(); } MOZ_COLD static void* AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code) { switch (code) { case X86Encoding::rax: return &RAX_sig(context); case X86Encoding::rcx: return &RCX_sig(context); case X86Encoding::rdx: return &RDX_sig(context); case X86Encoding::rbx: return &RBX_sig(context); case X86Encoding::rsp: return &RSP_sig(context); case X86Encoding::rbp: return &RBP_sig(context); case X86Encoding::rsi: return &RSI_sig(context); case X86Encoding::rdi: return &RDI_sig(context); case X86Encoding::r8: return &R8_sig(context); case X86Encoding::r9: return &R9_sig(context); case X86Encoding::r10: return &R10_sig(context); case X86Encoding::r11: return &R11_sig(context); case X86Encoding::r12: return &R12_sig(context); case X86Encoding::r13: return &R13_sig(context); case X86Encoding::r14: return &R14_sig(context); case X86Encoding::r15: return &R15_sig(context); default: break; } MOZ_CRASH(); } # else MOZ_COLD static void* AddressOfFPRegisterSlot(EMULATOR_CONTEXT* context, FloatRegisters::Encoding encoding) { switch (encoding) { case X86Encoding::xmm0: return &context->float_.__fpu_xmm0; case X86Encoding::xmm1: return &context->float_.__fpu_xmm1; case X86Encoding::xmm2: return &context->float_.__fpu_xmm2; case X86Encoding::xmm3: return &context->float_.__fpu_xmm3; case X86Encoding::xmm4: return &context->float_.__fpu_xmm4; case X86Encoding::xmm5: return &context->float_.__fpu_xmm5; case X86Encoding::xmm6: return &context->float_.__fpu_xmm6; case X86Encoding::xmm7: return &context->float_.__fpu_xmm7; case X86Encoding::xmm8: return &context->float_.__fpu_xmm8; case X86Encoding::xmm9: return &context->float_.__fpu_xmm9; case X86Encoding::xmm10: return &context->float_.__fpu_xmm10; case X86Encoding::xmm11: return &context->float_.__fpu_xmm11; case X86Encoding::xmm12: return &context->float_.__fpu_xmm12; case X86Encoding::xmm13: return &context->float_.__fpu_xmm13; case X86Encoding::xmm14: return &context->float_.__fpu_xmm14; case X86Encoding::xmm15: return &context->float_.__fpu_xmm15; default: break; } MOZ_CRASH(); } MOZ_COLD static void* AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code) { switch (code) { case X86Encoding::rax: return &context->thread.__rax; case X86Encoding::rcx: return &context->thread.__rcx; case X86Encoding::rdx: return &context->thread.__rdx; case X86Encoding::rbx: return &context->thread.__rbx; case X86Encoding::rsp: return &context->thread.__rsp; case X86Encoding::rbp: return &context->thread.__rbp; case X86Encoding::rsi: return &context->thread.__rsi; case X86Encoding::rdi: return &context->thread.__rdi; case X86Encoding::r8: return &context->thread.__r8; case X86Encoding::r9: return &context->thread.__r9; case X86Encoding::r10: return &context->thread.__r10; case X86Encoding::r11: return &context->thread.__r11; case X86Encoding::r12: return &context->thread.__r12; case X86Encoding::r13: return &context->thread.__r13; case X86Encoding::r14: return &context->thread.__r14; case X86Encoding::r15: return &context->thread.__r15; default: break; } MOZ_CRASH(); } # endif // !XP_DARWIN MOZ_COLD static void SetRegisterToCoercedUndefined(EMULATOR_CONTEXT* context, size_t size, const Disassembler::OtherOperand& value) { if (value.kind() == Disassembler::OtherOperand::FPR) SetFPRegToNaN(size, AddressOfFPRegisterSlot(context, value.fpr())); else SetGPRegToZero(AddressOfGPRegisterSlot(context, value.gpr())); } MOZ_COLD static void SetRegisterToLoadedValue(EMULATOR_CONTEXT* context, SharedMem addr, size_t size, const Disassembler::OtherOperand& value) { if (value.kind() == Disassembler::OtherOperand::FPR) SetFPRegToLoadedValue(addr, size, AddressOfFPRegisterSlot(context, value.fpr())); else SetGPRegToLoadedValue(addr, size, AddressOfGPRegisterSlot(context, value.gpr())); } MOZ_COLD static void SetRegisterToLoadedValueSext32(EMULATOR_CONTEXT* context, SharedMem addr, size_t size, const Disassembler::OtherOperand& value) { SetGPRegToLoadedValueSext32(addr, size, AddressOfGPRegisterSlot(context, value.gpr())); } MOZ_COLD static void StoreValueFromRegister(EMULATOR_CONTEXT* context, SharedMem addr, size_t size, const Disassembler::OtherOperand& value) { if (value.kind() == Disassembler::OtherOperand::FPR) StoreValueFromFPReg(addr, size, AddressOfFPRegisterSlot(context, value.fpr())); else if (value.kind() == Disassembler::OtherOperand::GPR) StoreValueFromGPReg(addr, size, AddressOfGPRegisterSlot(context, value.gpr())); else StoreValueFromGPImm(addr, size, value.imm()); } MOZ_COLD static uint8_t* ComputeAccessAddress(EMULATOR_CONTEXT* context, const Disassembler::ComplexAddress& address) { MOZ_RELEASE_ASSERT(!address.isPCRelative(), "PC-relative addresses not supported yet"); uintptr_t result = address.disp(); if (address.hasBase()) { uintptr_t base; StoreValueFromGPReg(SharedMem::unshared(&base), sizeof(uintptr_t), AddressOfGPRegisterSlot(context, address.base())); result += base; } if (address.hasIndex()) { uintptr_t index; StoreValueFromGPReg(SharedMem::unshared(&index), sizeof(uintptr_t), AddressOfGPRegisterSlot(context, address.index())); MOZ_ASSERT(address.scale() < 32, "address shift overflow"); result += index * (uintptr_t(1) << address.scale()); } return reinterpret_cast(result); } MOZ_COLD static void HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress, const Instance& instance, uint8_t** ppc) { MOZ_RELEASE_ASSERT(instance.codeSegment().containsFunctionPC(pc)); const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc); if (!memoryAccess) { // If there is no associated MemoryAccess for the faulting PC, this must be // experimental SIMD.js or Atomics. When these are converted to // non-experimental wasm features, this case, as well as outOfBoundsCode, // can be removed. *ppc = instance.codeSegment().outOfBoundsCode(); return; } MOZ_RELEASE_ASSERT(memoryAccess->insnOffset() == (pc - instance.codeBase())); // On WASM_HUGE_MEMORY platforms, asm.js code may fault. asm.js does not // trap on fault and so has no trap out-of-line path. Instead, stores are // silently ignored (by advancing the pc past the store and resuming) and // loads silently succeed with a JS-semantics-determined value. if (memoryAccess->hasTrapOutOfLineCode()) { *ppc = memoryAccess->trapOutOfLineCode(instance.codeBase()); return; } MOZ_RELEASE_ASSERT(instance.isAsmJS()); // Disassemble the instruction which caused the trap so that we can extract // information about it and decide what to do. Disassembler::HeapAccess access; uint8_t* end = Disassembler::DisassembleHeapAccess(pc, &access); const Disassembler::ComplexAddress& address = access.address(); MOZ_RELEASE_ASSERT(end > pc); MOZ_RELEASE_ASSERT(instance.codeSegment().containsFunctionPC(end)); // Check x64 asm.js heap access invariants. MOZ_RELEASE_ASSERT(address.disp() >= 0); MOZ_RELEASE_ASSERT(address.base() == HeapReg.code()); MOZ_RELEASE_ASSERT(!address.hasIndex() || address.index() != HeapReg.code()); MOZ_RELEASE_ASSERT(address.scale() == 0); if (address.hasBase()) { uintptr_t base; StoreValueFromGPReg(SharedMem::unshared(&base), sizeof(uintptr_t), AddressOfGPRegisterSlot(context, address.base())); MOZ_RELEASE_ASSERT(reinterpret_cast(base) == instance.memoryBase()); } if (address.hasIndex()) { uintptr_t index; StoreValueFromGPReg(SharedMem::unshared(&index), sizeof(uintptr_t), AddressOfGPRegisterSlot(context, address.index())); MOZ_RELEASE_ASSERT(uint32_t(index) == index); } // Determine the actual effective address of the faulting access. We can't // rely on the faultingAddress given to us by the OS, because we need the // address of the start of the access, and the OS may sometimes give us an // address somewhere in the middle of the heap access. uint8_t* accessAddress = ComputeAccessAddress(context, address); MOZ_RELEASE_ASSERT(size_t(faultingAddress - accessAddress) < access.size(), "Given faulting address does not appear to be within computed " "faulting address range"); MOZ_RELEASE_ASSERT(accessAddress >= instance.memoryBase(), "Access begins outside the asm.js heap"); MOZ_RELEASE_ASSERT(accessAddress + access.size() <= instance.memoryBase() + instance.memoryMappedSize(), "Access extends beyond the asm.js heap guard region"); MOZ_RELEASE_ASSERT(accessAddress + access.size() > instance.memoryBase() + instance.memoryLength(), "Computed access address is not actually out of bounds"); // The basic sandbox model is that all heap accesses are a heap base // register plus an index, and the index is always computed with 32-bit // operations, so we know it can only be 4 GiB off of the heap base. // // However, we wish to support the optimization of folding immediates // and scaled indices into addresses, and any address arithmetic we fold // gets done at full pointer width, so it doesn't get properly wrapped. // We support this by extending HugeMappedSize to the greatest size that // could be reached by such an unwrapped address, and then when we arrive // here in the signal handler for such an access, we compute the fully // wrapped address, and perform the load or store on it. // // Taking a signal is really slow, but in theory programs really shouldn't // be hitting this anyway. intptr_t unwrappedOffset = accessAddress - instance.memoryBase().unwrap(/* for value */); uint32_t wrappedOffset = uint32_t(unwrappedOffset); size_t size = access.size(); MOZ_RELEASE_ASSERT(wrappedOffset + size > wrappedOffset); bool inBounds = wrappedOffset + size < instance.memoryLength(); if (inBounds) { // We now know that this is an access that is actually in bounds when // properly wrapped. Complete the load or store with the wrapped // address. SharedMem wrappedAddress = instance.memoryBase() + wrappedOffset; MOZ_RELEASE_ASSERT(wrappedAddress >= instance.memoryBase()); MOZ_RELEASE_ASSERT(wrappedAddress + size > wrappedAddress); MOZ_RELEASE_ASSERT(wrappedAddress + size <= instance.memoryBase() + instance.memoryLength()); switch (access.kind()) { case Disassembler::HeapAccess::Load: SetRegisterToLoadedValue(context, wrappedAddress.cast(), size, access.otherOperand()); break; case Disassembler::HeapAccess::LoadSext32: SetRegisterToLoadedValueSext32(context, wrappedAddress.cast(), size, access.otherOperand()); break; case Disassembler::HeapAccess::Store: StoreValueFromRegister(context, wrappedAddress.cast(), size, access.otherOperand()); break; case Disassembler::HeapAccess::LoadSext64: MOZ_CRASH("no int64 accesses in asm.js"); case Disassembler::HeapAccess::Unknown: MOZ_CRASH("Failed to disassemble instruction"); } } else { // We now know that this is an out-of-bounds access made by an asm.js // load/store that we should handle. switch (access.kind()) { case Disassembler::HeapAccess::Load: case Disassembler::HeapAccess::LoadSext32: // Assign the JS-defined result value to the destination register // (ToInt32(undefined) or ToNumber(undefined), determined by the // type of the destination register). Very conveniently, we can // infer the type from the register class, since all SIMD accesses // throw on out of bounds (see above), so the only types using FP // registers are float32 and double. SetRegisterToCoercedUndefined(context, access.size(), access.otherOperand()); break; case Disassembler::HeapAccess::Store: // Do nothing. break; case Disassembler::HeapAccess::LoadSext64: MOZ_CRASH("no int64 accesses in asm.js"); case Disassembler::HeapAccess::Unknown: MOZ_CRASH("Failed to disassemble instruction"); } } *ppc = end; } #else // WASM_HUGE_MEMORY MOZ_COLD static void HandleMemoryAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress, const Instance& instance, uint8_t** ppc) { MOZ_RELEASE_ASSERT(instance.codeSegment().containsFunctionPC(pc)); const MemoryAccess* memoryAccess = instance.code().lookupMemoryAccess(pc); if (!memoryAccess) { // See explanation in the WASM_HUGE_MEMORY HandleMemoryAccess. *ppc = instance.codeSegment().outOfBoundsCode(); return; } MOZ_RELEASE_ASSERT(memoryAccess->hasTrapOutOfLineCode()); *ppc = memoryAccess->trapOutOfLineCode(instance.codeBase()); } #endif // WASM_HUGE_MEMORY MOZ_COLD static bool IsHeapAccessAddress(const Instance &instance, uint8_t* faultingAddress) { size_t accessLimit = instance.memoryMappedSize(); return instance.metadata().usesMemory() && faultingAddress >= instance.memoryBase() && faultingAddress < instance.memoryBase() + accessLimit; } #if defined(XP_WIN) static bool HandleFault(PEXCEPTION_POINTERS exception) { EXCEPTION_RECORD* record = exception->ExceptionRecord; CONTEXT* context = exception->ContextRecord; if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) return false; uint8_t** ppc = ContextToPC(context); uint8_t* pc = *ppc; if (record->NumberParameters < 2) return false; // Don't allow recursive handling of signals, see AutoSetHandlingSegFault. JSRuntime* rt = RuntimeForCurrentThread(); if (!rt || rt->handlingSegFault) return false; AutoSetHandlingSegFault handling(rt); WasmActivation* activation = rt->wasmActivationStack(); if (!activation) return false; const Instance* instance = activation->compartment()->wasm.lookupInstanceDeprecated(pc); if (!instance) return false; uint8_t* faultingAddress = reinterpret_cast(record->ExceptionInformation[1]); // This check isn't necessary, but, since we can, check anyway to make // sure we aren't covering up a real bug. if (!IsHeapAccessAddress(*instance, faultingAddress)) return false; if (!instance->codeSegment().containsFunctionPC(pc)) { // On Windows, it is possible for InterruptRunningCode to execute // between a faulting heap access and the handling of the fault due // to InterruptRunningCode's use of SuspendThread. When this happens, // after ResumeThread, the exception handler is called with pc equal to // instance.interrupt, which is logically wrong. The Right Thing would // be for the OS to make fault-handling atomic (so that CONTEXT.pc was // always the logically-faulting pc). Fortunately, we can detect this // case and silence the exception ourselves (the exception will // retrigger after the interrupt jumps back to resumePC). return pc == instance->codeSegment().interruptCode() && instance->codeSegment().containsFunctionPC(activation->resumePC()); } HandleMemoryAccess(context, pc, faultingAddress, *instance, ppc); return true; } static LONG WINAPI WasmFaultHandler(LPEXCEPTION_POINTERS exception) { if (HandleFault(exception)) return EXCEPTION_CONTINUE_EXECUTION; // No need to worry about calling other handlers, the OS does this for us. return EXCEPTION_CONTINUE_SEARCH; } #elif defined(XP_DARWIN) # include static uint8_t** ContextToPC(EMULATOR_CONTEXT* context) { # if defined(JS_CPU_X64) static_assert(sizeof(context->thread.__rip) == sizeof(void*), "stored IP should be compile-time pointer-sized"); return reinterpret_cast(&context->thread.__rip); # elif defined(JS_CPU_X86) static_assert(sizeof(context->thread.uts.ts32.__eip) == sizeof(void*), "stored IP should be compile-time pointer-sized"); return reinterpret_cast(&context->thread.uts.ts32.__eip); # elif defined(JS_CPU_ARM) || defined(__aarch64__) static_assert(sizeof(context->thread.__pc) == sizeof(void*), "stored IP should be compile-time pointer-sized"); return reinterpret_cast(&context->thread.__pc); # else # error Unsupported architecture # endif } // This definition was generated by mig (the Mach Interface Generator) for the // routine 'exception_raise' (exc.defs). #pragma pack(4) typedef struct { mach_msg_header_t Head; /* start of the kernel processed data */ mach_msg_body_t msgh_body; mach_msg_port_descriptor_t thread; mach_msg_port_descriptor_t task; /* end of the kernel processed data */ NDR_record_t NDR; exception_type_t exception; mach_msg_type_number_t codeCnt; int64_t code[2]; } Request__mach_exception_raise_t; #pragma pack() // The full Mach message also includes a trailer. struct ExceptionRequest { Request__mach_exception_raise_t body; mach_msg_trailer_t trailer; }; static bool HandleMachException(JSRuntime* rt, const ExceptionRequest& request) { // Don't allow recursive handling of signals, see AutoSetHandlingSegFault. if (rt->handlingSegFault) return false; AutoSetHandlingSegFault handling(rt); // Get the port of the JSRuntime's thread from the message. mach_port_t rtThread = request.body.thread.name; // Read out the JSRuntime thread's register state. EMULATOR_CONTEXT context; # if defined(JS_CPU_X64) unsigned int thread_state_count = x86_THREAD_STATE64_COUNT; unsigned int float_state_count = x86_FLOAT_STATE64_COUNT; int thread_state = x86_THREAD_STATE64; int float_state = x86_FLOAT_STATE64; # elif defined(JS_CPU_X86) unsigned int thread_state_count = x86_THREAD_STATE_COUNT; unsigned int float_state_count = x86_FLOAT_STATE_COUNT; int thread_state = x86_THREAD_STATE; int float_state = x86_FLOAT_STATE; # elif defined(JS_CPU_ARM) unsigned int thread_state_count = ARM_THREAD_STATE_COUNT; unsigned int float_state_count = ARM_NEON_STATE_COUNT; int thread_state = ARM_THREAD_STATE; int float_state = ARM_NEON_STATE; # elif defined(__aarch64__) unsigned int thread_state_count = ARM_THREAD_STATE64_COUNT; unsigned int float_state_count = ARM_NEON_STATE64_COUNT; int thread_state = ARM_THREAD_STATE64; int float_state = ARM_NEON_STATE64; # else # error Unsupported architecture # endif kern_return_t kret; kret = thread_get_state(rtThread, thread_state, (thread_state_t)&context.thread, &thread_state_count); if (kret != KERN_SUCCESS) return false; kret = thread_get_state(rtThread, float_state, (thread_state_t)&context.float_, &float_state_count); if (kret != KERN_SUCCESS) return false; uint8_t** ppc = ContextToPC(&context); uint8_t* pc = *ppc; if (request.body.exception != EXC_BAD_ACCESS || request.body.codeCnt != 2) return false; WasmActivation* activation = rt->wasmActivationStack(); if (!activation) return false; const Instance* instance = activation->compartment()->wasm.lookupInstanceDeprecated(pc); if (!instance || !instance->codeSegment().containsFunctionPC(pc)) return false; uint8_t* faultingAddress = reinterpret_cast(request.body.code[1]); // This check isn't necessary, but, since we can, check anyway to make // sure we aren't covering up a real bug. if (!IsHeapAccessAddress(*instance, faultingAddress)) return false; HandleMemoryAccess(&context, pc, faultingAddress, *instance, ppc); // Update the thread state with the new pc and register values. kret = thread_set_state(rtThread, float_state, (thread_state_t)&context.float_, float_state_count); if (kret != KERN_SUCCESS) return false; kret = thread_set_state(rtThread, thread_state, (thread_state_t)&context.thread, thread_state_count); if (kret != KERN_SUCCESS) return false; return true; } // Taken from mach_exc in /usr/include/mach/mach_exc.defs. static const mach_msg_id_t sExceptionId = 2405; // The choice of id here is arbitrary, the only constraint is that sQuitId != sExceptionId. static const mach_msg_id_t sQuitId = 42; static void MachExceptionHandlerThread(JSRuntime* rt) { mach_port_t port = rt->wasmMachExceptionHandler.port(); kern_return_t kret; while(true) { ExceptionRequest request; kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); // If we fail even receiving the message, we can't even send a reply! // Rather than hanging the faulting thread (hanging the browser), crash. if (kret != KERN_SUCCESS) { fprintf(stderr, "MachExceptionHandlerThread: mach_msg failed with %d\n", (int)kret); MOZ_CRASH(); } // There are only two messages we should be receiving: an exception // message that occurs when the runtime's thread faults and the quit // message sent when the runtime is shutting down. if (request.body.Head.msgh_id == sQuitId) break; if (request.body.Head.msgh_id != sExceptionId) { fprintf(stderr, "Unexpected msg header id %d\n", (int)request.body.Head.msgh_bits); MOZ_CRASH(); } // Some thread just commited an EXC_BAD_ACCESS and has been suspended by // the kernel. The kernel is waiting for us to reply with instructions. // Our default is the "not handled" reply (by setting the RetCode field // of the reply to KERN_FAILURE) which tells the kernel to continue // searching at the process and system level. If this is an asm.js // expected exception, we handle it and return KERN_SUCCESS. bool handled = HandleMachException(rt, request); kern_return_t replyCode = handled ? KERN_SUCCESS : KERN_FAILURE; // This magic incantation to send a reply back to the kernel was derived // from the exc_server generated by 'mig -v /usr/include/mach/mach_exc.defs'. __Reply__exception_raise_t reply; reply.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.body.Head.msgh_bits), 0); reply.Head.msgh_size = sizeof(reply); reply.Head.msgh_remote_port = request.body.Head.msgh_remote_port; reply.Head.msgh_local_port = MACH_PORT_NULL; reply.Head.msgh_id = request.body.Head.msgh_id + 100; reply.NDR = NDR_record; reply.RetCode = replyCode; mach_msg(&reply.Head, MACH_SEND_MSG, sizeof(reply), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); } } MachExceptionHandler::MachExceptionHandler() : installed_(false), thread_(), port_(MACH_PORT_NULL) {} void MachExceptionHandler::uninstall() { if (installed_) { thread_port_t thread = mach_thread_self(); kern_return_t kret = thread_set_exception_ports(thread, EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); mach_port_deallocate(mach_task_self(), thread); if (kret != KERN_SUCCESS) MOZ_CRASH(); installed_ = false; } if (thread_.joinable()) { // Break the handler thread out of the mach_msg loop. mach_msg_header_t msg; msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); msg.msgh_size = sizeof(msg); msg.msgh_remote_port = port_; msg.msgh_local_port = MACH_PORT_NULL; msg.msgh_reserved = 0; msg.msgh_id = sQuitId; kern_return_t kret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (kret != KERN_SUCCESS) { fprintf(stderr, "MachExceptionHandler: failed to send quit message: %d\n", (int)kret); MOZ_CRASH(); } // Wait for the handler thread to complete before deallocating the port. thread_.join(); } if (port_ != MACH_PORT_NULL) { DebugOnly kret = mach_port_destroy(mach_task_self(), port_); MOZ_ASSERT(kret == KERN_SUCCESS); port_ = MACH_PORT_NULL; } } bool MachExceptionHandler::install(JSRuntime* rt) { MOZ_ASSERT(!installed()); kern_return_t kret; mach_port_t thread; // Get a port which can send and receive data. kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port_); if (kret != KERN_SUCCESS) goto error; kret = mach_port_insert_right(mach_task_self(), port_, port_, MACH_MSG_TYPE_MAKE_SEND); if (kret != KERN_SUCCESS) goto error; // Create a thread to block on reading port_. if (!thread_.init(MachExceptionHandlerThread, rt)) goto error; // Direct exceptions on this thread to port_ (and thus our handler thread). // Note: we are totally clobbering any existing *thread* exception ports and // not even attempting to forward. Breakpad and gdb both use the *process* // exception ports which are only called if the thread doesn't handle the // exception, so we should be fine. thread = mach_thread_self(); kret = thread_set_exception_ports(thread, EXC_MASK_BAD_ACCESS, port_, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); mach_port_deallocate(mach_task_self(), thread); if (kret != KERN_SUCCESS) goto error; installed_ = true; return true; error: uninstall(); return false; } #else // If not Windows or Mac, assume Unix enum class Signal { SegFault, BusError }; // Be very cautious and default to not handling; we don't want to accidentally // silence real crashes from real bugs. template static bool HandleFault(int signum, siginfo_t* info, void* ctx) { // The signals we're expecting come from access violations, accessing // mprotected memory. If the signal originates anywhere else, don't try // to handle it. if (signal == Signal::SegFault) MOZ_RELEASE_ASSERT(signum == SIGSEGV); else MOZ_RELEASE_ASSERT(signum == SIGBUS); CONTEXT* context = (CONTEXT*)ctx; uint8_t** ppc = ContextToPC(context); uint8_t* pc = *ppc; // Don't allow recursive handling of signals, see AutoSetHandlingSegFault. JSRuntime* rt = RuntimeForCurrentThread(); if (!rt || rt->handlingSegFault) return false; AutoSetHandlingSegFault handling(rt); WasmActivation* activation = rt->wasmActivationStack(); if (!activation) return false; const Instance* instance = activation->compartment()->wasm.lookupInstanceDeprecated(pc); if (!instance || !instance->codeSegment().containsFunctionPC(pc)) return false; uint8_t* faultingAddress = reinterpret_cast(info->si_addr); // Although it's not strictly necessary, to make sure we're not covering up // any real bugs, check that the faulting address is indeed in the // instance's memory. if (!faultingAddress) { // On some Linux systems, the kernel apparently sometimes "gives up" and // passes a null faultingAddress with si_code set to SI_KERNEL. // This is observed on some automation machines for some out-of-bounds // atomic accesses on x86/64. #ifdef SI_KERNEL if (info->si_code != SI_KERNEL) return false; #else return false; #endif } else { if (!IsHeapAccessAddress(*instance, faultingAddress)) return false; } #ifdef JS_CODEGEN_ARM if (signal == Signal::BusError) { *ppc = instance->codeSegment().unalignedAccessCode(); return true; } #endif HandleMemoryAccess(context, pc, faultingAddress, *instance, ppc); return true; } static struct sigaction sPrevSEGVHandler; static struct sigaction sPrevSIGBUSHandler; template static void WasmFaultHandler(int signum, siginfo_t* info, void* context) { if (HandleFault(signum, info, context)) return; struct sigaction* previousSignal = signum == SIGSEGV ? &sPrevSEGVHandler : &sPrevSIGBUSHandler; // This signal is not for any asm.js code we expect, so we need to forward // the signal to the next handler. If there is no next handler (SIG_IGN or // SIG_DFL), then it's time to crash. To do this, we set the signal back to // its original disposition and return. This will cause the faulting op to // be re-executed which will crash in the normal way. The advantage of // doing this to calling _exit() is that we remove ourselves from the crash // stack which improves crash reports. If there is a next handler, call it. // It will either crash synchronously, fix up the instruction so that // execution can continue and return, or trigger a crash by returning the // signal to it's original disposition and returning. // // Note: the order of these tests matter. if (previousSignal->sa_flags & SA_SIGINFO) previousSignal->sa_sigaction(signum, info, context); else if (previousSignal->sa_handler == SIG_DFL || previousSignal->sa_handler == SIG_IGN) sigaction(signum, previousSignal, nullptr); else previousSignal->sa_handler(signum); } # endif // XP_WIN || XP_DARWIN || assume unix static void RedirectIonBackedgesToInterruptCheck(JSRuntime* rt) { if (jit::JitRuntime* jitRuntime = rt->jitRuntime()) { // If the backedge list is being mutated, the pc must be in C++ code and // thus not in a JIT iloop. We assume that the interrupt flag will be // checked at least once before entering JIT code (if not, no big deal; // the browser will just request another interrupt in a second). if (!jitRuntime->preventBackedgePatching()) jitRuntime->patchIonBackedges(rt, jit::JitRuntime::BackedgeInterruptCheck); } } // The return value indicates whether the PC was changed, not whether there was // a failure. static bool RedirectJitCodeToInterruptCheck(JSRuntime* rt, CONTEXT* context) { RedirectIonBackedgesToInterruptCheck(rt); if (WasmActivation* activation = rt->wasmActivationStack()) { #ifdef JS_SIMULATOR (void)ContextToPC(context); // silence static 'unused' errors void* pc = rt->simulator()->get_pc_as(); const Instance* instance = activation->compartment()->wasm.lookupInstanceDeprecated(pc); if (instance && instance->codeSegment().containsFunctionPC(pc)) rt->simulator()->set_resume_pc(instance->codeSegment().interruptCode()); #else uint8_t** ppc = ContextToPC(context); uint8_t* pc = *ppc; const Instance* instance = activation->compartment()->wasm.lookupInstanceDeprecated(pc); if (instance && instance->codeSegment().containsFunctionPC(pc)) { activation->setResumePC(pc); *ppc = instance->codeSegment().interruptCode(); return true; } #endif } return false; } #if !defined(XP_WIN) // For the interrupt signal, pick a signal number that: // - is not otherwise used by mozilla or standard libraries // - defaults to nostop and noprint on gdb/lldb so that noone is bothered // SIGVTALRM a relative of SIGALRM, so intended for user code, but, unlike // SIGALRM, not used anywhere else in Mozilla. static const int sInterruptSignal = SIGVTALRM; static void JitInterruptHandler(int signum, siginfo_t* info, void* context) { if (JSRuntime* rt = RuntimeForCurrentThread()) { RedirectJitCodeToInterruptCheck(rt, (CONTEXT*)context); rt->finishHandlingJitInterrupt(); } } #endif static bool sTriedInstallSignalHandlers = false; static bool sHaveSignalHandlers = false; static bool ProcessHasSignalHandlers() { // We assume that there are no races creating the first JSRuntime of the process. if (sTriedInstallSignalHandlers) return sHaveSignalHandlers; sTriedInstallSignalHandlers = true; // Developers might want to forcibly disable signals to avoid seeing // spurious SIGSEGVs in the debugger. if (getenv("JS_DISABLE_SLOW_SCRIPT_SIGNALS") || getenv("JS_NO_SIGNALS")) return false; // The interrupt handler allows the main thread to be paused from another // thread (see InterruptRunningJitCode). #if defined(XP_WIN) // Windows uses SuspendThread to stop the main thread from another thread. #else struct sigaction interruptHandler; interruptHandler.sa_flags = SA_SIGINFO; interruptHandler.sa_sigaction = &JitInterruptHandler; sigemptyset(&interruptHandler.sa_mask); struct sigaction prev; if (sigaction(sInterruptSignal, &interruptHandler, &prev)) MOZ_CRASH("unable to install interrupt handler"); // There shouldn't be any other handlers installed for sInterruptSignal. If // there are, we could always forward, but we need to understand what we're // doing to avoid problematic interference. if ((prev.sa_flags & SA_SIGINFO && prev.sa_sigaction) || (prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN)) { MOZ_CRASH("contention for interrupt signal"); } #endif // defined(XP_WIN) // Install a SIGSEGV handler to handle safely-out-of-bounds asm.js heap // access and/or unaligned accesses. # if defined(XP_WIN) if (!AddVectoredExceptionHandler(/* FirstHandler = */ true, WasmFaultHandler)) return false; # elif defined(XP_DARWIN) // OSX handles seg faults via the Mach exception handler above, so don't // install WasmFaultHandler. # else // SA_NODEFER allows us to reenter the signal handler if we crash while // handling the signal, and fall through to the Breakpad handler by testing // handlingSegFault. // Allow handling OOB with signals on all architectures struct sigaction faultHandler; faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER; faultHandler.sa_sigaction = WasmFaultHandler; sigemptyset(&faultHandler.sa_mask); if (sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler)) MOZ_CRASH("unable to install segv handler"); # if defined(JS_CODEGEN_ARM) // On Arm Handle Unaligned Accesses struct sigaction busHandler; busHandler.sa_flags = SA_SIGINFO | SA_NODEFER; busHandler.sa_sigaction = WasmFaultHandler; sigemptyset(&busHandler.sa_mask); if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler)) MOZ_CRASH("unable to install sigbus handler"); # endif # endif sHaveSignalHandlers = true; return true; } bool wasm::EnsureSignalHandlers(JSRuntime* rt) { // Nothing to do if the platform doesn't support it. if (!ProcessHasSignalHandlers()) return true; #if defined(XP_DARWIN) // On OSX, each JSRuntime gets its own handler thread. if (!rt->wasmMachExceptionHandler.installed() && !rt->wasmMachExceptionHandler.install(rt)) return false; #endif return true; } bool wasm::HaveSignalHandlers() { MOZ_ASSERT(sTriedInstallSignalHandlers); return sHaveSignalHandlers; } // JSRuntime::requestInterrupt sets interrupt_ (which is checked frequently by // C++ code at every Baseline JIT loop backedge) and jitStackLimit_ (which is // checked at every Baseline and Ion JIT function prologue). The remaining // sources of potential iloops (Ion loop backedges and all wasm code) are // handled by this function: // 1. Ion loop backedges are patched to instead point to a stub that handles // the interrupt; // 2. if the main thread's pc is inside wasm code, the pc is updated to point // to a stub that handles the interrupt. void js::InterruptRunningJitCode(JSRuntime* rt) { // If signal handlers weren't installed, then Ion and wasm emit normal // interrupt checks and don't need asynchronous interruption. if (!HaveSignalHandlers()) return; // Do nothing if we're already handling an interrupt here, to avoid races // below and in JitRuntime::patchIonBackedges. if (!rt->startHandlingJitInterrupt()) return; // If we are on runtime's main thread, then: pc is not in wasm code (so // nothing to do for wasm) and we can patch Ion backedges without any // special synchronization. if (rt == RuntimeForCurrentThread()) { RedirectIonBackedgesToInterruptCheck(rt); rt->finishHandlingJitInterrupt(); return; } // We are not on the runtime's main thread, so to do 1 and 2 above, we need // to halt the runtime's main thread first. #if defined(XP_WIN) // On Windows, we can simply suspend the main thread and work directly on // its context from this thread. SuspendThread can sporadically fail if the // thread is in the middle of a syscall. Rather than retrying in a loop, // just wait for the next request for interrupt. HANDLE thread = (HANDLE)rt->ownerThreadNative(); if (SuspendThread(thread) != -1) { CONTEXT context; context.ContextFlags = CONTEXT_CONTROL; if (GetThreadContext(thread, &context)) { if (RedirectJitCodeToInterruptCheck(rt, &context)) SetThreadContext(thread, &context); } ResumeThread(thread); } rt->finishHandlingJitInterrupt(); #else // On Unix, we instead deliver an async signal to the main thread which // halts the thread and callers our JitInterruptHandler (which has already // been installed by EnsureSignalHandlersInstalled). pthread_t thread = (pthread_t)rt->ownerThreadNative(); pthread_kill(thread, sInterruptSignal); #endif } MOZ_COLD bool js::wasm::IsPCInWasmCode(void *pc) { JSRuntime* rt = RuntimeForCurrentThread(); if (!rt) return false; MOZ_RELEASE_ASSERT(!rt->handlingSegFault); WasmActivation* activation = rt->wasmActivationStack(); if (!activation) return false; return !!activation->compartment()->wasm.lookupInstanceDeprecated(pc); }