[][src]Struct lucet_runtime_internals::context::Context

#[repr(C)]
pub struct Context { /* fields omitted */ }

Everything we need to make a context switch: a signal mask, and the registers and return values that are manipulated directly by assembly code.

Layout

The repr(C) and order of fields in this struct are very important, as the assembly code reads and writes hard-coded offsets from the base of the struct. Without repr(C), Rust is free to reorder the fields.

Contexts are also repr(align(64)) in order to align to cache lines and minimize contention when running multiple threads.

Movement

Context values must not be moved once they've been initialized. Contexts contain a pointer to their stack, which in turn contains a pointer back to the context. If the context gets moved, that pointer becomes invalid, and the behavior of returning from that context becomes undefined.

Methods

impl Context[src]

pub fn new() -> Self[src]

Create an all-zeroed Context.

impl Context[src]

pub fn init(
    stack: &mut [u64],
    parent: &mut Context,
    child: &mut Context,
    fptr: usize,
    args: &[Val]
) -> Result<(), Error>
[src]

Initialize a new child context.

  • stack: The stack for the child; must be 16-byte aligned.

  • parent: The context that the child will return to. Since swap initializes the fields in its from argument, this will typically be an empty context from ContextHandle::zero() that will later be passed to swap.

  • child: The context for the child. The fields of this structure will be overwritten by init.

  • fptr: A pointer to the entrypoint for the child. Note that while the type signature here is for a void function of no arguments (equivalent to void (*fptr)(void) in C), the entrypoint actually can be a function of any argument or return type that corresponds to a val::Val variant.

  • args: A slice of arguments for the fptr entrypoint. These must match the number and types of fptr's actual arguments exactly, otherwise swapping to this context will cause undefined behavior.

Errors

  • Error::UnalignedStack if the end of stack is not 16-byte aligned.

Examples

C entrypoint

This example initializes a context that will start in a C function entrypoint when first swapped to.

void entrypoint(uint64_t x, float y);
extern "C" { fn entrypoint(x: u64, y: f32); }
// allocating an even number of `u64`s seems to reliably yield
// properly aligned stacks, but TODO do better
let mut stack = vec![0u64; 1024].into_boxed_slice();
let mut parent = Context::new();
let mut child = Context::new();
let res = Context::init(
    &mut *stack,
    &mut parent,
    &mut child,
    entrypoint as usize,
    &[Val::U64(120), Val::F32(3.14)],
);
assert!(res.is_ok());

Rust entrypoint

This example initializes a context that will start in a Rust function entrypoint when first swapped to. Note that we mark entrypoint as extern "C" to make sure it is compiled with C calling conventions.

extern "C" fn entrypoint(x: u64, y: f32) { }
// allocating an even number of `u64`s seems to reliably yield
// properly aligned stacks, but TODO do better
let mut stack = vec![0u64; 1024].into_boxed_slice();
let mut parent = ContextHandle::new();
let mut child = Context::new();
let res = Context::init(
    &mut *stack,
    &mut parent,
    &mut child,
    entrypoint as usize,
    &[Val::U64(120), Val::F32(3.14)],
);
assert!(res.is_ok());

Implementation details

This prepares a stack for the child context structured as follows, assuming an 0x1000 byte stack:

0x1000: +-------------------------+
0x0ff8: | &child                  |
0x0ff0: | &parent                 | <-- `backstop_args`, which is stored to `rbp`.
0x0fe8: | NULL                    | // Null added if necessary for alignment.
0x0fe0: | spilled_arg_1           | // Guest arguments follow.
0x0fd8: | spilled_arg_2           |
0x0fd0: ~ spilled_arg_3           ~ // The three arguments here are just for show.
0x0fc8: | lucet_context_backstop  | <-- This forms an ABI-matching call frame for fptr.
0x0fc0: | fptr                    | <-- The actual guest code we want to run.
0x0fb8: | lucet_context_bootstrap | <-- The guest stack pointer starts here.
0x0fb0: |                         |
0x0XXX: ~                         ~ // Rest of the stack needs no preparation.
0x0000: |                         |
        +-------------------------+

This packing of data on the stack is interwoven with noteworthy constraints on what the backstop may do:

  • The backstop must not return on the guest stack.
    • The next value will be a spilled argument or NULL. Neither are an intended address.
  • The backstop cannot have ABI-conforming spilled arguments.
    • No code runs between fptr and lucet_context_backstop, so nothing exists to clean up fptr's arguments. lucet_context_backstop would have to adjust the stack pointer by a variable amount, and it does not, so rsp will continue to point to guest arguments.
    • This is why bootstrap recieves arguments via rbp, pointing elsewhere on the stack.

