From 3e5cef3e47a1ab8fd965e4ab272f557cfbb2a43e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 19 Mar 2020 12:20:21 -0700 Subject: [PATCH 1/2] Remove C++ dependency from `wasmtime` This commit removes the last wads of C++ that we have in wasmtime, meaning that building wasmtime no longer requires a C++ compiler. It still does require a C toolchain for some minor purposes, but hopefully we can remove that over time too! The motivation for doing this is to consolidate all our signal-handling code into one location in one language so you don't have to keep crossing back and forth when understanding what's going on. This also allows us to remove some extra cruft that wasn't necessary from the C++ original implementation. Additionally this should also make building wasmtime a bit more portable since it's often easier to acquire a C toolchain than it is to acquire a C++ toolchain. (e.g. if you're cross-compiling to a musl target) --- crates/runtime/Cargo.toml | 2 +- crates/runtime/build.rs | 23 +- .../runtime/signalhandlers/SignalHandlers.cpp | 755 ------------------ .../runtime/signalhandlers/SignalHandlers.hpp | 41 - .../Trampolines.cpp => src/helpers.c} | 13 +- crates/runtime/src/instance.rs | 4 +- crates/runtime/src/lib.rs | 1 - crates/runtime/src/signalhandlers.rs | 38 - crates/runtime/src/traphandlers.rs | 263 +++++- 9 files changed, 261 insertions(+), 879 deletions(-) delete mode 100644 crates/runtime/signalhandlers/SignalHandlers.cpp delete mode 100644 crates/runtime/signalhandlers/SignalHandlers.hpp rename crates/runtime/{signalhandlers/Trampolines.cpp => src/helpers.c} (68%) delete mode 100644 crates/runtime/src/signalhandlers.rs diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 6a40360d287e..cbf661731058 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -23,7 +23,7 @@ cfg-if = "0.1.9" backtrace = "0.3.42" [target.'cfg(target_os = "windows")'.dependencies] -winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] } +winapi = { version = "0.3.7", features = ["winbase", "memoryapi", "errhandlingapi"] } [build-dependencies] cc = "1.0" diff --git a/crates/runtime/build.rs b/crates/runtime/build.rs index a2db1468e8f9..cf9dd5144b41 100644 --- a/crates/runtime/build.rs +++ b/crates/runtime/build.rs @@ -1,20 +1,7 @@ fn main() { - println!("cargo:rerun-if-changed=signalhandlers/SignalHandlers.cpp"); - println!("cargo:rerun-if-changed=signalhandlers/SignalHandlers.hpp"); - println!("cargo:rerun-if-changed=signalhandlers/Trampolines.cpp"); - let target = std::env::var("TARGET").unwrap(); - let mut build = cc::Build::new(); - build - .cpp(true) - .warnings(false) - .file("signalhandlers/SignalHandlers.cpp") - .file("signalhandlers/Trampolines.cpp"); - if !target.contains("windows") { - build - .flag("-std=c++11") - .flag("-fno-exceptions") - .flag("-fno-rtti"); - } - - build.compile("signalhandlers"); + println!("cargo:rerun-if-changed=src/helpers.c"); + cc::Build::new() + .warnings(true) + .file("src/helpers.c") + .compile("helpers"); } diff --git a/crates/runtime/signalhandlers/SignalHandlers.cpp b/crates/runtime/signalhandlers/SignalHandlers.cpp deleted file mode 100644 index c715091703e5..000000000000 --- a/crates/runtime/signalhandlers/SignalHandlers.cpp +++ /dev/null @@ -1,755 +0,0 @@ -//! This file is largely derived from the code in WasmSignalHandlers.cpp in SpiderMonkey: -//! -//! https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmSignalHandlers.cpp -//! -//! Use of Mach ports on Darwin platforms (the USE_APPLE_MACH_PORTS code below) is -//! currently disabled. - -#include "SignalHandlers.hpp" - -#include -#include -#include -#include - -#if defined(_WIN32) - -# include -# include - -#elif defined(USE_APPLE_MACH_PORTS) -# include -# include -# include -#else -# include -#endif - -// ============================================================================= -// This following pile of macros and includes defines the ToRegisterState() and -// the ContextToPC() 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(_WIN32) -# 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 -#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.regs[31]) -# 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 -#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 -#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 -#elif defined(USE_APPLE_MACH_PORTS) -# 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) -#elif defined(__APPLE__) -# define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip) -# define EBP_sig(p) ((p)->uc_mcontext->__ss.__ebp) -# define ESP_sig(p) ((p)->uc_mcontext->__ss.__esp) -# define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip) -# define RBP_sig(p) ((p)->uc_mcontext->__ss.__rbp) -# define RSP_sig(p) ((p)->uc_mcontext->__ss.__rsp) -# define R11_sig(p) ((p)->uc_mcontext->__ss.__r11) -# define R13_sig(p) ((p)->uc_mcontext->__ss.__sp) -# define R14_sig(p) ((p)->uc_mcontext->__ss.__lr) -# 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(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(__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(USE_APPLE_MACH_PORTS) -# 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 -# else -# error Unsupported architecture -# endif -#elif !defined(_WIN32) -# 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) -#endif - -static void -SetContextPC(CONTEXT* context, const uint8_t* pc) -{ -#ifdef PC_sig - PC_sig(context) = reinterpret_cast(pc); -#else - abort(); -#endif -} - -static const uint8_t* -ContextToPC(CONTEXT* context) -{ -#ifdef PC_sig - return reinterpret_cast(static_cast(PC_sig(context))); -#else - abort(); -#endif -} - -// ============================================================================= -// 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 our 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 thread_local bool sAlreadyHandlingTrap; - -namespace { - -struct AutoHandlingTrap -{ - AutoHandlingTrap() { - assert(!sAlreadyHandlingTrap); - sAlreadyHandlingTrap = true; - } - - ~AutoHandlingTrap() { - assert(sAlreadyHandlingTrap); - sAlreadyHandlingTrap = false; - } -}; - -} - -// ============================================================================= -// The following platform-specific handlers funnel all signals/exceptions into -// the HandleTrap() function defined in Rust. Note that the Rust function has a -// different ABI depending on the platform. -// ============================================================================= - -#if defined(_WIN32) -// 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) { - return EXCEPTION_CONTINUE_SEARCH; - } - AutoHandlingTrap aht; - - EXCEPTION_RECORD* record = exception->ExceptionRecord; - if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION && - record->ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION && - record->ExceptionCode != EXCEPTION_STACK_OVERFLOW && - record->ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO && - record->ExceptionCode != EXCEPTION_INT_OVERFLOW) - { - return EXCEPTION_CONTINUE_SEARCH; - } - - void *JmpBuf = HandleTrap(ContextToPC(exception->ContextRecord), exception); - // Test if a custom instance signal handler handled the exception - if (((size_t) JmpBuf) == 1) - return EXCEPTION_CONTINUE_EXECUTION; - - // Otherwise test if we need to longjmp to this buffer - if (JmpBuf != nullptr) { - sAlreadyHandlingTrap = false; - Unwind(JmpBuf); - } - - // ... and otherwise keep looking for a handler - return EXCEPTION_CONTINUE_SEARCH; -} - -#elif defined(USE_APPLE_MACH_PORTS) -// 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 thread from the message. - mach_port_t cxThread = request.body.thread.name; - - // Read out the 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; -# 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; - } - - { - AutoHandlingTrap aht; - if (!HandleTrap(&context, false)) { - 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(void* arg) -{ - // 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); - abort(); - } - - if (request.body.Head.msgh_id != EXCEPTION_MSG_ID) { - fprintf(stderr, "Unexpected msg header id %d\n", (int)request.body.Head.msgh_bits); - abort(); - } - - // 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 - // 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); - } - - return nullptr; -} - -#else // If not Windows or Mac, assume Unix - -static struct sigaction sPrevSIGSEGVHandler; -static struct sigaction sPrevSIGBUSHandler; -static struct sigaction sPrevSIGILLHandler; -static struct sigaction sPrevSIGFPEHandler; - -static void -WasmTrapHandler(int signum, siginfo_t* info, void* context) -{ - if (!sAlreadyHandlingTrap) { - AutoHandlingTrap aht; - assert(signum == SIGSEGV || signum == SIGBUS || signum == SIGFPE || signum == SIGILL); - - void *JmpBuf = HandleTrap(ContextToPC(static_cast(context)), signum, info, context); - - // Test if a custom instance signal handler handled the exception - if (((size_t) JmpBuf) == 1) - return; - - // Otherwise test if we need to longjmp to this buffer - if (JmpBuf != nullptr) { - sAlreadyHandlingTrap = false; - Unwind(JmpBuf); - } - - // ... and otherwise call the previous signal handler, if one is there - } - - struct sigaction* previousSignal = nullptr; - switch (signum) { - case SIGSEGV: previousSignal = &sPrevSIGSEGVHandler; break; - case SIGBUS: previousSignal = &sPrevSIGBUSHandler; break; - case SIGFPE: previousSignal = &sPrevSIGFPEHandler; break; - case SIGILL: previousSignal = &sPrevSIGILLHandler; break; - } - assert(previousSignal); - - // This signal is not for any compiled wasm 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 // _WIN32 || __APPLE__ || assume unix - -#if defined(ANDROID) && defined(MOZ_LINKER) -extern "C" MFBT_API bool IsSignalHandlingBroken(); -#endif - -int -EnsureEagerSignalHandlers() -{ -#if defined(ANDROID) && defined(MOZ_LINKER) - // Signal handling is broken on some android systems. - if (IsSignalHandlingBroken()) { - return false; - } -#endif - - sAlreadyHandlingTrap = false; - - // Install whatever exception/signal handler is appropriate for the OS. -#if defined(_WIN32) - -# if defined(MOZ_ASAN) - // Under ASan we need to let the ASan runtime's ShadowExceptionHandler stay - // in the first handler position. This requires some coordination with - // MemoryProtectionExceptionHandler::isDisabled(). - 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 MemoryProtectionExceptionHandler 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 an abort(). - return false; - } - -#elif defined(USE_APPLE_MACH_PORTS) - // All the Mach setup in EnsureDarwinMachPorts. -#else - // SA_ONSTACK allows us to handle signals on an alternate stack, so that - // the handler can run in response to running out of stack space on the - // main stack. Rust installs an alternate stack with sigaltstack, so we - // rely on that. - - // 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, &sPrevSIGSEGVHandler)) { - perror("unable to install SIGSEGV handler"); - abort(); - } - -# if defined(__arm__) || defined(__APPLE__) - // On ARM, handle Unaligned Accesses. - // On Darwin, guard page accesses are raised as SIGBUS. - 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)) { - perror("unable to install SIGBUS handler"); - abort(); - } -# endif - -# if !defined(__mips__) - // Wasm traps for MIPS currently only raise integer overflow fp exception. - struct sigaction illHandler; - illHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; - illHandler.sa_sigaction = WasmTrapHandler; - sigemptyset(&illHandler.sa_mask); - if (sigaction(SIGILL, &illHandler, &sPrevSIGILLHandler)) { - perror("unable to install wasm SIGILL handler"); - abort(); - } -# endif - -# if defined(__i386__) || defined(__x86_64__) || defined(__mips__) - // x86 uses SIGFPE to report division by zero, and wasm traps for MIPS - // currently raise integer overflow fp exception. - struct sigaction fpeHandler; - fpeHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; - fpeHandler.sa_sigaction = WasmTrapHandler; - sigemptyset(&fpeHandler.sa_mask); - if (sigaction(SIGFPE, &fpeHandler, &sPrevSIGFPEHandler)) { - perror("unable to install wasm SIGFPE handler"); - abort(); - } -# endif - -#endif - - return true; -} diff --git a/crates/runtime/signalhandlers/SignalHandlers.hpp b/crates/runtime/signalhandlers/SignalHandlers.hpp deleted file mode 100644 index 5861da7a87e6..000000000000 --- a/crates/runtime/signalhandlers/SignalHandlers.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef signal_handlers_h -#define signal_handlers_h - -#include -#include -#ifndef __cplusplus -#include -#endif - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_WIN32) -#include -#include -void* HandleTrap(const uint8_t*, LPEXCEPTION_POINTERS); -#else -void* HandleTrap(const uint8_t*, int, siginfo_t *, void *); -#endif - -void Unwind(void*); - -// This function performs the low-overhead signal handler initialization that we -// want to do eagerly to ensure a more-deterministic global process state. This -// is especially relevant for signal handlers since handler ordering depends on -// installation order: the wasm signal handler must run *before* the other crash -// handlers and since POSIX signal handlers work LIFO, this function needs to be -// called at the end of the startup process, after other handlers have been -// installed. This function can thus be called multiple times, having no effect -// after the first call. -int -EnsureEagerSignalHandlers(void); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // signal_handlers_h diff --git a/crates/runtime/signalhandlers/Trampolines.cpp b/crates/runtime/src/helpers.c similarity index 68% rename from crates/runtime/signalhandlers/Trampolines.cpp rename to crates/runtime/src/helpers.c index e0702c349db3..213f34e5938d 100644 --- a/crates/runtime/signalhandlers/Trampolines.cpp +++ b/crates/runtime/src/helpers.c @@ -1,8 +1,5 @@ #include -#include "SignalHandlers.hpp" - -extern "C" int RegisterSetjmp( void **buf_storage, void (*body)(void*), @@ -16,8 +13,16 @@ int RegisterSetjmp( return 1; } -extern "C" void Unwind(void *JmpBuf) { jmp_buf *buf = (jmp_buf*) JmpBuf; longjmp(*buf, 1); } + + +#ifdef __APPLE__ +#include + +void* GetPcFromUContext(ucontext_t *cx) { + return (void*) cx->uc_mcontext->__ss.__rip; +} +#endif diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index a6eb25fda5fd..c604189a9174 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -6,8 +6,8 @@ use crate::export::Export; use crate::imports::Imports; use crate::jit_int::GdbJitImageRegistration; use crate::memory::LinearMemory; -use crate::signalhandlers; use crate::table::Table; +use crate::traphandlers; use crate::traphandlers::{catch_traps, Trap}; use crate::vmcontext::{ VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, @@ -980,7 +980,7 @@ impl InstanceHandle { // Ensure that our signal handlers are ready for action. // TODO: Move these calls out of `InstanceHandle`. - signalhandlers::init(); + traphandlers::init(); // The WebAssembly spec specifies that the start function is // invoked automatically at instantiation time. diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 4359db4806db..7b7b8342f85e 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -28,7 +28,6 @@ mod jit_int; mod memory; mod mmap; mod sig_registry; -mod signalhandlers; mod table; mod trap_registry; mod traphandlers; diff --git a/crates/runtime/src/signalhandlers.rs b/crates/runtime/src/signalhandlers.rs deleted file mode 100644 index 9813d3ca6cd7..000000000000 --- a/crates/runtime/src/signalhandlers.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Interface to low-level signal-handling mechanisms. - -use std::sync::Once; - -extern "C" { - fn EnsureEagerSignalHandlers() -> libc::c_int; -} - -/// This function performs the low-overhead signal handler initialization that -/// we want to do eagerly to ensure a more-deterministic global process state. -/// -/// This is especially relevant for signal handlers since handler ordering -/// depends on installation order: the wasm signal handler must run *before* -/// the other crash handlers and since POSIX signal handlers work LIFO, this -/// function needs to be called at the end of the startup process, after other -/// handlers have been installed. This function can thus be called multiple -/// times, having no effect after the first call. -pub fn init() { - static INIT: Once = Once::new(); - INIT.call_once(real_init); -} - -fn real_init() { - // This is a really weird and unfortunate function call. For all the gory - // details see #829, but the tl;dr; is that in a trap handler we have 2 - // pages of stack space on Linux, and calling into libunwind which triggers - // the dynamic loader blows the stack. - // - // This is a dumb hack to work around this system-specific issue by - // capturing a backtrace once in the lifetime of a process to ensure that - // when we capture a backtrace in the trap handler all caches are primed, - // aka the dynamic loader has resolved all the relevant symbols. - drop(backtrace::Backtrace::new_unresolved()); - - if unsafe { EnsureEagerSignalHandlers() == 0 } { - panic!("failed to install signal handlers"); - } -} diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index 64a59a9090dc..540acef02a3f 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -9,7 +9,9 @@ use std::any::Any; use std::cell::Cell; use std::error::Error; use std::fmt; +use std::io; use std::ptr; +use std::sync::Once; use wasmtime_environ::ir; extern "C" { @@ -23,40 +25,249 @@ extern "C" { cfg_if::cfg_if! { if #[cfg(unix)] { - #[no_mangle] - pub unsafe extern "C" fn HandleTrap( - pc: *mut u8, + use std::mem::{self, MaybeUninit}; + + static mut PREV_SIGSEGV: MaybeUninit = MaybeUninit::uninit(); + static mut PREV_SIGBUS: MaybeUninit = MaybeUninit::uninit(); + static mut PREV_SIGILL: MaybeUninit = MaybeUninit::uninit(); + static mut PREV_SIGFPE: MaybeUninit = MaybeUninit::uninit(); + + unsafe fn platform_init() { + let register = |slot: &mut MaybeUninit, signal: i32| { + let mut handler: libc::sigaction = mem::zeroed(); + // The flags here are relatively careful, and they are... + // + // SA_SIGINFO gives us access to information like the program + // counter from where the fault happened. + // + // SA_ONSTACK allows us to handle signals on an alternate stack, + // so that the handler can run in response to running out of + // stack space on the main stack. Rust installs an alternate + // stack with sigaltstack, so we rely on that. + // + // 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. + handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK; + handler.sa_sigaction = trap_handler as usize; + libc::sigemptyset(&mut handler.sa_mask); + if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 { + panic!( + "unable to install SIGSEGV handler: {}", + io::Error::last_os_error(), + ); + } + }; + + // Allow handling OOB with signals on all architectures + register(&mut PREV_SIGSEGV, libc::SIGSEGV); + + // Handle `unreachable` instructions which execute `ud2` right now + register(&mut PREV_SIGILL, libc::SIGILL); + + // x86 uses SIGFPE to report division by zero + if cfg!(target_arch = "x86") || cfg!(target_arch = "x86_64") { + register(&mut PREV_SIGFPE, libc::SIGFPE); + } + + // On ARM, handle Unaligned Accesses. + // On Darwin, guard page accesses are raised as SIGBUS. + if cfg!(target_arch = "arm") || cfg!(target_os = "macos") { + register(&mut PREV_SIGBUS, libc::SIGBUS); + } + } + + unsafe extern "C" fn trap_handler( signum: libc::c_int, siginfo: *mut libc::siginfo_t, context: *mut libc::c_void, - ) -> *const u8 { - tls::with(|info| { - match info { - Some(info) => info.handle_trap(pc, false, |handler| handler(signum, siginfo, context)), - None => ptr::null(), + ) { + let previous = match signum { + libc::SIGSEGV => &PREV_SIGSEGV, + libc::SIGBUS => &PREV_SIGBUS, + libc::SIGFPE => &PREV_SIGFPE, + libc::SIGILL => &PREV_SIGILL, + _ => panic!("unknown signal: {}", signum), + }; + let handled = tls::with(|info| { + // If no wasm code is executing, we don't handle this as a wasm + // trap. + let info = match info { + Some(info) => info, + None => return false, + }; + + // If we hit an exception while handling a previous trap, that's + // quite bad, so bail out and let the system handle this + // recursive segfault. + // + // Otherwise flag ourselves as handling a trap, do the trap + // handling, and reset our trap handling flag. Then we figure + // out what to do based on the result of the trap handling. + let jmp_buf = info.handle_trap( + get_pc(context), + false, + |handler| handler(signum, siginfo, context), + ); + info.handling_trap.set(false); + + // Figure out what to do based on the result of this handling of + // the trap. Note that our sentinel value of 1 means that the + // exception was handled by a custom exception handler, so we + // keep executing. + if jmp_buf.is_null() { + return false; + } else if jmp_buf as usize == 1 { + return true; + } else { + Unwind(jmp_buf) } - }) + }); + + if handled { + return; + } + + // This signal is not for any compiled wasm 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. 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. + let previous = &*previous.as_ptr(); + if previous.sa_flags & libc::SA_SIGINFO != 0 { + mem::transmute::< + usize, + extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void), + >(previous.sa_sigaction)(signum, siginfo, context) + } else if previous.sa_sigaction == libc::SIG_DFL || + previous.sa_sigaction == libc::SIG_IGN + { + libc::sigaction(signum, previous, ptr::null_mut()); + } else { + mem::transmute::( + previous.sa_sigaction + )(signum) + } + } + + unsafe fn get_pc(cx: *mut libc::c_void) -> *const u8 { + cfg_if::cfg_if! { + if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { + let cx = &*(cx as *const libc::ucontext_t); + cx.uc_mcontext.gregs[libc::REG_RIP as usize] as *const u8 + } else if #[cfg(target_os = "macos")] { + // FIXME(rust-lang/libc#1702) - once that lands and is + // released we should inline the definition here + extern "C" { + fn GetPcFromUContext(cx: *mut libc::c_void) -> *const u8; + } + GetPcFromUContext(cx) + } else { + compile_error!("unsupported platform"); + } + } } } else if #[cfg(target_os = "windows")] { - use winapi::um::winnt::PEXCEPTION_POINTERS; - use winapi::um::minwinbase::EXCEPTION_STACK_OVERFLOW; + use winapi::um::errhandlingapi::*; + use winapi::um::winnt::*; + use winapi::um::minwinbase::*; + use winapi::vc::excpt::*; + + unsafe fn platform_init() { + // our trap handler needs to go first, so that we can recover from + // wasm faults and continue execution, so pass `1` as a true value + // here. + if AddVectoredExceptionHandler(1, Some(exception_handler)).is_null() { + panic!("failed to add exception handler: {}", io::Error::last_os_error()); + } + } - #[no_mangle] - pub unsafe extern "C" fn HandleTrap( - pc: *mut u8, + unsafe extern "system" fn exception_handler( exception_info: PEXCEPTION_POINTERS - ) -> *const u8 { + ) -> LONG { + // Check the kind of exception, since we only handle a subset within + // wasm code. If anything else happens we want to defer to whatever + // the rest of the system wants to do for this exception. + let record = &*(*exception_info).ExceptionRecord; + if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION && + record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION && + record.ExceptionCode != EXCEPTION_STACK_OVERFLOW && + record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO && + record.ExceptionCode != EXCEPTION_INT_OVERFLOW + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // FIXME: this is what the previous C++ did to make sure that TLS + // works by the time we execute this trap handling code. This isn't + // exactly super easy to call from Rust though and it's not clear we + // necessarily need to do so. Leaving this here in case we need this + // in the future, but for now we can probably wait until we see a + // strange fault before figuring out how to reimplement this in + // Rust. + // + // if (!NtCurrentTeb()->Reserved1[sThreadLocalArrayPointerIndex]) { + // return EXCEPTION_CONTINUE_SEARCH; + // } + + // This is basically the same as the unix version above, only with a + // few parameters tweaked here and there. tls::with(|info| { - let reset_guard_page = (*(*exception_info).ExceptionRecord).ExceptionCode == EXCEPTION_STACK_OVERFLOW; - match info { - Some(info) => info.handle_trap(pc, reset_guard_page, |handler| handler(exception_info)), - None => ptr::null(), + let info = match info { + Some(info) => info, + None => return EXCEPTION_CONTINUE_SEARCH, + }; + let jmp_buf = info.handle_trap( + (*(*exception_info).ContextRecord).Rip as *const u8, + record.ExceptionCode == EXCEPTION_STACK_OVERFLOW, + |handler| handler(exception_info), + ); + if jmp_buf.is_null() { + EXCEPTION_CONTINUE_SEARCH + } else if jmp_buf as usize == 1 { + EXCEPTION_CONTINUE_EXECUTION + } else { + Unwind(jmp_buf) } }) } } } +/// This function performs the low-overhead signal handler initialization that +/// we want to do eagerly to ensure a more-deterministic global process state. +/// +/// This is especially relevant for signal handlers since handler ordering +/// depends on installation order: the wasm signal handler must run *before* +/// the other crash handlers and since POSIX signal handlers work LIFO, this +/// function needs to be called at the end of the startup process, after other +/// handlers have been installed. This function can thus be called multiple +/// times, having no effect after the first call. +pub fn init() { + static INIT: Once = Once::new(); + INIT.call_once(real_init); +} + +fn real_init() { + // This is a really weird and unfortunate function call. For all the gory + // details see #829, but the tl;dr; is that in a trap handler we have 2 + // pages of stack space on Linux, and calling into libunwind which triggers + // the dynamic loader blows the stack. + // + // This is a dumb hack to work around this system-specific issue by + // capturing a backtrace once in the lifetime of a process to ensure that + // when we capture a backtrace in the trap handler all caches are primed, + // aka the dynamic loader has resolved all the relevant symbols. + drop(backtrace::Backtrace::new_unresolved()); + unsafe { + platform_init(); + } +} + /// Raises a user-defined trap immediately. /// /// This function performs as-if a wasm trap was just executed, only the trap @@ -186,6 +397,7 @@ pub struct CallThreadState { reset_guard_page: Cell, prev: Option<*const CallThreadState>, vmctx: *mut VMContext, + handling_trap: Cell, } enum UnwindReason { @@ -204,6 +416,7 @@ impl CallThreadState { jmp_buf: Cell::new(ptr::null()), reset_guard_page: Cell::new(false), prev: None, + handling_trap: Cell::new(false), } } @@ -287,6 +500,15 @@ impl CallThreadState { reset_guard_page: bool, call_handler: impl Fn(&SignalHandler) -> bool, ) -> *const u8 { + // If we hit a fault while handling a previous trap, that's quite bad, + // so bail out and let the system handle this recursive segfault. + // + // Otherwise flag ourselves as handling a trap, do the trap handling, + // and reset our trap handling flag. + if self.handling_trap.replace(true) { + return ptr::null(); + } + // First up see if any instance registered has a custom trap handler, // in which case run them all. If anything handles the trap then we // return that the trap was handled. @@ -299,6 +521,7 @@ impl CallThreadState { i.instance().signal_handler.set(Some(handler)); return result; }) { + self.handling_trap.set(false); return 1 as *const _; } @@ -310,6 +533,7 @@ impl CallThreadState { // doesn't trap. Then, if we have called some WebAssembly code, it // means the trap is stack overflow. if self.jmp_buf.get().is_null() { + self.handling_trap.set(false); return ptr::null(); } let backtrace = Backtrace::new_unresolved(); @@ -318,6 +542,7 @@ impl CallThreadState { backtrace, pc: pc as usize, }); + self.handling_trap.set(false); self.jmp_buf.get() } } From 75096b6a3c854171bb5149bac6afd6c0b97dee53 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 20 Mar 2020 09:27:51 -0700 Subject: [PATCH 2/2] Typos --- crates/runtime/src/traphandlers.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index 540acef02a3f..86d578827af2 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -53,7 +53,7 @@ cfg_if::cfg_if! { libc::sigemptyset(&mut handler.sa_mask); if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 { panic!( - "unable to install SIGSEGV handler: {}", + "unable to install signal handler: {}", io::Error::last_os_error(), ); } @@ -109,7 +109,6 @@ cfg_if::cfg_if! { false, |handler| handler(signum, siginfo, context), ); - info.handling_trap.set(false); // Figure out what to do based on the result of this handling of // the trap. Note that our sentinel value of 1 means that the