/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * * Copyright 2014 Mozilla Foundation * * 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/ThreadLocal.h" #include "threading/Thread.h" #include "vm/JitActivation.h" // js::jit::JitActivation #include "vm/Realm.h" #include "vm/Runtime.h" #include "wasm/WasmCode.h" #include "wasm/WasmInstance.h" #if defined(XP_WIN) # include // must include before util/WindowsWrapper.h's `#undef`s # include "util/WindowsWrapper.h" #elif defined(XP_DARWIN) # include # include #else # include #endif using namespace js; using namespace js::wasm; using mozilla::DebugOnly; #if !defined(JS_CODEGEN_NONE) // ============================================================================= // This following pile of macros and includes defines the ToRegisterState() and // the ContextTo{PC,FP,SP,LR}() functions from the (highly) platform-specific // CONTEXT struct which is provided to the signal handler. // ============================================================================= # if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) # include // for ucontext_t, mcontext_t # endif # if defined(__x86_64__) # 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 EIP_sig(p) ((p)->Eip) # define EBP_sig(p) ((p)->Ebp) # define ESP_sig(p) ((p)->Esp) # define RIP_sig(p) ((p)->Rip) # define RSP_sig(p) ((p)->Rsp) # define RBP_sig(p) ((p)->Rbp) # define R11_sig(p) ((p)->R11) # define R13_sig(p) ((p)->R13) # define R14_sig(p) ((p)->R14) # define R15_sig(p) ((p)->R15) # define EPC_sig(p) ((p)->Pc) # define RFP_sig(p) ((p)->Fp) # define R31_sig(p) ((p)->Sp) # define RLR_sig(p) ((p)->Lr) # elif defined(__OpenBSD__) # define EIP_sig(p) ((p)->sc_eip) # define EBP_sig(p) ((p)->sc_ebp) # define ESP_sig(p) ((p)->sc_esp) # define RIP_sig(p) ((p)->sc_rip) # define RSP_sig(p) ((p)->sc_rsp) # define RBP_sig(p) ((p)->sc_rbp) # define R11_sig(p) ((p)->sc_r11) # if defined(__arm__) # define R13_sig(p) ((p)->sc_usr_sp) # define R14_sig(p) ((p)->sc_usr_lr) # define R15_sig(p) ((p)->sc_pc) # else # define R13_sig(p) ((p)->sc_r13) # define R14_sig(p) ((p)->sc_r14) # 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]) # define RLR_sig(p) ((p)->sc_lr) # define R31_sig(p) ((p)->sc_sp) # endif # if defined(__mips__) # define EPC_sig(p) ((p)->sc_pc) # define RFP_sig(p) ((p)->sc_regs[30]) # endif # if defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ defined(__PPC64LE__) # define R01_sig(p) ((p)->sc_frame.fixreg[1]) # define R32_sig(p) ((p)->sc_frame.srr0) # endif # elif defined(__linux__) || defined(__sun) # if defined(__linux__) # define EIP_sig(p) ((p)->uc_mcontext.gregs[REG_EIP]) # define EBP_sig(p) ((p)->uc_mcontext.gregs[REG_EBP]) # define ESP_sig(p) ((p)->uc_mcontext.gregs[REG_ESP]) # else # define EIP_sig(p) ((p)->uc_mcontext.gregs[REG_PC]) # define EBP_sig(p) ((p)->uc_mcontext.gregs[REG_EBP]) # define ESP_sig(p) ((p)->uc_mcontext.gregs[REG_ESP]) # endif # define RIP_sig(p) ((p)->uc_mcontext.gregs[REG_RIP]) # define RSP_sig(p) ((p)->uc_mcontext.gregs[REG_RSP]) # define RBP_sig(p) ((p)->uc_mcontext.gregs[REG_RBP]) # if defined(__linux__) && defined(__arm__) # define R11_sig(p) ((p)->uc_mcontext.arm_fp) # define R13_sig(p) ((p)->uc_mcontext.arm_sp) # define R14_sig(p) ((p)->uc_mcontext.arm_lr) # define R15_sig(p) ((p)->uc_mcontext.arm_pc) # else # define R11_sig(p) ((p)->uc_mcontext.gregs[REG_R11]) # 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]) # endif # if defined(__linux__) && defined(__aarch64__) # define EPC_sig(p) ((p)->uc_mcontext.pc) # define RFP_sig(p) ((p)->uc_mcontext.regs[29]) # define RLR_sig(p) ((p)->uc_mcontext.regs[30]) # define R31_sig(p) ((p)->uc_mcontext.sp) # endif # if defined(__linux__) && defined(__mips__) # define EPC_sig(p) ((p)->uc_mcontext.pc) # define RFP_sig(p) ((p)->uc_mcontext.gregs[30]) # define RSP_sig(p) ((p)->uc_mcontext.gregs[29]) # define R31_sig(p) ((p)->uc_mcontext.gregs[31]) # endif # if defined(__linux__) && (defined(__sparc__) && defined(__arch64__)) # define PC_sig(p) ((p)->uc_mcontext.mc_gregs[MC_PC]) # define FP_sig(p) ((p)->uc_mcontext.mc_fp) # define SP_sig(p) ((p)->uc_mcontext.mc_i7) # endif # if defined(__linux__) && (defined(__ppc64__) || defined(__PPC64__) || \ defined(__ppc64le__) || defined(__PPC64LE__)) # define R01_sig(p) ((p)->uc_mcontext.gp_regs[1]) # define R32_sig(p) ((p)->uc_mcontext.gp_regs[32]) # endif # if defined(__linux__) && defined(__loongarch__) # define EPC_sig(p) ((p)->uc_mcontext.__pc) # define RRA_sig(p) ((p)->uc_mcontext.__gregs[1]) # define R03_sig(p) ((p)->uc_mcontext.__gregs[3]) # define RFP_sig(p) ((p)->uc_mcontext.__gregs[22]) # endif # if defined(__linux__) && defined(__riscv) # define RPC_sig(p) ((p)->uc_mcontext.__gregs[REG_PC]) # define RRA_sig(p) ((p)->uc_mcontext.__gregs[REG_RA]) # define RFP_sig(p) ((p)->uc_mcontext.__gregs[REG_S0]) # define R02_sig(p) ((p)->uc_mcontext.__gregs[REG_SP]) # endif # if defined(__sun__) && defined(__sparc__) # define PC_sig(p) ((p)->uc_mcontext.gregs[REG_PC]) # define FP_sig(p) ((p)->uc_mcontext.gregs[REG_FPRS]) # define SP_sig(p) ((p)->uc_mcontext.gregs[REG_SP]) # endif # elif defined(__NetBSD__) # define EIP_sig(p) ((p)->uc_mcontext.__gregs[_REG_EIP]) # define EBP_sig(p) ((p)->uc_mcontext.__gregs[_REG_EBP]) # define ESP_sig(p) ((p)->uc_mcontext.__gregs[_REG_ESP]) # define RIP_sig(p) ((p)->uc_mcontext.__gregs[_REG_RIP]) # define RSP_sig(p) ((p)->uc_mcontext.__gregs[_REG_RSP]) # define RBP_sig(p) ((p)->uc_mcontext.__gregs[_REG_RBP]) # define R11_sig(p) ((p)->uc_mcontext.__gregs[_REG_R11]) # 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]) # define RLR_sig(p) ((p)->uc_mcontext.__gregs[_REG_X30]) # define R31_sig(p) ((p)->uc_mcontext.__gregs[_REG_SP]) # 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 # if defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ defined(__PPC64LE__) # define R01_sig(p) ((p)->uc_mcontext.__gregs[_REG_R1]) # define R32_sig(p) ((p)->uc_mcontext.__gregs[_REG_PC]) # endif # elif defined(__DragonFly__) || defined(__FreeBSD__) || \ defined(__FreeBSD_kernel__) # define EIP_sig(p) ((p)->uc_mcontext.mc_eip) # define EBP_sig(p) ((p)->uc_mcontext.mc_ebp) # define ESP_sig(p) ((p)->uc_mcontext.mc_esp) # define RIP_sig(p) ((p)->uc_mcontext.mc_rip) # define RSP_sig(p) ((p)->uc_mcontext.mc_rsp) # define RBP_sig(p) ((p)->uc_mcontext.mc_rbp) # if defined(__FreeBSD__) && defined(__arm__) # define R11_sig(p) ((p)->uc_mcontext.__gregs[_REG_R11]) # 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]) # else # define R11_sig(p) ((p)->uc_mcontext.mc_r11) # define R13_sig(p) ((p)->uc_mcontext.mc_r13) # define R14_sig(p) ((p)->uc_mcontext.mc_r14) # 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]) # define RLR_sig(p) ((p)->uc_mcontext.mc_gpregs.gp_lr) # define R31_sig(p) ((p)->uc_mcontext.mc_gpregs.gp_sp) # 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 # if defined(__FreeBSD__) && (defined(__ppc64__) || defined(__PPC64__) || \ defined(__ppc64le__) || defined(__PPC64LE__)) # define R01_sig(p) ((p)->uc_mcontext.mc_gpr[1]) # define R32_sig(p) ((p)->uc_mcontext.mc_srr0) # endif # elif defined(XP_DARWIN) # define EIP_sig(p) ((p)->thread.uts.ts32.__eip) # define EBP_sig(p) ((p)->thread.uts.ts32.__ebp) # define ESP_sig(p) ((p)->thread.uts.ts32.__esp) # define RIP_sig(p) ((p)->thread.__rip) # define RBP_sig(p) ((p)->thread.__rbp) # define RSP_sig(p) ((p)->thread.__rsp) # define R11_sig(p) ((p)->thread.__r[11]) # define R13_sig(p) ((p)->thread.__sp) # define R14_sig(p) ((p)->thread.__lr) # define R15_sig(p) ((p)->thread.__pc) # define EPC_sig(p) ((p)->thread.__pc) # define RFP_sig(p) ((p)->thread.__fp) # define R31_sig(p) ((p)->thread.__sp) # define RLR_sig(p) ((p)->thread.__lr) # else # error \ "Don't know how to read/write to the thread state via the mcontext_t." # endif # if defined(ANDROID) // Not all versions of the Android NDK define ucontext_t or mcontext_t. // Detect this and provide custom but compatible definitions. Note that these // follow the GLibc naming convention to access register values from // mcontext_t. // // See: https://chromiumcodereview.appspot.com/10829122/ // See: http://code.google.com/p/android/issues/detail?id=34784 # if !defined(__BIONIC_HAVE_UCONTEXT_T) # if defined(__arm__) // GLibc on ARM defines mcontext_t has a typedef for 'struct sigcontext'. // Old versions of the C library didn't define the type. # if !defined(__BIONIC_HAVE_STRUCT_SIGCONTEXT) # include # endif typedef struct sigcontext mcontext_t; typedef struct ucontext { uint32_t uc_flags; struct ucontext* uc_link; stack_t uc_stack; mcontext_t uc_mcontext; // Other fields are not used so don't define them here. } ucontext_t; # elif defined(__mips__) typedef struct { uint32_t regmask; uint32_t status; uint64_t pc; uint64_t gregs[32]; uint64_t fpregs[32]; uint32_t acx; uint32_t fpc_csr; uint32_t fpc_eir; uint32_t used_math; uint32_t dsp; uint64_t mdhi; uint64_t mdlo; uint32_t hi1; uint32_t lo1; uint32_t hi2; uint32_t lo2; uint32_t hi3; uint32_t lo3; } mcontext_t; typedef struct ucontext { uint32_t uc_flags; struct ucontext* uc_link; stack_t uc_stack; mcontext_t uc_mcontext; // Other fields are not used so don't define them here. } ucontext_t; # elif defined(__loongarch64) typedef struct { uint64_t pc; uint64_t gregs[32]; uint64_t fpregs[32]; uint32_t fpc_csr; } mcontext_t; typedef struct ucontext { uint32_t uc_flags; struct ucontext* uc_link; stack_t uc_stack; mcontext_t uc_mcontext; // Other fields are not used so don't define them here. } ucontext_t; # elif defined(__i386__) // x86 version for Android. typedef struct { uint32_t gregs[19]; void* fpregs; uint32_t oldmask; uint32_t cr2; } mcontext_t; typedef uint32_t kernel_sigset_t[2]; // x86 kernel uses 64-bit signal masks typedef struct ucontext { uint32_t uc_flags; struct ucontext* uc_link; stack_t uc_stack; mcontext_t uc_mcontext; // Other fields are not used by V8, don't define them here. } ucontext_t; enum { REG_EIP = 14 }; # endif // defined(__i386__) # endif // !defined(__BIONIC_HAVE_UCONTEXT_T) # endif // defined(ANDROID) # if defined(XP_DARWIN) # if defined(__x86_64__) struct macos_x64_context { x86_thread_state64_t thread; x86_float_state64_t float_; }; # define CONTEXT macos_x64_context # elif defined(__i386__) struct macos_x86_context { x86_thread_state_t thread; x86_float_state_t float_; }; # define CONTEXT macos_x86_context # elif defined(__arm__) struct macos_arm_context { arm_thread_state_t thread; arm_neon_state_t float_; }; # define CONTEXT macos_arm_context # elif defined(__aarch64__) struct macos_aarch64_context { arm_thread_state64_t thread; arm_neon_state64_t float_; }; # define CONTEXT macos_aarch64_context # else # error Unsupported architecture # endif # elif !defined(XP_WIN) # define CONTEXT ucontext_t # endif # if defined(_M_X64) || defined(__x86_64__) # define PC_sig(p) RIP_sig(p) # define FP_sig(p) RBP_sig(p) # define SP_sig(p) RSP_sig(p) # elif defined(_M_IX86) || defined(__i386__) # define PC_sig(p) EIP_sig(p) # define FP_sig(p) EBP_sig(p) # define SP_sig(p) ESP_sig(p) # elif defined(__arm__) # define FP_sig(p) R11_sig(p) # define SP_sig(p) R13_sig(p) # define LR_sig(p) R14_sig(p) # define PC_sig(p) R15_sig(p) # elif defined(_M_ARM64) || defined(__aarch64__) # define PC_sig(p) EPC_sig(p) # define FP_sig(p) RFP_sig(p) # define SP_sig(p) R31_sig(p) # define LR_sig(p) RLR_sig(p) # elif defined(__mips__) # define PC_sig(p) EPC_sig(p) # define FP_sig(p) RFP_sig(p) # define SP_sig(p) RSP_sig(p) # define LR_sig(p) R31_sig(p) # elif defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ defined(__PPC64LE__) # define PC_sig(p) R32_sig(p) # define SP_sig(p) R01_sig(p) # define FP_sig(p) R01_sig(p) # elif defined(__loongarch__) # define PC_sig(p) EPC_sig(p) # define FP_sig(p) RFP_sig(p) # define SP_sig(p) R03_sig(p) # define LR_sig(p) RRA_sig(p) # elif defined(__riscv) # define PC_sig(p) RPC_sig(p) # define FP_sig(p) RFP_sig(p) # define SP_sig(p) R02_sig(p) # define LR_sig(p) RRA_sig(p) # endif static void SetContextPC(CONTEXT* context, uint8_t* pc) { # ifdef PC_sig *reinterpret_cast(&PC_sig(context)) = pc; # else MOZ_CRASH(); # endif } static uint8_t* ContextToPC(CONTEXT* context) { # ifdef PC_sig return reinterpret_cast(PC_sig(context)); # else MOZ_CRASH(); # endif } static uint8_t* ContextToFP(CONTEXT* context) { # ifdef FP_sig return reinterpret_cast(FP_sig(context)); # else MOZ_CRASH(); # endif } static uint8_t* ContextToSP(CONTEXT* context) { # ifdef SP_sig return reinterpret_cast(SP_sig(context)); # else MOZ_CRASH(); # endif } # if defined(__arm__) || defined(__aarch64__) || defined(__mips__) || \ defined(__loongarch__) || defined(__riscv) static uint8_t* ContextToLR(CONTEXT* context) { # ifdef LR_sig return reinterpret_cast(LR_sig(context)); # else MOZ_CRASH(); # endif } # endif static JS::ProfilingFrameIterator::RegisterState ToRegisterState( CONTEXT* context) { JS::ProfilingFrameIterator::RegisterState state; state.fp = ContextToFP(context); state.pc = ContextToPC(context); state.sp = ContextToSP(context); # if defined(__arm__) || defined(__aarch64__) || defined(__mips__) || \ defined(__loongarch__) || defined(__riscv) state.lr = ContextToLR(context); # else state.lr = (void*)UINTPTR_MAX; # endif return state; } // ============================================================================= // All signals/exceptions funnel down to this one trap-handling function which // tests whether the pc is in a wasm module and, if so, whether there is // actually a trap expected at this pc. These tests both avoid real bugs being // silently converted to wasm traps and provides the trapping wasm bytecode // offset we need to report in the error. // // Crashing inside wasm trap handling (due to a bug in trap handling or exposed // during trap handling) must be reported like a normal crash, not cause the // crash report to be lost. On Windows and non-Mach Unix, a crash during the // handler reenters the handler, possibly repeatedly until exhausting the stack, // and so we prevent recursion with the thread-local sAlreadyHandlingTrap. On // Mach, the wasm exception handler has its own thread and is installed only on // the thread-level debugging ports of JSRuntime threads, so a crash on // exception handler thread will not recurse; it will bubble up to the // process-level debugging ports (where Breakpad is installed). // ============================================================================= static MOZ_THREAD_LOCAL(bool) sAlreadyHandlingTrap; struct AutoHandlingTrap { AutoHandlingTrap() { MOZ_ASSERT(!sAlreadyHandlingTrap.get()); sAlreadyHandlingTrap.set(true); } ~AutoHandlingTrap() { MOZ_ASSERT(sAlreadyHandlingTrap.get()); sAlreadyHandlingTrap.set(false); } }; [[nodiscard]] static bool HandleTrap(CONTEXT* context, JSContext* assertCx = nullptr) { MOZ_ASSERT(sAlreadyHandlingTrap.get()); uint8_t* pc = ContextToPC(context); const CodeSegment* codeSegment = LookupCodeSegment(pc); if (!codeSegment || !codeSegment->isModule()) { return false; } const ModuleSegment& segment = *codeSegment->asModule(); Trap trap; BytecodeOffset bytecode; if (!segment.code().lookupTrap(pc, &trap, &bytecode)) { return false; } // We have a safe, expected wasm trap, so fp is well-defined to be a Frame*. // For the first sanity check, the Trap::IndirectCallBadSig special case is // due to this trap occurring in the indirect call prologue, while fp points // to the caller's Frame which can be in a different Module. In any case, // though, the containing JSContext is the same. auto* frame = reinterpret_cast(ContextToFP(context)); Instance* instance = GetNearestEffectiveInstance(frame); MOZ_RELEASE_ASSERT(&instance->code() == &segment.code() || trap == Trap::IndirectCallBadSig); JSContext* cx = instance->realm()->runtimeFromAnyThread()->mainContextFromAnyThread(); MOZ_RELEASE_ASSERT(!assertCx || cx == assertCx); // JitActivation::startWasmTrap() stores enough register state from the // point of the trap to allow stack unwinding or resumption, both of which // will call finishWasmTrap(). jit::JitActivation* activation = cx->activation()->asJit(); activation->startWasmTrap(trap, bytecode.offset(), ToRegisterState(context)); SetContextPC(context, segment.trapCode()); return true; } // ============================================================================= // The following platform-specific handlers funnel all signals/exceptions into // the shared HandleTrap() above. // ============================================================================= # if defined(XP_WIN) // Obtained empirically from thread_local codegen on x86/x64/arm64. // Compiled in all user binaries, so should be stable over time. static const unsigned sThreadLocalArrayPointerIndex = 11; static LONG WINAPI WasmTrapHandler(LPEXCEPTION_POINTERS exception) { // Make sure TLS is initialized before reading sAlreadyHandlingTrap. if (!NtCurrentTeb()->Reserved1[sThreadLocalArrayPointerIndex]) { return EXCEPTION_CONTINUE_SEARCH; } if (sAlreadyHandlingTrap.get()) { return EXCEPTION_CONTINUE_SEARCH; } AutoHandlingTrap aht; EXCEPTION_RECORD* record = exception->ExceptionRecord; if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION && record->ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION) { return EXCEPTION_CONTINUE_SEARCH; } JSContext* cx = TlsContext.get(); // Cold signal handling code if (!HandleTrap(exception->ContextRecord, cx)) { return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_CONTINUE_EXECUTION; } # elif defined(XP_DARWIN) // On OSX we are forced to use the lower-level Mach exception mechanism instead // of Unix signals because breakpad uses Mach exceptions and would otherwise // report a crash before wasm gets a chance to handle the exception. // 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(const ExceptionRequest& request) { // Get the port of the JSContext's thread from the message. mach_port_t cxThread = request.body.thread.name; // Read out the JSRuntime thread's register state. CONTEXT context; # if defined(__x86_64__) 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(__i386__) 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(__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(cxThread, thread_state, (thread_state_t)&context.thread, &thread_state_count); if (kret != KERN_SUCCESS) { return false; } kret = thread_get_state(cxThread, float_state, (thread_state_t)&context.float_, &float_state_count); if (kret != KERN_SUCCESS) { return false; } if (request.body.exception != EXC_BAD_ACCESS && request.body.exception != EXC_BAD_INSTRUCTION) { return false; } { AutoNoteSingleThreadedRegion anstr; AutoHandlingTrap aht; if (!HandleTrap(&context)) { return false; } } // Update the thread state with the new pc and register values. kret = thread_set_state(cxThread, float_state, (thread_state_t)&context.float_, float_state_count); if (kret != KERN_SUCCESS) { return false; } kret = thread_set_state(cxThread, thread_state, (thread_state_t)&context.thread, thread_state_count); if (kret != KERN_SUCCESS) { return false; } return true; } static mach_port_t sMachDebugPort = MACH_PORT_NULL; static void MachExceptionHandlerThread() { ThisThread::SetName("JS Wasm MachExceptionHandler"); // Taken from mach_exc in /usr/include/mach/mach_exc.defs. static const unsigned EXCEPTION_MSG_ID = 2405; while (true) { ExceptionRequest request; kern_return_t kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request), sMachDebugPort, 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(); } if (request.body.Head.msgh_id != EXCEPTION_MSG_ID) { 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(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); } } # else // If not Windows or Mac, assume Unix # if defined(__mips__) || defined(__loongarch__) static const uint32_t kWasmTrapSignal = SIGFPE; # else static const uint32_t kWasmTrapSignal = SIGILL; # endif static struct sigaction sPrevSEGVHandler; static struct sigaction sPrevSIGBUSHandler; static struct sigaction sPrevWasmTrapHandler; static void WasmTrapHandler(int signum, siginfo_t* info, void* context) { if (!sAlreadyHandlingTrap.get()) { AutoHandlingTrap aht; MOZ_RELEASE_ASSERT(signum == SIGSEGV || signum == SIGBUS || signum == kWasmTrapSignal); JSContext* cx = TlsContext.get(); // Cold signal handling code if (HandleTrap((CONTEXT*)context, cx)) { return; } } struct sigaction* previousSignal = nullptr; switch (signum) { case SIGSEGV: previousSignal = &sPrevSEGVHandler; break; case SIGBUS: previousSignal = &sPrevSIGBUSHandler; break; case kWasmTrapSignal: previousSignal = &sPrevWasmTrapHandler; break; } MOZ_ASSERT(previousSignal); // 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 # if defined(ANDROID) && defined(MOZ_LINKER) extern "C" MFBT_API bool IsSignalHandlingBroken(); # endif struct InstallState { bool tried; bool success; InstallState() : tried(false), success(false) {} }; static ExclusiveData sEagerInstallState( mutexid::WasmSignalInstallState); #endif // !(JS_CODEGEN_NONE) void wasm::EnsureEagerProcessSignalHandlers() { #ifdef JS_CODEGEN_NONE // If there is no JIT, then there should be no Wasm signal handlers. return; #else auto eagerInstallState = sEagerInstallState.lock(); if (eagerInstallState->tried) { return; } eagerInstallState->tried = true; MOZ_RELEASE_ASSERT(eagerInstallState->success == false); # if defined(ANDROID) && defined(MOZ_LINKER) // Signal handling is broken on some android systems. if (IsSignalHandlingBroken()) { return; } # endif sAlreadyHandlingTrap.infallibleInit(); // Install whatever exception/signal handler is appropriate for the OS. # if defined(XP_WIN) # if defined(MOZ_ASAN) // Under ASan we need to let the ASan runtime's ShadowExceptionHandler stay // in the first handler position. const bool firstHandler = false; # else // Otherwise, WasmTrapHandler needs to go first, so that we can recover // from wasm faults and continue execution without triggering handlers // such as Breakpad that assume we are crashing. const bool firstHandler = true; # endif if (!AddVectoredExceptionHandler(firstHandler, WasmTrapHandler)) { // Windows has all sorts of random security knobs for disabling things // so make this a dynamic failure that disables wasm, not a MOZ_CRASH(). return; } # elif defined(XP_DARWIN) // All the Mach setup in EnsureLazyProcessSignalHandlers. # 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 | SA_ONSTACK; faultHandler.sa_sigaction = WasmTrapHandler; 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 | SA_ONSTACK; busHandler.sa_sigaction = WasmTrapHandler; sigemptyset(&busHandler.sa_mask); if (sigaction(SIGBUS, &busHandler, &sPrevSIGBUSHandler)) { MOZ_CRASH("unable to install sigbus handler"); } # endif // Install a handler to handle the instructions that are emitted to implement // wasm traps. struct sigaction wasmTrapHandler; wasmTrapHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; wasmTrapHandler.sa_sigaction = WasmTrapHandler; sigemptyset(&wasmTrapHandler.sa_mask); if (sigaction(kWasmTrapSignal, &wasmTrapHandler, &sPrevWasmTrapHandler)) { MOZ_CRASH("unable to install wasm trap handler"); } # endif eagerInstallState->success = true; #endif } #ifndef JS_CODEGEN_NONE static ExclusiveData sLazyInstallState( mutexid::WasmSignalInstallState); static bool EnsureLazyProcessSignalHandlers() { auto lazyInstallState = sLazyInstallState.lock(); if (lazyInstallState->tried) { return lazyInstallState->success; } lazyInstallState->tried = true; MOZ_RELEASE_ASSERT(lazyInstallState->success == false); # ifdef XP_DARWIN // Create the port that all JSContext threads will redirect their traps to. kern_return_t kret; kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sMachDebugPort); if (kret != KERN_SUCCESS) { return false; } kret = mach_port_insert_right(mach_task_self(), sMachDebugPort, sMachDebugPort, MACH_MSG_TYPE_MAKE_SEND); if (kret != KERN_SUCCESS) { return false; } // Create the thread that will wait on and service sMachDebugPort. // It's not useful to destroy this thread on process shutdown so // immediately detach on successful start. Thread handlerThread; if (!handlerThread.init(MachExceptionHandlerThread)) { return false; } handlerThread.detach(); # endif lazyInstallState->success = true; return true; } #endif // JS_CODEGEN_NONE bool wasm::EnsureFullSignalHandlers(JSContext* cx) { #ifdef JS_CODEGEN_NONE return false; #else if (cx->wasm().triedToInstallSignalHandlers) { return cx->wasm().haveSignalHandlers; } cx->wasm().triedToInstallSignalHandlers = true; MOZ_RELEASE_ASSERT(!cx->wasm().haveSignalHandlers); { auto eagerInstallState = sEagerInstallState.lock(); MOZ_RELEASE_ASSERT(eagerInstallState->tried); if (!eagerInstallState->success) { return false; } } if (!EnsureLazyProcessSignalHandlers()) { return false; } # ifdef XP_DARWIN // In addition to the process-wide signal handler setup, OSX needs each // thread configured to send its exceptions to sMachDebugPort. While there // are also task-level (i.e. process-level) exception ports, those are // "claimed" by breakpad and chaining Mach exceptions is dark magic that we // avoid by instead intercepting exceptions at the thread level before they // propagate to the process-level. This works because there are no other // uses of thread-level exception ports. MOZ_RELEASE_ASSERT(sMachDebugPort != MACH_PORT_NULL); thread_port_t thisThread = mach_thread_self(); kern_return_t kret = thread_set_exception_ports( thisThread, EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION, sMachDebugPort, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); mach_port_deallocate(mach_task_self(), thisThread); if (kret != KERN_SUCCESS) { return false; } # endif cx->wasm().haveSignalHandlers = true; return true; #endif } bool wasm::MemoryAccessTraps(const RegisterState& regs, uint8_t* addr, uint32_t numBytes, uint8_t** newPC) { #ifdef JS_CODEGEN_NONE return false; #else const wasm::CodeSegment* codeSegment = wasm::LookupCodeSegment(regs.pc); if (!codeSegment || !codeSegment->isModule()) { return false; } const wasm::ModuleSegment& segment = *codeSegment->asModule(); Trap trap; BytecodeOffset bytecode; if (!segment.code().lookupTrap(regs.pc, &trap, &bytecode)) { return false; } switch (trap) { case Trap::OutOfBounds: break; case Trap::NullPointerDereference: break; # ifdef WASM_HAS_HEAPREG case Trap::IndirectCallToNull: // We use the null pointer exception from loading the heapreg to // handle indirect calls to null. break; # endif default: return false; } const Instance& instance = *GetNearestEffectiveInstance(Frame::fromUntaggedWasmExitFP(regs.fp)); MOZ_ASSERT(&instance.code() == &segment.code()); switch (trap) { case Trap::OutOfBounds: if (!instance.memoryAccessInGuardRegion((uint8_t*)addr, numBytes)) { return false; } break; case Trap::NullPointerDereference: if ((uintptr_t)addr >= NullPtrGuardSize) { return false; } break; # ifdef WASM_HAS_HEAPREG case Trap::IndirectCallToNull: // Null pointer plus the appropriate offset. if (addr != reinterpret_cast(wasm::Instance::offsetOfMemoryBase())) { return false; } break; # endif default: MOZ_CRASH("Should not happen"); } JSContext* cx = TlsContext.get(); // Cold simulator helper function jit::JitActivation* activation = cx->activation()->asJit(); activation->startWasmTrap(trap, bytecode.offset(), regs); *newPC = segment.trapCode(); return true; #endif } bool wasm::HandleIllegalInstruction(const RegisterState& regs, uint8_t** newPC) { #ifdef JS_CODEGEN_NONE return false; #else const wasm::CodeSegment* codeSegment = wasm::LookupCodeSegment(regs.pc); if (!codeSegment || !codeSegment->isModule()) { return false; } const wasm::ModuleSegment& segment = *codeSegment->asModule(); Trap trap; BytecodeOffset bytecode; if (!segment.code().lookupTrap(regs.pc, &trap, &bytecode)) { return false; } JSContext* cx = TlsContext.get(); // Cold simulator helper function jit::JitActivation* activation = cx->activation()->asJit(); activation->startWasmTrap(trap, bytecode.offset(), regs); *newPC = segment.trapCode(); return true; #endif }