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, FrameOrHostCode, StoreBacktrace, 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         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 
68 impl<'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.
91 pub 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)]
123 pub 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 
131 impl<'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).
278 struct 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 
295 impl 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.
326 struct 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 
348 impl 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).
392 unsafe 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")]
457 pub(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 
488 impl<'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 }
494 impl<'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)]
503 pub 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.
563 pub 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)]
578 pub(crate) struct BreakpointState {
579     /// Single-step mode.
580     single_step: bool,
581     /// Breakpoints added individually.
582     breakpoints: BTreeSet<BreakpointKey>,
583 }
584 
585 /// A breakpoint.
586 pub 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)]
594 struct BreakpointKey(CompiledModuleId, u32);
595 
596 impl 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.
618 pub 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 
628 impl 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 
649 impl<'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 
750 impl<'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")]
793 fn 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"))]
807 fn abort_on_republish_error(e: anyhow::Error) -> ! {
808     panic!("Failed to re-publish executable code: {e:?}");
809 }
810