The bootstrap function must be careful, but is less constrained since it can clean up and prepare a context for fptr.

pub unsafe fn swap(from: &mut Context, to: &Context)[src]

Save the current context, and swap to another context.

  • from: the current context is written here
  • to: the context to read from and swap to

The current registers, including the stack pointer, are saved to from. The current stack pointer is then replaced by the value saved in to.gpr.rsp, so when swap returns, it will return to the pointer saved in to's stack.

If to was freshly initialized by passing it as the child to init, swap will return to the function that bootstraps arguments and then calls the entrypoint that was passed to init.

If to was previously passed as the from argument to another call to swap, the program will return as if from that first call to swap.

Safety

The value in to.gpr.rsp must be a valid pointer into the stack that was originally passed to init when the to context was initialized, or to the original stack created implicitly by Rust.

The registers saved in the to context must match the arguments expected by the entrypoint of the function passed to init, or be unaltered from when they were previously written by swap.

If from is never returned to, swapped to, or set to, resources could leak due to implicit drops never being called:

fn f(x: Box<u64>, child: &Context) {
    let mut xs = vec![187; 410757864530];
    xs[0] += *x;

    // manually drop here to avoid leaks
    drop(x);
    drop(xs);

    let mut parent = Context::new();
    unsafe { Context::swap(&mut parent, child); }
    // implicit `drop(x)` and `drop(xs)` here never get called unless we swap back
}

Examples

The typical case is to initialize a new child context, and then swap to it from a zeroed parent context.

let mut parent = Context::new();
let mut child = Context::new();
Context::init(
    &mut stack,
    &mut parent,
    &mut child,
    entrypoint as usize,
    &[],
).unwrap();

unsafe { Context::swap(&mut parent, &child); }

pub unsafe fn set(to: &Context) -> ![src]

Swap to another context without saving the current context.

  • to: the context to read from and swap to

The current registers, including the stack pointer, are discarded. The current stack pointer is then replaced by the value saved in to.gpr.rsp, so when swap returns, it will return to the pointer saved in to's stack.

If to was freshly initialized by passing it as the child to init, swap will return to the function that bootstraps arguments and then calls the entrypoint that was passed to init.

If to was previously passed as the from argument to another call to swap, the program will return as if from the call to swap.

Safety

Stack and registers

The value in to.gpr.rsp must be a valid pointer into the stack that was originally passed to init when the context was initialized, or to the original stack created implicitly by Rust.

The registers saved in to must match the arguments expected by the entrypoint of the function passed to init, or be unaltered from when they were previously written by swap.

Returning

If to is a context freshly initialized by init, at least one of the following must be true, otherwise the program will return to a context with uninitialized registers:

  • The fptr argument to init is a function that never returns

  • The parent argument to init was passed as the from argument to swap before this call to set

Resource leaks

Since control flow will not return to the calling context, care must be taken to ensure that any resources owned by the calling context are manually dropped. The implicit drops inserted by Rust at the end of the calling scope will not be reached:

fn f(x: Box<u64>, child: &Context) {
    let mut xs = vec![187; 410757864530];
    xs[0] += *x;

    // manually drop here to avoid leaks
    drop(x);
    drop(xs);

    unsafe { Context::set(child); }
    // implicit `drop(x)` and `drop(xs)` here never get called
}

pub unsafe fn set_from_signal(to: &Context) -> Result<(), Error>[src]

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.

pub fn clear_retvals(&mut self)[src]

Clear (zero) return values.

pub fn get_retval_gp(&self, idx: usize) -> u64[src]

Get the general-purpose return value at index idx.

If this method is called before the context has returned from its original entrypoint, the result will be 0.

pub fn get_retval_fp(&self) -> __m128[src]

Get the floating point return value.

If this method is called before the context has returned from its original entrypoint, the result will be 0.0.

pub fn get_untyped_retval(&self) -> UntypedRetVal[src]

Get the return value as an UntypedRetVal.

This combines the 0th general-purpose return value, and the single floating-point return value.

Auto Trait Implementations

impl Send for Context

impl Sync for Context

impl Unpin for Context

impl UnwindSafe for Context

impl RefUnwindSafe for Context

Blanket Implementations

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

impl<T> From<T> for T[src]

impl<T, U> TryFrom<U> for T where
    U: Into<T>, 
[src]

type Error = Infallible

The type returned in the event of a conversion error.

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.

impl<T> Borrow<T> for T where
    T: ?Sized
[src]

impl<T> BorrowMut<T> for T where
    T: ?Sized
[src]

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T> Same<T> for T

type Output = T

Should always be Self