lucet_runtime_internals/
vmctx.rs

1//! Interfaces for accessing instance data from hostcalls.
2//!
3//! This module contains both a Rust-friendly API ([`Vmctx`](struct.Vmctx.html)) as well as C-style
4//! exports for compatibility with hostcalls written against `lucet-runtime-c`.
5
6pub use crate::c_api::lucet_vmctx;
7
8use crate::alloc::instance_heap_offset;
9use crate::context::Context;
10use crate::error::Error;
11use crate::instance::{
12    EmptyYieldVal, Instance, InstanceInternal, State, TerminationDetails, YieldedVal,
13    CURRENT_INSTANCE, HOST_CTX,
14};
15use lucet_module::{FunctionHandle, GlobalValue};
16use std::any::Any;
17use std::borrow::{Borrow, BorrowMut};
18use std::cell::{Ref, RefCell, RefMut};
19use std::marker::PhantomData;
20
21/// An opaque handle to a running instance's context.
22#[derive(Debug)]
23pub struct Vmctx {
24    vmctx: *mut lucet_vmctx,
25    /// A view of the underlying instance's heap.
26    ///
27    /// This must never be dropped automatically, as the view does not own the heap. Rather, this is
28    /// a value used to implement dynamic borrowing of the heap contents that are owned and managed
29    /// by the instance and its `Alloc`.
30    heap_view: RefCell<Box<[u8]>>,
31    /// A view of the underlying instance's globals.
32    ///
33    /// This must never be dropped automatically, as the view does not own the globals. Rather, this
34    /// is a value used to implement dynamic borrowing of the globals that are owned and managed by
35    /// the instance and its `Alloc`.
36    globals_view: RefCell<Box<[GlobalValue]>>,
37}
38
39impl Drop for Vmctx {
40    fn drop(&mut self) {
41        let heap_view = self.heap_view.replace(Box::new([]));
42        let globals_view = self.globals_view.replace(Box::new([]));
43        // as described in the definition of `Vmctx`, we cannot allow the boxed views of the heap
44        // and globals to be dropped
45        Box::leak(heap_view);
46        Box::leak(globals_view);
47    }
48}
49
50pub trait VmctxInternal {
51    /// Get a reference to the `Instance` for this guest.
52    fn instance(&self) -> &Instance;
53
54    /// Get a mutable reference to the `Instance` for this guest.
55    ///
56    /// ### Safety
57    ///
58    /// Using this method, you could hold on to multiple mutable references to the same
59    /// `Instance`. Only use one at a time! This method does not take `&mut self` because otherwise
60    /// you could not use orthogonal `&mut` refs that come from `Vmctx`, like the heap or
61    /// terminating the instance.
62    unsafe fn instance_mut(&self) -> &mut Instance;
63
64    /// Try to take and return the value passed to `Instance::resume_with_val()`.
65    ///
66    /// If there is no resumed value, or if the dynamic type check of the value fails, this returns
67    /// `None`.
68    fn try_take_resumed_val<R: Any + 'static>(&self) -> Option<R>;
69
70    /// Suspend the instance, returning a value in
71    /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
72    /// or resumed.
73    ///
74    /// After suspending, the instance may be resumed by calling
75    /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the
76    /// host with a value of type `R`. If resumed with a value of some other type, this returns
77    /// `None`.
78    ///
79    /// The dynamic type checks used by the other yield methods should make this explicit option
80    /// type redundant, however this interface is used to avoid exposing a panic to the C API.
81    fn yield_val_try_val<A: Any + 'static, R: Any + 'static>(&mut self, val: A) -> Option<R>;
82}
83
84impl VmctxInternal for Vmctx {
85    fn instance(&self) -> &Instance {
86        unsafe { instance_from_vmctx(self.vmctx) }
87    }
88
89    unsafe fn instance_mut(&self) -> &mut Instance {
90        instance_from_vmctx(self.vmctx)
91    }
92
93    fn try_take_resumed_val<R: Any + 'static>(&self) -> Option<R> {
94        let inst = unsafe { self.instance_mut() };
95        if let Some(val) = inst.resumed_val.take() {
96            match val.downcast() {
97                Ok(val) => Some(*val),
98                Err(val) => {
99                    inst.resumed_val = Some(val);
100                    None
101                }
102            }
103        } else {
104            None
105        }
106    }
107
108    fn yield_val_try_val<A: Any + 'static, R: Any + 'static>(&mut self, val: A) -> Option<R> {
109        self.yield_impl::<A, R>(val);
110        self.try_take_resumed_val()
111    }
112}
113
114impl Vmctx {
115    /// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest function.
116    ///
117    /// This is almost certainly not what you want to use to get a `Vmctx`; instead use the first
118    /// argument of a function with the `#[lucet_hostcall]` attribute, which must have the type
119    /// `&mut Vmctx`.
120    pub unsafe fn from_raw(vmctx: *mut lucet_vmctx) -> Vmctx {
121        let inst = instance_from_vmctx(vmctx);
122        assert!(inst.valid_magic());
123
124        let res = Vmctx {
125            vmctx,
126            heap_view: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())),
127            globals_view: RefCell::new(Box::<[GlobalValue]>::from_raw(inst.globals_mut())),
128        };
129        res
130    }
131
132    /// Return the underlying `vmctx` pointer.
133    pub fn as_raw(&self) -> *mut lucet_vmctx {
134        self.vmctx
135    }
136
137    /// Return the WebAssembly heap as a slice of bytes.
138    ///
139    /// If the heap is already mutably borrowed by `heap_mut()`, the instance will
140    /// terminate with `TerminationDetails::BorrowError`.
141    pub fn heap(&self) -> Ref<'_, [u8]> {
142        unsafe {
143            self.reconstitute_heap_view_if_needed();
144        }
145        let r = self
146            .heap_view
147            .try_borrow()
148            .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap")));
149        Ref::map(r, |b| b.borrow())
150    }
151
152    /// Return the WebAssembly heap as a mutable slice of bytes.
153    ///
154    /// If the heap is already borrowed by `heap()` or `heap_mut()`, the instance will terminate
155    /// with `TerminationDetails::BorrowError`.
156    pub fn heap_mut(&self) -> RefMut<'_, [u8]> {
157        unsafe {
158            self.reconstitute_heap_view_if_needed();
159        }
160        let r = self
161            .heap_view
162            .try_borrow_mut()
163            .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap_mut")));
164        RefMut::map(r, |b| b.borrow_mut())
165    }
166
167    /// Check whether the heap has grown, and replace the heap view if it has.
168    ///
169    /// This handles the case where `Vmctx::grow_memory()` and `Vmctx::heap()` are called in
170    /// sequence. Since `Vmctx::grow_memory()` takes `&mut self`, heap references cannot live across
171    /// it.
172    ///
173    /// TODO: There is still an unsound case, though, when a heap reference is held across a call
174    /// back into the guest via `Vmctx::get_func_from_idx()`. That guest code may grow the heap as
175    /// well, causing any outstanding heap references to become invalid. We will address this when
176    /// we rework the interface for calling back into the guest.
177    unsafe fn reconstitute_heap_view_if_needed(&self) {
178        let inst = self.instance_mut();
179        if inst.heap_mut().len() != self.heap_view.borrow().len() {
180            let old_heap_view = self
181                .heap_view
182                .replace(Box::<[u8]>::from_raw(inst.heap_mut()));
183            // as described in the definition of `Vmctx`, we cannot allow the boxed view of the heap
184            // to be dropped
185            Box::leak(old_heap_view);
186        }
187    }
188
189    /// Check whether a given range in the host address space overlaps with the memory that backs
190    /// the instance heap.
191    pub fn check_heap<T>(&self, ptr: *const T, len: usize) -> bool {
192        self.instance().check_heap(ptr, len)
193    }
194
195    /// Check whether a context value of a particular type exists.
196    pub fn contains_embed_ctx<T: Any>(&self) -> bool {
197        self.instance().contains_embed_ctx::<T>()
198    }
199
200    /// Get a reference to a context value of a particular type.
201    ///
202    /// If a context of that type does not exist, the instance will terminate with
203    /// `TerminationDetails::CtxNotFound`.
204    ///
205    /// If the context is already mutably borrowed by `get_embed_ctx_mut`, the instance will
206    /// terminate with `TerminationDetails::BorrowError`.
207    pub fn get_embed_ctx<T: Any>(&self) -> Ref<'_, T> {
208        match self.instance().embed_ctx.try_get::<T>() {
209            Some(Ok(t)) => t,
210            Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx")),
211            None => panic!(TerminationDetails::CtxNotFound),
212        }
213    }
214
215    /// Get a mutable reference to a context value of a particular type.
216    ///
217    /// If a context of that type does not exist, the instance will terminate with
218    /// `TerminationDetails::CtxNotFound`.
219    ///
220    /// If the context is already borrowed by some other use of `get_embed_ctx` or
221    /// `get_embed_ctx_mut`, the instance will terminate with `TerminationDetails::BorrowError`.
222    pub fn get_embed_ctx_mut<T: Any>(&self) -> RefMut<'_, T> {
223        match unsafe { self.instance_mut().embed_ctx.try_get_mut::<T>() } {
224            Some(Ok(t)) => t,
225            Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx_mut")),
226            None => panic!(TerminationDetails::CtxNotFound),
227        }
228    }
229
230    /// Terminate this guest and return to the host context without unwinding.
231    ///
232    /// This is almost certainly not what you want to use to terminate an instance from a hostcall,
233    /// as any resources currently in scope will not be dropped. Instead, use
234    /// `lucet_hostcall_terminate!` which unwinds to the enclosing hostcall body.
235    pub unsafe fn terminate_no_unwind(&mut self, details: TerminationDetails) -> ! {
236        self.instance_mut().terminate(details)
237    }
238
239    /// Grow the guest memory by the given number of WebAssembly pages.
240    ///
241    /// On success, returns the number of pages that existed before the call.
242    pub fn grow_memory(&mut self, additional_pages: u32) -> Result<u32, Error> {
243        unsafe { self.instance_mut().grow_memory(additional_pages) }
244    }
245
246    /// Return the WebAssembly globals as a slice of `i64`s.
247    ///
248    /// If the globals are already mutably borrowed by `globals_mut()`, the instance will terminate
249    /// with `TerminationDetails::BorrowError`.
250    pub fn globals(&self) -> Ref<'_, [GlobalValue]> {
251        let r = self
252            .globals_view
253            .try_borrow()
254            .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals")));
255        Ref::map(r, |b| b.borrow())
256    }
257
258    /// Return the WebAssembly globals as a mutable slice of `i64`s.
259    ///
260    /// If the globals are already borrowed by `globals()` or `globals_mut()`, the instance will
261    /// terminate with `TerminationDetails::BorrowError`.
262    pub fn globals_mut(&self) -> RefMut<'_, [GlobalValue]> {
263        let r = self
264            .globals_view
265            .try_borrow_mut()
266            .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals_mut")));
267        RefMut::map(r, |b| b.borrow_mut())
268    }
269
270    /// Get a function pointer by WebAssembly table and function index.
271    ///
272    /// This is useful when a hostcall takes a function pointer as its argument, as WebAssembly uses
273    /// table indices as its runtime representation of function pointers.
274    ///
275    /// # Safety
276    ///
277    /// We do not currently reflect function type information into the Rust type system, so callers
278    /// of the returned function must take care to cast it to the correct type before calling. The
279    /// correct type will include the `vmctx` argument, which the caller is responsible for passing
280    /// from its own context.
281    ///
282    /// There is currently no guarantee that guest functions will return before faulting, or
283    /// terminating the instance in a subsequent hostcall. This means that any Rust resources that
284    /// are held open when the guest function is called might be leaked if the guest function, for
285    /// example, divides by zero. Work to make this safer is
286    /// [ongoing](https://github.com/bytecodealliance/lucet/pull/254).
287    ///
288    /// ```no_run
289    /// use lucet_runtime_macros::lucet_hostcall;
290    /// use lucet_runtime_internals::lucet_hostcall_terminate;
291    /// use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx};
292    ///
293    /// #[lucet_hostcall]
294    /// #[no_mangle]
295    /// pub unsafe extern "C" fn hostcall_call_binop(
296    ///     vmctx: &mut Vmctx,
297    ///     binop_table_idx: u32,
298    ///     binop_func_idx: u32,
299    ///     operand1: u32,
300    ///     operand2: u32,
301    /// ) -> u32 {
302    ///     if let Ok(binop) = vmctx.get_func_from_idx(binop_table_idx, binop_func_idx) {
303    ///         let typed_binop = std::mem::transmute::<
304    ///             usize,
305    ///             extern "C" fn(*mut lucet_vmctx, u32, u32) -> u32,
306    ///         >(binop.ptr.as_usize());
307    ///         unsafe { (typed_binop)(vmctx.as_raw(), operand1, operand2) }
308    ///     } else {
309    ///         lucet_hostcall_terminate!("invalid function index")
310    ///     }
311    /// }
312    /// ```
313    pub fn get_func_from_idx(
314        &self,
315        table_idx: u32,
316        func_idx: u32,
317    ) -> Result<FunctionHandle, Error> {
318        self.instance()
319            .module()
320            .get_func_from_idx(table_idx, func_idx)
321    }
322
323    /// Suspend the instance, returning an empty
324    /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
325    /// or resumed.
326    ///
327    /// After suspending, the instance may be resumed by the host using
328    /// [`Instance::resume()`](../struct.Instance.html#method.resume).
329    ///
330    /// (The reason for the trailing underscore in the name is that Rust reserves `yield` as a
331    /// keyword for future use.)
332    pub fn yield_(&mut self) {
333        self.yield_val_expecting_val::<EmptyYieldVal, EmptyYieldVal>(EmptyYieldVal);
334    }
335
336    /// Suspend the instance, returning an empty
337    /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
338    /// or resumed.
339    ///
340    /// After suspending, the instance may be resumed by calling
341    /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the
342    /// host with a value of type `R`.
343    pub fn yield_expecting_val<R: Any + 'static>(&mut self) -> R {
344        self.yield_val_expecting_val::<EmptyYieldVal, R>(EmptyYieldVal)
345    }
346
347    /// Suspend the instance, returning a value in
348    /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
349    /// or resumed.
350    ///
351    /// After suspending, the instance may be resumed by the host using
352    /// [`Instance::resume()`](../struct.Instance.html#method.resume).
353    pub fn yield_val<A: Any + 'static>(&mut self, val: A) {
354        self.yield_val_expecting_val::<A, EmptyYieldVal>(val);
355    }
356
357    /// Suspend the instance, returning a value in
358    /// [`RunResult::Yielded`](../enum.RunResult.html#variant.Yielded) to where the instance was run
359    /// or resumed.
360    ///
361    /// After suspending, the instance may be resumed by calling
362    /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val) from the
363    /// host with a value of type `R`.
364    pub fn yield_val_expecting_val<A: Any + 'static, R: Any + 'static>(&mut self, val: A) -> R {
365        self.yield_impl::<A, R>(val);
366        self.take_resumed_val()
367    }
368
369    fn yield_impl<A: Any + 'static, R: Any + 'static>(&mut self, val: A) {
370        let inst = unsafe { self.instance_mut() };
371        let expecting: Box<PhantomData<R>> = Box::new(PhantomData);
372        inst.state = State::Yielding {
373            val: YieldedVal::new(val),
374            expecting: expecting as Box<dyn Any>,
375        };
376
377        HOST_CTX.with(|host_ctx| unsafe { Context::swap(&mut inst.ctx, &mut *host_ctx.get()) });
378    }
379
380    /// Take and return the value passed to
381    /// [`Instance::resume_with_val()`](../struct.Instance.html#method.resume_with_val), terminating
382    /// the instance if there is no value present, or the dynamic type check of the value fails.
383    fn take_resumed_val<R: Any + 'static>(&self) -> R {
384        self.try_take_resumed_val()
385            .unwrap_or_else(|| panic!(TerminationDetails::YieldTypeMismatch))
386    }
387}
388
389/// Get an `Instance` from the `vmctx` pointer.
390///
391/// Only safe to call from within the guest context.
392pub unsafe fn instance_from_vmctx<'a>(vmctx: *mut lucet_vmctx) -> &'a mut Instance {
393    assert!(!vmctx.is_null(), "vmctx is not null");
394
395    let inst_ptr = (vmctx as usize - instance_heap_offset()) as *mut Instance;
396
397    // We shouldn't actually need to access the thread local, only the exception handler should
398    // need to. But, as long as the thread local exists, we should make sure that the guest
399    // hasn't pulled any shenanigans and passed a bad vmctx. (Codegen should ensure the guest
400    // cant pull any shenanigans but there have been bugs before.)
401    CURRENT_INSTANCE.with(|current_instance| {
402        if let Some(current_inst_ptr) = current_instance.borrow().map(|nn| nn.as_ptr()) {
403            assert_eq!(
404                inst_ptr, current_inst_ptr,
405                "vmctx corresponds to current instance"
406            );
407        } else {
408            panic!(
409                "current instance is not set; thread local storage failure can indicate \
410                 dynamic linking issues"
411            );
412        }
413    });
414
415    let inst = inst_ptr.as_mut().unwrap();
416    assert!(inst.valid_magic());
417    inst
418}
419
420impl Instance {
421    /// Terminate the guest and swap back to the host context without unwinding.
422    ///
423    /// This is almost certainly not what you want to use to terminate from a hostcall; use panics
424    /// with `TerminationDetails` instead.
425    pub(crate) unsafe fn terminate(&mut self, details: TerminationDetails) -> ! {
426        self.state = State::Terminating { details };
427        #[allow(unused_unsafe)] // The following unsafe will be incorrectly warned as unused
428        HOST_CTX.with(|host_ctx| unsafe { Context::set(&*host_ctx.get()) })
429    }
430}