use super::trapcode::TrapCode;
use crate::instance::{Instance, SignalHandler};
use crate::vmcontext::{VMFunctionBody, VMFunctionEnvironment, VMTrampoline};
use backtrace::Backtrace;
use std::any::Any;
use std::cell::Cell;
use std::error::Error;
use std::io;
use std::mem;
use std::ptr;
use std::sync::Once;
extern "C" {
fn RegisterSetjmp(
jmp_buf: *mut *const u8,
callback: extern "C" fn(*mut u8),
payload: *mut u8,
) -> i32;
fn Unwind(jmp_buf: *const u8) -> !;
}
cfg_if::cfg_if! {
if #[cfg(unix)] {
use std::mem::MaybeUninit;
static mut PREV_SIGSEGV: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
static mut PREV_SIGILL: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
static mut PREV_SIGFPE: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
unsafe fn platform_init() {
let register = |slot: &mut MaybeUninit<libc::sigaction>, signal: i32| {
let mut handler: libc::sigaction = mem::zeroed();
handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK;
handler.sa_sigaction = trap_handler as usize;
libc::sigemptyset(&mut handler.sa_mask);
if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 {
panic!(
"unable to install signal handler: {}",
io::Error::last_os_error(),
);
}
};
register(&mut PREV_SIGSEGV, libc::SIGSEGV);
register(&mut PREV_SIGILL, libc::SIGILL);
if cfg!(target_arch = "x86") || cfg!(target_arch = "x86_64") {
register(&mut PREV_SIGFPE, libc::SIGFPE);
}
if cfg!(target_arch = "arm") || cfg!(target_os = "macos") {
register(&mut PREV_SIGBUS, libc::SIGBUS);
}
}
#[cfg(target_os = "macos")]
unsafe fn thread_stack() -> (usize, usize) {
let this_thread = libc::pthread_self();
let stackaddr = libc::pthread_get_stackaddr_np(this_thread);
let stacksize = libc::pthread_get_stacksize_np(this_thread);
(stackaddr as usize - stacksize, stacksize)
}
#[cfg(not(target_os = "macos"))]
unsafe fn thread_stack() -> (usize, usize) {
let this_thread = libc::pthread_self();
let mut thread_attrs: libc::pthread_attr_t = mem::zeroed();
#[cfg(not(target_os = "freebsd"))]
libc::pthread_getattr_np(this_thread, &mut thread_attrs);
#[cfg(target_os = "freebsd")]
libc::pthread_attr_get_np(this_thread, &mut thread_attrs);
let mut stackaddr: *mut libc::c_void = ptr::null_mut();
let mut stacksize: libc::size_t = 0;
libc::pthread_attr_getstack(&thread_attrs, &mut stackaddr, &mut stacksize);
(stackaddr as usize, stacksize)
}
unsafe extern "C" fn trap_handler(
signum: libc::c_int,
siginfo: *mut libc::siginfo_t,
context: *mut libc::c_void,
) {
let previous = match signum {
libc::SIGSEGV => &PREV_SIGSEGV,
libc::SIGBUS => &PREV_SIGBUS,
libc::SIGFPE => &PREV_SIGFPE,
libc::SIGILL => &PREV_SIGILL,
_ => panic!("unknown signal: {}", signum),
};
let maybe_signal_trap = match signum {
libc::SIGSEGV | libc::SIGBUS => {
let addr = (*siginfo).si_addr() as usize;
let (stackaddr, stacksize) = thread_stack();
if stackaddr - region::page::size() <= addr && addr < stackaddr + stacksize {
Some(TrapCode::StackOverflow)
} else {
Some(TrapCode::HeapAccessOutOfBounds)
}
}
_ => None,
};
let handled = tls::with(|info| {
let info = match info {
Some(info) => info,
None => return false,
};
let jmp_buf = info.handle_trap(
get_pc(context),
false,
maybe_signal_trap,
|handler| handler(signum, siginfo, context),
);
if jmp_buf.is_null() {
false
} else if jmp_buf as usize == 1 {
true
} else {
Unwind(jmp_buf)
}
});
if handled {
return;
}
let previous = &*previous.as_ptr();
if previous.sa_flags & libc::SA_SIGINFO != 0 {
mem::transmute::<
usize,
extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void),
>(previous.sa_sigaction)(signum, siginfo, context)
} else if previous.sa_sigaction == libc::SIG_DFL ||
previous.sa_sigaction == libc::SIG_IGN
{
libc::sigaction(signum, previous, ptr::null_mut());
} else {
mem::transmute::<usize, extern "C" fn(libc::c_int)>(
previous.sa_sigaction
)(signum)
}
}
unsafe fn get_pc(cx: *mut libc::c_void) -> *const u8 {
cfg_if::cfg_if! {
if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
cx.uc_mcontext.gregs[libc::REG_RIP as usize] as *const u8
} else if #[cfg(all(target_os = "linux", target_arch = "x86"))] {
let cx = &*(cx as *const libc::ucontext_t);
cx.uc_mcontext.gregs[libc::REG_EIP as usize] as *const u8
} else if #[cfg(all(target_os = "linux", target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::ucontext_t);
cx.uc_mcontext.pc as *const u8
} else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
(*cx.uc_mcontext).__ss.__rip as *const u8
} else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
use std::mem;
#[allow(non_camel_case_types)]
pub struct __darwin_arm_thread_state64 {
pub __x: [u64; 29],
pub __fp: u64,
pub __lr: u64,
pub __sp: u64,
pub __pc: u64,
pub __cpsr: u32,
pub __pad: u32,
}
let cx = &*(cx as *const libc::ucontext_t);
let uc_mcontext = mem::transmute::<_, *const __darwin_arm_thread_state64>(&(*cx.uc_mcontext).__ss);
(*uc_mcontext).__pc as *const u8
} else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
cx.uc_mcontext.mc_rip as *const u8
} else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
#[repr(align(16))]
#[allow(non_camel_case_types)]
pub struct gpregs {
pub gp_x: [libc::register_t; 30],
pub gp_lr: libc::register_t,
pub gp_sp: libc::register_t,
pub gp_elr: libc::register_t,
pub gp_spsr: u32,
pub gp_pad: libc::c_int,
};
#[repr(align(16))]
#[allow(non_camel_case_types)]
pub struct fpregs {
pub fp_q: [u128; 32],
pub fp_sr: u32,
pub fp_cr: u32,
pub fp_flags: libc::c_int,
pub fp_pad: libc::c_int,
};
#[repr(align(16))]
#[allow(non_camel_case_types)]
pub struct mcontext_t {
pub mc_gpregs: gpregs,
pub mc_fpregs: fpregs,
pub mc_flags: libc::c_int,
pub mc_pad: libc::c_int,
pub mc_spare: [u64; 8],
};
#[repr(align(16))]
#[allow(non_camel_case_types)]
pub struct ucontext_t {
pub uc_sigmask: libc::sigset_t,
pub uc_mcontext: mcontext_t,
pub uc_link: *mut ucontext_t,
pub uc_stack: libc::stack_t,
pub uc_flags: libc::c_int,
__spare__: [libc::c_int; 4],
}
let cx = &*(cx as *const ucontext_t);
cx.uc_mcontext.mc_gpregs.gp_elr as *const u8
} else {
compile_error!("unsupported platform");
}
}
}
} else if #[cfg(target_os = "windows")] {
use winapi::um::errhandlingapi::*;
use winapi::um::winnt::*;
use winapi::um::minwinbase::*;
use winapi::vc::excpt::*;
unsafe fn platform_init() {
if AddVectoredExceptionHandler(1, Some(exception_handler)).is_null() {
panic!("failed to add exception handler: {}", io::Error::last_os_error());
}
}
unsafe extern "system" fn exception_handler(
exception_info: PEXCEPTION_POINTERS
) -> LONG {
let record = &*(*exception_info).ExceptionRecord;
if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION &&
record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION &&
record.ExceptionCode != EXCEPTION_STACK_OVERFLOW &&
record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO &&
record.ExceptionCode != EXCEPTION_INT_OVERFLOW
{
return EXCEPTION_CONTINUE_SEARCH;
}
tls::with(|info| {
let info = match info {
Some(info) => info,
None => return EXCEPTION_CONTINUE_SEARCH,
};
let jmp_buf = info.handle_trap(
(*(*exception_info).ContextRecord).Rip as *const u8,
record.ExceptionCode == EXCEPTION_STACK_OVERFLOW,
None,
|handler| handler(exception_info),
);
if jmp_buf.is_null() {
EXCEPTION_CONTINUE_SEARCH
} else if jmp_buf as usize == 1 {
EXCEPTION_CONTINUE_EXECUTION
} else {
Unwind(jmp_buf)
}
})
}
}
}
pub fn init_traps() {
static INIT: Once = Once::new();
INIT.call_once(real_init);
}
fn real_init() {
unsafe {
platform_init();
}
}
pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data)))
}
pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap)))
}
pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload)))
}
#[cfg(target_os = "windows")]
fn reset_guard_page() {
extern "C" {
fn _resetstkoflw() -> winapi::ctypes::c_int;
}
if unsafe { _resetstkoflw() } == 0 {
panic!("failed to restore stack guard page");
}
}
#[cfg(not(target_os = "windows"))]
fn reset_guard_page() {}
#[derive(Debug)]
pub enum Trap {
User(Box<dyn Error + Send + Sync>),
Wasm {
pc: usize,
backtrace: Backtrace,
signal_trap: Option<TrapCode>,
},
Runtime {
trap_code: TrapCode,
backtrace: Backtrace,
},
}
impl Trap {
pub fn new_from_wasm(pc: usize, backtrace: Backtrace, signal_trap: Option<TrapCode>) -> Self {
Self::Wasm {
pc,
backtrace,
signal_trap,
}
}
pub fn new_from_runtime(trap_code: TrapCode) -> Self {
let backtrace = Backtrace::new_unresolved();
Self::Runtime {
trap_code,
backtrace,
}
}
pub fn new_from_user(error: Box<dyn Error + Send + Sync>) -> Self {
Self::User(error)
}
}
pub unsafe fn wasmer_call_trampoline(
vmctx: VMFunctionEnvironment,
trampoline: VMTrampoline,
callee: *const VMFunctionBody,
values_vec: *mut u8,
) -> Result<(), Trap> {
catch_traps(vmctx, || {
mem::transmute::<_, extern "C" fn(VMFunctionEnvironment, *const VMFunctionBody, *mut u8)>(
trampoline,
)(vmctx, callee, values_vec)
})
}
pub unsafe fn catch_traps<F>(vmctx: VMFunctionEnvironment, mut closure: F) -> Result<(), Trap>
where
F: FnMut(),
{
#[cfg(unix)]
setup_unix_sigaltstack()?;
return CallThreadState::new(vmctx).with(|cx| {
RegisterSetjmp(
cx.jmp_buf.as_ptr(),
call_closure::<F>,
&mut closure as *mut F as *mut u8,
)
});
extern "C" fn call_closure<F>(payload: *mut u8)
where
F: FnMut(),
{
unsafe { (*(payload as *mut F))() }
}
}
pub unsafe fn catch_traps_with_result<F, R>(
vmctx: VMFunctionEnvironment,
mut closure: F,
) -> Result<R, Trap>
where
F: FnMut() -> R,
{
let mut global_results = mem::MaybeUninit::<R>::uninit();
catch_traps(vmctx, || {
global_results.as_mut_ptr().write(closure());
})?;
Ok(global_results.assume_init())
}
pub struct CallThreadState {
unwind: Cell<UnwindReason>,
jmp_buf: Cell<*const u8>,
reset_guard_page: Cell<bool>,
prev: Option<*const CallThreadState>,
vmctx: VMFunctionEnvironment,
handling_trap: Cell<bool>,
}
enum UnwindReason {
None,
Panic(Box<dyn Any + Send>),
UserTrap(Box<dyn Error + Send + Sync>),
LibTrap(Trap),
RuntimeTrap {
backtrace: Backtrace,
pc: usize,
signal_trap: Option<TrapCode>,
},
}
impl CallThreadState {
fn new(vmctx: VMFunctionEnvironment) -> Self {
Self {
unwind: Cell::new(UnwindReason::None),
vmctx,
jmp_buf: Cell::new(ptr::null()),
reset_guard_page: Cell::new(false),
prev: None,
handling_trap: Cell::new(false),
}
}
fn with(mut self, closure: impl FnOnce(&Self) -> i32) -> Result<(), Trap> {
tls::with(|prev| {
self.prev = prev.map(|p| p as *const _);
let ret = tls::set(&self, || closure(&self));
match self.unwind.replace(UnwindReason::None) {
UnwindReason::None => {
debug_assert_eq!(ret, 1);
Ok(())
}
UnwindReason::UserTrap(data) => {
debug_assert_eq!(ret, 0);
Err(Trap::new_from_user(data))
}
UnwindReason::LibTrap(trap) => Err(trap),
UnwindReason::RuntimeTrap {
backtrace,
pc,
signal_trap,
} => {
debug_assert_eq!(ret, 0);
Err(Trap::new_from_wasm(pc, backtrace, signal_trap))
}
UnwindReason::Panic(panic) => {
debug_assert_eq!(ret, 0);
std::panic::resume_unwind(panic)
}
}
})
}
fn any_instance(&self, func: impl Fn(&Instance) -> bool) -> bool {
unsafe {
if func(
self.vmctx
.vmctx
.as_ref()
.expect("`VMContext` is null in `any_instance`")
.instance(),
) {
return true;
}
match self.prev {
Some(prev) => (*prev).any_instance(func),
None => false,
}
}
}
fn unwind_with(&self, reason: UnwindReason) -> ! {
self.unwind.replace(reason);
unsafe {
Unwind(self.jmp_buf.get());
}
}
fn handle_trap(
&self,
pc: *const u8,
reset_guard_page: bool,
signal_trap: Option<TrapCode>,
call_handler: impl Fn(&SignalHandler) -> bool,
) -> *const u8 {
if self.handling_trap.replace(true) {
return ptr::null();
}
let any_instance = self.any_instance(|instance: &Instance| {
let handler = match instance.signal_handler.replace(None) {
Some(handler) => handler,
None => return false,
};
let result = call_handler(&handler);
instance.signal_handler.set(Some(handler));
result
});
if any_instance {
self.handling_trap.set(false);
return 1 as *const _;
}
if self.jmp_buf.get().is_null() {
self.handling_trap.set(false);
return ptr::null();
}
let backtrace = Backtrace::new_unresolved();
self.reset_guard_page.set(reset_guard_page);
self.unwind.replace(UnwindReason::RuntimeTrap {
backtrace,
signal_trap,
pc: pc as usize,
});
self.handling_trap.set(false);
self.jmp_buf.get()
}
}
impl Drop for CallThreadState {
fn drop(&mut self) {
if self.reset_guard_page.get() {
reset_guard_page();
}
}
}
mod tls {
use super::CallThreadState;
use std::cell::Cell;
use std::ptr;
thread_local!(static PTR: Cell<*const CallThreadState> = Cell::new(ptr::null()));
pub fn set<R>(ptr: &CallThreadState, closure: impl FnOnce() -> R) -> R {
struct Reset<'a, T: Copy>(&'a Cell<T>, T);
impl<T: Copy> Drop for Reset<'_, T> {
fn drop(&mut self) {
self.0.set(self.1);
}
}
PTR.with(|p| {
let _r = Reset(p, p.replace(ptr));
closure()
})
}
pub fn with<R>(closure: impl FnOnce(Option<&CallThreadState>) -> R) -> R {
PTR.with(|ptr| {
let p = ptr.get();
unsafe { closure(if p.is_null() { None } else { Some(&*p) }) }
})
}
}
#[cfg(unix)]
fn setup_unix_sigaltstack() -> Result<(), Trap> {
use std::cell::RefCell;
use std::ptr::null_mut;
thread_local! {
static TLS: RefCell<Tls> = RefCell::new(Tls::None);
}
const MIN_STACK_SIZE: usize = 16 * 4096;
enum Tls {
None,
Allocated {
mmap_ptr: *mut libc::c_void,
mmap_size: usize,
},
BigEnough,
}
return TLS.with(|slot| unsafe {
let mut slot = slot.borrow_mut();
match *slot {
Tls::None => {}
_ => return Ok(()),
}
let mut old_stack = mem::zeroed();
let r = libc::sigaltstack(ptr::null(), &mut old_stack);
assert_eq!(r, 0, "learning about sigaltstack failed");
if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
*slot = Tls::BigEnough;
return Ok(());
}
let page_size: usize = region::page::size();
let guard_size = page_size;
let alloc_size = guard_size + MIN_STACK_SIZE;
let ptr = libc::mmap(
null_mut(),
alloc_size,
libc::PROT_NONE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
);
if ptr == libc::MAP_FAILED {
return Err(Trap::new_from_runtime(TrapCode::VMOutOfMemory));
}
let stack_ptr = (ptr as usize + guard_size) as *mut libc::c_void;
let r = libc::mprotect(
stack_ptr,
MIN_STACK_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
);
assert_eq!(r, 0, "mprotect to configure memory for sigaltstack failed");
let new_stack = libc::stack_t {
ss_sp: stack_ptr,
ss_flags: 0,
ss_size: MIN_STACK_SIZE,
};
let r = libc::sigaltstack(&new_stack, ptr::null_mut());
assert_eq!(r, 0, "registering new sigaltstack failed");
*slot = Tls::Allocated {
mmap_ptr: ptr,
mmap_size: alloc_size,
};
Ok(())
});
impl Drop for Tls {
fn drop(&mut self) {
let (ptr, size) = match self {
Self::Allocated {
mmap_ptr,
mmap_size,
} => (*mmap_ptr, *mmap_size),
_ => return,
};
unsafe {
let r = libc::munmap(ptr, size);
debug_assert_eq!(r, 0, "munmap failed during thread shutdown");
}
}
}
}