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