1 //! Debugging API.
2 
3 use crate::{
4     AnyRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, Instance, Module, OwnedRooted,
5     StoreContext, StoreContextMut, Val,
6     store::{AutoAssertNoGc, StoreOpaque},
7     vm::{CurrentActivationBacktrace, VMContext},
8 };
9 use alloc::vec;
10 use alloc::vec::Vec;
11 use core::{ffi::c_void, ptr::NonNull};
12 #[cfg(feature = "gc")]
13 use wasmtime_environ::FrameTable;
14 use wasmtime_environ::{
15     DefinedFuncIndex, FrameInstPos, FrameStackShape, FrameStateSlot, FrameStateSlotOffset,
16     FrameTableDescriptorIndex, FrameValType, FuncKey, Trap,
17 };
18 use wasmtime_unwinder::Frame;
19 
20 use super::store::AsStoreOpaque;
21 
22 impl<'a, T> StoreContextMut<'a, T> {
23     /// Provide an object that captures Wasm stack state, including
24     /// Wasm VM-level values (locals and operand stack).
25     ///
26     /// This object views all activations for the current store that
27     /// are on the stack. An activation is a contiguous sequence of
28     /// Wasm frames (called functions) that were called from host code
29     /// and called back out to host code. If there are activations
30     /// from multiple stores on the stack, for example if Wasm code in
31     /// one store calls out to host code which invokes another Wasm
32     /// function in another store, then the other stores are "opaque"
33     /// to our view here in the same way that host code is.
34     ///
35     /// Returns `None` if debug instrumentation is not enabled for
36     /// the engine containing this store.
37     pub fn debug_frames(self) -> Option<DebugFrameCursor<'a, T>> {
38         if !self.engine().tunables().debug_guest {
39             return None;
40         }
41 
42         // SAFETY: This takes a mutable borrow of `self` (the
43         // `StoreOpaque`), which owns all active stacks in the
44         // store. We do not provide any API that could mutate the
45         // frames that we are walking on the `DebugFrameCursor`.
46         let iter = unsafe { CurrentActivationBacktrace::new(self) };
47         let mut view = DebugFrameCursor {
48             iter,
49             is_trapping_frame: false,
50             frames: vec![],
51             current: None,
52         };
53         view.move_to_parent(); // Load the first frame.
54         Some(view)
55     }
56 }
57 
58 /// A view of an active stack frame, with the ability to move up the
59 /// stack.
60 ///
61 /// See the documentation on `Store::stack_value` for more information
62 /// about which frames this view will show.
63 pub struct DebugFrameCursor<'a, T: 'static> {
64     /// Iterator over frames.
65     ///
66     /// This iterator owns the store while the view exists (accessible
67     /// as `iter.store`).
68     iter: CurrentActivationBacktrace<'a, T>,
69 
70     /// Is the next frame to be visited by the iterator a trapping
71     /// frame?
72     ///
73     /// This alters how we interpret `pc`: for a trap, we look at the
74     /// instruction that *starts* at `pc`, while for all frames
75     /// further up the stack (i.e., at a callsite), we look at the
76     /// instruction that *ends* at `pc`.
77     is_trapping_frame: bool,
78 
79     /// Virtual frame queue: decoded from `iter`, not yet
80     /// yielded. Innermost frame on top (last).
81     ///
82     /// This is only non-empty when there is more than one virtual
83     /// frame in a physical frame (i.e., for inlining); thus, its size
84     /// is bounded by our inlining depth.
85     frames: Vec<VirtualFrame>,
86 
87     /// Currently focused virtual frame.
88     current: Option<FrameData>,
89 }
90 
91 impl<'a, T: 'static> DebugFrameCursor<'a, T> {
92     /// Move up to the next frame in the activation.
93     pub fn move_to_parent(&mut self) {
94         // If there are no virtual frames to yield, take and decode
95         // the next physical frame.
96         //
97         // Note that `if` rather than `while` here, and the assert
98         // that we get some virtual frames back, enforce the invariant
99         // that each physical frame decodes to at least one virtual
100         // frame (i.e., there are no physical frames for interstitial
101         // functions or other things that we completely ignore). If
102         // this ever changes, we can remove the assert and convert
103         // this to a loop that polls until it finds virtual frames.
104         self.current = None;
105         if self.frames.is_empty() {
106             let Some(next_frame) = self.iter.next() else {
107                 return;
108             };
109             self.frames = VirtualFrame::decode(
110                 self.iter.store.0.as_store_opaque(),
111                 next_frame,
112                 self.is_trapping_frame,
113             );
114             debug_assert!(!self.frames.is_empty());
115             self.is_trapping_frame = false;
116         }
117 
118         // Take a frame and focus it as the current one.
119         self.current = self.frames.pop().map(|vf| FrameData::compute(vf));
120     }
121 
122     /// Has the iterator reached the end of the activation?
123     pub fn done(&self) -> bool {
124         self.current.is_none()
125     }
126 
127     fn frame_data(&self) -> &FrameData {
128         self.current.as_ref().expect("No current frame")
129     }
130 
131     fn raw_instance(&self) -> &crate::vm::Instance {
132         // Read out the vmctx slot.
133 
134         // SAFETY: vmctx is always at offset 0 in the slot.
135         // (See crates/cranelift/src/func_environ.rs in `update_stack_slot_vmctx()`.)
136         let vmctx: *mut VMContext = unsafe { *(self.frame_data().slot_addr as *mut _) };
137         let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot");
138         // SAFETY: the stored vmctx value is a valid instance in this
139         // store; we only visit frames from this store in the
140         // backtrace.
141         let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) };
142         // SAFETY: the instance pointer read above is valid.
143         unsafe { instance.as_ref() }
144     }
145 
146     /// Get the instance associated with the current frame.
147     pub fn instance(&mut self) -> Instance {
148         let instance = self.raw_instance();
149         Instance::from_wasmtime(instance.id(), self.iter.store.0.as_store_opaque())
150     }
151 
152     /// Get the module associated with the current frame, if any
153     /// (i.e., not a container instance for a host-created entity).
154     pub fn module(&self) -> Option<&Module> {
155         let instance = self.raw_instance();
156         instance.runtime_module()
157     }
158 
159     /// Get the raw function index associated with the current frame, and the
160     /// PC as an offset within its code section, if it is a Wasm
161     /// function directly from the given `Module` (rather than a
162     /// trampoline).
163     pub fn wasm_function_index_and_pc(&self) -> Option<(DefinedFuncIndex, u32)> {
164         let data = self.frame_data();
165         let FuncKey::DefinedWasmFunction(module, func) = data.func_key else {
166             return None;
167         };
168         debug_assert_eq!(
169             module,
170             self.module()
171                 .expect("module should be defined if this is a defined function")
172                 .env_module()
173                 .module_index
174         );
175         Some((func, data.wasm_pc))
176     }
177 
178     /// Get the number of locals in this frame.
179     pub fn num_locals(&self) -> u32 {
180         u32::try_from(self.frame_data().locals.len()).unwrap()
181     }
182 
183     /// Get the depth of the operand stack in this frame.
184     pub fn num_stacks(&self) -> u32 {
185         u32::try_from(self.frame_data().stack.len()).unwrap()
186     }
187 
188     /// Get the type and value of the given local in this frame.
189     ///
190     /// # Panics
191     ///
192     /// Panics if the index is out-of-range (greater than
193     /// `num_locals()`).
194     pub fn local(&mut self, index: u32) -> Val {
195         let data = self.frame_data();
196         let (offset, ty) = data.locals[usize::try_from(index).unwrap()];
197         let slot_addr = data.slot_addr;
198         // SAFETY: compiler produced metadata to describe this local
199         // slot and stored a value of the correct type into it.
200         unsafe { read_value(&mut self.iter.store.0, slot_addr, offset, ty) }
201     }
202 
203     /// Get the type and value of the given operand-stack value in
204     /// this frame.
205     ///
206     /// Index 0 corresponds to the bottom-of-stack, and higher indices
207     /// from there are more recently pushed values.  In other words,
208     /// index order reads the Wasm virtual machine's abstract stack
209     /// state left-to-right.
210     pub fn stack(&mut self, index: u32) -> Val {
211         let data = self.frame_data();
212         let (offset, ty) = data.stack[usize::try_from(index).unwrap()];
213         let slot_addr = data.slot_addr;
214         // SAFETY: compiler produced metadata to describe this
215         // operand-stack slot and stored a value of the correct type
216         // into it.
217         unsafe { read_value(&mut self.iter.store.0, slot_addr, offset, ty) }
218     }
219 }
220 
221 /// Internal data pre-computed for one stack frame.
222 ///
223 /// This combines physical frame info (pc, fp) with the module this PC
224 /// maps to (yielding a frame table) and one frame as produced by the
225 /// progpoint lookup (Wasm PC, frame descriptor index, stack shape).
226 struct VirtualFrame {
227     /// The frame pointer.
228     fp: *const u8,
229     /// The resolved module handle for the physical PC.
230     ///
231     /// The module for each inlined frame within the physical frame is
232     /// resolved from the vmctx reachable for each such frame; this
233     /// module isused only for looking up the frame table.
234     module: Module,
235     /// The Wasm PC for this frame.
236     wasm_pc: u32,
237     /// The frame descriptor for this frame.
238     frame_descriptor: FrameTableDescriptorIndex,
239     /// The stack shape for this frame.
240     stack_shape: FrameStackShape,
241 }
242 
243 impl VirtualFrame {
244     /// Return virtual frames corresponding to a physical frame, from
245     /// outermost to innermost.
246     fn decode(store: &mut StoreOpaque, frame: Frame, is_trapping_frame: bool) -> Vec<VirtualFrame> {
247         let (module_with_code, pc) = store
248             .modules()
249             .module_and_code_by_pc(frame.pc())
250             .expect("Wasm frame PC does not correspond to a module");
251         let module = module_with_code.module();
252         let table = module.frame_table().unwrap();
253         let pc = u32::try_from(pc).expect("PC offset too large");
254         let pos = if is_trapping_frame {
255             FrameInstPos::Pre
256         } else {
257             FrameInstPos::Post
258         };
259         let program_points = table.find_program_point(pc, pos).expect("There must be a program point record in every frame when debug instrumentation is enabled");
260 
261         program_points
262             .map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame {
263                 fp: core::ptr::with_exposed_provenance(frame.fp()),
264                 module: module.clone(),
265                 wasm_pc,
266                 frame_descriptor,
267                 stack_shape,
268             })
269             .collect()
270     }
271 }
272 
273 /// Data computed when we visit a given frame.
274 struct FrameData {
275     slot_addr: *const u8,
276     func_key: FuncKey,
277     wasm_pc: u32,
278     /// Shape of locals in this frame.
279     ///
280     /// We need to store this locally because `FrameView` cannot
281     /// borrow the store: it needs a mut borrow, and an iterator
282     /// cannot yield the same mut borrow multiple times because it
283     /// cannot control the lifetime of the values it yields (the
284     /// signature of `next()` does not bound the return value to the
285     /// `&mut self` arg).
286     locals: Vec<(FrameStateSlotOffset, FrameValType)>,
287     /// Shape of the stack slots at this program point in this frame.
288     ///
289     /// In addition to the borrowing-related reason above, we also
290     /// materialize this because we want to provide O(1) access to the
291     /// stack by depth, and the frame slot descriptor stores info in a
292     /// linked-list (actually DAG, with dedup'ing) way.
293     stack: Vec<(FrameStateSlotOffset, FrameValType)>,
294 }
295 
296 impl FrameData {
297     fn compute(frame: VirtualFrame) -> Self {
298         let frame_table = frame.module.frame_table().unwrap();
299         // Parse the frame descriptor.
300         let (data, slot_to_fp_offset) = frame_table
301             .frame_descriptor(frame.frame_descriptor)
302             .unwrap();
303         let frame_state_slot = FrameStateSlot::parse(data).unwrap();
304         let slot_addr = frame
305             .fp
306             .wrapping_sub(usize::try_from(slot_to_fp_offset).unwrap());
307 
308         // Materialize the stack shape so we have O(1) access to its
309         // elements, and so we don't need to keep the borrow to the
310         // module alive.
311         let mut stack = frame_state_slot
312             .stack(frame.stack_shape)
313             .collect::<Vec<_>>();
314         stack.reverse(); // Put top-of-stack last.
315 
316         // Materialize the local offsets/types so we don't need to
317         // keep the borrow to the module alive.
318         let locals = frame_state_slot.locals().collect::<Vec<_>>();
319 
320         FrameData {
321             slot_addr,
322             func_key: frame_state_slot.func_key(),
323             wasm_pc: frame.wasm_pc,
324             stack,
325             locals,
326         }
327     }
328 }
329 
330 /// Read the value at the given offset.
331 ///
332 /// # Safety
333 ///
334 /// The `offset` and `ty` must correspond to a valid value written
335 /// to the frame by generated code of the correct type. This will
336 /// be the case if this information comes from the frame tables
337 /// (as long as the frontend that generates the tables and
338 /// instrumentation is correct, and as long as the tables are
339 /// preserved through serialization).
340 unsafe fn read_value(
341     store: &mut StoreOpaque,
342     slot_base: *const u8,
343     offset: FrameStateSlotOffset,
344     ty: FrameValType,
345 ) -> Val {
346     let address = unsafe { slot_base.offset(isize::try_from(offset.offset()).unwrap()) };
347 
348     // SAFETY: each case reads a value from memory that should be
349     // valid according to our safety condition.
350     match ty {
351         FrameValType::I32 => {
352             let value = unsafe { *(address as *const i32) };
353             Val::I32(value)
354         }
355         FrameValType::I64 => {
356             let value = unsafe { *(address as *const i64) };
357             Val::I64(value)
358         }
359         FrameValType::F32 => {
360             let value = unsafe { *(address as *const u32) };
361             Val::F32(value)
362         }
363         FrameValType::F64 => {
364             let value = unsafe { *(address as *const u64) };
365             Val::F64(value)
366         }
367         FrameValType::V128 => {
368             let value = unsafe { *(address as *const u128) };
369             Val::V128(value.into())
370         }
371         FrameValType::AnyRef => {
372             let mut nogc = AutoAssertNoGc::new(store);
373             let value = unsafe { *(address as *const u32) };
374             let value = AnyRef::_from_raw(&mut nogc, value);
375             Val::AnyRef(value)
376         }
377         FrameValType::ExnRef => {
378             let mut nogc = AutoAssertNoGc::new(store);
379             let value = unsafe { *(address as *const u32) };
380             let value = ExnRef::_from_raw(&mut nogc, value);
381             Val::ExnRef(value)
382         }
383         FrameValType::ExternRef => {
384             let mut nogc = AutoAssertNoGc::new(store);
385             let value = unsafe { *(address as *const u32) };
386             let value = ExternRef::_from_raw(&mut nogc, value);
387             Val::ExternRef(value)
388         }
389         FrameValType::FuncRef => {
390             let value = unsafe { *(address as *const *mut c_void) };
391             let value = unsafe { Func::_from_raw(store, value) };
392             Val::FuncRef(value)
393         }
394         FrameValType::ContRef => {
395             unimplemented!("contref values are not implemented in the host API yet")
396         }
397     }
398 }
399 
400 /// Compute raw pointers to all GC refs in the given frame.
401 // Note: ideally this would be an impl Iterator, but this is quite
402 // awkward because of the locally computed data (FrameStateSlot::parse
403 // structured result) within the closure borrowed by a nested closure.
404 #[cfg(feature = "gc")]
405 pub(crate) fn gc_refs_in_frame<'a>(ft: FrameTable<'a>, pc: u32, fp: *mut usize) -> Vec<*mut u32> {
406     let fp = fp.cast::<u8>();
407     let mut ret = vec![];
408     if let Some(frames) = ft.find_program_point(pc, FrameInstPos::Post) {
409         for (_wasm_pc, frame_desc, stack_shape) in frames {
410             let (frame_desc_data, slot_to_fp_offset) = ft.frame_descriptor(frame_desc).unwrap();
411             let frame_base = unsafe { fp.offset(-isize::try_from(slot_to_fp_offset).unwrap()) };
412             let frame_desc = FrameStateSlot::parse(frame_desc_data).unwrap();
413             for (offset, ty) in frame_desc.stack_and_locals(stack_shape) {
414                 match ty {
415                     FrameValType::AnyRef | FrameValType::ExnRef | FrameValType::ExternRef => {
416                         let slot = unsafe {
417                             frame_base
418                                 .offset(isize::try_from(offset.offset()).unwrap())
419                                 .cast::<u32>()
420                         };
421                         ret.push(slot);
422                     }
423                     FrameValType::ContRef | FrameValType::FuncRef => {}
424                     FrameValType::I32
425                     | FrameValType::I64
426                     | FrameValType::F32
427                     | FrameValType::F64
428                     | FrameValType::V128 => {}
429                 }
430             }
431         }
432     }
433     ret
434 }
435 
436 impl<'a, T: 'static> AsContext for DebugFrameCursor<'a, T> {
437     type Data = T;
438     fn as_context(&self) -> StoreContext<'_, Self::Data> {
439         StoreContext(self.iter.store.0)
440     }
441 }
442 impl<'a, T: 'static> AsContextMut for DebugFrameCursor<'a, T> {
443     fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> {
444         StoreContextMut(self.iter.store.0)
445     }
446 }
447 
448 /// One debug event that occurs when running Wasm code on a store with
449 /// a debug handler attached.
450 #[derive(Debug)]
451 pub enum DebugEvent<'a> {
452     /// An `anyhow::Error` was raised by a hostcall.
453     HostcallError(&'a anyhow::Error),
454     /// An exception is thrown and caught by Wasm. The current state
455     /// is at the throw-point.
456     CaughtExceptionThrown(OwnedRooted<ExnRef>),
457     /// An exception was not caught and is escaping to the host.
458     UncaughtExceptionThrown(OwnedRooted<ExnRef>),
459     /// A Wasm trap occurred.
460     Trap(Trap),
461 }
462 
463 /// A handler for debug events.
464 ///
465 /// This is an async callback that is invoked directly within the
466 /// context of a debug event that occurs, i.e., with the Wasm code
467 /// still on the stack. The callback can thus observe that stack, up
468 /// to the most recent entry to Wasm.[^1]
469 ///
470 /// Because this callback receives a `StoreContextMut`, it has full
471 /// access to any state that any other hostcall has, including the
472 /// `T`. In that way, it is like an epoch-deadline callback or a
473 /// call-hook callback. It also "freezes" the entire store for the
474 /// duration of the debugger callback future.
475 ///
476 /// In the future, we expect to provide an "externally async" API on
477 /// the `Store` that allows receiving a stream of debug events and
478 /// accessing the store mutably while frozen; that will need to
479 /// integrate with [`Store::run_concurrent`] to properly timeslice and
480 /// scope the mutable access to the store, and has not been built
481 /// yet. In the meantime, it should be possible to build a fully
482 /// functional debugger with this async-callback API by channeling
483 /// debug events out, and requests to read the store back in, over
484 /// message-passing channels between the callback and an external
485 /// debugger main loop.
486 ///
487 /// Note that the `handle` hook may use its mutable store access to
488 /// invoke another Wasm. Debug events will also be caught and will
489 /// cause further `handle` invocations during this recursive
490 /// invocation. It is up to the debugger to handle any implications of
491 /// this reentrancy (e.g., implications on a duplex channel protocol
492 /// with an event/continue handshake) if it does so.
493 ///
494 /// Note also that this trait has `Clone` as a supertrait, and the
495 /// handler is cloned at every invocation as an artifact of the
496 /// internal ownership structure of Wasmtime: the handler itself is
497 /// owned by the store, but also receives a mutable borrow to the
498 /// whole store, so we need to clone it out to invoke it. It is
499 /// recommended that this trait be implemented by a type that is cheap
500 /// to clone: for example, a single `Arc` handle to debugger state.
501 ///
502 /// [^1]: Providing visibility further than the most recent entry to
503 ///       Wasm is not directly possible because it could see into
504 ///       another async stack, and the stack that polls the future
505 ///       running a particular Wasm invocation could change after each
506 ///       suspend point in the handler.
507 pub trait DebugHandler: Clone + Send + Sync + 'static {
508     /// The data expected on the store that this handler is attached
509     /// to.
510     type Data;
511 
512     /// Handle a debug event.
513     fn handle(
514         &self,
515         store: StoreContextMut<'_, Self::Data>,
516         event: DebugEvent<'_>,
517     ) -> impl Future<Output = ()> + Send;
518 }
519