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