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