wasmtime/runtime/
debug.rs

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