use crate::{
common::Error,
signal::{ExceptionInfo, UserSignalHandler},
};
use cfg_if::cfg_if;
use nix::{
libc::{c_void, siginfo_t},
sys::{signal, signal::SigHandler},
};
use std::{io, sync::OnceLock};
static OLD_SIG_HANDLER: OnceLock<SigHandler> = OnceLock::new();
cfg_if! {
if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] {
unsafe fn ucontext_get_write(ucontext: *mut nix::libc::ucontext_t) -> Option<bool> {
let error_reg = nix::libc::REG_ERR as usize;
let error_code = (*ucontext).uc_mcontext.gregs[error_reg];
Some(error_code & 0b10 == 0b10)
}
} else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] {
unsafe fn ucontext_get_write(ucontext: *mut nix::libc::ucontext_t) -> Option<bool> {
const WRITE_BIT_MASK: u32 = 0b10;
const TRAPNO: u16 = 0xe; let mcontext = (*ucontext).uc_mcontext;
let exception_state = (*mcontext).__es;
let trapno = exception_state.__trapno;
let err = exception_state.__err;
assert_eq!(trapno, TRAPNO);
Some(err & WRITE_BIT_MASK == WRITE_BIT_MASK)
}
} else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
unsafe fn ucontext_get_write(ucontext: *mut nix::libc::ucontext_t) -> Option<bool> {
const WNR_BIT_MASK: u32 = 0b100_0000; const EXCEPTION_CLASS_SHIFT: u32 = u32::BITS - 6;
const EXCEPTION_CLASS: u32 = 0b10_0100; let ucontext = ucontext.as_mut()?;
let mcontext = ucontext.uc_mcontext;
let exception_state = (*mcontext).__es;
let esr = exception_state.__esr;
let exception_class = esr >> EXCEPTION_CLASS_SHIFT;
assert_eq!(exception_class, EXCEPTION_CLASS);
Some(esr & WNR_BIT_MASK == WNR_BIT_MASK)
}
} else {
compile_error!("lazy-pages are not supported on your system. Disable `lazy-pages` feature");
}
}
extern "C" fn handle_sigsegv<H>(sig: i32, info: *mut siginfo_t, ucontext: *mut c_void)
where
H: UserSignalHandler,
{
unsafe {
let addr = (*info).si_addr();
let is_write = ucontext_get_write(ucontext as *mut _);
let exc_info = ExceptionInfo {
fault_addr: addr as *mut _,
is_write,
};
if let Err(err) = H::handle(exc_info) {
let old_sig_handler_works = match err {
Error::OutOfWasmMemoryAccess | Error::WasmMemAddrIsNotSet => {
old_sig_handler(sig, info, ucontext)
}
_ => false,
};
if !old_sig_handler_works {
panic!("Signal handler failed: {}", err);
}
}
}
}
use errno::Errno;
#[derive(Debug, Clone, Copy, derive_more::Display)]
enum ThreadInitError {
#[display(fmt = "Cannot get information about old signal stack: {_0}")]
OldStack(Errno),
#[display(fmt = "Cannot mmap space for signal stack: {_0}")]
Mmap(Errno),
#[display(fmt = "Cannot set new signal stack: {_0}")]
SigAltStack(Errno),
}
fn init_for_thread_internal() -> Result<(), ThreadInitError> {
use core::{mem, ptr};
const SIGNAL_STACK_SIZE: usize = 0x20000;
enum StackInfo {
UseOldStack,
NewStack(*mut libc::c_void),
}
impl Drop for StackInfo {
fn drop(&mut self) {
if let StackInfo::NewStack(mmap_ptr) = self {
unsafe {
if libc::munmap(*mmap_ptr, SIGNAL_STACK_SIZE) != 0 {
log::error!(
"Cannot deallocate signal stack memory during the thread shutdown: {}",
errno::errno()
);
}
}
}
}
}
unsafe fn init_sigstack() -> Result<StackInfo, ThreadInitError> {
let mut old_stack = mem::zeroed();
let res = libc::sigaltstack(ptr::null(), &mut old_stack);
if res != 0 {
return Err(ThreadInitError::OldStack(errno::errno()));
}
if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= SIGNAL_STACK_SIZE {
return Ok(StackInfo::UseOldStack);
}
let ptr = libc::mmap(
ptr::null_mut(),
SIGNAL_STACK_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
);
if ptr == libc::MAP_FAILED {
return Err(ThreadInitError::Mmap(errno::errno()));
}
let new_stack = libc::stack_t {
ss_sp: ptr,
ss_flags: 0,
ss_size: SIGNAL_STACK_SIZE,
};
let res = libc::sigaltstack(&new_stack, ptr::null_mut());
if res != 0 {
return Err(ThreadInitError::SigAltStack(errno::errno()));
}
log::debug!(
"Set new signal stack: ptr = {:?}, size = {:#x}",
ptr,
SIGNAL_STACK_SIZE
);
Ok(StackInfo::NewStack(ptr))
}
thread_local! {
static TLS: Result<StackInfo, ThreadInitError> = unsafe { init_sigstack() };
}
TLS.with(|tls| tls.as_ref().map(|_| ()).map_err(|err| *err))
}
pub(crate) unsafe fn init_for_thread() -> Result<(), String> {
init_for_thread_internal().map_err(|err| err.to_string())
}
pub(crate) unsafe fn setup_signal_handler<H>() -> io::Result<()>
where
H: UserSignalHandler,
{
let handler = signal::SigHandler::SigAction(handle_sigsegv::<H>);
let sig_action = signal::SigAction::new(
handler,
signal::SaFlags::SA_SIGINFO | signal::SaFlags::SA_ONSTACK | signal::SaFlags::SA_NODEFER,
signal::SigSet::empty(),
);
let signal = if cfg!(target_os = "macos") {
signal::SIGBUS
} else {
signal::SIGSEGV
};
let old_sigaction = signal::sigaction(signal, &sig_action).map_err(io::Error::from)?;
let handler = old_sigaction.handler();
let _ = OLD_SIG_HANDLER
.set(handler)
.map(|_| log::trace!("Save old signal handler: {:?}", handler));
Ok(())
}
unsafe fn old_sig_handler(sig: i32, info: *mut siginfo_t, ucontext: *mut c_void) -> bool {
if let Some(old_sig_handler) = OLD_SIG_HANDLER.get() {
match old_sig_handler {
SigHandler::SigDfl | SigHandler::SigIgn => false,
SigHandler::Handler(func) => {
func(sig);
true
}
SigHandler::SigAction(func) => {
func(sig, info, ucontext);
true
}
}
} else {
false
}
}