Implement wasm trap handlers. (#27)
* Implement wasm trap handlers. This adds signal handlers based on SpiderMonkey's signal-handler code. The functionality for looking up the trap code and wasm bytecode offset isn't yet implemented, but this is a start. I considered rewriting this code in Rust, but decided against it for now as C++ allows us to talk to the relevant OS APIs more directly. Fixes #15. * Compile with -std=c++11. * Refactor InstallState initialization. * Compile with -fPIC. * Factor out the code for calling a wasm function with a given index. * Fix unclear wording in a comment.
This commit is contained in:
@@ -13,9 +13,16 @@ readme = "README.md"
|
|||||||
cranelift-codegen = "0.25.0"
|
cranelift-codegen = "0.25.0"
|
||||||
cranelift-entity = "0.25.0"
|
cranelift-entity = "0.25.0"
|
||||||
cranelift-wasm = "0.25.0"
|
cranelift-wasm = "0.25.0"
|
||||||
region = "1.0.0"
|
|
||||||
wasmtime-environ = { path = "../environ" }
|
wasmtime-environ = { path = "../environ" }
|
||||||
|
region = "1.0.0"
|
||||||
memmap = "0.7.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]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|||||||
38
lib/execute/build.rs
Normal file
38
lib/execute/build.rs
Normal file
@@ -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!");
|
||||||
|
}
|
||||||
8
lib/execute/signalhandlers/CMakeLists.txt
Normal file
8
lib/execute/signalhandlers/CMakeLists.txt
Normal file
@@ -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 .)
|
||||||
785
lib/execute/signalhandlers/SignalHandlers.cpp
Normal file
785
lib/execute/signalhandlers/SignalHandlers.cpp
Normal file
@@ -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 <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# include <winternl.h> // must include before util/Windows.h's `#undef`s
|
||||||
|
# include "util/Windows.h"
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
# include <mach/exc.h>
|
||||||
|
# include <mach/mach.h>
|
||||||
|
#else
|
||||||
|
# include <signal.h>
|
||||||
|
#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 <sys/ucontext.h> // for ucontext_t, mcontext_t
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
# if defined(__DragonFly__)
|
||||||
|
# include <machine/npx.h> // for union savefpu
|
||||||
|
# elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
|
||||||
|
defined(__NetBSD__) || defined(__OpenBSD__)
|
||||||
|
# include <machine/fpu.h> // 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 <signal.h> didn't define the type.
|
||||||
|
# if !defined(__BIONIC_HAVE_STRUCT_SIGCONTEXT)
|
||||||
|
# include <asm/sigcontext.h>
|
||||||
|
# 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<greg_t>(pc);
|
||||||
|
#else
|
||||||
|
abort();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t*
|
||||||
|
ContextToPC(CONTEXT* context)
|
||||||
|
{
|
||||||
|
#ifdef PC_sig
|
||||||
|
return reinterpret_cast<const uint8_t*>(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<const uint8_t*>(&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*>(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
|
||||||
55
lib/execute/signalhandlers/SignalHandlers.h
Normal file
55
lib/execute/signalhandlers/SignalHandlers.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef signal_handlers_h
|
||||||
|
#define signal_handlers_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#include <stdbool.h>
|
||||||
|
#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
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
use cranelift_codegen::binemit::Reloc;
|
use cranelift_codegen::binemit::Reloc;
|
||||||
use cranelift_codegen::isa::TargetIsa;
|
use cranelift_codegen::isa::TargetIsa;
|
||||||
use cranelift_entity::{EntityRef, PrimaryMap};
|
use cranelift_entity::{EntityRef, PrimaryMap};
|
||||||
use cranelift_wasm::{DefinedFuncIndex, MemoryIndex, TableIndex};
|
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, MemoryIndex, TableIndex};
|
||||||
use instance::Instance;
|
use instance::Instance;
|
||||||
use memory::LinearMemory;
|
use memory::LinearMemory;
|
||||||
use region::protect;
|
use region::protect;
|
||||||
use region::Protection;
|
use region::Protection;
|
||||||
|
use signalhandlers::{ensure_eager_signal_handlers, ensure_full_signal_handlers, TrapContext};
|
||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
use std::ptr::{self, write_unaligned};
|
use std::ptr::{self, write_unaligned};
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
use traphandlers::call_wasm;
|
||||||
use wasmtime_environ::{
|
use wasmtime_environ::{
|
||||||
compile_module, Compilation, Export, Module, ModuleTranslation, Relocation, RelocationTarget,
|
compile_module, Compilation, Export, Module, ModuleTranslation, Relocation, RelocationTarget,
|
||||||
};
|
};
|
||||||
@@ -165,22 +167,10 @@ pub fn finish_instantiation(
|
|||||||
.map(LinearMemory::base_addr)
|
.map(LinearMemory::base_addr)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
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 {
|
if let Some(start_index) = module.start_func {
|
||||||
let code_buf =
|
execute_by_index(module, compilation, &mut vmctx, start_index)?;
|
||||||
&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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vmctx)
|
Ok(vmctx)
|
||||||
@@ -199,18 +189,39 @@ pub fn execute(
|
|||||||
None => return Err(format!("no export named \"{}\"", function)),
|
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 =
|
let code_buf =
|
||||||
&compilation.functions[module
|
&compilation.functions[module
|
||||||
.defined_func_index(fn_index)
|
.defined_func_index(fn_index)
|
||||||
.expect("imported start functions not supported yet")];
|
.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
|
// 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 Rust ABI for calling a function with no arguments and no return values matches the one
|
||||||
// the generated code. Thanks to this, we can transmute the code region into a first-class
|
// of the generated code. Thanks to this, we can transmute the code region into a first-class
|
||||||
// Rust function and call it.
|
// Rust function and call it.
|
||||||
unsafe {
|
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());
|
let func = transmute::<_, fn(*const *mut u8)>(code_buf.as_ptr());
|
||||||
func(vmctx.as_ptr());
|
call_wasm(|| func(vmctx.as_mut_ptr()))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,13 +36,19 @@ extern crate wasmtime_environ;
|
|||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
mod execute;
|
mod execute;
|
||||||
mod instance;
|
mod instance;
|
||||||
mod memory;
|
mod memory;
|
||||||
|
mod signalhandlers;
|
||||||
|
mod traphandlers;
|
||||||
|
|
||||||
pub use execute::{compile_and_link_module, execute, finish_instantiation};
|
pub use execute::{compile_and_link_module, execute, finish_instantiation};
|
||||||
pub use instance::Instance;
|
pub use instance::Instance;
|
||||||
|
pub use traphandlers::{call_wasm, LookupCodeSegment, RecordTrap, Unwind};
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
mod std {
|
mod std {
|
||||||
|
|||||||
101
lib/execute/src/signalhandlers.rs
Normal file
101
lib/execute/src/signalhandlers.rs
Normal file
@@ -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<InstallState> = RwLock::new(InstallState::new());
|
||||||
|
static ref LAZY_INSTALL_STATE: RwLock<InstallState> = 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;
|
||||||
|
}
|
||||||
102
lib/execute/src/traphandlers.rs
Normal file
102
lib/execute/src/traphandlers.rs
Normal file
@@ -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<TrapData> = Cell::new(TrapData { pc: ptr::null() });
|
||||||
|
static JMP_BUFS: RefCell<Vec<jmp_buf>> = 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: 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(())
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user