Skip to content

Commit 071bbba

Browse files
committed
Implement signal-handling for MMU epochs and an epoch-ending method on Store.
epoch_mmu_trap_via_signal_handler() test demonstrates the signal handler's detection of epoch interrupts and shows a do-nothing version of the asm trampoline returns control successfully to wasm.
1 parent 3f223b7 commit 071bbba

4 files changed

Lines changed: 113 additions & 1 deletion

File tree

crates/wasmtime/src/runtime/code_memory.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,18 @@ impl CodeMemory {
311311
&self.mmap[self.trap_data.clone()]
312312
}
313313

314+
/// Returns the address at which to resume after the given epoch check.
315+
pub fn return_address_for_epoch_check(&self, check_offset: u32) -> Option<*const ()> {
316+
let section = unsafe {
317+
std::slice::from_raw_parts(
318+
self.mmap[self.epoch_check_data.clone()].as_ptr() as *const u8,
319+
self.epoch_check_data.len(),
320+
)
321+
};
322+
return_offset_for_epoch_check(section, check_offset)
323+
.map(|offset| (self.text().as_ptr().addr() + offset as usize) as *const ())
324+
}
325+
314326
/// Publishes the internal ELF image to be ready for execution.
315327
///
316328
/// This method can only be when the image is not published (its

crates/wasmtime/src/runtime/store.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,12 @@ impl<T> Store<T> {
11781178
self.inner.epoch_deadline_callback(Box::new(callback));
11791179
}
11801180

1181+
/// Iff epoch_interruption_via_mmu() is on, end the current epoch, causing
1182+
/// Wasm code belonging to this store to yield to a different task.
1183+
pub fn end_mmu_epoch(&self) {
1184+
self.inner.vm_store_context().protect_interrupt_page();
1185+
}
1186+
11811187
/// Set an exception as the currently pending exception, and
11821188
/// return an error that propagates the throw.
11831189
///

crates/wasmtime/src/runtime/vm/sys/unix/signals.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Trap handling on Unix based on POSIX signals.
22
33
use crate::prelude::*;
4+
use crate::runtime::module::lookup_code;
45
use crate::runtime::vm::traphandlers::{TrapRegisters, TrapTest, tls};
6+
use core::arch::naked_asm;
57
use std::cell::RefCell;
68
use std::io;
79
use std::mem;
@@ -18,6 +20,10 @@ static mut PREV_SIGBUS: libc::sigaction = UNINIT_SIGACTION;
1820
static mut PREV_SIGILL: libc::sigaction = UNINIT_SIGACTION;
1921
static mut PREV_SIGFPE: libc::sigaction = UNINIT_SIGACTION;
2022

23+
// From signal.h. Not yet exposed in libc. Value is valid for Linux and Mac; not
24+
// sure about elsewhere.
25+
#[cfg(all(target_os = "linux"))]
26+
const SEGV_ACCERR: libc::c_int = 2;
2127
pub struct TrapHandler;
2228

2329
impl TrapHandler {
@@ -128,6 +134,26 @@ now.
128134
}
129135
}
130136

