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 }
531 
532 /// A handler for debug events.
533 ///
534 /// This is an async callback that is invoked directly within the
535 /// context of a debug event that occurs, i.e., with the Wasm code
536 /// still on the stack. The callback can thus observe that stack, up
537 /// to the most recent entry to Wasm.[^1]
538 ///
539 /// Because this callback receives a `StoreContextMut`, it has full
540 /// access to any state that any other hostcall has, including the
541 /// `T`. In that way, it is like an epoch-deadline callback or a
542 /// call-hook callback. It also "freezes" the entire store for the
543 /// duration of the debugger callback future.
544 ///
545 /// In the future, we expect to provide an "externally async" API on
546 /// the `Store` that allows receiving a stream of debug events and
547 /// accessing the store mutably while frozen; that will need to
548 /// integrate with [`Store::run_concurrent`] to properly timeslice and
549 /// scope the mutable access to the store, and has not been built
550 /// yet. In the meantime, it should be possible to build a fully
551 /// functional debugger with this async-callback API by channeling
552 /// debug events out, and requests to read the store back in, over
553 /// message-passing channels between the callback and an external
554 /// debugger main loop.
555 ///
556 /// Note that the `handle` hook may use its mutable store access to
557 /// invoke another Wasm. Debug events will also be caught and will
558 /// cause further `handle` invocations during this recursive
559 /// invocation. It is up to the debugger to handle any implications of
560 /// this reentrancy (e.g., implications on a duplex channel protocol
561 /// with an event/continue handshake) if it does so.
562 ///
563 /// Note also that this trait has `Clone` as a supertrait, and the
564 /// handler is cloned at every invocation as an artifact of the
565 /// internal ownership structure of Wasmtime: the handler itself is
566 /// owned by the store, but also receives a mutable borrow to the
567 /// whole store, so we need to clone it out to invoke it. It is
568 /// recommended that this trait be implemented by a type that is cheap
569 /// to clone: for example, a single `Arc` handle to debugger state.
570 ///
571 /// [^1]: Providing visibility further than the most recent entry to
572 ///       Wasm is not directly possible because it could see into
573 ///       another async stack, and the stack that polls the future
574 ///       running a particular Wasm invocation could change after each
575 ///       suspend point in the handler.
576 pub trait DebugHandler: Clone + Send + Sync + 'static {
577     /// The data expected on the store that this handler is attached
578     /// to.
579     type Data;
580 
581     /// Handle a debug event.
582     fn handle(
583         &self,
584         store: StoreContextMut<'_, Self::Data>,
585         event: DebugEvent<'_>,
586     ) -> impl Future<Output = ()> + Send;
587 }
588 
589 /// Breakpoint state for modules within a store.
590 #[derive(Default)]
591 pub(crate) struct BreakpointState {
592     /// Single-step mode.
593     single_step: bool,
594     /// Breakpoints added individually.
595     breakpoints: BTreeSet<BreakpointKey>,
596 }
597 
598 /// A breakpoint.
599 pub struct Breakpoint {
600     /// Reference to the module in which we are setting the breakpoint.
601     pub module: Module,
602     /// Wasm PC offset within the module.
603     pub pc: u32,
604 }
605 
606 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
607 struct BreakpointKey(CompiledModuleId, u32);
608 
609 impl BreakpointKey {
610     fn from_raw(module: &Module, pc: u32) -> BreakpointKey {
611         BreakpointKey(module.id(), pc)
612     }
613 
614     fn get(&self, registry: &ModuleRegistry) -> Breakpoint {
615         let module = registry
616             .module_by_compiled_id(self.0)
617             .expect("Module should not have been removed from Store")
618             .clone();
619         Breakpoint { module, pc: self.1 }
620     }
621 }
622 
623 /// A breakpoint-editing session.
624 ///
625 /// This enables updating breakpoint state (setting or unsetting
626 /// individual breakpoints or the store-global single-step flag) in a
627 /// batch. It is more efficient to batch these updates because
628 /// "re-publishing" the newly patched code, with update breakpoint
629 /// settings, typically requires a syscall to re-enable execute
630 /// permissions.
631 pub struct BreakpointEdit<'a> {
632     state: &'a mut BreakpointState,
633     registry: &'a mut ModuleRegistry,
634     /// Modules that have been edited.
635     ///
636     /// Invariant: each of these modules' CodeMemory objects is
637     /// *unpublished* when in the dirty set.
638     dirty_modules: BTreeSet<StoreCodePC>,
639 }
640 
641 impl BreakpointState {
642     pub(crate) fn edit<'a>(&'a mut self, registry: &'a mut ModuleRegistry) -> BreakpointEdit<'a> {
643         BreakpointEdit {
644             state: self,
645             registry,
646             dirty_modules: BTreeSet::new(),
647         }
648     }
649 
650     pub(crate) fn breakpoints<'a>(
651         &'a self,
652         registry: &'a ModuleRegistry,
653     ) -> impl Iterator<Item = Breakpoint> + 'a {
654         self.breakpoints.iter().map(|key| key.get(registry))
655     }
656 
657     pub(crate) fn is_single_step(&self) -> bool {
658         self.single_step
659     }
660 }
661 
662 impl<'a> BreakpointEdit<'a> {
663     fn get_code_memory<'b>(
664         registry: &'b mut ModuleRegistry,
665         dirty_modules: &mut BTreeSet<StoreCodePC>,
666         module: &Module,
667     ) -> Result<&'b mut CodeMemory> {
668         let store_code_pc = registry.store_code_base_or_register(module)?;
669         let code_memory = registry
670             .store_code_mut(store_code_pc)
671             .expect("Just checked presence above")
672             .code_memory_mut()
673             .expect("Must have unique ownership of StoreCode in guest-debug mode");
674         if dirty_modules.insert(store_code_pc) {
675             code_memory.unpublish()?;
676         }
677         Ok(code_memory)
678     }
679 
680     fn patch<'b>(
681         patches: impl Iterator<Item = FrameTableBreakpointData<'b>> + 'b,
682         mem: &mut CodeMemory,
683         enable: bool,
684     ) {
685         let mem = mem.text_mut();
686         for patch in patches {
687             let data = if enable { patch.enable } else { patch.disable };
688             let mem = &mut mem[patch.offset..patch.offset + data.len()];
689             log::trace!(
690                 "patch: offset 0x{:x} with enable={enable}: data {data:?} replacing {mem:?}",
691                 patch.offset
692             );
693             mem.copy_from_slice(data);
694         }
695     }
696 
697     /// Add a breakpoint in the given module at the given PC in that
698     /// module.
699     ///
700     /// No effect if the breakpoint is already set.
701     pub fn add_breakpoint(&mut self, module: &Module, pc: u32) -> Result<()> {
702         let key = BreakpointKey::from_raw(module, pc);
703         self.state.breakpoints.insert(key);
704         log::trace!("patching in breakpoint {key:?}");
705         let mem = Self::get_code_memory(self.registry, &mut self.dirty_modules, module)?;
706         let frame_table = module
707             .frame_table()
708             .expect("Frame table must be present when guest-debug is enabled");
709         let patches = frame_table.lookup_breakpoint_patches_by_pc(pc);
710         Self::patch(patches, mem, true);
711         Ok(())
712     }
713 
714     /// Remove a breakpoint in the given module at the given PC in
715     /// that module.
716     ///
717     /// No effect if the breakpoint was not set.
718     pub fn remove_breakpoint(&mut self, module: &Module, pc: u32) -> Result<()> {
719         let key = BreakpointKey::from_raw(module, pc);
720         self.state.breakpoints.remove(&key);
721         if !self.state.single_step {
722             let mem = Self::get_code_memory(self.registry, &mut self.dirty_modules, module)?;
723             let frame_table = module
724                 .frame_table()
725                 .expect("Frame table must be present when guest-debug is enabled");
726             let patches = frame_table.lookup_breakpoint_patches_by_pc(pc);
727             Self::patch(patches, mem, false);
728         }
729         Ok(())
730     }
731 
732     /// Turn on or off single-step mode.
733     ///
734     /// In single-step mode, a breakpoint event is emitted at every
735     /// Wasm PC.
736     pub fn single_step(&mut self, enabled: bool) -> Result<()> {
737         log::trace!(
738             "single_step({enabled}) with breakpoint set {:?}",
739             self.state.breakpoints
740         );
741         let modules = self.registry.all_modules().cloned().collect::<Vec<_>>();
742         for module in modules {
743             let mem = Self::get_code_memory(self.registry, &mut self.dirty_modules, &module)?;
744             let table = module
745                 .frame_table()
746                 .expect("Frame table must be present when guest-debug is enabled");
747             for (wasm_pc, patch) in table.breakpoint_patches() {
748                 let key = BreakpointKey::from_raw(&module, wasm_pc);
749                 let this_enabled = enabled || self.state.breakpoints.contains(&key);
750                 log::trace!(
751                     "single_step: enabled {enabled} key {key:?} -> this_enabled {this_enabled}"
752                 );
753                 Self::patch(core::iter::once(patch), mem, this_enabled);
754             }
755         }
756 
757         self.state.single_step = enabled;
758 
759         Ok(())
760     }
761 }
762 
763 impl<'a> Drop for BreakpointEdit<'a> {
764     fn drop(&mut self) {
765         for &store_code_base in &self.dirty_modules {
766             let store_code = self.registry.store_code_mut(store_code_base).unwrap();
767             if let Err(e) = store_code
768                 .code_memory_mut()
769                 .expect("Must have unique ownership of StoreCode in guest-debug mode")
770                 .publish()
771             {
772                 abort_on_republish_error(e);
773             }
774         }
775     }
776 }
777 
778 /// Abort when we cannot re-publish executable code.
779 ///
780 /// Note that this puts us in quite a conundrum. Typically we will
781 /// have been editing breakpoints from within a hostcall context
782 /// (e.g. inside a debugger hook while execution is paused) with JIT
783 /// code on the stack. Wasmtime's usual path to return errors is back
784 /// through that JIT code: we do not panic-unwind across the JIT code,
785 /// we return into the exit trampoline and that then re-enters the
786 /// raise libcall to use a Cranelift exception-throw to cross most of
787 /// the JIT frames to the entry trampoline. When even trampolines are
788 /// no longer executable, we have no way out. Even an ordinary
789 /// `panic!` cannot work, because we catch panics and carry them
790 /// across JIT code using that trampoline-based error path. Our only
791 /// way out is to directly abort the whole process.
792 ///
793 /// This is not without precedent: other engines have similar failure
794 /// paths. For example, SpiderMonkey directly aborts the process when
795 /// failing to re-apply executable permissions (see [1]).
796 ///
797 /// Note that we don't really expect to ever hit this case in
798 /// practice: it's unlikely that `mprotect` applying `PROT_EXEC` would
799 /// fail due to, e.g., resource exhaustion in the kernel, because we
800 /// will have the same net number of virtual memory areas before and
801 /// after the permissions change. Nevertheless, we have to account for
802 /// the possibility of error.
803 ///
804 /// [1]: https://searchfox.org/firefox-main/rev/7496c8515212669451d7e775a00c2be07da38ca5/js/src/jit/AutoWritableJitCode.h#26-56
805 #[cfg(feature = "std")]
806 fn abort_on_republish_error(e: anyhow::Error) -> ! {
807     log::error!(
808         "Failed to re-publish executable code: {e:?}. Wasmtime cannot return through JIT code on the stack and cannot even panic; aborting the process."
809     );
810     std::process::abort();
811 }
812 
813 /// In the `no_std` case, we don't have a concept of a "process
814 /// abort", so rely on `panic!`. Typically an embedded scenario that
815 /// uses `no_std` will build with `panic=abort` so the effect is the
816 /// same. If it doesn't, there is truly nothing we can do here so
817 /// let's panic anyway; the panic propagation through the trampolines
818 /// will at least deterministically crash.
819 #[cfg(not(feature = "std"))]
820 fn abort_on_republish_error(e: anyhow::Error) -> ! {
821     panic!("Failed to re-publish executable code: {e:?}");
822 }
823