Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/api/src/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/runtime/signalhandlers/SignalHandlers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions crates/runtime/signalhandlers/Trampolines.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,69 @@
#include <unistd.h>
#include <setjmp.h>
#include <stdlib.h>
#include <sys/mman.h>

#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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this test be done before we start mmap'ing? We could check to see if the sigaltstack size is already big enough or if it's disabled then we we can act before we mmap our own and set it.

abort();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come there's an OOM trap, but then this is an abort? Could this translate to a trap as well?

}

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;
Expand Down
33 changes: 33 additions & 0 deletions crates/runtime/src/traphandlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -127,13 +145,20 @@ 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 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Trap::User(user) => user.fmt(f),
Trap::Wasm { desc, .. } => desc.fmt(f),
Trap::OOM { .. } => write!(f, "Out of memory"),
}
}
}
Expand All @@ -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`.
Expand Down