1 //! Debugging API.
2 
3 use crate::Result;
4 use crate::{
5     AnyRef, AsContext, AsContextMut, CodeMemory, ExnRef, Extern, ExternRef, Func, Instance, Module,
6     OwnedRooted, StoreContext, StoreContextMut, Val,
7     code::StoreCodePC,
8     module::ModuleRegistry,
9     store::{AutoAssertNoGc, StoreOpaque},
10     vm::{CompiledModuleId, FrameOrHostCode, StoreBacktrace, VMContext},
11 };
12 use alloc::collections::BTreeSet;
13 use alloc::vec;
14 use alloc::vec::Vec;
15 use core::{ffi::c_void, ptr::NonNull};
16 #[cfg(feature = "gc")]
17 use wasmtime_environ::FrameTable;
18 use wasmtime_environ::{
19     DefinedFuncIndex, EntityIndex, FrameInstPos, FrameStackShape, FrameStateSlot,
20     FrameStateSlotOffset, FrameTableBreakpointData, FrameTableDescriptorIndex, FrameValType,
21     FuncIndex, FuncKey, GlobalIndex, MemoryIndex, TableIndex, TagIndex, Trap,
22 };
23 use wasmtime_unwinder::Frame;
24 
25 use super::store::AsStoreOpaque;
26 
27 impl<'a, T> StoreContextMut<'a, T> {
28     /// Provide an object that captures Wasm stack state, including
29     /// Wasm VM-level values (locals and operand stack).
30     ///
31     /// This object views all activations for the current store that
32     /// are on the stack. An activation is a contiguous sequence of
33     /// Wasm frames (called functions) that were called from host code
34     /// and called back out to host code. If there are activations
35     /// from multiple stores on the stack, for example if Wasm code in
36     /// one store calls out to host code which invokes another Wasm
37     /// function in another store, then the other stores are "opaque"
38     /// to our view here in the same way that host code is.
39     ///
40     /// Returns `None` if debug instrumentation is not enabled for
41     /// the engine containing this store.
42     pub fn debug_frames(self) -> Option<DebugFrameCursor<'a, T>> {
43         if !self.engine().tunables().debug_guest {
44             return None;
45         }
46 
47         let iter = StoreBacktrace::new(self);
48         let mut view = DebugFrameCursor {
49             iter,
50             is_trapping_frame: false,
51             frames: vec![],
52             current: None,
53         };
54         view.move_to_parent(); // Load the first frame.
55         Some(view)
56     }
57 
58     /// Start an edit session to update breakpoints.
59     pub fn edit_breakpoints(self) -> Option<BreakpointEdit<'a>> {
60         if !self.engine().tunables().debug_guest {
61             return None;
62         }
63 
64         let (breakpoints, registry) = self.0.breakpoints_and_registry_mut();
65         Some(breakpoints.edit(registry))
66     }
67 }
68 
69 impl Instance {
70     /// Get access to a global within this instance's globals index
71     /// space.
72     ///
73     /// This permits accessing globals whether they are exported or
74     /// not. However, it is only available for purposes of debugging,
75     /// and so is only permitted when `guest_debug` is enabled in the
76     /// Engine's configuration. The intent of the Wasmtime API is to
77     /// enforce the Wasm type system's encapsulation even in the host
78     /// API, except where necessary for developer tooling.
79     ///
80     /// `None` is returned for any global index that is out-of-bounds.
81     ///
82     /// `None` is returned if guest-debugging is not enabled in the
83     /// engine configuration for this Store.
84     pub fn debug_global(
85         &self,
86         mut store: impl AsContextMut,
87         global_index: u32,
88     ) -> Option<crate::Global> {
89         self.debug_export(
90             store.as_context_mut().0,
91             GlobalIndex::from_bits(global_index).into(),
92         )
93         .and_then(|s| s.into_global())
94     }
95 
96     /// Get access to a memory (unshared only) within this instance's
97     /// memory index space.
98     ///
99     /// This permits accessing memories whether they are exported or
100     /// not. However, it is only available for purposes of debugging,
101     /// and so is only permitted when `guest_debug` is enabled in the
102     /// Engine's configuration. The intent of the Wasmtime API is to
103     /// enforce the Wasm type system's encapsulation even in the host
104     /// API, except where necessary for developer tooling.
105     ///
106     /// `None` is returned for any memory index that is out-of-bounds.
107     ///
108     /// `None` is returned for any shared memory (use
109     /// `debug_shared_memory` instead).
110     ///
111     /// `None` is returned if guest-debugging is not enabled in the
112     /// engine configuration for this Store.
113     pub fn debug_memory(
114         &self,
115         mut store: impl AsContextMut,
116         memory_index: u32,
117     ) -> Option<crate::Memory> {
118         self.debug_export(
119             store.as_context_mut().0,
120             MemoryIndex::from_bits(memory_index).into(),
121         )
122         .and_then(|s| s.into_memory())
123     }
124 
125     /// Get access to a shared memory within this instance's memory
126     /// index space.
127     ///
128     /// This permits accessing memories whether they are exported or
129     /// not. However, it is only available for purposes of debugging,
130     /// and so is only permitted when `guest_debug` is enabled in the
131     /// Engine's configuration. The intent of the Wasmtime API is to
132     /// enforce the Wasm type system's encapsulation even in the host
133     /// API, except where necessary for developer tooling.
134     ///
135     /// `None` is returned for any memory index that is out-of-bounds.
136     ///
137     /// `None` is returned for any unshared memory (use `debug_memory`
138     /// instead).
139     ///
140     /// `None` is returned if guest-debugging is not enabled in the
141     /// engine configuration for this Store.
142     pub fn debug_shared_memory(
143         &self,
144         mut store: impl AsContextMut,
145         memory_index: u32,
146     ) -> Option<crate::SharedMemory> {
147         self.debug_export(
148             store.as_context_mut().0,
149             MemoryIndex::from_bits(memory_index).into(),
150         )
151         .and_then(|s| s.into_shared_memory())
152     }
153 
154     /// Get access to a table within this instance's table index
155     /// space.
156     ///
157     /// This permits accessing tables whether they are exported or
158     /// not. However, it is only available for purposes of debugging,
159     /// and so is only permitted when `guest_debug` is enabled in the
160     /// Engine's configuration. The intent of the Wasmtime API is to
161     /// enforce the Wasm type system's encapsulation even in the host
162     /// API, except where necessary for developer tooling.
163     ///
164     /// `None` is returned for any table index that is out-of-bounds.
165     ///
166     /// `None` is returned if guest-debugging is not enabled in the
167     /// engine configuration for this Store.
168     pub fn debug_table(
169         &self,
170         mut store: impl AsContextMut,
171         table_index: u32,
172     ) -> Option<crate::Table> {
173         self.debug_export(
174             store.as_context_mut().0,
175             TableIndex::from_bits(table_index).into(),
176         )
177         .and_then(|s| s.into_table())
178     }
179 
180     /// Get access to a function within this instance's function index
181     /// space.
182     ///
183     /// This permits accessing functions whether they are exported or
184     /// not. However, it is only available for purposes of debugging,
185     /// and so is only permitted when `guest_debug` is enabled in the
186     /// Engine's configuration. The intent of the Wasmtime API is to
187     /// enforce the Wasm type system's encapsulation even in the host
188     /// API, except where necessary for developer tooling.
189     ///
190     /// `None` is returned for any function index that is
191     /// out-of-bounds.
192     ///
193     /// `None` is returned if guest-debugging is not enabled in the
194     /// engine configuration for this Store.
195     pub fn debug_function(
196         &self,
197         mut store: impl AsContextMut,
198         function_index: u32,
199     ) -> Option<crate::Func> {
200         self.debug_export(
201             store.as_context_mut().0,
202             FuncIndex::from_bits(function_index).into(),
203         )
204         .and_then(|s| s.into_func())
205     }
206 
207     /// Get access to a tag within this instance's tag index space.
208     ///
209     /// This permits accessing tags whether they are exported or
210     /// not. However, it is only available for purposes of debugging,
211     /// and so is only permitted when `guest_debug` is enabled in the
212     /// Engine's configuration. The intent of the Wasmtime API is to
213     /// enforce the Wasm type system's encapsulation even in the host
214     /// API, except where necessary for developer tooling.
215     ///
216     /// `None` is returned for any tag index that is out-of-bounds.
217     ///
218     /// `None` is returned if guest-debugging is not enabled in the
219     /// engine configuration for this Store.
220     pub fn debug_tag(&self, mut store: impl AsContextMut, tag_index: u32) -> Option<crate::Tag> {
221         self.debug_export(
222             store.as_context_mut().0,
223             TagIndex::from_bits(tag_index).into(),
224         )
225         .and_then(|s| s.into_tag())
226     }
227 
228     fn debug_export(&self, store: &mut StoreOpaque, index: EntityIndex) -> Option<Extern> {
229         if !store.engine().tunables().debug_guest {
230             return None;
231         }
232 
233         let env_module = self._module(store).env_module();
234         if !env_module.is_valid(index) {
235             return None;
236         }
237         let store_id = store.id();
238         let (instance, registry) = store.instance_and_module_registry_mut(self.id());
239         // SAFETY: the `store` and `registry` are associated with
240         // this instance as we fetched the instance directly from
241         // the store above.
242         let export = unsafe { instance.get_export_by_index_mut(registry, store_id, index) };
243         Some(Extern::from_wasmtime_export(export, store))
244     }
245 }
246 
247 impl<'a, T> StoreContext<'a, T> {
248     /// Return all breakpoints.
249     pub fn breakpoints(self) -> Option<impl Iterator<Item = Breakpoint> + 'a> {
250         if !self.engine().tunables().debug_guest {
251             return None;
252         }
253 
254         let (breakpoints, registry) = self.0.breakpoints_and_registry();
255         Some(breakpoints.breakpoints(registry))
256     }
257 
258     /// Indicate whether single-step mode is enabled.
259     pub fn is_single_step(&self) -> bool {
260         let (breakpoints, _) = self.0.breakpoints_and_registry();
261         breakpoints.is_single_step()
262     }
263 }
264 
265 /// A view of an active stack frame, with the ability to move up the
266 /// stack.
267 ///
268 /// See the documentation on `Store::debug_frames` for more information
269 /// about which frames this view will show.
270 pub struct DebugFrameCursor<'a, T: 'static> {
271     /// Iterator over frames.
272     ///
273     /// This iterator owns the store while the view exists (accessible
274     /// as `iter.store`).
275     iter: StoreBacktrace<'a, T>,
276 
277     /// Is the next frame to be visited by the iterator a trapping
278     /// frame?
279     ///
280     /// This alters how we interpret `pc`: for a trap, we look at the
281     /// instruction that *starts* at `pc`, while for all frames
282     /// further up the stack (i.e., at a callsite), we look at the
283     /// instruction that *ends* at `pc`.
284     is_trapping_frame: bool,
285 
286     /// Virtual frame queue: decoded from `iter`, not yet
287     /// yielded. Innermost frame on top (last).
288     ///
289     /// This is only non-empty when there is more than one virtual
290     /// frame in a physical frame (i.e., for inlining); thus, its size
291     /// is bounded by our inlining depth.
292     frames: Vec<VirtualFrame>,
293 
294     /// Currently focused virtual frame.
295     current: Option<FrameData>,
296 }
297 
298 /// The result type from `DebugFrameCursor::move_to_parent()`:
299 /// indicates whether the cursor skipped over host code to move to the
300 /// next Wasm frame.
301 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
302 pub enum FrameParentResult {
303     /// The new frame is in the same Wasm activation.
304     SameActivation,
305     /// The new frame is in the next higher Wasm activation on the
306     /// stack.
307     NewActivation,
308 }
309 
310 impl<'a, T: 'static> DebugFrameCursor<'a, T> {
311     /// Move up to the next frame in the activation.
312     ///
313     /// Returns `FrameParentMove` as an indication whether the
314     /// moved-to frame is in the same activation or skipped over host
315     /// code.
316     pub fn move_to_parent(&mut self) -> FrameParentResult {
317         // If there are no virtual frames to yield, take and decode
318         // the next physical frame.
319         //
320         // Note that `if` rather than `while` here, and the assert
321         // that we get some virtual frames back, enforce the invariant
322         // that each physical frame decodes to at least one virtual
323         // frame (i.e., there are no physical frames for interstitial
324         // functions or other things that we completely ignore). If
325         // this ever changes, we can remove the assert and convert
326         // this to a loop that polls until it finds virtual frames.
327         let mut result = FrameParentResult::SameActivation;
328         self.current = None;
329         while self.frames.is_empty() {
330             let Some(next_frame) = self.iter.next() else {
331                 return result;
332             };
333             self.frames = match next_frame {
334                 FrameOrHostCode::Frame(frame) => VirtualFrame::decode(
335                     self.iter.store_mut().0.as_store_opaque(),
336                     frame,
337                     self.is_trapping_frame,
338                 ),
339                 FrameOrHostCode::HostCode => {
340                     result = FrameParentResult::NewActivation;
341                     continue;
342                 }
343             };
344             debug_assert!(!self.frames.is_empty());
345             self.is_trapping_frame = false;
346         }
347 
348         // Take a frame and focus it as the current one.
349         self.current = self.frames.pop().map(|vf| FrameData::compute(vf));
350         result
351     }
352 
353     /// Has the iterator reached the end of the activation?
354     pub fn done(&self) -> bool {
355         self.current.is_none()
356     }
357 
358     fn frame_data(&self) -> &FrameData {
359         self.current.as_ref().expect("No current frame")
360     }
361 
362     fn raw_instance(&self) -> &crate::vm::Instance {
363         // Read out the vmctx slot.
364 
365         // SAFETY: vmctx is always at offset 0 in the slot.
366         // (See crates/cranelift/src/func_environ.rs in `update_stack_slot_vmctx()`.)
367         let vmctx: *mut VMContext = unsafe { *(self.frame_data().slot_addr as *mut _) };
368         let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot");
369         // SAFETY: the stored vmctx value is a valid instance in this
370         // store; we only visit frames from this store in the
371         // backtrace.
372         let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) };
373         // SAFETY: the instance pointer read above is valid.
374         unsafe { instance.as_ref() }
375     }
376 
377     /// Get the instance associated with the current frame.
378     pub fn instance(&mut self) -> Instance {
379         let instance = self.raw_instance();
380         Instance::from_wasmtime(instance.id(), self.iter.store_mut().0.as_store_opaque())
381     }
382 
383     /// Get the module associated with the current frame, if any
384     /// (i.e., not a container instance for a host-created entity).
385     pub fn module(&self) -> Option<&Module> {
386         let instance = self.raw_instance();
387         instance.runtime_module()
388     }
389 
390     /// Get the raw function index associated with the current frame, and the
391     /// PC as an offset within its code section, if it is a Wasm
392     /// function directly from the given `Module` (rather than a
393     /// trampoline).
394     pub fn wasm_function_index_and_pc(&self) -> Option<(DefinedFuncIndex, u32)> {
395         let data = self.frame_data();
396         let FuncKey::DefinedWasmFunction(module, func) = data.func_key else {
397             return None;
398         };
399         debug_assert_eq!(
400             module,
401             self.module()
402                 .expect("module should be defined if this is a defined function")
403                 .env_module()
404                 .module_index
405         );
406         Some((func, data.wasm_pc))
407     }
408 
409     /// Get the number of locals in this frame.
410     pub fn num_locals(&self) -> u32 {
411         u32::try_from(self.frame_data().locals.len()).unwrap()
412     }
413 
414     /// Get the depth of the operand stack in this frame.
415     pub fn num_stacks(&self) -> u32 {
416         u32::try_from(self.frame_data().stack.len()).unwrap()
417     }
418 
419     /// Get the type and value of the given local in this frame.
420     ///
421     /// # Panics
422     ///
423     /// Panics if the index is out-of-range (greater than
424     /// `num_locals()`).
425     pub fn local(&mut self, index: u32) -> Val {
426         let data = self.frame_data();
427         let (offset, ty) = data.locals[usize::try_from(index).unwrap()];
428         let slot_addr = data.slot_addr;
429         // SAFETY: compiler produced metadata to describe this local
430         // slot and stored a value of the correct type into it.
431         unsafe { read_value(&mut self.iter.store_mut().0, slot_addr, offset, ty) }
432     }
433 
434     /// Get the type and value of the given operand-stack value in
435     /// this frame.
436     ///
437     /// Index 0 corresponds to the bottom-of-stack, and higher indices
438     /// from there are more recently pushed values.  In other words,
439     /// index order reads the Wasm virtual machine's abstract stack
440     /// state left-to-right.
441     pub fn stack(&mut self, index: u32) -> Val {
442         let data = self.frame_data();
443         let (offset, ty) = data.stack[usize::try_from(index).unwrap()];
444         let slot_addr = data.slot_addr;
445         // SAFETY: compiler produced metadata to describe this
446         // operand-stack slot and stored a value of the correct type
447         // into it.
448         unsafe { read_value(&mut self.iter.store_mut().0, slot_addr, offset, ty) }
449     }
450 }
451 
452 /// Internal data pre-computed for one stack frame.
453 ///
454 /// This combines physical frame info (pc, fp) with the module this PC
455 /// maps to (yielding a frame table) and one frame as produced by the
456 /// progpoint lookup (Wasm PC, frame descriptor index, stack shape).
457 struct VirtualFrame {
458     /// The frame pointer.
459     fp: *const u8,
460     /// The resolved module handle for the physical PC.
461     ///
462     /// The module for each inlined frame within the physical frame is
463     /// resolved from the vmctx reachable for each such frame; this
464     /// module isused only for looking up the frame table.
465     module: Module,
466     /// The Wasm PC for this frame.
467     wasm_pc: u32,
468     /// The frame descriptor for this frame.
469     frame_descriptor: FrameTableDescriptorIndex,
470     /// The stack shape for this frame.
471     stack_shape: FrameStackShape,
472 }
473 
474 impl VirtualFrame {
475     /// Return virtual frames corresponding to a physical frame, from
476     /// outermost to innermost.
477     fn decode(store: &mut StoreOpaque, frame: Frame, is_trapping_frame: bool) -> Vec<VirtualFrame> {
478         let (module_with_code, pc) = store
479             .modules()
480             .module_and_code_by_pc(frame.pc())
481             .expect("Wasm frame PC does not correspond to a module");
482         let module = module_with_code.module();
483         let table = module.frame_table().unwrap();
484         let pc = u32::try_from(pc).expect("PC offset too large");
485         let pos = if is_trapping_frame {
486             FrameInstPos::Pre
487         } else {
488             FrameInstPos::Post
489         };
490         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");
491 
492         program_points
493             .map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame {
494                 fp: core::ptr::with_exposed_provenance(frame.fp()),
495                 module: module.clone(),
496                 wasm_pc,
497                 frame_descriptor,
498                 stack_shape,
499             })
500             .collect()
501     }
502 }
503 
504 /// Data computed when we visit a given frame.
505 struct FrameData {
506     slot_addr: *const u8,
507     func_key: FuncKey,
508     wasm_pc: u32,
509     /// Shape of locals in this frame.
510     ///
511     /// We need to store this locally because `FrameView` cannot
512     /// borrow the store: it needs a mut borrow, and an iterator
513     /// cannot yield the same mut borrow multiple times because it
514     /// cannot control the lifetime of the values it yields (the
515     /// signature of `next()` does not bound the return value to the
516     /// `&mut self` arg).
517     locals: Vec<(FrameStateSlotOffset, FrameValType)>,
518     /// Shape of the stack slots at this program point in this frame.
519     ///
520     /// In addition to the borrowing-related reason above, we also
521     /// materialize this because we want to provide O(1) access to the
522     /// stack by depth, and the frame slot descriptor stores info in a
523     /// linked-list (actually DAG, with dedup'ing) way.
524     stack: Vec<(FrameStateSlotOffset, FrameValType)>,
525 }
526 
527 impl FrameData {
528     fn compute(frame: VirtualFrame) -> Self {
529         let frame_table = frame.module.frame_table().unwrap();
530         // Parse the frame descriptor.
531         let (data, slot_to_fp_offset) = frame_table
532             .frame_descriptor(frame.frame_descriptor)
533             .unwrap();
534         let frame_state_slot = FrameStateSlot::parse(data).unwrap();
535         let slot_addr = frame
536             .fp
537             .wrapping_sub(usize::try_from(slot_to_fp_offset).unwrap());
538 
539         // Materialize the stack shape so we have O(1) access to its
540         // elements, and so we don't need to keep the borrow to the
541         // module alive.
542         let mut stack = frame_state_slot
543             .stack(frame.stack_shape)
544             .collect::<Vec<_>>();
545         stack.reverse(); // Put top-of-stack last.
546 
547         // Materialize the local offsets/types so we don't need to
548         // keep the borrow to the module alive.
549         let locals = frame_state_slot.locals().collect::<Vec<_>>();
550 
551         FrameData {
552             slot_addr,
553             func_key: frame_state_slot.func_key(),
554             wasm_pc: frame.wasm_pc,
555             stack,
556             locals,
557         }
558     }
559 }
560 
561 /// Read the value at the given offset.
562 ///
563 /// # Safety
564 ///
565 /// The `offset` and `ty` must correspond to a valid value written
566 /// to the frame by generated code of the correct type. This will
567 /// be the case if this information comes from the frame tables
568 /// (as long as the frontend that generates the tables and
569 /// instrumentation is correct, and as long as the tables are
570 /// preserved through serialization).
571 unsafe fn read_value(
572     store: &mut StoreOpaque,
573     slot_base: *const u8,
574     offset: FrameStateSlotOffset,
575     ty: FrameValType,
576 ) -> Val {
577     let address = unsafe { slot_base.offset(isize::try_from(offset.offset()).unwrap()) };
578 
579     // SAFETY: each case reads a value from memory that should be
580     // valid according to our safety condition.
581     match ty {
582         FrameValType::I32 => {
583             let value = unsafe { *(address as *const i32) };
584             Val::I32(value)
585         }
586         FrameValType::I64 => {
587             let value = unsafe { *(address as *const i64) };
588             Val::I64(value)
589         }
590         FrameValType::F32 => {
591             let value = unsafe { *(address as *const u32) };
592             Val::F32(value)
593         }
594         FrameValType::F64 => {
595             let value = unsafe { *(address as *const u64) };
596             Val::F64(value)
597         }
598         FrameValType::V128 => {
599             let value = unsafe { *(address as *const u128) };
600             Val::V128(value.into())
601         }
602         FrameValType::AnyRef => {
603             let mut nogc = AutoAssertNoGc::new(store);
604             let value = unsafe { *(address as *const u32) };
605             let value = AnyRef::_from_raw(&mut nogc, value);
606             Val::AnyRef(value)
607         }
608         FrameValType::ExnRef => {
609             let mut nogc = AutoAssertNoGc::new(store);
610             let value = unsafe { *(address as *const u32) };
611             let value = ExnRef::_from_raw(&mut nogc, value);
612             Val::ExnRef(value)
613         }
614         FrameValType::ExternRef => {
615             let mut nogc = AutoAssertNoGc::new(store);
616             let value = unsafe { *(address as *const u32) };
617             let value = ExternRef::_from_raw(&mut nogc, value);
618             Val::ExternRef(value)
619         }
620         FrameValType::FuncRef => {
621             let value = unsafe { *(address as *const *mut c_void) };
622             let value = unsafe { Func::_from_raw(store, value) };
623             Val::FuncRef(value)
624         }
625         FrameValType::ContRef => {
626             unimplemented!("contref values are not implemented in the host API yet")
627         }
628     }
629 }
630 
631 /// Compute raw pointers to all GC refs in the given frame.
632 // Note: ideally this would be an impl Iterator, but this is quite
633 // awkward because of the locally computed data (FrameStateSlot::parse
634 // structured result) within the closure borrowed by a nested closure.
635 #[cfg(feature = "gc")]
636 pub(crate) fn gc_refs_in_frame<'a>(ft: FrameTable<'a>, pc: u32, fp: *mut usize) -> Vec<*mut u32> {
637     let fp = fp.cast::<u8>();
638     let mut ret = vec![];
639     if let Some(frames) = ft.find_program_point(pc, FrameInstPos::Post) {
640         for (_wasm_pc, frame_desc, stack_shape) in frames {
641             let (frame_desc_data, slot_to_fp_offset) = ft.frame_descriptor(frame_desc).unwrap();
642             let frame_base = unsafe { fp.offset(-isize::try_from(slot_to_fp_offset).unwrap()) };
643             let frame_desc = FrameStateSlot::parse(frame_desc_data).unwrap();
644             for (offset, ty) in frame_desc.stack_and_locals(stack_shape) {
645                 match ty {
646                     FrameValType::AnyRef | FrameValType::ExnRef | FrameValType::ExternRef => {
647                         let slot = unsafe {
648                             frame_base
649                                 .offset(isize::try_from(offset.offset()).unwrap())
650                                 .cast::<u32>()
651                         };
652                         ret.push(slot);
653                     }
654                     FrameValType::ContRef | FrameValType::FuncRef => {}
655                     FrameValType::I32
656                     | FrameValType::I64
657                     | FrameValType::F32
658                     | FrameValType::F64
659                     | FrameValType::V128 => {}
660                 }
661             }
662         }
663     }
664     ret
665 }
666 
667 impl<'a, T: 'static> AsContext for DebugFrameCursor<'a, T> {
668     type Data = T;
669     fn as_context(&self) -> StoreContext<'_, Self::Data> {
670         StoreContext(self.iter.store().0)
671     }
672 }
673 impl<'a, T: 'static> AsContextMut for DebugFrameCursor<'a, T> {
674     fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> {
675         StoreContextMut(self.iter.store_mut().0)
676     }
677 }
678 
679 /// One debug event that occurs when running Wasm code on a store with
680 /// a debug handler attached.
681 #[derive(Debug)]
682 pub enum DebugEvent<'a> {
683     /// A [`wasmtime::Error`] was raised by a hostcall.
684     HostcallError(&'a crate::Error),
685     /// An exception is thrown and caught by Wasm. The current state
686     /// is at the throw-point.
687     CaughtExceptionThrown(OwnedRooted<ExnRef>),
688     /// An exception was not caught and is escaping to the host.
689     UncaughtExceptionThrown(OwnedRooted<ExnRef>),
690     /// A Wasm trap occurred.
691     Trap(Trap),
692     /// A breakpoint was reached.
693     Breakpoint,
694     /// An epoch yield occurred.
695     EpochYield,
696 }
697 
698 /// A handler for debug events.
699 ///
700 /// This is an async callback that is invoked directly within the
701 /// context of a debug event that occurs, i.e., with the Wasm code
702 /// still on the stack. The callback can thus observe that stack, up
703 /// to the most recent entry to Wasm.[^1]
704 ///
705 /// Because this callback receives a `StoreContextMut`, it has full
706 /// access to any state that any other hostcall has, including the
707 /// `T`. In that way, it is like an epoch-deadline callback or a
708 /// call-hook callback. It also "freezes" the entire store for the
709 /// duration of the debugger callback future.
710 ///
711 /// In the future, we expect to provide an "externally async" API on
712 /// the `Store` that allows receiving a stream of debug events and
713 /// accessing the store mutably while frozen; that will need to
714 /// integrate with [`Store::run_concurrent`] to properly timeslice and
715 /// scope the mutable access to the store, and has not been built
716 /// yet. In the meantime, it should be possible to build a fully
717 /// functional debugger with this async-callback API by channeling
718 /// debug events out, and requests to read the store back in, over
719 /// message-passing channels between the callback and an external
720 /// debugger main loop.
721 ///
722 /// Note that the `handle` hook may use its mutable store access to
723 /// invoke another Wasm. Debug events will also be caught and will
724 /// cause further `handle` invocations during this recursive
725 /// invocation. It is up to the debugger to handle any implications of
726 /// this reentrancy (e.g., implications on a duplex channel protocol
727 /// with an event/continue handshake) if it does so.
728 ///
729 /// Note also that this trait has `Clone` as a supertrait, and the
730 /// handler is cloned at every invocation as an artifact of the
731 /// internal ownership structure of Wasmtime: the handler itself is
732 /// owned by the store, but also receives a mutable borrow to the
733 /// whole store, so we need to clone it out to invoke it. It is
734 /// recommended that this trait be implemented by a type that is cheap
735 /// to clone: for example, a single `Arc` handle to debugger state.
736 ///
737 /// [^1]: Providing visibility further than the most recent entry to
738 ///       Wasm is not directly possible because it could see into
739 ///       another async stack, and the stack that polls the future
740 ///       running a particular Wasm invocation could change after each
741 ///       suspend point in the handler.
742 pub trait DebugHandler: Clone + Send + Sync + 'static {
743     /// The data expected on the store that this handler is attached
744     /// to.
745     type Data;
746 
747     /// Handle a debug event.
748     fn handle(
749         &self,
750         store: StoreContextMut<'_, Self::Data>,
751         event: DebugEvent<'_>,
752     ) -> impl Future<Output = ()> + Send;
753 }
754 
755 /// Breakpoint state for modules within a store.
756 #[derive(Default)]
757 pub(crate) struct BreakpointState {
758     /// Single-step mode.
759     single_step: bool,
760     /// Breakpoints added individually.
761     breakpoints: BTreeSet<BreakpointKey>,
762 }
763 
764 /// A breakpoint.
765 pub struct Breakpoint {
766     /// Reference to the module in which we are setting the breakpoint.
767     pub module: Module,
768     /// Wasm PC offset within the module.
769     pub pc: u32,
770 }
771 
772 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
773 struct BreakpointKey(CompiledModuleId, u32);
774 
775 impl BreakpointKey {
776     fn from_raw(module: &Module, pc: u32) -> BreakpointKey {
777         BreakpointKey(module.id(), pc)
778     }
779 
780     fn get(&self, registry: &ModuleRegistry) -> Breakpoint {
781         let module = registry
782             .module_by_compiled_id(self.0)
783             .expect("Module should not have been removed from Store")
784             .clone();
785         Breakpoint { module, pc: self.1 }
786     }
787 }
788 
789 /// A breakpoint-editing session.
790 ///
791 /// This enables updating breakpoint state (setting or unsetting
792 /// individual breakpoints or the store-global single-step flag) in a
793 /// batch. It is more efficient to batch these updates because
794 /// "re-publishing" the newly patched code, with update breakpoint
795 /// settings, typically requires a syscall to re-enable execute
796 /// permissions.
797 pub struct BreakpointEdit<'a> {
798     state: &'a mut BreakpointState,
799     registry: &'a mut ModuleRegistry,
800     /// Modules that have been edited.
801     ///
802     /// Invariant: each of these modules' CodeMemory objects is
803     /// *unpublished* when in the dirty set.
804     dirty_modules: BTreeSet<StoreCodePC>,
805 }
806 
807 impl BreakpointState {
808     pub(crate) fn edit<'a>(&'a mut self, registry: &'a mut ModuleRegistry) -> BreakpointEdit<'a> {
809         BreakpointEdit {
810             state: self,
811             registry,
812             dirty_modules: BTreeSet::new(),
813         }
814     }
815 
816     pub(crate) fn breakpoints<'a>(
817         &'a self,
818         registry: &'a ModuleRegistry,
819     ) -> impl Iterator<Item = Breakpoint> + 'a {
820         self.breakpoints.iter().map(|key| key.get(registry))
821     }
822 
823     pub(crate) fn is_single_step(&self) -> bool {
824         self.single_step
825     }
826 }
827 
828 impl<'a> BreakpointEdit<'a> {
829     fn get_code_memory<'b>(
830         registry: &'b mut ModuleRegistry,
831         dirty_modules: &mut BTreeSet<StoreCodePC>,
832         module: &Module,
833     ) -> Result<&'b mut CodeMemory> {
834         let store_code_pc = registry.store_code_base_or_register(module)?;
835         let code_memory = registry
836             .store_code_mut(store_code_pc)
837             .expect("Just checked presence above")
838             .code_memory_mut()
839             .expect("Must have unique ownership of StoreCode in guest-debug mode");
840         if dirty_modules.insert(store_code_pc) {
841             code_memory.unpublish()?;
842         }
843         Ok(code_memory)
844     }
845 
846     fn patch<'b>(
847         patches: impl Iterator<Item = FrameTableBreakpointData<'b>> + 'b,
848         mem: &mut CodeMemory,
849         enable: bool,
850     ) {
851         let mem = mem.text_mut();
852         for patch in patches {
853             let data = if enable { patch.enable } else { patch.disable };
854             let mem = &mut mem[patch.offset..patch.offset + data.len()];
855             log::trace!(
856                 "patch: offset 0x{:x} with enable={enable}: data {data:?} replacing {mem:?}",
857                 patch.offset
858             );
859             mem.copy_from_slice(data);
860         }
861     }
862 
863     /// Add a breakpoint in the given module at the given PC in that
864     /// module.
865     ///
866     /// No effect if the breakpoint is already set.
867     pub fn add_breakpoint(&mut self, module: &Module, pc: u32) -> Result<()> {
868         let key = BreakpointKey::from_raw(module, pc);
869         self.state.breakpoints.insert(key);
870         log::trace!("patching in breakpoint {key:?}");
871         let mem = Self::get_code_memory(self.registry, &mut self.dirty_modules, module)?;
872         let frame_table = module
873             .frame_table()
874             .expect("Frame table must be present when guest-debug is enabled");
875         let patches = frame_table.lookup_breakpoint_patches_by_pc(pc);
876         Self::patch(patches, mem, true);
877         Ok(())
878     }
879 
880     /// Remove a breakpoint in the given module at the given PC in
881     /// that module.
882     ///
883     /// No effect if the breakpoint was not set.
884     pub fn remove_breakpoint(&mut self, module: &Module, pc: u32) -> Result<()> {
885         let key = BreakpointKey::from_raw(module, pc);
886         self.state.breakpoints.remove(&key);
887         if !self.state.single_step {
888             let mem = Self::get_code_memory(self.registry, &mut self.dirty_modules, module)?;
889             let frame_table = module
890                 .frame_table()
891                 .expect("Frame table must be present when guest-debug is enabled");
892             let patches = frame_table.lookup_breakpoint_patches_by_pc(pc);
893             Self::patch(patches, mem, false);
894         }
895         Ok(())
896     }
897 
898     /// Turn on or off single-step mode.
899     ///
900     /// In single-step mode, a breakpoint event is emitted at every
901     /// Wasm PC.
902     pub fn single_step(&mut self, enabled: bool) -> Result<()> {
903         log::trace!(
904             "single_step({enabled}) with breakpoint set {:?}",
905             self.state.breakpoints
906         );
907         let modules = self.registry.all_modules().cloned().collect::<Vec<_>>();
908         for module in modules {
909             let mem = Self::get_code_memory(self.registry, &mut self.dirty_modules, &module)?;
910             let table = module
911                 .frame_table()
912                 .expect("Frame table must be present when guest-debug is enabled");
913             for (wasm_pc, patch) in table.breakpoint_patches() {
914                 let key = BreakpointKey::from_raw(&module, wasm_pc);
915                 let this_enabled = enabled || self.state.breakpoints.contains(&key);
916                 log::trace!(
917                     "single_step: enabled {enabled} key {key:?} -> this_enabled {this_enabled}"
918                 );
919                 Self::patch(core::iter::once(patch), mem, this_enabled);
920             }
921         }
922 
923         self.state.single_step = enabled;
924 
925         Ok(())
926     }
927 }
928 
929 impl<'a> Drop for BreakpointEdit<'a> {
930     fn drop(&mut self) {
931         for &store_code_base in &self.dirty_modules {
932             let store_code = self.registry.store_code_mut(store_code_base).unwrap();
933             if let Err(e) = store_code
934                 .code_memory_mut()
935                 .expect("Must have unique ownership of StoreCode in guest-debug mode")
936                 .publish()
937             {
938                 abort_on_republish_error(e);
939             }
940         }
941     }
942 }
943 
944 /// Abort when we cannot re-publish executable code.
945 ///
946 /// Note that this puts us in quite a conundrum. Typically we will
947 /// have been editing breakpoints from within a hostcall context
948 /// (e.g. inside a debugger hook while execution is paused) with JIT
949 /// code on the stack. Wasmtime's usual path to return errors is back
950 /// through that JIT code: we do not panic-unwind across the JIT code,
951 /// we return into the exit trampoline and that then re-enters the
952 /// raise libcall to use a Cranelift exception-throw to cross most of
953 /// the JIT frames to the entry trampoline. When even trampolines are
954 /// no longer executable, we have no way out. Even an ordinary
955 /// `panic!` cannot work, because we catch panics and carry them
956 /// across JIT code using that trampoline-based error path. Our only
957 /// way out is to directly abort the whole process.
958 ///
959 /// This is not without precedent: other engines have similar failure
960 /// paths. For example, SpiderMonkey directly aborts the process when
961 /// failing to re-apply executable permissions (see [1]).
962 ///
963 /// Note that we don't really expect to ever hit this case in
964 /// practice: it's unlikely that `mprotect` applying `PROT_EXEC` would
965 /// fail due to, e.g., resource exhaustion in the kernel, because we
966 /// will have the same net number of virtual memory areas before and
967 /// after the permissions change. Nevertheless, we have to account for
968 /// the possibility of error.
969 ///
970 /// [1]: https://searchfox.org/firefox-main/rev/7496c8515212669451d7e775a00c2be07da38ca5/js/src/jit/AutoWritableJitCode.h#26-56
971 #[cfg(feature = "std")]
972 fn abort_on_republish_error(e: crate::Error) -> ! {
973     log::error!(
974         "Failed to re-publish executable code: {e:?}. Wasmtime cannot return through JIT code on the stack and cannot even panic; aborting the process."
975     );
976     std::process::abort();
977 }
978 
979 /// In the `no_std` case, we don't have a concept of a "process
980 /// abort", so rely on `panic!`. Typically an embedded scenario that
981 /// uses `no_std` will build with `panic=abort` so the effect is the
982 /// same. If it doesn't, there is truly nothing we can do here so
983 /// let's panic anyway; the panic propagation through the trampolines
984 /// will at least deterministically crash.
985 #[cfg(not(feature = "std"))]
986 fn abort_on_republish_error(e: crate::Error) -> ! {
987     panic!("Failed to re-publish executable code: {e:?}");
988 }
989