Skip to content
This repository has been archived by the owner on Mar 24, 2022. It is now read-only.

Multiplatform signals #437

Merged
merged 3 commits into from
Apr 1, 2020
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ jobs:

- name: Install wasi-sdk (macos)
run: |
curl -sS -L -O https://github.com/CraneStation/wasi-sdk/releases/download/wasi-sdk-7/wasi-sdk-7.0-macos.tar.gz
tar xf wasi-sdk-7.0-macos.tar.gz
sudo mv wasi-sdk-7.0/opt /opt
curl -sS -L -O https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-8/wasi-sdk-8.0-macos.tar.gz
tar xf wasi-sdk-8.0-macos.tar.gz
sudo mkdir -p /opt/wasi-sdk
sudo mv wasi-sdk-8.0/* /opt/wasi-sdk/

- name: Test Lucet
run: make test-except-fuzz
Expand Down
28 changes: 4 additions & 24 deletions lucet-runtime/lucet-runtime-internals/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ mod tests;
use crate::instance::Instance;
use crate::val::{val_to_reg, val_to_stack, RegVal, UntypedRetVal, Val};

use nix::sys::signal;
use std::arch::x86_64::{__m128, _mm_setzero_ps};
use std::ptr::NonNull;
use std::{mem, ptr};
Expand Down Expand Up @@ -123,7 +122,6 @@ pub struct Context {
// TODO ACF 2019-10-23: make Instance into a generic parameter?
backstop_callback: *const unsafe extern "C" fn(*mut Instance),
callback_data: *mut Instance,
sigset: signal::SigSet,
}

impl Context {
Expand All @@ -137,7 +135,6 @@ impl Context {
parent_ctx: ptr::null_mut(),
backstop_callback: Context::default_backstop_callback as *const _,
callback_data: ptr::null_mut(),
sigset: signal::SigSet::empty(),
}
}

Expand Down Expand Up @@ -468,23 +465,16 @@ impl Context {

let (stack, stack_start) = stack_builder.into_inner();

// RSP, RBP, and sigset still remain to be initialized.
// Stack pointer: this points to the return address that will be used by `swap`, in place
// of the original (eg, in the host) return address. The return address this points to is
// the address of the first function to run on `swap`: `lucet_context_bootstrap`.
child.gpr.rsp = &mut stack[stack.len() - stack_start] as *mut u64 as u64;

// Base pointer: `rbp` will be saved through all guest code, and preserved for when we
// reach the backstop. This allows us to prepare an argument for `lucet_context_backstop`
// even at the entrypoint of the guest.
child.gpr.rbp = child as *const Context as u64;

// Read the mask to be restored if we ever need to jump out of a signal handler. If this
// isn't possible, die.
signal::pthread_sigmask(
signal::SigmaskHow::SIG_SETMASK,
None,
Some(&mut child.sigset),
)
.expect("pthread_sigmask could not be retrieved");

Ok(())
}

Expand Down Expand Up @@ -629,16 +619,6 @@ impl Context {
lucet_context_set(to as *const Context);
}

/// Like `set`, but also manages the return from a signal handler.
///
/// TODO: the return type of this function should really be `Result<!, nix::Error>`, but using
/// `!` as a type like that is currently experimental.
#[inline]
iximeow marked this conversation as resolved.
Show resolved Hide resolved
pub unsafe fn set_from_signal(to: &Context) -> Result<(), nix::Error> {
signal::pthread_sigmask(signal::SigmaskHow::SIG_SETMASK, Some(&to.sigset), None)?;
Context::set(to)
}

/// Clear (zero) return values.
pub fn clear_retvals(&mut self) {
self.retvals_gp = [0; 2];
Expand Down Expand Up @@ -726,7 +706,7 @@ extern "C" {
/// Performs the context switch; implemented in assembly.
///
/// Never returns because the current context is discarded.
fn lucet_context_set(to: *const Context) -> !;
pub(crate) fn lucet_context_set(to: *const Context) -> !;

/// Runs an entry callback after performing a context switch. Implemented in assembly.
///
Expand Down
23 changes: 17 additions & 6 deletions lucet-runtime/lucet-runtime-internals/src/instance/signals.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::alloc::validate_sigstack_size;
use crate::context::Context;
use crate::error::Error;
use crate::instance::{
siginfo_ext::SiginfoExt, FaultDetails, Instance, State, TerminationDetails, CURRENT_INSTANCE,
Expand Down Expand Up @@ -333,11 +332,23 @@ extern "C" fn handle_signal(signum: c_int, siginfo_ptr: *mut siginfo_t, ucontext
});

if switch_to_host {
HOST_CTX.with(|host_ctx| unsafe {
Context::set_from_signal(&*host_ctx.get())
.expect("can successfully switch back to the host context");
});
unreachable!()
// Switch to host by preparing the context to switch when we return from the signal andler.
// We must return from the signal handler for POSIX reasons, so instead prepare the context
// that the signal handler will resume the program as if a call were made. First, by
// pointing the instruction pointer at `lucet_context_set`, then by preparing the argument
// that `lucet_context_set` should read from `rdi` - the context to switch to.
//
// NOTE: it is absolutely critical that `lucet_context_set` does not use the guest stack!
Copy link
Contributor Author

Choose a reason for hiding this comment

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

full disclosure: I left most of the branch unchanged while adjusting the commits, but this NOTE: is new. I'd realized that there wasn't anything discussing the specific constraints on the function we continue into after handling a signal, and this addresses that.

// If it did, and the signal being handled were a segfault from reaching the guard page,
// there would be no stack available for the function we return to. By not using stack
// space, `lucet_context_set` is safe for use even in handling guard page faults.
//
// TODO: `rdi` is only correct for SysV (unixy) calling conventions! For Windows x86_64 this
// would be `rcx`, with other architectures being their own question.
ctx.set_ip(crate::context::lucet_context_set as *const c_void);
HOST_CTX.with(|host_ctx| {
ctx.set_rdi(host_ctx.get() as u64);
})
}
}

Expand Down
9 changes: 8 additions & 1 deletion lucet-runtime/lucet-runtime-internals/src/region/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,17 @@ impl MmapRegion {

// lay out the other sections in memory
let heap = mem as usize + instance_heap_offset();
let stack = heap + region.limits.heap_address_space_size + host_page_size();
let stack_guard = heap + region.limits.heap_address_space_size;
let stack = stack_guard + host_page_size();
let globals = stack + region.limits.stack_size;
let sigstack = globals + region.limits.globals_size + host_page_size();

// ensure we've accounted for all space
assert_eq!(
sigstack + region.limits.signal_stack_size - mem as usize,
region.limits.total_memory_size()
);

Ok(Slot {
start: mem,
heap: heap as *mut c_void,
Expand Down
38 changes: 22 additions & 16 deletions lucet-runtime/lucet-runtime-internals/src/sysdeps/linux.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,56 @@
use libc::{c_void, ucontext_t, REG_RIP};
use libc::{c_void, ucontext_t, REG_RDI, REG_RIP};

#[derive(Clone, Copy, Debug)]
pub struct UContextPtr(*const ucontext_t);
pub struct UContextPtr(*mut ucontext_t);

impl UContextPtr {
#[inline]
pub fn new(ptr: *const c_void) -> Self {
pub fn new(ptr: *mut c_void) -> Self {
assert!(!ptr.is_null(), "non-null context");
UContextPtr(ptr as *const ucontext_t)
UContextPtr(ptr as *mut ucontext_t)
}

#[inline]
pub fn get_ip(self) -> *const c_void {
let mcontext = &unsafe { *(self.0) }.uc_mcontext;
let mcontext = &unsafe { self.0.as_ref().unwrap() }.uc_mcontext;
mcontext.gregs[REG_RIP as usize] as *const _
}

#[inline]
pub fn set_ip(self, new_ip: *const c_void) {
let mut mcontext = &mut unsafe { self.0.as_mut().unwrap() }.uc_mcontext;
mcontext.gregs[REG_RIP as usize] = new_ip as i64;
}

#[inline]
pub fn set_rdi(self, new_rdi: u64) {
let mut mcontext = &mut unsafe { self.0.as_mut().unwrap() }.uc_mcontext;
mcontext.gregs[REG_RDI as usize] = new_rdi as i64;
}
}

#[repr(C)]
#[derive(Clone, Copy)]
pub struct UContext {
context: ucontext_t,
context: *mut ucontext_t,
}

impl UContext {
#[inline]
pub fn new(ptr: *const c_void) -> Self {
pub fn new(ptr: *mut c_void) -> Self {
UContext {
context: *unsafe {
(ptr as *const ucontext_t)
.as_ref()
.expect("non-null context")
},
context: unsafe { (ptr as *mut ucontext_t).as_mut().expect("non-null context") },
}
}

pub fn as_ptr(&mut self) -> UContextPtr {
UContextPtr::new(&self.context as *const _ as *const _)
UContextPtr::new(self.context as *mut _ as *mut _)
}
}

impl Into<UContext> for UContextPtr {
#[inline]
fn into(self) -> UContext {
UContext {
context: unsafe { *(self.0) },
}
UContext { context: self.0 }
}
}
41 changes: 23 additions & 18 deletions lucet-runtime/lucet-runtime-internals/src/sysdeps/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,54 +118,59 @@ pub struct ucontext_t {
pub uc_stack: sigaltstack,
pub uc_link: *const ucontext_t,
pub uc_mcsize: size_t,
pub uc_mcontext: *const mcontext64,
pub uc_mcontext: *mut mcontext64,
}

#[derive(Clone, Copy, Debug)]
pub struct UContextPtr(*const ucontext_t);
pub struct UContextPtr(*mut ucontext_t);

impl UContextPtr {
#[inline]
pub fn new(ptr: *const c_void) -> Self {
pub fn new(ptr: *mut c_void) -> Self {
assert!(!ptr.is_null(), "non-null context");
UContextPtr(ptr as *const ucontext_t)
UContextPtr(ptr as *mut ucontext_t)
}

#[inline]
pub fn get_ip(self) -> *const c_void {
let mcontext = &unsafe { *(*self.0).uc_mcontext };
iximeow marked this conversation as resolved.
Show resolved Hide resolved
let mcontext = unsafe { (*self.0).uc_mcontext.as_ref().unwrap() };
mcontext.ss.rip as *const _
}

#[inline]
pub fn set_ip(self, new_ip: *const c_void) {
let mcontext: &mut mcontext64 = unsafe { &mut (*self.0).uc_mcontext.as_mut().unwrap() };
mcontext.ss.rip = new_ip as u64;
}

#[inline]
pub fn set_rdi(self, new_rdi: u64) {
let mcontext: &mut mcontext64 = unsafe { &mut (*self.0).uc_mcontext.as_mut().unwrap() };
mcontext.ss.rdi = new_rdi;
}
}

#[derive(Clone, Copy)]
#[repr(C)]
pub struct UContext {
context: ucontext_t,
mcontext: mcontext64,
context: *mut ucontext_t,
}

impl UContext {
#[inline]
pub fn new(ptr: *const c_void) -> Self {
let context = *unsafe {
(ptr as *const ucontext_t)
.as_ref()
.expect("non-null context")
};
let mcontext = unsafe { *context.uc_mcontext };
UContext { context, mcontext }
pub fn new(ptr: *mut c_void) -> Self {
let context = unsafe { (ptr as *mut ucontext_t).as_mut().expect("non-null context") };
UContext { context }
}

pub fn as_ptr(&mut self) -> UContextPtr {
self.context.uc_mcontext = &self.mcontext;
UContextPtr::new(&self.context as *const _ as *const _)
UContextPtr::new(self.context as *mut _ as *mut _)
}
}

impl Into<UContext> for UContextPtr {
#[inline]
fn into(self) -> UContext {
UContext::new(self.0 as *const _)
UContext::new(self.0 as *mut _)
}
}
Loading