Skip to main content

wasmtime/runtime/
debug.rs

1//! Debugging API.
2
3use super::store::AsStoreOpaque;
4use crate::code::StoreCode;
5use crate::module::RegisterBreakpointState;
6use crate::store::StoreId;
7use crate::vm::{Activation, Backtrace};
8use crate::{
9    AnyRef, AsContextMut, CodeMemory, ExnRef, Extern, ExternRef, Func, Instance, Module,
10    OwnedRooted, StoreContext, StoreContextMut, Val,
11    code::StoreCodePC,
12    module::ModuleRegistry,
13    store::{AutoAssertNoGc, StoreOpaque},
14    vm::{CompiledModuleId, VMContext},
15};
16use crate::{Caller, Result, Store};
17use alloc::collections::{BTreeMap, BTreeSet, btree_map::Entry};
18use alloc::vec;
19use alloc::vec::Vec;
20use core::{ffi::c_void, ptr::NonNull};
21#[cfg(feature = "gc")]
22use wasmtime_environ::FrameTable;
23// Re-export ModulePC so downstream crates can use it.
24pub use wasmtime_environ::ModulePC;
25use wasmtime_environ::{
26    DefinedFuncIndex, EntityIndex, FrameInstPos, FrameStackShape, FrameStateSlot,
27    FrameStateSlotOffset, FrameTableBreakpointData, FrameTableDescriptorIndex, FrameValType,
28    FuncIndex, FuncKey, GlobalIndex, MemoryIndex, TableIndex, TagIndex, Trap,
29};
30use wasmtime_unwinder::{Frame, FrameCursor};
31
32impl<T> Store<T> {
33    /// Provide a frame handle for all activations, in order from
34    /// innermost (most recently called) to outermost on the stack.
35    ///
36    /// An activation is a contiguous sequence of Wasm frames (called
37    /// functions) that were called from host code and called back out
38    /// to host code. If there are activations from multiple stores on
39    /// the stack, for example if Wasm code in one store calls out to
40    /// host code which invokes another Wasm function in another
41    /// store, then the other stores are "opaque" to our view here in
42    /// the same way that host code is.
43    ///
44    /// Returns an empty list if debug instrumentation is not enabled
45    /// for the engine containing this store.
46    pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
47        self.as_store_opaque().debug_exit_frames()
48    }
49
50    /// Borrow this store's [`BreakpointState`], to edit its
51    /// breakpoints, single-stepping, or watchpoints.
52    ///
53    /// Returns `None` if guest debugging is not enabled.
54    pub fn edit_breakpoints<'a>(&'a mut self) -> Option<BreakpointState<'a>> {
55        self.as_store_opaque().edit_breakpoints()
56    }
57
58    /// Get a vector of all Instances held in the Store, for debug
59    /// purposes.
60    ///
61    /// Guest debugging must be enabled for this accessor to return
62    /// any instances. If it is not, an empty vector is returned.
63    pub fn debug_all_instances(&mut self) -> Vec<Instance> {
64        self.as_store_opaque().debug_all_instances()
65    }
66
67    /// Get a vector of all Modules held in the Store, for debug
68    /// purposes.
69    ///
70    /// Guest debugging must be enabled for this accessor to return
71    /// any modules. If it is not, an empty vector is returned.
72    pub fn debug_all_modules(&mut self) -> Vec<Module> {
73        self.as_store_opaque().debug_all_modules()
74    }
75}
76
77impl<'a, T> StoreContextMut<'a, T> {
78    /// Provide a frame handle for all activations, in order from
79    /// innermost (most recently called) to outermost on the stack.
80    ///
81    /// See [`Store::debug_exit_frames`] for more details.
82    pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
83        self.0.as_store_opaque().debug_exit_frames()
84    }
85
86    /// Borrow this store's [`BreakpointState`].
87    ///
88    /// See [`Store::edit_breakpoints`] for more details.
89    pub fn edit_breakpoints(self) -> Option<BreakpointState<'a>> {
90        self.0.as_store_opaque().edit_breakpoints()
91    }
92
93    /// Get a vector of all Instances held in the Store, for debug
94    /// purposes.
95    ///
96    /// See [`Store::debug_all_instances`] for more details.
97    pub fn debug_all_instances(self) -> Vec<Instance> {
98        self.0.as_store_opaque().debug_all_instances()
99    }
100
101    /// Get a vector of all Modules held in the Store, for debug
102    /// purposes.
103    ///
104    /// See [`Store::debug_all_modules`] for more details.
105    pub fn debug_all_modules(self) -> Vec<Module> {
106        self.0.as_store_opaque().debug_all_modules()
107    }
108}
109
110impl<'a, T> Caller<'a, T> {
111    /// Provide a frame handle for all activations, in order from
112    /// innermost (most recently called) to outermost on the stack.
113    ///
114    /// See [`Store::debug_exit_frames`] for more details.
115    pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
116        self.store.0.as_store_opaque().debug_exit_frames()
117    }
118
119    /// Borrow this store's [`BreakpointState`].
120    ///
121    /// See [`Store::edit_breakpoints`] for more details.
122    pub fn edit_breakpoints<'b>(&'b mut self) -> Option<BreakpointState<'b>> {
123        self.store.0.as_store_opaque().edit_breakpoints()
124    }
125
126    /// Get a vector of all Instances held in the Store, for debug
127    /// purposes.
128    ///
129    /// See [`Store::debug_all_instances`] for more details.
130    pub fn debug_all_instances(&mut self) -> Vec<Instance> {
131        self.store.0.as_store_opaque().debug_all_instances()
132    }
133
134    /// Get a vector of all Modules held in the Store, for debug
135    /// purposes.
136    ///
137    /// See [`Store::debug_all_modules`] for more details.
138    pub fn debug_all_modules(&mut self) -> Vec<Module> {
139        self.store.0.as_store_opaque().debug_all_modules()
140    }
141}
142
143impl StoreOpaque {
144    /// Sets or clears the watch bits of the bytes in `range` of `memory`; see
145    /// [`BreakpointState::watch`].
146    pub(crate) fn debug_set_watch(
147        &mut self,
148        memory: crate::Memory,
149        range: core::ops::Range<u64>,
150        watch: bool,
151    ) -> Result<()> {
152        let Some(shadow) = memory.vm_shadow_mut(self) else {
153            crate::error::bail!("watchpoints require guest debugging");
154        };
155        let bytes = shadow.bytes_mut();
156        let start = usize::try_from(range.start)?;
157        let end = usize::try_from(range.end)?;
158        let Some(bytes) = bytes.get_mut(start..end) else {
159            crate::error::bail!("watchpoint range is out of bounds of the memory");
160        };
161        for byte in bytes {
162            if watch {
163                *byte |= crate::runtime::vm::WATCH_DEBUG;
164            } else {
165                *byte &= !crate::runtime::vm::WATCH_DEBUG;
166            }
167        }
168        Ok(())
169    }
170
171    fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
172        let activations = if self.engine().tunables().debug_guest {
173            Backtrace::activations(self)
174        } else {
175            vec![]
176        };
177
178        activations
179            .into_iter()
180            // SAFETY: each activation is currently active and will
181            // remain so (we have a mutable borrow of the store).
182            .filter_map(|act| unsafe { FrameHandle::exit_frame(self, act) })
183    }
184
185    pub(crate) fn edit_breakpoints<'a>(&'a mut self) -> Option<BreakpointState<'a>> {
186        if !self.engine().tunables().debug_guest {
187            return None;
188        }
189
190        Some(BreakpointState {
191            store: self,
192            dirty_modules: BTreeSet::new(),
193        })
194    }
195
196    fn debug_all_instances(&mut self) -> Vec<Instance> {
197        if !self.engine().tunables().debug_guest {
198            return vec![];
199        }
200
201        self.all_instances().collect()
202    }
203
204    fn debug_all_modules(&self) -> Vec<Module> {
205        if !self.engine().tunables().debug_guest {
206            return vec![];
207        }
208
209        self.modules()
210            .all_modules()
211            .map(|(_, m)| m.clone())
212            .collect()
213    }
214}
215
216impl Instance {
217    /// Get access to a global within this instance's globals index
218    /// space.
219    ///
220    /// This permits accessing globals whether they are exported or
221    /// not. However, it is only available for purposes of debugging,
222    /// and so is only permitted when `guest_debug` is enabled in the
223    /// Engine's configuration. The intent of the Wasmtime API is to
224    /// enforce the Wasm type system's encapsulation even in the host
225    /// API, except where necessary for developer tooling.
226    ///
227    /// `None` is returned for any global index that is out-of-bounds.
228    ///
229    /// `None` is returned if guest-debugging is not enabled in the
230    /// engine configuration for this Store.
231    pub fn debug_global(
232        &self,
233        mut store: impl AsContextMut,
234        global_index: u32,
235    ) -> Option<crate::Global> {
236        self.debug_export(
237            store.as_context_mut().0,
238            GlobalIndex::from_bits(global_index).into(),
239        )
240        .and_then(|s| s.into_global())
241    }
242
243    /// Get access to a memory (unshared only) within this instance's
244    /// memory index space.
245    ///
246    /// This permits accessing memories whether they are exported or
247    /// not. However, it is only available for purposes of debugging,
248    /// and so is only permitted when `guest_debug` is enabled in the
249    /// Engine's configuration. The intent of the Wasmtime API is to
250    /// enforce the Wasm type system's encapsulation even in the host
251    /// API, except where necessary for developer tooling.
252    ///
253    /// `None` is returned for any memory index that is out-of-bounds.
254    ///
255    /// `None` is returned for any shared memory (use
256    /// `debug_shared_memory` instead).
257    ///
258    /// `None` is returned if guest-debugging is not enabled in the
259    /// engine configuration for this Store.
260    pub fn debug_memory(
261        &self,
262        mut store: impl AsContextMut,
263        memory_index: u32,
264    ) -> Option<crate::Memory> {
265        self.debug_export(
266            store.as_context_mut().0,
267            MemoryIndex::from_bits(memory_index).into(),
268        )
269        .and_then(|s| s.into_memory())
270    }
271
272    /// Get access to a shared memory within this instance's memory
273    /// index space.
274    ///
275    /// This permits accessing memories whether they are exported or
276    /// not. However, it is only available for purposes of debugging,
277    /// and so is only permitted when `guest_debug` is enabled in the
278    /// Engine's configuration. The intent of the Wasmtime API is to
279    /// enforce the Wasm type system's encapsulation even in the host
280    /// API, except where necessary for developer tooling.
281    ///
282    /// `None` is returned for any memory index that is out-of-bounds.
283    ///
284    /// `None` is returned for any unshared memory (use `debug_memory`
285    /// instead).
286    ///
287    /// `None` is returned if guest-debugging is not enabled in the
288    /// engine configuration for this Store.
289    pub fn debug_shared_memory(
290        &self,
291        mut store: impl AsContextMut,
292        memory_index: u32,
293    ) -> Option<crate::SharedMemory> {
294        self.debug_export(
295            store.as_context_mut().0,
296            MemoryIndex::from_bits(memory_index).into(),
297        )
298        .and_then(|s| s.into_shared_memory())
299    }
300
301    /// Get access to a table within this instance's table index
302    /// space.
303    ///
304    /// This permits accessing tables whether they are exported or
305    /// not. However, it is only available for purposes of debugging,
306    /// and so is only permitted when `guest_debug` is enabled in the
307    /// Engine's configuration. The intent of the Wasmtime API is to
308    /// enforce the Wasm type system's encapsulation even in the host
309    /// API, except where necessary for developer tooling.
310    ///
311    /// `None` is returned for any table index that is out-of-bounds.
312    ///
313    /// `None` is returned if guest-debugging is not enabled in the
314    /// engine configuration for this Store.
315    pub fn debug_table(
316        &self,
317        mut store: impl AsContextMut,
318        table_index: u32,
319    ) -> Option<crate::Table> {
320        self.debug_export(
321            store.as_context_mut().0,
322            TableIndex::from_bits(table_index).into(),
323        )
324        .and_then(|s| s.into_table())
325    }
326
327    /// Get access to a function within this instance's function index
328    /// space.
329    ///
330    /// This permits accessing functions whether they are exported or
331    /// not. However, it is only available for purposes of debugging,
332    /// and so is only permitted when `guest_debug` is enabled in the
333    /// Engine's configuration. The intent of the Wasmtime API is to
334    /// enforce the Wasm type system's encapsulation even in the host
335    /// API, except where necessary for developer tooling.
336    ///
337    /// `None` is returned for any function index that is
338    /// out-of-bounds.
339    ///
340    /// `None` is returned if guest-debugging is not enabled in the
341    /// engine configuration for this Store.
342    pub fn debug_function(
343        &self,
344        mut store: impl AsContextMut,
345        function_index: u32,
346    ) -> Option<crate::Func> {
347        self.debug_export(
348            store.as_context_mut().0,
349            FuncIndex::from_bits(function_index).into(),
350        )
351        .and_then(|s| s.into_func())
352    }
353
354    /// Get access to a tag within this instance's tag index space.
355    ///
356    /// This permits accessing tags whether they are exported or
357    /// not. However, it is only available for purposes of debugging,
358    /// and so is only permitted when `guest_debug` is enabled in the
359    /// Engine's configuration. The intent of the Wasmtime API is to
360    /// enforce the Wasm type system's encapsulation even in the host
361    /// API, except where necessary for developer tooling.
362    ///
363    /// `None` is returned for any tag index that is out-of-bounds.
364    ///
365    /// `None` is returned if guest-debugging is not enabled in the
366    /// engine configuration for this Store.
367    pub fn debug_tag(&self, mut store: impl AsContextMut, tag_index: u32) -> Option<crate::Tag> {
368        self.debug_export(
369            store.as_context_mut().0,
370            TagIndex::from_bits(tag_index).into(),
371        )
372        .and_then(|s| s.into_tag())
373    }
374
375    fn debug_export(&self, store: &mut StoreOpaque, index: EntityIndex) -> Option<Extern> {
376        if !store.engine().tunables().debug_guest {
377            return None;
378        }
379
380        let env_module = self._module(store).env_module();
381        if !env_module.is_valid(index) {
382            return None;
383        }
384        let store_id = store.id();
385        let (instance, registry) = store.instance_and_module_registry_mut(self.id());
386        // SAFETY: the `store` and `registry` are associated with
387        // this instance as we fetched the instance directly from
388        // the store above.
389        let export = unsafe { instance.get_export_by_index_mut(registry, store_id, index) };
390        Some(Extern::from_wasmtime_export(export, store.engine()))
391    }
392}
393
394impl<'a, T> StoreContext<'a, T> {
395    /// Return all breakpoints.
396    pub fn breakpoints(self) -> Option<impl Iterator<Item = Breakpoint> + 'a> {
397        if !self.engine().tunables().debug_guest {
398            return None;
399        }
400
401        let (breakpoints, registry) = self.0.breakpoints_and_registry();
402        Some(breakpoints.breakpoints(registry))
403    }
404
405    /// Indicate whether single-step mode is enabled.
406    pub fn is_single_step(&self) -> bool {
407        let (breakpoints, _) = self.0.breakpoints_and_registry();
408        breakpoints.is_single_step()
409    }
410}
411
412/// A handle to a stack frame, valid as long as execution is not
413/// resumed in the associated `Store`.
414///
415/// This handle can be held and cloned and used to refer to a frame
416/// within a paused store. It is cheap: it internally consists of a
417/// pointer to the actual frame, together with some metadata to
418/// determine when that pointer has gone stale.
419///
420/// At the API level, any usage of this frame handle requires a
421/// mutable borrow of the `Store`, because the `Store` logically owns
422/// the stack(s) for any execution within it. However, the existence
423/// of the handle itself does not hold a borrow on the `Store`; hence,
424/// the `Store` can continue to be used and queried, and some state
425/// (e.g. memories, tables, GC objects) can even be mutated, as long
426/// as execution is not resumed. The intent of this API is to allow a
427/// wide variety of debugger implementation strategies that expose
428/// stack frames and also allow other commands/actions at the same
429/// time.
430///
431/// The user can use [`FrameHandle::is_valid`] to determine if the
432/// handle is still valid and usable.
433#[derive(Clone)]
434pub struct FrameHandle {
435    /// The unwinder cursor at this frame.
436    cursor: FrameCursor,
437
438    /// The index of the virtual frame within the physical frame.
439    virtual_frame_idx: usize,
440
441    /// The unique Store this frame came from, to ensure the handle is
442    /// used with the correct Store.
443    store_id: StoreId,
444
445    /// Store `execution_version`.
446    store_version: u64,
447}
448
449impl FrameHandle {
450    /// Create a new FrameHandle at the exit frame of an activation.
451    ///
452    /// # Safety
453    ///
454    /// The provided activation must be valid currently.
455    unsafe fn exit_frame(store: &mut StoreOpaque, activation: Activation) -> Option<FrameHandle> {
456        // SAFETY: activation is valid as per our safety condition.
457        let mut cursor = unsafe { activation.cursor() };
458
459        // Find the first virtual frame. Each physical frame may have
460        // zero or more virtual frames.
461        while !cursor.done() {
462            let (cache, registry) = store.frame_data_cache_mut_and_registry();
463            let frames = cache.lookup_or_compute(registry, cursor.frame());
464            if frames.len() > 0 {
465                return Some(FrameHandle {
466                    cursor,
467                    virtual_frame_idx: 0,
468                    store_id: store.id(),
469                    store_version: store.vm_store_context().execution_version,
470                });
471            }
472            // SAFETY: activation is still valid (valid on entry per
473            // our safety condition, and we have not returned control
474            // since above).
475            unsafe {
476                cursor.advance(store.unwinder());
477            }
478        }
479
480        None
481    }
482
483    /// Determine whether this handle can still be used to refer to a
484    /// frame.
485    pub fn is_valid(&self, mut store: impl AsContextMut) -> bool {
486        let store = store.as_context_mut();
487        self.is_valid_impl(store.0.as_store_opaque())
488    }
489
490    fn is_valid_impl(&self, store: &StoreOpaque) -> bool {
491        let id = store.id();
492        let version = store.vm_store_context().execution_version;
493        self.store_id == id && self.store_version == version
494    }
495
496    /// Get a handle to the next frame up the activation (the one that
497    /// called this frame), if any.
498    pub fn parent(&self, mut store: impl AsContextMut) -> Result<Option<FrameHandle>> {
499        let mut store = store.as_context_mut();
500        if !self.is_valid(&mut store) {
501            crate::error::bail!("Frame handle is no longer valid.");
502        }
503
504        let mut parent = self.clone();
505        parent.virtual_frame_idx += 1;
506
507        while !parent.cursor.done() {
508            let (cache, registry) = store
509                .0
510                .as_store_opaque()
511                .frame_data_cache_mut_and_registry();
512            let frames = cache.lookup_or_compute(registry, parent.cursor.frame());
513            if parent.virtual_frame_idx < frames.len() {
514                return Ok(Some(parent));
515            }
516            parent.virtual_frame_idx = 0;
517            // SAFETY: activation is valid because we checked validity
518            // wrt execution version at the top of this function, and
519            // we have not returned since.
520            unsafe {
521                parent.cursor.advance(store.0.as_store_opaque().unwinder());
522            }
523        }
524
525        Ok(None)
526    }
527
528    fn frame_data<'a>(&self, store: &'a mut StoreOpaque) -> Result<&'a FrameData> {
529        if !self.is_valid_impl(store) {
530            crate::error::bail!("Frame handle is no longer valid.");
531        }
532        let (cache, registry) = store.frame_data_cache_mut_and_registry();
533        let frames = cache.lookup_or_compute(registry, self.cursor.frame());
534        // `virtual_frame_idx` counts up for ease of iteration
535        // behavior, while the frames are stored in outer-to-inner
536        // (i.e., caller to callee) order, so we need to reverse here.
537        Ok(&frames[frames.len() - 1 - self.virtual_frame_idx])
538    }
539
540    fn raw_instance<'a>(&self, store: &mut StoreOpaque) -> Result<&'a crate::vm::Instance> {
541        let frame_data = self.frame_data(store)?;
542
543        // Read out the vmctx slot.
544
545        // SAFETY: vmctx is always at offset 0 in the slot.  (See
546        // crates/cranelift/src/func_environ.rs in
547        // `update_stack_slot_vmctx()`.)  The frame/activation is
548        // still valid because we verified this in `frame_data` above.
549        let vmctx: usize =
550            unsafe { *(frame_data.slot_addr(self.cursor.frame().fp()) as *mut usize) };
551        let vmctx: *mut VMContext = core::ptr::with_exposed_provenance_mut(vmctx);
552        let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot");
553        // SAFETY: the stored vmctx value is a valid instance in this
554        // store; we only visit frames from this store in the
555        // backtrace.
556        let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) };
557        // SAFETY: the instance pointer read above is valid.
558        Ok(unsafe { instance.as_ref() })
559    }
560
561    /// Get the instance associated with the current frame.
562    pub fn instance(&self, mut store: impl AsContextMut) -> Result<Instance> {
563        let store = store.as_context_mut();
564        let instance = self.raw_instance(store.0.as_store_opaque())?;
565        let id = instance.id();
566        Ok(Instance::from_wasmtime(id, store.0.as_store_opaque()))
567    }
568
569    /// Get the module associated with the current frame, if any
570    /// (i.e., not a container instance for a host-created entity).
571    pub fn module<'a, T: 'static>(
572        &self,
573        store: impl Into<StoreContextMut<'a, T>>,
574    ) -> Result<Option<&'a Module>> {
575        let store = store.into();
576        let instance = self.raw_instance(store.0.as_store_opaque())?;
577        Ok(instance.runtime_module())
578    }
579
580    /// Get the raw function index associated with the current frame, and the
581    /// module-relative PC as an offset within the module binary, if
582    /// this is a Wasm function directly from the given `Module`
583    /// (rather than a trampoline).
584    pub fn wasm_function_index_and_pc(
585        &self,
586        mut store: impl AsContextMut,
587    ) -> Result<Option<(DefinedFuncIndex, ModulePC)>> {
588        let mut store = store.as_context_mut();
589        let frame_data = self.frame_data(store.0.as_store_opaque())?;
590        let FuncKey::DefinedWasmFunction(module, func) = frame_data.func_key else {
591            return Ok(None);
592        };
593        let wasm_pc = frame_data.wasm_pc;
594        debug_assert_eq!(
595            module,
596            self.module(&mut store)?
597                .expect("module should be defined if this is a defined function")
598                .env_module()
599                .module_index
600        );
601        Ok(Some((func, wasm_pc)))
602    }
603
604    /// Get the number of locals in this frame.
605    pub fn num_locals(&self, mut store: impl AsContextMut) -> Result<u32> {
606        let store = store.as_context_mut();
607        let frame_data = self.frame_data(store.0.as_store_opaque())?;
608        Ok(u32::try_from(frame_data.locals.len()).unwrap())
609    }
610
611    /// Get the depth of the operand stack in this frame.
612    pub fn num_stacks(&self, mut store: impl AsContextMut) -> Result<u32> {
613        let store = store.as_context_mut();
614        let frame_data = self.frame_data(store.0.as_store_opaque())?;
615        Ok(u32::try_from(frame_data.stack.len()).unwrap())
616    }
617
618    /// Get the type and value of the given local in this frame.
619    ///
620    /// # Panics
621    ///
622    /// Panics if the index is out-of-range (greater than
623    /// `num_locals()`).
624    pub fn local(&self, mut store: impl AsContextMut, index: u32) -> Result<Val> {
625        let store = store.as_context_mut();
626        let frame_data = self.frame_data(store.0.as_store_opaque())?;
627        let (offset, ty) = frame_data.locals[usize::try_from(index).unwrap()];
628        let slot_addr = frame_data.slot_addr(self.cursor.frame().fp());
629        // SAFETY: compiler produced metadata to describe this local
630        // slot and stored a value of the correct type into it. Slot
631        // address is valid because we checked liveness of the
632        // activation/frame via `frame_data` above.
633        Ok(unsafe { read_value(store.0.as_store_opaque(), slot_addr, offset, ty) })
634    }
635
636    /// Get the type and value of the given operand-stack value in
637    /// this frame.
638    ///
639    /// Index 0 corresponds to the bottom-of-stack, and higher indices
640    /// from there are more recently pushed values.  In other words,
641    /// index order reads the Wasm virtual machine's abstract stack
642    /// state left-to-right.
643    pub fn stack(&self, mut store: impl AsContextMut, index: u32) -> Result<Val> {
644        let store = store.as_context_mut();
645        let frame_data = self.frame_data(store.0.as_store_opaque())?;
646        let (offset, ty) = frame_data.stack[usize::try_from(index).unwrap()];
647        let slot_addr = frame_data.slot_addr(self.cursor.frame().fp());
648        // SAFETY: compiler produced metadata to describe this
649        // operand-stack slot and stored a value of the correct type
650        // into it. Slot address is valid because we checked liveness
651        // of the activation/frame via `frame_data` above.
652        Ok(unsafe { read_value(store.0.as_store_opaque(), slot_addr, offset, ty) })
653    }
654}
655
656/// A cache from `StoreCodePC`s for modules' private code within a
657/// store to pre-computed layout data for the virtual stack frame(s)
658/// present at that physical PC.
659pub(crate) struct FrameDataCache {
660    /// For a given physical PC, the list of virtual frames, from
661    /// inner (most recently called/inlined) to outer.
662    by_pc: BTreeMap<StoreCodePC, Vec<FrameData>>,
663}
664
665impl FrameDataCache {
666    pub(crate) fn new() -> FrameDataCache {
667        FrameDataCache {
668            by_pc: BTreeMap::new(),
669        }
670    }
671
672    /// Look up (or compute) the list of `FrameData`s from a physical
673    /// `Frame`.
674    fn lookup_or_compute<'a>(
675        &'a mut self,
676        registry: &ModuleRegistry,
677        frame: Frame,
678    ) -> &'a [FrameData] {
679        let pc = StoreCodePC::from_raw(frame.pc());
680        match self.by_pc.entry(pc) {
681            Entry::Occupied(frames) => frames.into_mut(),
682            Entry::Vacant(v) => {
683                // Although inlining can mix modules, `module` is the
684                // module that actually contains the physical PC
685                // (i.e., the outermost function that inlined the
686                // others).
687                let (store_code, frames) = VirtualFrame::decode(registry, frame.pc());
688                let frames = frames
689                    .into_iter()
690                    .map(|frame| FrameData::compute(frame, store_code))
691                    .collect::<Vec<_>>();
692                v.insert(frames)
693            }
694        }
695    }
696}
697
698/// Internal data pre-computed for one stack frame.
699///
700/// This represents one frame as produced by the progpoint lookup
701/// (Wasm PC, frame descriptor index, stack shape).
702struct VirtualFrame {
703    /// The module-relative Wasm PC for this frame.
704    wasm_pc: ModulePC,
705    /// The frame descriptor for this frame.
706    frame_descriptor: FrameTableDescriptorIndex,
707    /// The stack shape for this frame.
708    stack_shape: FrameStackShape,
709}
710
711impl VirtualFrame {
712    /// Return virtual frames corresponding to a physical frame, from
713    /// outermost to innermost.
714    fn decode(registry: &ModuleRegistry, pc: usize) -> (&StoreCode, Vec<VirtualFrame>) {
715        let (store_code, pc) = registry
716            .store_code_by_pc(pc)
717            .expect("Wasm frame PC does not correspond to a module");
718        let table = store_code.code_memory().frame_table().unwrap();
719        let pc = u32::try_from(pc).expect("PC offset too large");
720        // Functions without any program points (e.g. the module
721        // start function, or trampolines) have no virtual frames.
722        let frames = table
723            .find_program_point(pc, FrameInstPos::Post)
724            .into_iter()
725            .flatten()
726            .map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame {
727                wasm_pc,
728                frame_descriptor,
729                stack_shape,
730            })
731            .collect();
732
733        (store_code, frames)
734    }
735}
736
737/// Data computed when we visit a given frame.
738struct FrameData {
739    slot_to_fp_offset: usize,
740    func_key: FuncKey,
741    wasm_pc: ModulePC,
742    /// Shape of locals in this frame.
743    ///
744    /// We need to store this locally because `FrameView` cannot
745    /// borrow the store: it needs a mut borrow, and an iterator
746    /// cannot yield the same mut borrow multiple times because it
747    /// cannot control the lifetime of the values it yields (the
748    /// signature of `next()` does not bound the return value to the
749    /// `&mut self` arg).
750    locals: Vec<(FrameStateSlotOffset, FrameValType)>,
751    /// Shape of the stack slots at this program point in this frame.
752    ///
753    /// In addition to the borrowing-related reason above, we also
754    /// materialize this because we want to provide O(1) access to the
755    /// stack by depth, and the frame slot descriptor stores info in a
756    /// linked-list (actually DAG, with dedup'ing) way.
757    stack: Vec<(FrameStateSlotOffset, FrameValType)>,
758}
759
760impl FrameData {
761    fn compute(frame: VirtualFrame, store_code: &StoreCode) -> Self {
762        let frame_table = store_code.code_memory().frame_table().unwrap();
763        // Parse the frame descriptor.
764        let (data, slot_to_fp_offset) = frame_table
765            .frame_descriptor(frame.frame_descriptor)
766            .unwrap();
767        let frame_state_slot = FrameStateSlot::parse(data).unwrap();
768        let slot_to_fp_offset = usize::try_from(slot_to_fp_offset).unwrap();
769
770        // Materialize the stack shape so we have O(1) access to its
771        // elements, and so we don't need to keep the borrow to the
772        // module alive.
773        let mut stack = frame_state_slot
774            .stack(frame.stack_shape)
775            .collect::<Vec<_>>();
776        stack.reverse(); // Put top-of-stack last.
777
778        // Materialize the local offsets/types so we don't need to
779        // keep the borrow to the module alive.
780        let locals = frame_state_slot.locals().collect::<Vec<_>>();
781
782        FrameData {
783            slot_to_fp_offset,
784            func_key: frame_state_slot.func_key(),
785            wasm_pc: frame.wasm_pc,
786            stack,
787            locals,
788        }
789    }
790
791    fn slot_addr(&self, fp: usize) -> *mut u8 {
792        let fp: *mut u8 = core::ptr::with_exposed_provenance_mut(fp);
793        fp.wrapping_sub(self.slot_to_fp_offset)
794    }
795}
796
797/// Read the value at the given offset.
798///
799/// # Safety
800///
801/// The `offset` and `ty` must correspond to a valid value written
802/// to the frame by generated code of the correct type. This will
803/// be the case if this information comes from the frame tables
804/// (as long as the frontend that generates the tables and
805/// instrumentation is correct, and as long as the tables are
806/// preserved through serialization).
807unsafe fn read_value(
808    store: &mut StoreOpaque,
809    slot_base: *const u8,
810    offset: FrameStateSlotOffset,
811    ty: FrameValType,
812) -> Val {
813    let address = unsafe { slot_base.offset(isize::try_from(offset.offset()).unwrap()) };
814
815    // SAFETY: each case reads a value from memory that should be valid
816    // according to our safety condition. State-slot values are packed without
817    // alignment padding, so these loads must accept unaligned addresses.
818    match ty {
819        FrameValType::I32 => {
820            let value = unsafe { (address as *const i32).read_unaligned() };
821            Val::I32(value)
822        }
823        FrameValType::I64 => {
824            let value = unsafe { (address as *const i64).read_unaligned() };
825            Val::I64(value)
826        }
827        FrameValType::F32 => {
828            let value = unsafe { (address as *const u32).read_unaligned() };
829            Val::F32(value)
830        }
831        FrameValType::F64 => {
832            let value = unsafe { (address as *const u64).read_unaligned() };
833            Val::F64(value)
834        }
835        FrameValType::V128 => {
836            // Vectors are always stored as little-endian.
837            let value =
838                unsafe { u128::from_le_bytes((address as *const [u8; 16]).read_unaligned()) };
839            Val::V128(value.into())
840        }
841        FrameValType::AnyRef => {
842            let mut nogc = AutoAssertNoGc::new(store);
843            let value = unsafe { (address as *const u32).read_unaligned() };
844            let value = AnyRef::_from_raw(&mut nogc, value);
845            Val::AnyRef(value)
846        }
847        FrameValType::ExnRef => {
848            let mut nogc = AutoAssertNoGc::new(store);
849            let value = unsafe { (address as *const u32).read_unaligned() };
850            let value = ExnRef::_from_raw(&mut nogc, value);
851            Val::ExnRef(value)
852        }
853        FrameValType::ExternRef => {
854            let mut nogc = AutoAssertNoGc::new(store);
855            let value = unsafe { (address as *const u32).read_unaligned() };
856            let value = ExternRef::_from_raw(&mut nogc, value);
857            Val::ExternRef(value)
858        }
859        FrameValType::FuncRef => {
860            let value = unsafe { (address as *const *mut c_void).read_unaligned() };
861            let value = unsafe { Func::_from_raw(store, value) };
862            Val::FuncRef(value)
863        }
864        FrameValType::ContRef => {
865            unimplemented!("contref values are not implemented in the host API yet")
866        }
867    }
868}
869
870/// Compute raw pointers to all GC refs in the given frame.
871// Note: ideally this would be an impl Iterator, but this is quite
872// awkward because of the locally computed data (FrameStateSlot::parse
873// structured result) within the closure borrowed by a nested closure.
874#[cfg(feature = "gc")]
875pub(crate) fn gc_refs_in_frame<'a>(ft: FrameTable<'a>, pc: u32, fp: *mut usize) -> Vec<*mut u32> {
876    let fp = fp.cast::<u8>();
877    let mut ret = vec![];
878    if let Some(frames) = ft.find_program_point(pc, FrameInstPos::Post) {
879        for (_wasm_pc, frame_desc, stack_shape) in frames {
880            let (frame_desc_data, slot_to_fp_offset) = ft.frame_descriptor(frame_desc).unwrap();
881            let frame_base = unsafe { fp.offset(-isize::try_from(slot_to_fp_offset).unwrap()) };
882            let frame_desc = FrameStateSlot::parse(frame_desc_data).unwrap();
883            for (offset, ty) in frame_desc.stack_and_locals(stack_shape) {
884                match ty {
885                    FrameValType::AnyRef | FrameValType::ExnRef | FrameValType::ExternRef => {
886                        let slot = unsafe {
887                            frame_base
888                                .offset(isize::try_from(offset.offset()).unwrap())
889                                .cast::<u32>()
890                        };
891                        ret.push(slot);
892                    }
893                    FrameValType::ContRef | FrameValType::FuncRef => {}
894                    FrameValType::I32
895                    | FrameValType::I64
896                    | FrameValType::F32
897                    | FrameValType::F64
898                    | FrameValType::V128 => {}
899                }
900            }
901        }
902    }
903    ret
904}
905
906/// One debug event that occurs when running Wasm code on a store with
907/// a debug handler attached.
908#[derive(Debug)]
909pub enum DebugEvent<'a> {
910    /// A [`wasmtime::Error`](crate::Error) was raised by a hostcall.
911    HostcallError(&'a crate::Error),
912    /// An exception is thrown by wasm.
913    ///
914    /// Note that the exception may be caught by wasm if there's an appropriate
915    /// handler on the stack, but the stack hasn't been searched yet. The
916    /// debugger can inject its own exception or overwrite this exception if
917    /// desired.
918    Exception(OwnedRooted<ExnRef>),
919    /// A Wasm trap occurred.
920    Trap(Trap),
921    /// A breakpoint was reached.
922    Breakpoint,
923    /// An epoch yield occurred.
924    EpochYield,
925    /// Guest code is about to write watched bytes of a linear memory; see
926    /// [`BreakpointState::watch`]. The write happens
927    /// once the handler returns.
928    Watchpoint(WatchpointHit),
929}
930
931/// A guest write to watched bytes of a linear memory, reported before the
932/// write happens.
933#[derive(Clone, Copy, Debug)]
934pub struct WatchpointHit {
935    /// The memory being written.
936    pub memory: crate::Memory,
937    /// The address of the write's first byte.
938    pub address: u64,
939    /// The number of bytes written, of which at least one is watched.
940    pub len: u64,
941    /// For a store of up to 16 bytes, the bits being stored (in
942    /// little-endian order, so the first byte is the least significant).
943    /// `None` for bulk memory operations.
944    pub value: Option<u128>,
945}
946
947impl PartialEq for WatchpointHit {
948    fn eq(&self, other: &Self) -> bool {
949        self.memory.same(&other.memory)
950            && (self.address, self.len, self.value) == (other.address, other.len, other.value)
951    }
952}
953
954impl Eq for WatchpointHit {}
955
956/// A handler for debug events.
957///
958/// This is an async callback that is invoked directly within the
959/// context of a debug event that occurs, i.e., with the Wasm code
960/// still on the stack. The callback can thus observe that stack, up
961/// to the most recent entry to Wasm.[^1]
962///
963/// Because this callback receives a `StoreContextMut`, it has full
964/// access to any state that any other hostcall has, including the
965/// `T`. In that way, it is like an epoch-deadline callback or a
966/// call-hook callback. It also "freezes" the entire store for the
967/// duration of the debugger callback future.
968///
969/// In the future, we expect to provide an "externally async" API on
970/// the `Store` that allows receiving a stream of debug events and
971/// accessing the store mutably while frozen; that will need to
972/// integrate with [`Store::run_concurrent`] to properly timeslice and
973/// scope the mutable access to the store, and has not been built
974/// yet. In the meantime, it should be possible to build a fully
975/// functional debugger with this async-callback API by channeling
976/// debug events out, and requests to read the store back in, over
977/// message-passing channels between the callback and an external
978/// debugger main loop.
979///
980/// Note that the `handle` hook may use its mutable store access to
981/// invoke another Wasm. Debug events will also be caught and will
982/// cause further `handle` invocations during this recursive
983/// invocation. It is up to the debugger to handle any implications of
984/// this reentrancy (e.g., implications on a duplex channel protocol
985/// with an event/continue handshake) if it does so.
986///
987/// Note also that this trait has `Clone` as a supertrait, and the
988/// handler is cloned at every invocation as an artifact of the
989/// internal ownership structure of Wasmtime: the handler itself is
990/// owned by the store, but also receives a mutable borrow to the
991/// whole store, so we need to clone it out to invoke it. It is
992/// recommended that this trait be implemented by a type that is cheap
993/// to clone: for example, a single `Arc` handle to debugger state.
994///
995/// [^1]: Providing visibility further than the most recent entry to
996///       Wasm is not directly possible because it could see into
997///       another async stack, and the stack that polls the future
998///       running a particular Wasm invocation could change after each
999///       suspend point in the handler.
1000///
1001/// [`Store::run_concurrent`]: crate::Store::run_concurrent
1002pub trait DebugHandler: Clone + Send + Sync + 'static {
1003    /// The data expected on the store that this handler is attached
1004    /// to.
1005    type Data;
1006
1007    /// Handle a debug event.
1008    fn handle(
1009        &self,
1010        store: StoreContextMut<'_, Self::Data>,
1011        event: DebugEvent<'_>,
1012    ) -> impl Future<Output = ()> + Send;
1013}
1014
1015/// Breakpoint state for modules within a store.
1016#[derive(Default)]
1017pub(crate) struct Breakpoints {
1018    /// Single-step mode.
1019    single_step: bool,
1020    /// Breakpoints added individually. Maps from the actual
1021    /// (possibly slipped-forward) breakpoint key to a reference
1022    /// count. Multiple requested PCs may map to the same actual
1023    /// breakpoint when they are slipped forward.
1024    breakpoints: BTreeMap<BreakpointKey, usize>,
1025    /// When a requested breakpoint PC does not exactly match an
1026    /// opcode boundary, we "slip" it forward to the next available
1027    /// PC. This map records the redirect from the requested key to
1028    /// the actual key so that `remove_breakpoint` can undo it.
1029    breakpoint_redirects: BTreeMap<BreakpointKey, BreakpointKey>,
1030}
1031
1032/// A breakpoint.
1033pub struct Breakpoint {
1034    /// Reference to the module in which we are setting the breakpoint.
1035    pub module: Module,
1036    /// Module-relative Wasm PC offset.
1037    pub pc: ModulePC,
1038}
1039
1040#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1041struct BreakpointKey(CompiledModuleId, ModulePC);
1042
1043impl BreakpointKey {
1044    fn from_raw(module: &Module, pc: ModulePC) -> BreakpointKey {
1045        BreakpointKey(module.id(), pc)
1046    }
1047
1048    fn get(&self, registry: &ModuleRegistry) -> Breakpoint {
1049        let module = registry
1050            .module_by_compiled_id(self.0)
1051            .expect("Module should not have been removed from Store")
1052            .clone();
1053        Breakpoint { module, pc: self.1 }
1054    }
1055}
1056
1057/// A store's breakpoint state: its breakpoints, single-stepping, and
1058/// watchpoints, borrowed for editing.
1059///
1060/// Obtained with [`Store::edit_breakpoints`] (or the same method on
1061/// [`StoreContextMut`] and [`Caller`]).
1062///
1063/// Changes to breakpoints and single-stepping are batched for
1064/// efficiency, because code-patching has some overhead (e.g. syscalls
1065/// to write-protect/re-publish code).
1066pub struct BreakpointState<'a> {
1067    store: &'a mut StoreOpaque,
1068    /// Modules that have been edited.
1069    ///
1070    /// Invariant: each of these modules' CodeMemory objects is
1071    /// *unpublished* when in the dirty set.
1072    dirty_modules: BTreeSet<StoreCodePC>,
1073}
1074
1075impl Breakpoints {
1076    pub(crate) fn breakpoints<'a>(
1077        &'a self,
1078        registry: &'a ModuleRegistry,
1079    ) -> impl Iterator<Item = Breakpoint> + 'a {
1080        self.breakpoints.keys().map(|key| key.get(registry))
1081    }
1082
1083    pub(crate) fn is_single_step(&self) -> bool {
1084        self.single_step
1085    }
1086
1087    /// Internal helper to patch a new module for
1088    /// single-stepping. When a module is newly registered in a
1089    /// `Store`, we need to patch all breakpoints into the copy for
1090    /// this `Store` if single-stepping is currently enabled.
1091    pub(crate) fn patch_new_module(&self, code: &mut StoreCode, module: &Module) -> Result<()> {
1092        // Apply single-step state if single-stepping is enabled. Note
1093        // that no other individual breakpoints will exist yet (as
1094        // this is a newly registered module).
1095        if self.single_step {
1096            let mem = code.code_memory_mut().unwrap();
1097            mem.unpublish()?;
1098            BreakpointState::apply_single_step(mem, module, true, |_key| false)?;
1099            mem.publish()?;
1100        }
1101        Ok(())
1102    }
1103}
1104
1105impl<'a> BreakpointState<'a> {
1106    /// Errors if `module` does not belong to the same engine as the store
1107    /// being edited.
1108    ///
1109    /// The module registry rejects foreign modules on its own, but breakpoint
1110    /// bookkeeping is updated before a module reaches the registry, and
1111    /// removing a breakpoint that was never added does not touch the registry
1112    /// at all. So this editing session checks up front as well.
1113    fn check_engine(&self, module: &Module) -> Result<()> {
1114        crate::ensure!(
1115            crate::Engine::same(self.store.engine(), module.engine()),
1116            "cross-`Engine` breakpoint editing is not supported"
1117        );
1118        Ok(())
1119    }
1120
1121    fn get_code_memory<'b>(
1122        breakpoints: &Breakpoints,
1123        registry: &'b mut ModuleRegistry,
1124        dirty_modules: &mut BTreeSet<StoreCodePC>,
1125        module: &Module,
1126    ) -> Result<&'b mut CodeMemory> {
1127        let store_code_pc =
1128            registry.store_code_base_or_register(module, RegisterBreakpointState(breakpoints))?;
1129        let code_memory = registry
1130            .store_code_mut(store_code_pc)
1131            .expect("Just checked presence above")
1132            .code_memory_mut()
1133            .expect("Must have unique ownership of StoreCode in guest-debug mode");
1134        if dirty_modules.insert(store_code_pc) {
1135            code_memory.unpublish()?;
1136        }
1137        Ok(code_memory)
1138    }
1139
1140    fn patch<'b>(
1141        patches: impl Iterator<Item = FrameTableBreakpointData<'b>> + 'b,
1142        mem: &mut CodeMemory,
1143        enable: bool,
1144    ) {
1145        let mem = mem.text_mut();
1146        for patch in patches {
1147            let data = if enable { patch.enable } else { patch.disable };
1148            let mem = &mut mem[patch.offset..patch.offset + data.len()];
1149            log::trace!(
1150                "patch: offset 0x{:x} with enable={enable}: data {data:?} replacing {mem:?}",
1151                patch.offset
1152            );
1153            mem.copy_from_slice(data);
1154        }
1155    }
1156
1157    /// Add a breakpoint in the given module at the given PC in that
1158    /// module.
1159    ///
1160    /// If the requested PC does not fall exactly on an opcode
1161    /// boundary, the breakpoint is "slipped" forward to the next
1162    /// available opcode PC.
1163    ///
1164    /// No effect if the breakpoint is already set.
1165    ///
1166    /// # Errors
1167    ///
1168    /// Returns an error if `module` was not compiled by the same engine as
1169    /// the store being edited.
1170    pub fn add_breakpoint(&mut self, module: &Module, pc: ModulePC) -> Result<()> {
1171        self.check_engine(module)?;
1172        let frame_table = module
1173            .frame_table()
1174            .expect("Frame table must be present when guest-debug is enabled");
1175        let actual_pc = frame_table.nearest_breakpoint(pc).unwrap_or(pc);
1176        let requested_key = BreakpointKey::from_raw(module, pc);
1177        let actual_key = BreakpointKey::from_raw(module, actual_pc);
1178
1179        let (state, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1180        if actual_pc != pc {
1181            log::trace!("slipping breakpoint from {requested_key:?} to {actual_key:?}");
1182            state.breakpoint_redirects.insert(requested_key, actual_key);
1183        }
1184
1185        let refcount = state.breakpoints.entry(actual_key).or_insert(0);
1186        *refcount += 1;
1187        if *refcount == 1 {
1188            // First reference: actually patch the code.
1189            let mem = Self::get_code_memory(state, registry, &mut self.dirty_modules, module)?;
1190            let patches = frame_table.lookup_breakpoint_patches_by_pc(actual_pc);
1191            Self::patch(patches, mem, true);
1192        }
1193        Ok(())
1194    }
1195
1196    /// Remove a breakpoint in the given module at the given PC in
1197    /// that module.
1198    ///
1199    /// No effect if the breakpoint was not set.
1200    ///
1201    /// # Errors
1202    ///
1203    /// Returns an error if `module` was not compiled by the same engine as
1204    /// the store being edited.
1205    pub fn remove_breakpoint(&mut self, module: &Module, pc: ModulePC) -> Result<()> {
1206        self.check_engine(module)?;
1207        let (state, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1208        let requested_key = BreakpointKey::from_raw(module, pc);
1209        let actual_key = state
1210            .breakpoint_redirects
1211            .remove(&requested_key)
1212            .unwrap_or(requested_key);
1213        let actual_pc = actual_key.1;
1214
1215        if let Some(refcount) = state.breakpoints.get_mut(&actual_key) {
1216            *refcount -= 1;
1217            if *refcount == 0 {
1218                state.breakpoints.remove(&actual_key);
1219                if !state.single_step {
1220                    let mem =
1221                        Self::get_code_memory(state, registry, &mut self.dirty_modules, module)?;
1222                    let frame_table = module
1223                        .frame_table()
1224                        .expect("Frame table must be present when guest-debug is enabled");
1225                    let patches = frame_table.lookup_breakpoint_patches_by_pc(actual_pc);
1226                    Self::patch(patches, mem, false);
1227                }
1228            }
1229        }
1230        Ok(())
1231    }
1232
1233    fn apply_single_step<F: Fn(&BreakpointKey) -> bool>(
1234        mem: &mut CodeMemory,
1235        module: &Module,
1236        enabled: bool,
1237        key_enabled: F,
1238    ) -> Result<()> {
1239        let table = module
1240            .frame_table()
1241            .expect("Frame table must be present when guest-debug is enabled");
1242        // A component's modules share one code image, and so its frame table: leave the
1243        // other modules' code to them, or this module's breakpoints decide theirs.
1244        let index = module.compiled_module().module_index();
1245        for (patch_module, wasm_pc, patch) in table.breakpoint_patches() {
1246            if patch_module != index {
1247                continue;
1248            }
1249            let key = BreakpointKey::from_raw(&module, wasm_pc);
1250            let this_enabled = enabled || key_enabled(&key);
1251            log::trace!(
1252                "single_step: enabled {enabled} key {key:?} -> this_enabled {this_enabled}"
1253            );
1254            Self::patch(core::iter::once(patch), mem, this_enabled);
1255        }
1256        Ok(())
1257    }
1258
1259    /// Turn on or off single-step mode.
1260    ///
1261    /// In single-step mode, a breakpoint event is emitted at every
1262    /// Wasm PC.
1263    pub fn single_step(&mut self, enabled: bool) -> Result<()> {
1264        let (state, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1265        log::trace!(
1266            "single_step({enabled}) with breakpoint set {:?}",
1267            state.breakpoints
1268        );
1269        if state.single_step == enabled {
1270            // No change to current state; don't go through the effort of re-patching and
1271            // re-publishing code.
1272            return Ok(());
1273        }
1274        let modules = registry
1275            .all_modules()
1276            .map(|(_, m)| m.clone())
1277            .collect::<Vec<_>>();
1278        for module in modules {
1279            let mem = Self::get_code_memory(state, registry, &mut self.dirty_modules, &module)?;
1280            Self::apply_single_step(mem, &module, enabled, |key| {
1281                state.breakpoints.contains_key(key)
1282            })?;
1283        }
1284
1285        state.single_step = enabled;
1286
1287        Ok(())
1288    }
1289
1290    /// Watches guest writes to the bytes in `range` of `memory`. A guest
1291    /// store or bulk memory operation that writes a watched byte raises
1292    /// [`DebugEvent::Watchpoint`] before writing.
1293    ///
1294    /// Whether a byte is watched is a per-byte property: watching an
1295    /// already-watched byte has no further effect. See
1296    /// [`BreakpointState::unwatch`].
1297    ///
1298    /// # Errors
1299    ///
1300    /// Returns an error if `range` extends beyond the memory's current size.
1301    ///
1302    /// # Panics
1303    ///
1304    /// Panics if `memory` does not belong to this store.
1305    pub fn watch(&mut self, memory: crate::Memory, range: core::ops::Range<u64>) -> Result<()> {
1306        self.store.debug_set_watch(memory, range, true)
1307    }
1308
1309    /// Stops watching guest writes to the bytes in `range` of `memory`.
1310    ///
1311    /// Whether a byte is watched is a per-byte property, so this unwatches
1312    /// every byte in `range`, regardless of how many (possibly overlapping)
1313    /// calls to [`BreakpointState::watch`] watched it.
1314    ///
1315    /// # Errors
1316    ///
1317    /// Returns an error if `range` extends beyond the memory's current size.
1318    ///
1319    /// # Panics
1320    ///
1321    /// Panics if `memory` does not belong to this store.
1322    pub fn unwatch(&mut self, memory: crate::Memory, range: core::ops::Range<u64>) -> Result<()> {
1323        self.store.debug_set_watch(memory, range, false)
1324    }
1325}
1326
1327impl<'a> Drop for BreakpointState<'a> {
1328    fn drop(&mut self) {
1329        let (_, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1330        for &store_code_base in &self.dirty_modules {
1331            let store_code = registry.store_code_mut(store_code_base).unwrap();
1332            if let Err(e) = store_code
1333                .code_memory_mut()
1334                .expect("Must have unique ownership of StoreCode in guest-debug mode")
1335                .publish()
1336            {
1337                abort_on_republish_error(e);
1338            }
1339        }
1340    }
1341}
1342
1343/// Abort when we cannot re-publish executable code.
1344///
1345/// Note that this puts us in quite a conundrum. Typically we will
1346/// have been editing breakpoints from within a hostcall context
1347/// (e.g. inside a debugger hook while execution is paused) with JIT
1348/// code on the stack. Wasmtime's usual path to return errors is back
1349/// through that JIT code: we do not panic-unwind across the JIT code,
1350/// we return into the exit trampoline and that then re-enters the
1351/// raise libcall to use a Cranelift exception-throw to cross most of
1352/// the JIT frames to the entry trampoline. When even trampolines are
1353/// no longer executable, we have no way out. Even an ordinary
1354/// `panic!` cannot work, because we catch panics and carry them
1355/// across JIT code using that trampoline-based error path. Our only
1356/// way out is to directly abort the whole process.
1357///
1358/// This is not without precedent: other engines have similar failure
1359/// paths. For example, SpiderMonkey directly aborts the process when
1360/// failing to re-apply executable permissions (see [1]).
1361///
1362/// Note that we don't really expect to ever hit this case in
1363/// practice: it's unlikely that `mprotect` applying `PROT_EXEC` would
1364/// fail due to, e.g., resource exhaustion in the kernel, because we
1365/// will have the same net number of virtual memory areas before and
1366/// after the permissions change. Nevertheless, we have to account for
1367/// the possibility of error.
1368///
1369/// [1]: https://searchfox.org/firefox-main/rev/7496c8515212669451d7e775a00c2be07da38ca5/js/src/jit/AutoWritableJitCode.h#26-56
1370#[cfg(feature = "std")]
1371fn abort_on_republish_error(e: crate::Error) -> ! {
1372    log::error!(
1373        "Failed to re-publish executable code: {e:?}. Wasmtime cannot return through JIT code on the stack and cannot even panic; aborting the process."
1374    );
1375    std::process::abort();
1376}
1377
1378/// In the `no_std` case, we don't have a concept of a "process
1379/// abort", so rely on `panic!`. Typically an embedded scenario that
1380/// uses `no_std` will build with `panic=abort` so the effect is the
1381/// same. If it doesn't, there is truly nothing we can do here so
1382/// let's panic anyway; the panic propagation through the trampolines
1383/// will at least deterministically crash.
1384#[cfg(not(feature = "std"))]
1385fn abort_on_republish_error(e: crate::Error) -> ! {
1386    panic!("Failed to re-publish executable code: {e:?}");
1387}