diff --git a/lib/execute/Cargo.toml b/lib/execute/Cargo.toml index 3d32771bf8d9..04f7c24ce813 100644 --- a/lib/execute/Cargo.toml +++ b/lib/execute/Cargo.toml @@ -13,9 +13,16 @@ readme = "README.md" cranelift-codegen = "0.25.0" cranelift-entity = "0.25.0" cranelift-wasm = "0.25.0" -region = "1.0.0" wasmtime-environ = { path = "../environ" } +region = "1.0.0" memmap = "0.7.0" +lazy_static = "1.2.0" +libc = "0.2.44" + +[build-dependencies] +cmake = "0.1.35" +bindgen = "0.43.2" +regex = "1.0.6" [features] default = ["std"] diff --git a/lib/execute/build.rs b/lib/execute/build.rs new file mode 100644 index 000000000000..2b4be9af709a --- /dev/null +++ b/lib/execute/build.rs @@ -0,0 +1,38 @@ +extern crate bindgen; +extern crate cmake; +extern crate regex; + +use cmake::Config; +use regex::Regex; +use std::env; +use std::path::PathBuf; + +fn main() { + let dst = Config::new("signalhandlers").build(); + + println!("cargo:rustc-link-search=native={}", dst.display()); + println!("cargo:rustc-link-lib=static=SignalHandlers"); + + let mut bindings_builder = bindgen::Builder::default() + .header("signalhandlers/SignalHandlers.h") + .whitelist_type("CodeSegment") + .whitelist_type("TrapContext") + .whitelist_type("jmp_buf") + .whitelist_function("EnsureEagerSignalHandlers"); + + // If we're compiling for Darwin, compile in extra Darwin support routines. + if Regex::new(r"-darwin[[:digit:].]*$") + .unwrap() + .is_match(&env::var("TARGET").unwrap()) + { + bindings_builder = bindings_builder.whitelist_function("EnsureDarwinMachPorts"); + } + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + + bindings_builder + .generate() + .expect("Unable to generate bindings") + .write_to_file(out_path.join("signalhandlers.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/lib/execute/signalhandlers/CMakeLists.txt b/lib/execute/signalhandlers/CMakeLists.txt new file mode 100644 index 000000000000..5fb9a0457ad4 --- /dev/null +++ b/lib/execute/signalhandlers/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0) +project(SignalHandlers CXX) + +set(CMAKE_CXX_FLAGS "-std=c++11 -fno-exceptions -fno-rtti -fPIC") + +add_library(SignalHandlers STATIC SignalHandlers.cpp) + +install(TARGETS SignalHandlers DESTINATION .) diff --git a/lib/execute/signalhandlers/SignalHandlers.cpp b/lib/execute/signalhandlers/SignalHandlers.cpp new file mode 100644 index 000000000000..0fc454565bf6 --- /dev/null +++ b/lib/execute/signalhandlers/SignalHandlers.cpp @@ -0,0 +1,785 @@ +//! 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 + +#include "SignalHandlers.h" + +#include +#include +#include +#include + +#if defined(_WIN32) +# include // must include before util/Windows.h's `#undef`s +# include "util/Windows.h" +#elif defined(__APPLE__) +# 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(__APPLE__) +# 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) +#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(__APPLE__) +# 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(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; + } +}; + +} + +static +#if defined(__GNUC__) || defined(__clang__) +__attribute__ ((warn_unused_result)) +#endif +bool +HandleTrap(CONTEXT* context) +{ + assert(sAlreadyHandlingTrap); + + const uint8_t* pc = ContextToPC(context); + const CodeSegment* codeSegment = LookupCodeSegment(pc); + if (!codeSegment) { + return false; + } + + RecordTrap(pc, codeSegment); + + // For now, just call Unwind directly, rather than redirecting the PC there, + // so that it runs on the alternate signal handler stack. To run on the main + // stack, reroute the context PC like this: + // SetContextPC(context, reinterpret_cast(&Unwind)); + + Unwind(); + + return true; +} + +// ============================================================================= +// The following platform-specific handlers funnel all signals/exceptions into +// the shared HandleTrap() above. +// ============================================================================= + +#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) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + if (!HandleTrap(exception->ContextRecord)) { + return EXCEPTION_CONTINUE_SEARCH; + } + + return EXCEPTION_CONTINUE_EXECUTION; +} + +#elif defined(__APPLE__) +// 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; + } + + { + 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(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); + } +} + +#else // If not Windows or Mac, assume Unix + +#ifdef __mips__ +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) { + AutoHandlingTrap aht; + assert(signum == SIGSEGV || signum == SIGBUS || signum == kWasmTrapSignal); + if (HandleTrap(static_cast(context))) { + return; + } + } + + struct sigaction* previousSignal = nullptr; + switch (signum) { + case SIGSEGV: previousSignal = &sPrevSEGVHandler; break; + case SIGBUS: previousSignal = &sPrevSIGBUSHandler; break; + case kWasmTrapSignal: previousSignal = &sPrevWasmTrapHandler; break; + } + assert(previousSignal); + + // This signal is not for any JIT 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 + +bool +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(__APPLE__) + // All the Mach setup in EnsureLazyProcessSignalHandlers. +#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, &sPrevSEGVHandler)) { + perror("unable to install segv handler"); + abort(); + } + +# if defined(__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)) { + perror("unable to install sigbus handler"); + abort(); + } +# endif + + // Install a handler to handle the instructions that are emitted to implement + // wasm traps. + struct sigaction trapHandler; + trapHandler.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; + trapHandler.sa_sigaction = WasmTrapHandler; + sigemptyset(&trapHandler.sa_mask); + if (sigaction(kWasmTrapSignal, &trapHandler, &sPrevWasmTrapHandler)) { + perror("unable to install wasm trap handler"); + abort(); + } +#endif + + return true; +} + +#ifdef __APPLE__ +bool +EnsureDarwinMachPorts() +{ + pthread_attr_t handlerThreadAttr; + int r = pthread_attr_init(&handlerThreadAttr); + if (r != 0) { + return false; + } + + // Create the port that all of our 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. + pthread_t handlerThread; + r = pthread_create(&handlerThread, &handlerThreadAttr, MachExceptionHandlerThread, nullptr); + if (r != 0) { + return false; + } + r = pthread_detach(&handlerThread); + assert(r != 0); + + // 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. + 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; + } + + return true; +} +#endif diff --git a/lib/execute/signalhandlers/SignalHandlers.h b/lib/execute/signalhandlers/SignalHandlers.h new file mode 100644 index 000000000000..99999d9d6b00 --- /dev/null +++ b/lib/execute/signalhandlers/SignalHandlers.h @@ -0,0 +1,55 @@ +#ifndef signal_handlers_h +#define signal_handlers_h + +#include +#include +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct CodeSegment; + +// Record the Trap code and wasm bytecode offset in TLS somewhere +void RecordTrap(const uint8_t* pc, const struct CodeSegment* codeSegment); + +// Initiate an unwind. +void Unwind(void); + +// Return the CodeSegment containing the given pc, if any exist in the process. +// This method does not take a lock. +const struct CodeSegment* +LookupCodeSegment(const void* pc); + +// Trap initialization state. +struct TrapContext { + bool triedToInstallSignalHandlers; + bool haveSignalHandlers; +}; + +// 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. +bool +EnsureEagerSignalHandlers(void); + +// Assuming EnsureEagerProcessSignalHandlers() has already been called, +// this function performs the full installation of signal handlers which must +// be performed per-thread. This operation may incur some overhead and +// so should be done only when needed to use wasm. +bool +EnsureDarwinMachPorts(struct TrapContext* cx); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // signal_handlers_h diff --git a/lib/execute/src/execute.rs b/lib/execute/src/execute.rs index feec4f88f88e..fb313ebd3492 100644 --- a/lib/execute/src/execute.rs +++ b/lib/execute/src/execute.rs @@ -1,15 +1,17 @@ use cranelift_codegen::binemit::Reloc; use cranelift_codegen::isa::TargetIsa; use cranelift_entity::{EntityRef, PrimaryMap}; -use cranelift_wasm::{DefinedFuncIndex, MemoryIndex, TableIndex}; +use cranelift_wasm::{DefinedFuncIndex, FuncIndex, MemoryIndex, TableIndex}; use instance::Instance; use memory::LinearMemory; use region::protect; use region::Protection; +use signalhandlers::{ensure_eager_signal_handlers, ensure_full_signal_handlers, TrapContext}; use std::mem::transmute; use std::ptr::{self, write_unaligned}; use std::string::String; use std::vec::Vec; +use traphandlers::call_wasm; use wasmtime_environ::{ compile_module, Compilation, Export, Module, ModuleTranslation, Relocation, RelocationTarget, }; @@ -165,22 +167,10 @@ pub fn finish_instantiation( .map(LinearMemory::base_addr) .collect::>(); - let vmctx = make_vmctx(instance, &mut mem_base_addrs); + let mut vmctx = make_vmctx(instance, &mut mem_base_addrs); if let Some(start_index) = module.start_func { - let code_buf = - &compilation.functions[module - .defined_func_index(start_index) - .expect("imported start functions not supported yet")]; - - // Rather than writing inline assembly to jump to the code region, we use the fact that - // the Rust ABI for calling a function with no arguments and no return matches the one of - // the generated code. Thanks to this, we can transmute the code region into a first-class - // Rust function and call it. - unsafe { - let start_func = transmute::<_, fn(*const *mut u8)>(code_buf.as_ptr()); - start_func(vmctx.as_ptr()); - } + execute_by_index(module, compilation, &mut vmctx, start_index)?; } Ok(vmctx) @@ -199,18 +189,39 @@ pub fn execute( None => return Err(format!("no export named \"{}\"", function)), }; + execute_by_index(module, compilation, vmctx, fn_index) +} + +fn execute_by_index( + module: &Module, + compilation: &Compilation, + vmctx: &mut Vec<*mut u8>, + fn_index: FuncIndex, +) -> Result<(), String> { let code_buf = &compilation.functions[module .defined_func_index(fn_index) .expect("imported start functions not supported yet")]; + let mut traps = TrapContext { + triedToInstallSignalHandlers: false, + haveSignalHandlers: false, + }; + // Rather than writing inline assembly to jump to the code region, we use the fact that - // the Rust ABI for calling a function with no arguments and no return matches the one of - // the generated code. Thanks to this, we can transmute the code region into a first-class + // the Rust ABI for calling a function with no arguments and no return values matches the one + // of the generated code. Thanks to this, we can transmute the code region into a first-class // Rust function and call it. unsafe { + // Ensure that our signal handlers are ready for action. + ensure_eager_signal_handlers(); + ensure_full_signal_handlers(&mut traps); + if !traps.haveSignalHandlers { + return Err("failed to install signal handlers".to_string()); + } + let func = transmute::<_, fn(*const *mut u8)>(code_buf.as_ptr()); - func(vmctx.as_ptr()); + call_wasm(|| func(vmctx.as_mut_ptr()))?; } Ok(()) } diff --git a/lib/execute/src/lib.rs b/lib/execute/src/lib.rs index 92ba1c9a04f9..b03046ef7510 100644 --- a/lib/execute/src/lib.rs +++ b/lib/execute/src/lib.rs @@ -36,13 +36,19 @@ extern crate wasmtime_environ; #[cfg(not(feature = "std"))] #[macro_use] extern crate alloc; +#[macro_use] +extern crate lazy_static; +extern crate libc; mod execute; mod instance; mod memory; +mod signalhandlers; +mod traphandlers; pub use execute::{compile_and_link_module, execute, finish_instantiation}; pub use instance::Instance; +pub use traphandlers::{call_wasm, LookupCodeSegment, RecordTrap, Unwind}; #[cfg(not(feature = "std"))] mod std { diff --git a/lib/execute/src/signalhandlers.rs b/lib/execute/src/signalhandlers.rs new file mode 100644 index 000000000000..b0cf3e52eea5 --- /dev/null +++ b/lib/execute/src/signalhandlers.rs @@ -0,0 +1,101 @@ +//! Interface to low-level signal-handling mechanisms. + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use std::borrow::{Borrow, BorrowMut}; +use std::sync::RwLock; + +include!(concat!(env!("OUT_DIR"), "/signalhandlers.rs")); + +struct InstallState { + tried: bool, + success: bool, +} + +impl InstallState { + fn new() -> Self { + Self { + tried: false, + success: false, + } + } +} + +lazy_static! { + static ref EAGER_INSTALL_STATE: RwLock = RwLock::new(InstallState::new()); + static ref LAZY_INSTALL_STATE: RwLock = RwLock::new(InstallState::new()); +} + +/// 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 ensure_eager_signal_handlers() { + let mut locked = EAGER_INSTALL_STATE.write().unwrap(); + let state = locked.borrow_mut(); + + if state.tried { + return; + } + + state.tried = true; + assert!(state.success == false); + + if !unsafe { EnsureEagerSignalHandlers() } { + return; + } + + state.success = true; +} + +#[cfg(any(target_os = "macos", target_os = "ios"))] +fn ensure_darwin_mach_ports() { + let mut locked = LAZY_INSTALL_STATE.write().unwrap(); + let state = locked.borrow_mut(); + + if state.tried { + return; + } + + state.tried = true; + assert!(state.success == false); + + if !unsafe { EnsureDarwinMachPorts() } { + return; + } + + state.success = true; +} + +/// Assuming `EnsureEagerProcessSignalHandlers` has already been called, +/// this function performs the full installation of signal handlers which must +/// be performed per-thread. This operation may incur some overhead and +/// so should be done only when needed to use wasm. +pub fn ensure_full_signal_handlers(cx: &mut TrapContext) { + if cx.triedToInstallSignalHandlers { + return; + } + + cx.triedToInstallSignalHandlers = true; + assert!(!cx.haveSignalHandlers); + + { + let locked = EAGER_INSTALL_STATE.read().unwrap(); + let state = locked.borrow(); + assert!(state.tried); + if !state.success { + return; + } + } + + #[cfg(any(target_os = "macos", target_os = "ios"))] + ensure_darwin_mach_ports(); + + cx.haveSignalHandlers = true; +} diff --git a/lib/execute/src/traphandlers.rs b/lib/execute/src/traphandlers.rs new file mode 100644 index 000000000000..a435269569f5 --- /dev/null +++ b/lib/execute/src/traphandlers.rs @@ -0,0 +1,102 @@ +//! WebAssembly trap handling, which is built on top of the lower-level +//! signalhandling mechanisms. + +use libc::c_int; +use signalhandlers::{jmp_buf, CodeSegment}; +use std::cell::{Cell, RefCell}; +use std::mem; +use std::ptr; + +// Currently we uset setjmp/longjmp to unwind out of a signal handler +// and back to the point where WebAssembly was called (via `call_wasm`). +// This works because WebAssembly code currently does not use any EH +// or require any cleanups, and we never unwind through non-wasm frames. +// In the future, we'll likely replace this with fancier stack unwinding. +extern "C" { + fn setjmp(env: *mut jmp_buf) -> c_int; + fn longjmp(env: *const jmp_buf, val: c_int) -> !; +} + +#[derive(Copy, Clone, Debug)] +struct TrapData { + pc: *const u8, +} + +thread_local! { + static TRAP_DATA: Cell = Cell::new(TrapData { pc: ptr::null() }); + static JMP_BUFS: RefCell> = RefCell::new(Vec::new()); +} + +/// Record the Trap code and wasm bytecode offset in TLS somewhere +#[doc(hidden)] +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn RecordTrap(pc: *const u8, _codeSegment: *const CodeSegment) { + // TODO: Look up the wasm bytecode offset and trap code and record them instead. + TRAP_DATA.with(|data| data.set(TrapData { pc })); +} + +/// Initiate an unwind. +#[doc(hidden)] +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn Unwind() { + JMP_BUFS.with(|bufs| unsafe { + let buf = bufs.borrow_mut().pop().unwrap(); + longjmp(&buf, 1); + }) +} + +/// Return the CodeSegment containing the given pc, if any exist in the process. +/// This method does not take a lock. +#[doc(hidden)] +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn LookupCodeSegment(_pc: *const ::std::os::raw::c_void) -> *const CodeSegment { + // TODO: Implement this. + unsafe { mem::transmute(-1isize) } +} + +/// A simple guard to ensure that `JMP_BUFS` is reset when we're done. +struct ScopeGuard { + orig_num_bufs: usize, +} + +impl ScopeGuard { + fn new() -> Self { + Self { + orig_num_bufs: JMP_BUFS.with(|bufs| bufs.borrow().len()), + } + } +} + +impl Drop for ScopeGuard { + fn drop(&mut self) { + let orig_num_bufs = self.orig_num_bufs; + // TODO: Use `shrink_to` once it stablizes. + JMP_BUFS.with(|bufs| { + bufs.borrow_mut() + .resize(orig_num_bufs, unsafe { mem::uninitialized() }) + }); + } +} + +/// Call the wasm function poined to by `f`. +pub fn call_wasm(f: F) -> Result<(), String> +where + F: FnOnce(), +{ + // In case wasm code calls Rust that panics and unwinds past this point, + // ensure that JMP_BUFS is unwound to its incoming state. + let _ = ScopeGuard::new(); + + JMP_BUFS.with(|bufs| { + let mut buf = unsafe { mem::uninitialized() }; + if unsafe { setjmp(&mut buf) } != 0 { + return TRAP_DATA.with(|data| Err(format!("wasm trap at {:?}", data.get().pc))); + } + bufs.borrow_mut().push(buf); + f(); + Ok(()) + }) +}