137+
/// Switches tasks in response to a signal thrown under MMU-based epoch
138+
/// interruption.
139+
///
140+
/// Saves register state, makes a host call to switch tasks, restores state, and
141+
/// returns.
142+
#[unsafe(naked)]
143+
unsafe extern "C" fn task_switch_trampoline(_vmctx: usize) {
144+
naked_asm!(
145+
"
146+
// Save regs.
147+
// Call hostcall to do task switch, passing in vmctx.
148+
// Restore regs (including r10).
149+
150+
// Resume right after the load instruction that triggered the signal
151+
// handler.
152+
jmp r10
153+
"
154+
);
155+
}
156+
131157
unsafe extern "C" fn trap_handler(
132158
signum: libc::c_int,
133159
siginfo: *mut libc::siginfo_t,
@@ -148,6 +174,37 @@ unsafe extern "C" fn trap_handler(
148174
None => return false,
149175
};
150176

177+
// Check for segfaults meant as cues to end an epoch.
178+
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
179+
if signum == libc::SIGSEGV && unsafe { (*siginfo).si_code } == SEGV_ACCERR {
180+
// Compare it with the offsets of epoch-check instructions as stored
181+
// in the object file.
182+
let ucontext = unsafe { &mut *(context as *mut libc::ucontext_t) };
183+
let pc = ucontext.uc_mcontext.gregs[libc::REG_RIP as usize] as usize;
184+
// Now things get expensive: we call lookup_code(), which takes a global lock.
185+
if let Some((code_memory, offset_within_code)) = lookup_code(pc) {
186+
// We're within a 'target_arch = "x86_64"', so we can just treat
187+
// the stored little-endians as native u32s.
188+
if let Some(return_address) = code_memory.return_address_for_epoch_check(
189+
offset_within_code
190+
.try_into()
191+
.expect("epoch-check location should fit in 32 bits"),
192+
) {
193+
// It is an epoch check. Arrange to resume at asm trampoline
194+
// after signal handler exits:
195+
ucontext.uc_mcontext.gregs[libc::REG_RIP as usize] =
196+
task_switch_trampoline as *const () as i64;
197+
// Put original resumption address in R10:
198+
ucontext.uc_mcontext.gregs[libc::REG_R10 as usize] = return_address as i64;
199+
// Trampoline can expect the vmctx in RDI.
200+
201+
return true;
202+
}
203+
}
204+
// Else it is an ordinary trap or the epochchecks section is somehow
205+
// missing from the binary; continue on.
206+
}
207+
151208
// If we hit an exception while handling a previous trap, that's
152209
// quite bad, so bail out and let the system handle this
153210
// recursive segfault.

tests/all/epoch_mmu.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![cfg(not(miri))]
22

33
use object::{LittleEndian, Object, ObjectSection, U32Bytes};
4-
use wasmtime::{Config, Engine};
4+
use wasmtime::{Config, Engine, Instance, Module, Store};
55
use wasmtime_environ::obj::ELF_WASMTIME_EPOCH_CHECKS;
66

77
/// Asserts that each epoch-check offset encoded into the binary points to the
@@ -60,3 +60,40 @@ fn epoch_check_offsets() {
6060
"Neither check's load instruction uses R12 of RSP as its source, so all length bits should be 0."
6161
);
6262
}
63+
64+
/// Runs a wasm function that loops forever with MMU-based epoch interruption
65+
/// enabled. Incrementing the epoch past the deadline triggers the signal
66+
/// handler (`trap_handler`) which converts the SIGSEGV into a trap.
67+
#[test]
68+
fn epoch_mmu_trap_via_signal_handler() {
69+
let mut config = Config::new();
70+
config.epoch_interruption_via_mmu(true);
71+
let engine = Engine::new(&config).unwrap();
72+
let module = Module::new(
73+
&engine,
74+
r#"(module
75+
(memory 0)
76+
(func (export "answer") (result i32)
77+
i32.const 42
78+
)
79+
)"#,
80+
)
81+
.unwrap();
82+
83+
// Trap as soon as the first epoch check is encountered, in the function
84+
// prologue. Recall that MMU-based epochs don't operate based on a numeric
85+
// deadline but on an external entity protecting the memory page, typically
86+
// on a timer.
87+
let mut store = Store::new(&engine, ());
88+
store.epoch_deadline_trap(); // Allegedly the default.
89+
// Protect that page:
90+
store.end_mmu_epoch();
91+
92+
let instance = Instance::new(&mut store, &module, &[]).unwrap();
93+
let func = instance
94+
.get_typed_func::<(), i32>(&mut store, "answer")
95+
.unwrap();
96+
97+
let result = func.call(&mut store, ()).unwrap();
98+
assert_eq!(result, 42);
99+
}

0 commit comments

Comments
 (0)