diff --git a/crates/api/src/trap.rs b/crates/api/src/trap.rs index 8b5d6f7b984d..6776c98f2c51 100644 --- a/crates/api/src/trap.rs +++ b/crates/api/src/trap.rs @@ -50,6 +50,9 @@ impl Trap { wasmtime_runtime::Trap::Wasm { desc, backtrace } => { Trap::new_with_trace(desc.to_string(), backtrace) } + wasmtime_runtime::Trap::OOM { backtrace } => { + Trap::new_with_trace("Out of memory".to_string(), backtrace) + } } } diff --git a/crates/runtime/signalhandlers/SignalHandlers.hpp b/crates/runtime/signalhandlers/SignalHandlers.hpp index 5861da7a87e6..3556c0cecfe3 100644 --- a/crates/runtime/signalhandlers/SignalHandlers.hpp +++ b/crates/runtime/signalhandlers/SignalHandlers.hpp @@ -34,6 +34,9 @@ void Unwind(void*); int EnsureEagerSignalHandlers(void); +/// Report a fatal dynamic memory allocation failure. +void RaiseOOMTrap(void) __attribute__((noreturn)); + #ifdef __cplusplus } // extern "C" #endif diff --git a/crates/runtime/signalhandlers/Trampolines.cpp b/crates/runtime/signalhandlers/Trampolines.cpp index e0702c349db3..06af34a3f54b 100644 --- a/crates/runtime/signalhandlers/Trampolines.cpp +++ b/crates/runtime/signalhandlers/Trampolines.cpp @@ -1,12 +1,69 @@ +#include #include +#include +#include #include "SignalHandlers.hpp" +// The size of the sigaltstack (not including the guard, which will be added). +// Make this large enough to run our signal handlers. +static const size_t sigaltstack_size = 4 * SIGSTKSZ; + +// A utility to register a new sigaltstack. +namespace { + static thread_local class SigAltStack { + size_t guard_size; + size_t sigaltstack_alloc_size; + stack_t new_stack; + + public: + SigAltStack(); + ~SigAltStack(); + } thread_sigaltstack; +} + +SigAltStack::SigAltStack() + : guard_size(sysconf(_SC_PAGESIZE)) + , sigaltstack_alloc_size(guard_size + sigaltstack_size) +{ + // Allocate memory. + void *ptr = mmap(NULL, sigaltstack_alloc_size, PROT_NONE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (ptr == MAP_FAILED) + RaiseOOMTrap(); + + // Prepare the stack, register it, and sanity check the old stack. + void *stack_ptr = (void *)((uintptr_t)ptr + guard_size); + new_stack = (stack_t) { stack_ptr, 0, sigaltstack_size }; + stack_t old_stack; + if (mprotect(stack_ptr, sigaltstack_size, PROT_READ | PROT_WRITE) != 0 || + sigaltstack(&new_stack, &old_stack) != 0 || + old_stack.ss_flags != 0 || + old_stack.ss_size > sigaltstack_size) + abort(); +} + +SigAltStack::~SigAltStack() { + // Disable the sigaltstack. We don't restore the old sigaltstack because + // Rust may have restored its old sigaltstack already (the Rust at_exit + // mechanism doesn't interleave with __cxa_atexit). Fortunately, the thread + // is exiting so there's no need; we just make sure our sigaltstack is no + // longer registered before we free it. + static const stack_t disable_stack = { NULL, SS_DISABLE, SIGSTKSZ }; + void *alloc_ptr = (void *)((uintptr_t)new_stack.ss_sp - guard_size); + if (sigaltstack(&disable_stack, NULL) != 0 || + munmap(alloc_ptr, sigaltstack_alloc_size) != 0) + abort(); +} + extern "C" int RegisterSetjmp( void **buf_storage, void (*body)(void*), void *payload) { + // Ensure that the thread-local sigaltstack is initialized. + thread_sigaltstack; + jmp_buf buf; if (setjmp(buf) != 0) { return 0; diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index 7182a6506d66..b03c581d6638 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -58,6 +58,11 @@ cfg_if::cfg_if! { } } +#[no_mangle] +pub unsafe extern "C" fn RaiseOOMTrap() -> ! { + raise_oom_trap() +} + /// Raises a user-defined trap immediately. /// /// This function performs as-if a wasm trap was just executed, only the trap @@ -87,6 +92,19 @@ pub unsafe fn raise_lib_trap(trap: Trap) -> ! { tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap))) } +/// Raises an out-of-memory trap immediately. +/// +/// # Safety +/// +/// Only safe to call when wasm code is on the stack, aka `wasmtime_call` or +/// `wasmtime_call_trampoline` must have been previously called. +pub unsafe fn raise_oom_trap() -> ! { + tls::with(|info| { + info.unwrap() + .unwind_with(UnwindReason::LibTrap(Trap::oom())) + }) +} + /// Carries a Rust panic across wasm code and resumes the panic on the other /// side. /// @@ -127,6 +145,12 @@ pub enum Trap { /// Native stack backtrace at the time the trap occurred backtrace: Backtrace, }, + + /// A trap indicating that the runtime was unable to allocate sufficient memory. + OOM { + /// Native stack backtrace at the time the OOM occurred + backtrace: Backtrace, + }, } impl fmt::Display for Trap { @@ -134,6 +158,7 @@ impl fmt::Display for Trap { match self { Trap::User(user) => user.fmt(f), Trap::Wasm { desc, .. } => desc.fmt(f), + Trap::OOM { .. } => write!(f, "Out of memory"), } } } @@ -152,6 +177,14 @@ impl Trap { let backtrace = Backtrace::new(); Trap::Wasm { desc, backtrace } } + + /// Construct a new OOM trap with the given source location and trap code. + /// + /// Internally saves a backtrace when constructed. + pub fn oom() -> Self { + let backtrace = Backtrace::new(); + Trap::OOM { backtrace } + } } /// Call the wasm function pointed to by `callee`.