[−][src]Struct lucet_runtime_internals::context::Context
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]
impl Context
[src]
pub fn init(
stack: &mut [u64],
parent: &mut Context,
child: &mut Context,
fptr: usize,
args: &[Val]
) -> Result<(), Error>
[src]
stack: &mut [u64],
parent: &mut Context,
child: &mut Context,
fptr: usize,
args: &[Val]
) -> Result<(), Error>
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. Sinceswap
initializes the fields in itsfrom
argument, this will typically be an empty context fromContextHandle::zero()
that will later be passed toswap
. -
child
: The context for the child. The fields of this structure will be overwritten byinit
. -
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 tovoid (*fptr)(void)
in C), the entrypoint actually can be a function of any argument or return type that corresponds to aval::Val
variant. -
args
: A slice of arguments for thefptr
entrypoint. These must match the number and types offptr
's actual arguments exactly, otherwise swapping to this context will cause undefined behavior.
Errors
Error::UnalignedStack
if the end ofstack
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
andlucet_context_backstop
, so nothing exists to clean upfptr
's arguments.lucet_context_backstop
would have to adjust the stack pointer by a variable amount, and it does not, sorsp
will continue to point to guest arguments. - This is why bootstrap recieves arguments via rbp, pointing elsewhere on the stack.
- No code runs between
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 hereto
: 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, swap
ped to, or set
to, resources could leak due to
implicit drop
s 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 toinit
is a function that never returns -
The
parent
argument toinit
was passed as thefrom
argument toswap
before this call toset
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 drop
s
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]
U: From<T>,
impl<T> From<T> for T
[src]
impl<T, U> TryFrom<U> for T where
U: Into<T>,
[src]
U: Into<T>,
type Error = Infallible
The type returned in the event of a conversion error.
fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>
[src]
impl<T, U> TryInto<U> for T where
U: TryFrom<T>,
[src]
U: TryFrom<T>,
type Error = <U as TryFrom<T>>::Error
The type returned in the event of a conversion error.
fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>
[src]
impl<T> Borrow<T> for T where
T: ?Sized,
[src]
T: ?Sized,
impl<T> BorrowMut<T> for T where
T: ?Sized,
[src]
T: ?Sized,
fn borrow_mut(&mut self) -> &mut T
[src]
impl<T> Any for T where
T: 'static + ?Sized,
[src]
T: 'static + ?Sized,
impl<T> Same<T> for T
type Output = T
Should always be Self