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}