mod backtrace;
#[cfg(feature = "coredump")]
#[path = "traphandlers/coredump_enabled.rs"]
mod coredump;
#[cfg(not(feature = "coredump"))]
#[path = "traphandlers/coredump_disabled.rs"]
mod coredump;
#[cfg(all(has_native_signals))]
mod signals;
#[cfg(all(has_native_signals))]
pub use self::signals::*;
use crate::runtime::module::lookup_code;
use crate::runtime::store::{ExecutorRef, StoreOpaque};
use crate::runtime::vm::sys::traphandlers;
use crate::runtime::vm::{InterpreterRef, VMContext, VMStoreContext, f32x4, f64x2, i8x16};
use crate::{EntryStoreContext, prelude::*};
use crate::{StoreContextMut, WasmBacktrace};
use core::cell::Cell;
use core::num::NonZeroU32;
use core::ptr::{self, NonNull};
pub use self::backtrace::Backtrace;
#[cfg(feature = "gc")]
pub use wasmtime_unwinder::Frame;
pub use self::coredump::CoreDumpStack;
pub use self::tls::tls_eager_initialize;
#[cfg(feature = "async")]
pub use self::tls::{AsyncWasmCallState, PreviousAsyncWasmCallState};
pub use traphandlers::SignalHandler;
pub(crate) struct TrapRegisters {
pub pc: usize,
pub fp: usize,
}
pub(crate) enum TrapTest {
NotWasm,
#[cfg(has_host_compiler_backend)]
#[cfg_attr(miri, expect(dead_code, reason = "using #[cfg] too unergonomic"))]
HandledByEmbedder,
#[cfg_attr(miri, expect(dead_code, reason = "using #[cfg] too unergonomic"))]
Trap {
#[cfg(has_host_compiler_backend)]
jmp_buf: *const u8,
},
}
fn lazy_per_thread_init() {
traphandlers::lazy_per_thread_init();
}
#[cfg(has_host_compiler_backend)]
pub(super) unsafe fn raise_preexisting_trap() -> ! {
tls::with(|info| info.unwrap().unwind())
}
pub fn catch_unwind_and_record_trap<R>(f: impl FnOnce() -> R) -> R::Abi
where
R: HostResult,
{
let (ret, unwind) = R::maybe_catch_unwind(f);
if let Some(unwind) = unwind {
tls::with(|info| info.unwrap().record_unwind(unwind));
}
ret
}
pub trait HostResult {
type Abi: Copy;
fn maybe_catch_unwind(f: impl FnOnce() -> Self) -> (Self::Abi, Option<UnwindReason>);
}
macro_rules! host_result_no_catch {
($($t:ty,)*) => {
$(
impl HostResult for $t {
type Abi = $t;
fn maybe_catch_unwind(f: impl FnOnce() -> $t) -> ($t, Option<UnwindReason>) {
(f(), None)
}
}
)*
}
}
host_result_no_catch! {
(),
bool,
u32,
*mut u8,
u64,
f32,
f64,
i8x16,
f32x4,
f64x2,
}
impl HostResult for NonNull<u8> {
type Abi = *mut u8;
fn maybe_catch_unwind(f: impl FnOnce() -> Self) -> (*mut u8, Option<UnwindReason>) {
(f().as_ptr(), None)
}
}
impl<T, E> HostResult for Result<T, E>
where
T: HostResultHasUnwindSentinel,
E: Into<TrapReason>,
{
type Abi = T::Abi;
fn maybe_catch_unwind(f: impl FnOnce() -> Result<T, E>) -> (T::Abi, Option<UnwindReason>) {
let f = move || match f() {
Ok(ret) => (ret.into_abi(), None),
Err(reason) => (T::SENTINEL, Some(UnwindReason::Trap(reason.into()))),
};
#[cfg(all(feature = "std", panic = "unwind"))]
{
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(result) => result,
Err(err) => (T::SENTINEL, Some(UnwindReason::Panic(err))),
}
}
#[cfg(not(all(feature = "std", panic = "unwind")))]
{
f()
}
}
}
pub unsafe trait HostResultHasUnwindSentinel {
type Abi: Copy;
const SENTINEL: Self::Abi;
fn into_abi(self) -> Self::Abi;
}
unsafe impl HostResultHasUnwindSentinel for () {
type Abi = bool;
const SENTINEL: bool = false;
fn into_abi(self) -> bool {
true
}
}
unsafe impl HostResultHasUnwindSentinel for NonZeroU32 {
type Abi = u32;
const SENTINEL: Self::Abi = 0;
fn into_abi(self) -> Self::Abi {
self.get()
}
}
unsafe impl HostResultHasUnwindSentinel for u32 {
type Abi = u64;
const SENTINEL: u64 = u64::MAX;
fn into_abi(self) -> u64 {
self.into()
}
}
unsafe impl HostResultHasUnwindSentinel for core::convert::Infallible {
type Abi = ();
const SENTINEL: () = ();
fn into_abi(self) {
match self {}
}
}
unsafe impl HostResultHasUnwindSentinel for bool {
type Abi = u32;
const SENTINEL: Self::Abi = u32::MAX;
fn into_abi(self) -> Self::Abi {
u32::from(self)
}
}
#[derive(Debug)]
pub struct Trap {
pub reason: TrapReason,
pub backtrace: Option<Backtrace>,
pub coredumpstack: Option<CoreDumpStack>,
}
#[derive(Debug)]
pub enum TrapReason {
User(Error),
Jit {
pc: usize,
faulting_addr: Option<usize>,
trap: wasmtime_environ::Trap,
},
Wasm(wasmtime_environ::Trap),
}
impl From<Error> for TrapReason {
fn from(err: Error) -> Self {
TrapReason::User(err)
}
}
impl From<wasmtime_environ::Trap> for TrapReason {
fn from(code: wasmtime_environ::Trap) -> Self {
TrapReason::Wasm(code)
}
}
pub unsafe fn catch_traps<T, F>(
store: &mut StoreContextMut<'_, T>,
old_state: &EntryStoreContext,
mut closure: F,
) -> Result<(), Box<Trap>>
where
F: FnMut(NonNull<VMContext>, Option<InterpreterRef<'_>>) -> bool,
{
let caller = store.0.default_caller();
let result = CallThreadState::new(store.0, old_state).with(|cx| match store.0.executor() {
ExecutorRef::Interpreter(r) => {
cx.jmp_buf
.set(CallThreadState::JMP_BUF_INTERPRETER_SENTINEL);
closure(caller, Some(r))
}
#[cfg(has_host_compiler_backend)]
ExecutorRef::Native => traphandlers::wasmtime_setjmp(
cx.jmp_buf.as_ptr(),
{
extern "C" fn call_closure<F>(payload: *mut u8, caller: NonNull<VMContext>) -> bool
where
F: FnMut(NonNull<VMContext>, Option<InterpreterRef<'_>>) -> bool,
{
unsafe { (*(payload as *mut F))(caller, None) }
}
call_closure::<F>
},
&mut closure as *mut F as *mut u8,
caller,
),
});
return match result {
Ok(x) => Ok(x),
Err((UnwindReason::Trap(reason), backtrace, coredumpstack)) => Err(Box::new(Trap {
reason,
backtrace,
coredumpstack,
})),
#[cfg(all(feature = "std", panic = "unwind"))]
Err((UnwindReason::Panic(panic), _, _)) => std::panic::resume_unwind(panic),
};
}
mod call_thread_state {
use super::*;
use crate::EntryStoreContext;
use crate::runtime::vm::{Unwind, VMStackChain};
pub struct CallThreadState {
pub(super) unwind: Cell<Option<(UnwindReason, Option<Backtrace>, Option<CoreDumpStack>)>>,
pub(super) jmp_buf: Cell<*const u8>,
#[cfg(all(has_native_signals))]
pub(super) signal_handler: Option<*const SignalHandler>,
pub(super) capture_backtrace: bool,
#[cfg(feature = "coredump")]
pub(super) capture_coredump: bool,
pub(crate) vm_store_context: NonNull<VMStoreContext>,
pub(crate) unwinder: &'static dyn Unwind,
pub(super) prev: Cell<tls::Ptr>,
old_state: *const EntryStoreContext,
}
impl Drop for CallThreadState {
fn drop(&mut self) {
debug_assert!(self.unwind.replace(None).is_none());
}
}
impl CallThreadState {
pub const JMP_BUF_INTERPRETER_SENTINEL: *mut u8 = 1 as *mut u8;
#[inline]
pub(super) fn new(
store: &mut StoreOpaque,
old_state: *const EntryStoreContext,
) -> CallThreadState {
CallThreadState {
unwind: Cell::new(None),
unwinder: store.unwinder(),
jmp_buf: Cell::new(ptr::null()),
#[cfg(all(has_native_signals))]
signal_handler: store.signal_handler(),
capture_backtrace: store.engine().config().wasm_backtrace,
#[cfg(feature = "coredump")]
capture_coredump: store.engine().config().coredump_on_trap,
vm_store_context: store.vm_store_context_ptr(),
prev: Cell::new(ptr::null()),
old_state,
}
}
pub unsafe fn old_last_wasm_exit_fp(&self) -> usize {
(&*self.old_state).last_wasm_exit_fp
}
pub unsafe fn old_last_wasm_exit_pc(&self) -> usize {
(&*self.old_state).last_wasm_exit_pc
}
pub unsafe fn old_last_wasm_entry_fp(&self) -> usize {
(&*self.old_state).last_wasm_entry_fp
}
pub unsafe fn old_stack_chain(&self) -> VMStackChain {
(&*self.old_state).stack_chain.clone()
}
pub fn prev(&self) -> tls::Ptr {
self.prev.get()
}
#[inline]
pub(crate) unsafe fn push(&self) {
assert!(self.prev.get().is_null());
self.prev.set(tls::raw::replace(self));
}
#[inline]
pub(crate) unsafe fn pop(&self) {
let prev = self.prev.replace(ptr::null());
let head = tls::raw::replace(prev);
assert!(core::ptr::eq(head, self));
}
}
}
pub use call_thread_state::*;
pub enum UnwindReason {
#[cfg(all(feature = "std", panic = "unwind"))]
Panic(Box<dyn std::any::Any + Send>),
Trap(TrapReason),
}
impl CallThreadState {
#[inline]
fn with(
mut self,
closure: impl FnOnce(&CallThreadState) -> bool,
) -> Result<(), (UnwindReason, Option<Backtrace>, Option<CoreDumpStack>)> {
let succeeded = tls::set(&mut self, |me| closure(me));
if succeeded {
Ok(())
} else {
Err(self.read_unwind())
}
}
#[cold]
fn read_unwind(&self) -> (UnwindReason, Option<Backtrace>, Option<CoreDumpStack>) {
self.unwind.replace(None).unwrap()
}
fn record_unwind(&self, reason: UnwindReason) {
if cfg!(debug_assertions) {
let prev = self.unwind.replace(None);
assert!(prev.is_none());
}
let (backtrace, coredump) = match &reason {
#[cfg(all(feature = "std", panic = "unwind"))]
UnwindReason::Panic(_) => (None, None),
UnwindReason::Trap(TrapReason::User(err))
if err.downcast_ref::<WasmBacktrace>().is_some() =>
{
(None, None)
}
UnwindReason::Trap(trap) => {
log::trace!("Capturing backtrace and coredump for {trap:?}");
(
self.capture_backtrace(self.vm_store_context.as_ptr(), None),
self.capture_coredump(self.vm_store_context.as_ptr(), None),
)
}
};
self.unwind.set(Some((reason, backtrace, coredump)));
}
#[cfg(has_host_compiler_backend)]
unsafe fn unwind(&self) -> ! {
debug_assert!(!self.jmp_buf.get().is_null());
debug_assert!(self.jmp_buf.get() != CallThreadState::JMP_BUF_INTERPRETER_SENTINEL);
traphandlers::wasmtime_longjmp(self.jmp_buf.get());
}
fn capture_backtrace(
&self,
limits: *const VMStoreContext,
trap_pc_and_fp: Option<(usize, usize)>,
) -> Option<Backtrace> {
if !self.capture_backtrace {
return None;
}
Some(unsafe { Backtrace::new_with_trap_state(limits, self.unwinder, self, trap_pc_and_fp) })
}
pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Self> + 'a {
let mut state = Some(self);
core::iter::from_fn(move || {
let this = state?;
state = unsafe { this.prev().as_ref() };
Some(this)
})
}
pub(crate) fn test_if_trap(
&self,
regs: TrapRegisters,
faulting_addr: Option<usize>,
call_handler: impl Fn(&SignalHandler) -> bool,
) -> TrapTest {
if self.jmp_buf.get().is_null() {
return TrapTest::NotWasm;
}
let _ = &call_handler;
#[cfg(all(has_native_signals, not(miri)))]
if let Some(handler) = self.signal_handler {
if unsafe { call_handler(&*handler) } {
return TrapTest::HandledByEmbedder;
}
}
let Some((code, text_offset)) = lookup_code(regs.pc) else {
return TrapTest::NotWasm;
};
let Some(trap) = code.lookup_trap_code(text_offset) else {
return TrapTest::NotWasm;
};
self.set_jit_trap(regs, faulting_addr, trap);
TrapTest::Trap {
#[cfg(has_host_compiler_backend)]
jmp_buf: self.take_jmp_buf(),
}
}
#[cfg(has_host_compiler_backend)]
pub(crate) fn take_jmp_buf(&self) -> *const u8 {
self.jmp_buf.replace(ptr::null())
}
pub(crate) fn set_jit_trap(
&self,
TrapRegisters { pc, fp, .. }: TrapRegisters,
faulting_addr: Option<usize>,
trap: wasmtime_environ::Trap,
) {
let backtrace = self.capture_backtrace(self.vm_store_context.as_ptr(), Some((pc, fp)));
let coredump = self.capture_coredump(self.vm_store_context.as_ptr(), Some((pc, fp)));
self.unwind.set(Some((
UnwindReason::Trap(TrapReason::Jit {
pc,
faulting_addr,
trap,
}),
backtrace,
coredump,
)))
}
}
pub(crate) mod tls {
use super::CallThreadState;
pub use raw::Ptr;
pub(super) mod raw {
use super::CallThreadState;
pub type Ptr = *const CallThreadState;
const _: () = {
assert!(core::mem::align_of::<CallThreadState>() > 1);
};
fn tls_get() -> (Ptr, bool) {
let mut initialized = false;
let p = crate::runtime::vm::sys::tls_get().map_addr(|a| {
initialized = (a & 1) != 0;
a & !1
});
(p.cast(), initialized)
}
fn tls_set(ptr: Ptr, initialized: bool) {
let encoded = ptr.map_addr(|a| a | usize::from(initialized));
crate::runtime::vm::sys::tls_set(encoded.cast_mut().cast::<u8>());
}
#[cfg_attr(feature = "async", inline(never))] #[cfg_attr(not(feature = "async"), inline)]
pub fn replace(val: Ptr) -> Ptr {
let (prev, initialized) = tls_get();
if !initialized {
super::super::lazy_per_thread_init();
}
tls_set(val, true);
prev
}
#[cfg_attr(feature = "async", inline(never))] #[cfg_attr(not(feature = "async"), inline)]
pub fn initialize() {
let (state, initialized) = tls_get();
if initialized {
return;
}
super::super::lazy_per_thread_init();
tls_set(state, true);
}
#[cfg_attr(feature = "async", inline(never))] #[cfg_attr(not(feature = "async"), inline)]
pub fn get() -> Ptr {
tls_get().0
}
}
pub use raw::initialize as tls_eager_initialize;
#[cfg(feature = "async")]
pub struct AsyncWasmCallState {
state: raw::Ptr,
}
#[cfg(feature = "async")]
impl AsyncWasmCallState {
pub fn new() -> AsyncWasmCallState {
AsyncWasmCallState {
state: core::ptr::null_mut(),
}
}
pub unsafe fn push(self) -> PreviousAsyncWasmCallState {
let ret = PreviousAsyncWasmCallState { state: raw::get() };
let mut ptr = self.state;
while let Some(state) = ptr.as_ref() {
ptr = state.prev.replace(core::ptr::null_mut());
state.push();
}
ret
}
pub fn assert_null(&self) {
assert!(self.state.is_null());
}
pub fn assert_current_state_not_in_range(range: core::ops::Range<usize>) {
let p = raw::get() as usize;
assert!(p < range.start || range.end < p);
}
}
#[cfg(feature = "async")]
pub struct PreviousAsyncWasmCallState {
state: raw::Ptr,
}
#[cfg(feature = "async")]
impl PreviousAsyncWasmCallState {
pub unsafe fn restore(self) -> AsyncWasmCallState {
let thread_head = self.state;
core::mem::forget(self);
let mut ret = AsyncWasmCallState::new();
loop {
let ptr = raw::get();
if ptr == thread_head {
break ret;
}
(*ptr).pop();
if let Some(state) = ret.state.as_ref() {
(*ptr).prev.set(state);
}
ret.state = ptr;
}
}
}
#[cfg(feature = "async")]
impl Drop for PreviousAsyncWasmCallState {
fn drop(&mut self) {
panic!("must be consumed with `restore`");
}
}
#[inline]
pub fn set<R>(state: &mut CallThreadState, closure: impl FnOnce(&CallThreadState) -> R) -> R {
struct Reset<'a> {
state: &'a CallThreadState,
}
impl Drop for Reset<'_> {
#[inline]
fn drop(&mut self) {
unsafe {
self.state.pop();
}
}
}
unsafe {
state.push();
let reset = Reset { state };
closure(reset.state)
}
}
pub fn with<R>(closure: impl FnOnce(Option<&CallThreadState>) -> R) -> R {
let p = raw::get();
unsafe { closure(if p.is_null() { None } else { Some(&*p) }) }
}
}