wasmtime/runtime/
debug.rs

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