wasmtime/runtime/debug.rs
1//! Debugging API.
2
3use super::store::AsStoreOpaque;
4use crate::code::StoreCode;
5use crate::module::RegisterBreakpointState;
6use crate::store::StoreId;
7use crate::vm::{Activation, Backtrace};
8use crate::{
9 AnyRef, AsContextMut, CodeMemory, ExnRef, Extern, ExternRef, Func, Instance, Module,
10 OwnedRooted, StoreContext, StoreContextMut, Val,
11 code::StoreCodePC,
12 module::ModuleRegistry,
13 store::{AutoAssertNoGc, StoreOpaque},
14 vm::{CompiledModuleId, VMContext},
15};
16use crate::{Caller, Result, Store};
17use alloc::collections::{BTreeMap, BTreeSet, btree_map::Entry};
18use alloc::vec;
19use alloc::vec::Vec;
20use core::{ffi::c_void, ptr::NonNull};
21#[cfg(feature = "gc")]
22use wasmtime_environ::FrameTable;
23// Re-export ModulePC so downstream crates can use it.
24pub use wasmtime_environ::ModulePC;
25use wasmtime_environ::{
26 DefinedFuncIndex, EntityIndex, FrameInstPos, FrameStackShape, FrameStateSlot,
27 FrameStateSlotOffset, FrameTableBreakpointData, FrameTableDescriptorIndex, FrameValType,
28 FuncIndex, FuncKey, GlobalIndex, MemoryIndex, TableIndex, TagIndex, Trap,
29};
30use wasmtime_unwinder::{Frame, FrameCursor};
31
32impl<T> Store<T> {
33 /// Provide a frame handle for all activations, in order from
34 /// innermost (most recently called) to outermost on the stack.
35 ///
36 /// An activation is a contiguous sequence of Wasm frames (called
37 /// functions) that were called from host code and called back out
38 /// to host code. If there are activations from multiple stores on
39 /// the stack, for example if Wasm code in one store calls out to
40 /// host code which invokes another Wasm function in another
41 /// store, then the other stores are "opaque" to our view here in
42 /// the same way that host code is.
43 ///
44 /// Returns an empty list if debug instrumentation is not enabled
45 /// for the engine containing this store.
46 pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
47 self.as_store_opaque().debug_exit_frames()
48 }
49
50 /// Borrow this store's [`BreakpointState`], to edit its
51 /// breakpoints, single-stepping, or watchpoints.
52 ///
53 /// Returns `None` if guest debugging is not enabled.
54 pub fn edit_breakpoints<'a>(&'a mut self) -> Option<BreakpointState<'a>> {
55 self.as_store_opaque().edit_breakpoints()
56 }
57
58 /// Get a vector of all Instances held in the Store, for debug
59 /// purposes.
60 ///
61 /// Guest debugging must be enabled for this accessor to return
62 /// any instances. If it is not, an empty vector is returned.
63 pub fn debug_all_instances(&mut self) -> Vec<Instance> {
64 self.as_store_opaque().debug_all_instances()
65 }
66
67 /// Get a vector of all Modules held in the Store, for debug
68 /// purposes.
69 ///
70 /// Guest debugging must be enabled for this accessor to return
71 /// any modules. If it is not, an empty vector is returned.
72 pub fn debug_all_modules(&mut self) -> Vec<Module> {
73 self.as_store_opaque().debug_all_modules()
74 }
75}
76
77impl<'a, T> StoreContextMut<'a, T> {
78 /// Provide a frame handle for all activations, in order from
79 /// innermost (most recently called) to outermost on the stack.
80 ///
81 /// See [`Store::debug_exit_frames`] for more details.
82 pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
83 self.0.as_store_opaque().debug_exit_frames()
84 }
85
86 /// Borrow this store's [`BreakpointState`].
87 ///
88 /// See [`Store::edit_breakpoints`] for more details.
89 pub fn edit_breakpoints(self) -> Option<BreakpointState<'a>> {
90 self.0.as_store_opaque().edit_breakpoints()
91 }
92
93 /// Get a vector of all Instances held in the Store, for debug
94 /// purposes.
95 ///
96 /// See [`Store::debug_all_instances`] for more details.
97 pub fn debug_all_instances(self) -> Vec<Instance> {
98 self.0.as_store_opaque().debug_all_instances()
99 }
100
101 /// Get a vector of all Modules held in the Store, for debug
102 /// purposes.
103 ///
104 /// See [`Store::debug_all_modules`] for more details.
105 pub fn debug_all_modules(self) -> Vec<Module> {
106 self.0.as_store_opaque().debug_all_modules()
107 }
108}
109
110impl<'a, T> Caller<'a, T> {
111 /// Provide a frame handle for all activations, in order from
112 /// innermost (most recently called) to outermost on the stack.
113 ///
114 /// See [`Store::debug_exit_frames`] for more details.
115 pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
116 self.store.0.as_store_opaque().debug_exit_frames()
117 }
118
119 /// Borrow this store's [`BreakpointState`].
120 ///
121 /// See [`Store::edit_breakpoints`] for more details.
122 pub fn edit_breakpoints<'b>(&'b mut self) -> Option<BreakpointState<'b>> {
123 self.store.0.as_store_opaque().edit_breakpoints()
124 }
125
126 /// Get a vector of all Instances held in the Store, for debug
127 /// purposes.
128 ///
129 /// See [`Store::debug_all_instances`] for more details.
130 pub fn debug_all_instances(&mut self) -> Vec<Instance> {
131 self.store.0.as_store_opaque().debug_all_instances()
132 }
133
134 /// Get a vector of all Modules held in the Store, for debug
135 /// purposes.
136 ///
137 /// See [`Store::debug_all_modules`] for more details.
138 pub fn debug_all_modules(&mut self) -> Vec<Module> {
139 self.store.0.as_store_opaque().debug_all_modules()
140 }
141}
142
143impl StoreOpaque {
144 /// Sets or clears the watch bits of the bytes in `range` of `memory`; see
145 /// [`BreakpointState::watch`].
146 pub(crate) fn debug_set_watch(
147 &mut self,
148 memory: crate::Memory,
149 range: core::ops::Range<u64>,
150 watch: bool,
151 ) -> Result<()> {
152 let Some(shadow) = memory.vm_shadow_mut(self) else {
153 crate::error::bail!("watchpoints require guest debugging");
154 };
155 let bytes = shadow.bytes_mut();
156 let start = usize::try_from(range.start)?;
157 let end = usize::try_from(range.end)?;
158 let Some(bytes) = bytes.get_mut(start..end) else {
159 crate::error::bail!("watchpoint range is out of bounds of the memory");
160 };
161 for byte in bytes {
162 if watch {
163 *byte |= crate::runtime::vm::WATCH_DEBUG;
164 } else {
165 *byte &= !crate::runtime::vm::WATCH_DEBUG;
166 }
167 }
168 Ok(())
169 }
170
171 fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
172 let activations = if self.engine().tunables().debug_guest {
173 Backtrace::activations(self)
174 } else {
175 vec![]
176 };
177
178 activations
179 .into_iter()
180 // SAFETY: each activation is currently active and will
181 // remain so (we have a mutable borrow of the store).
182 .filter_map(|act| unsafe { FrameHandle::exit_frame(self, act) })
183 }
184
185 pub(crate) fn edit_breakpoints<'a>(&'a mut self) -> Option<BreakpointState<'a>> {
186 if !self.engine().tunables().debug_guest {
187 return None;
188 }
189
190 Some(BreakpointState {
191 store: self,
192 dirty_modules: BTreeSet::new(),
193 })
194 }
195
196 fn debug_all_instances(&mut self) -> Vec<Instance> {
197 if !self.engine().tunables().debug_guest {
198 return vec![];
199 }
200
201 self.all_instances().collect()
202 }
203
204 fn debug_all_modules(&self) -> Vec<Module> {
205 if !self.engine().tunables().debug_guest {
206 return vec![];
207 }
208
209 self.modules()
210 .all_modules()
211 .map(|(_, m)| m.clone())
212 .collect()
213 }
214}
215
216impl Instance {
217 /// Get access to a global within this instance's globals index
218 /// space.
219 ///
220 /// This permits accessing globals whether they are exported or
221 /// not. However, it is only available for purposes of debugging,
222 /// and so is only permitted when `guest_debug` is enabled in the
223 /// Engine's configuration. The intent of the Wasmtime API is to
224 /// enforce the Wasm type system's encapsulation even in the host
225 /// API, except where necessary for developer tooling.
226 ///
227 /// `None` is returned for any global index that is out-of-bounds.
228 ///
229 /// `None` is returned if guest-debugging is not enabled in the
230 /// engine configuration for this Store.
231 pub fn debug_global(
232 &self,
233 mut store: impl AsContextMut,
234 global_index: u32,
235 ) -> Option<crate::Global> {
236 self.debug_export(
237 store.as_context_mut().0,
238 GlobalIndex::from_bits(global_index).into(),
239 )
240 .and_then(|s| s.into_global())
241 }
242
243 /// Get access to a memory (unshared only) within this instance's
244 /// memory index space.
245 ///
246 /// This permits accessing memories whether they are exported or
247 /// not. However, it is only available for purposes of debugging,
248 /// and so is only permitted when `guest_debug` is enabled in the
249 /// Engine's configuration. The intent of the Wasmtime API is to
250 /// enforce the Wasm type system's encapsulation even in the host
251 /// API, except where necessary for developer tooling.
252 ///
253 /// `None` is returned for any memory index that is out-of-bounds.
254 ///
255 /// `None` is returned for any shared memory (use
256 /// `debug_shared_memory` instead).
257 ///
258 /// `None` is returned if guest-debugging is not enabled in the
259 /// engine configuration for this Store.
260 pub fn debug_memory(
261 &self,
262 mut store: impl AsContextMut,
263 memory_index: u32,
264 ) -> Option<crate::Memory> {
265 self.debug_export(
266 store.as_context_mut().0,
267 MemoryIndex::from_bits(memory_index).into(),
268 )
269 .and_then(|s| s.into_memory())
270 }
271
272 /// Get access to a shared memory within this instance's memory
273 /// index space.
274 ///
275 /// This permits accessing memories whether they are exported or
276 /// not. However, it is only available for purposes of debugging,
277 /// and so is only permitted when `guest_debug` is enabled in the
278 /// Engine's configuration. The intent of the Wasmtime API is to
279 /// enforce the Wasm type system's encapsulation even in the host
280 /// API, except where necessary for developer tooling.
281 ///
282 /// `None` is returned for any memory index that is out-of-bounds.
283 ///
284 /// `None` is returned for any unshared memory (use `debug_memory`
285 /// instead).
286 ///
287 /// `None` is returned if guest-debugging is not enabled in the
288 /// engine configuration for this Store.
289 pub fn debug_shared_memory(
290 &self,
291 mut store: impl AsContextMut,
292 memory_index: u32,
293 ) -> Option<crate::SharedMemory> {
294 self.debug_export(
295 store.as_context_mut().0,
296 MemoryIndex::from_bits(memory_index).into(),
297 )
298 .and_then(|s| s.into_shared_memory())
299 }
300
301 /// Get access to a table within this instance's table index
302 /// space.
303 ///
304 /// This permits accessing tables whether they are exported or
305 /// not. However, it is only available for purposes of debugging,
306 /// and so is only permitted when `guest_debug` is enabled in the
307 /// Engine's configuration. The intent of the Wasmtime API is to
308 /// enforce the Wasm type system's encapsulation even in the host
309 /// API, except where necessary for developer tooling.
310 ///
311 /// `None` is returned for any table index that is out-of-bounds.
312 ///
313 /// `None` is returned if guest-debugging is not enabled in the
314 /// engine configuration for this Store.
315 pub fn debug_table(
316 &self,
317 mut store: impl AsContextMut,
318 table_index: u32,
319 ) -> Option<crate::Table> {
320 self.debug_export(
321 store.as_context_mut().0,
322 TableIndex::from_bits(table_index).into(),
323 )
324 .and_then(|s| s.into_table())
325 }
326
327 /// Get access to a function within this instance's function index
328 /// space.
329 ///
330 /// This permits accessing functions whether they are exported or
331 /// not. However, it is only available for purposes of debugging,
332 /// and so is only permitted when `guest_debug` is enabled in the
333 /// Engine's configuration. The intent of the Wasmtime API is to
334 /// enforce the Wasm type system's encapsulation even in the host
335 /// API, except where necessary for developer tooling.
336 ///
337 /// `None` is returned for any function index that is
338 /// out-of-bounds.
339 ///
340 /// `None` is returned if guest-debugging is not enabled in the
341 /// engine configuration for this Store.
342 pub fn debug_function(
343 &self,
344 mut store: impl AsContextMut,
345 function_index: u32,
346 ) -> Option<crate::Func> {
347 self.debug_export(
348 store.as_context_mut().0,
349 FuncIndex::from_bits(function_index).into(),
350 )
351 .and_then(|s| s.into_func())
352 }
353
354 /// Get access to a tag within this instance's tag index space.
355 ///
356 /// This permits accessing tags whether they are exported or
357 /// not. However, it is only available for purposes of debugging,
358 /// and so is only permitted when `guest_debug` is enabled in the
359 /// Engine's configuration. The intent of the Wasmtime API is to
360 /// enforce the Wasm type system's encapsulation even in the host
361 /// API, except where necessary for developer tooling.
362 ///
363 /// `None` is returned for any tag index that is out-of-bounds.
364 ///
365 /// `None` is returned if guest-debugging is not enabled in the
366 /// engine configuration for this Store.
367 pub fn debug_tag(&self, mut store: impl AsContextMut, tag_index: u32) -> Option<crate::Tag> {
368 self.debug_export(
369 store.as_context_mut().0,
370 TagIndex::from_bits(tag_index).into(),
371 )
372 .and_then(|s| s.into_tag())
373 }
374
375 fn debug_export(&self, store: &mut StoreOpaque, index: EntityIndex) -> Option<Extern> {
376 if !store.engine().tunables().debug_guest {
377 return None;
378 }
379
380 let env_module = self._module(store).env_module();
381 if !env_module.is_valid(index) {
382 return None;
383 }
384 let store_id = store.id();
385 let (instance, registry) = store.instance_and_module_registry_mut(self.id());
386 // SAFETY: the `store` and `registry` are associated with
387 // this instance as we fetched the instance directly from
388 // the store above.
389 let export = unsafe { instance.get_export_by_index_mut(registry, store_id, index) };
390 Some(Extern::from_wasmtime_export(export, store.engine()))
391 }
392}
393
394impl<'a, T> StoreContext<'a, T> {
395 /// Return all breakpoints.
396 pub fn breakpoints(self) -> Option<impl Iterator<Item = Breakpoint> + 'a> {
397 if !self.engine().tunables().debug_guest {
398 return None;
399 }
400
401 let (breakpoints, registry) = self.0.breakpoints_and_registry();
402 Some(breakpoints.breakpoints(registry))
403 }
404
405 /// Indicate whether single-step mode is enabled.
406 pub fn is_single_step(&self) -> bool {
407 let (breakpoints, _) = self.0.breakpoints_and_registry();
408 breakpoints.is_single_step()
409 }
410}
411
412/// A handle to a stack frame, valid as long as execution is not
413/// resumed in the associated `Store`.
414///
415/// This handle can be held and cloned and used to refer to a frame
416/// within a paused store. It is cheap: it internally consists of a
417/// pointer to the actual frame, together with some metadata to
418/// determine when that pointer has gone stale.
419///
420/// At the API level, any usage of this frame handle requires a
421/// mutable borrow of the `Store`, because the `Store` logically owns
422/// the stack(s) for any execution within it. However, the existence
423/// of the handle itself does not hold a borrow on the `Store`; hence,
424/// the `Store` can continue to be used and queried, and some state
425/// (e.g. memories, tables, GC objects) can even be mutated, as long
426/// as execution is not resumed. The intent of this API is to allow a
427/// wide variety of debugger implementation strategies that expose
428/// stack frames and also allow other commands/actions at the same
429/// time.
430///
431/// The user can use [`FrameHandle::is_valid`] to determine if the
432/// handle is still valid and usable.
433#[derive(Clone)]
434pub struct FrameHandle {
435 /// The unwinder cursor at this frame.
436 cursor: FrameCursor,
437
438 /// The index of the virtual frame within the physical frame.
439 virtual_frame_idx: usize,
440
441 /// The unique Store this frame came from, to ensure the handle is
442 /// used with the correct Store.
443 store_id: StoreId,
444
445 /// Store `execution_version`.
446 store_version: u64,
447}
448
449impl FrameHandle {
450 /// Create a new FrameHandle at the exit frame of an activation.
451 ///
452 /// # Safety
453 ///
454 /// The provided activation must be valid currently.
455 unsafe fn exit_frame(store: &mut StoreOpaque, activation: Activation) -> Option<FrameHandle> {
456 // SAFETY: activation is valid as per our safety condition.
457 let mut cursor = unsafe { activation.cursor() };
458
459 // Find the first virtual frame. Each physical frame may have
460 // zero or more virtual frames.
461 while !cursor.done() {
462 let (cache, registry) = store.frame_data_cache_mut_and_registry();
463 let frames = cache.lookup_or_compute(registry, cursor.frame());
464 if frames.len() > 0 {
465 return Some(FrameHandle {
466 cursor,
467 virtual_frame_idx: 0,
468 store_id: store.id(),
469 store_version: store.vm_store_context().execution_version,
470 });
471 }
472 // SAFETY: activation is still valid (valid on entry per
473 // our safety condition, and we have not returned control
474 // since above).
475 unsafe {
476 cursor.advance(store.unwinder());
477 }
478 }
479
480 None
481 }
482
483 /// Determine whether this handle can still be used to refer to a
484 /// frame.
485 pub fn is_valid(&self, mut store: impl AsContextMut) -> bool {
486 let store = store.as_context_mut();
487 self.is_valid_impl(store.0.as_store_opaque())
488 }
489
490 fn is_valid_impl(&self, store: &StoreOpaque) -> bool {
491 let id = store.id();
492 let version = store.vm_store_context().execution_version;
493 self.store_id == id && self.store_version == version
494 }
495
496 /// Get a handle to the next frame up the activation (the one that
497 /// called this frame), if any.
498 pub fn parent(&self, mut store: impl AsContextMut) -> Result<Option<FrameHandle>> {
499 let mut store = store.as_context_mut();
500 if !self.is_valid(&mut store) {
501 crate::error::bail!("Frame handle is no longer valid.");
502 }
503
504 let mut parent = self.clone();
505 parent.virtual_frame_idx += 1;
506
507 while !parent.cursor.done() {
508 let (cache, registry) = store
509 .0
510 .as_store_opaque()
511 .frame_data_cache_mut_and_registry();
512 let frames = cache.lookup_or_compute(registry, parent.cursor.frame());
513 if parent.virtual_frame_idx < frames.len() {
514 return Ok(Some(parent));
515 }
516 parent.virtual_frame_idx = 0;
517 // SAFETY: activation is valid because we checked validity
518 // wrt execution version at the top of this function, and
519 // we have not returned since.
520 unsafe {
521 parent.cursor.advance(store.0.as_store_opaque().unwinder());
522 }
523 }
524
525 Ok(None)
526 }
527
528 fn frame_data<'a>(&self, store: &'a mut StoreOpaque) -> Result<&'a FrameData> {
529 if !self.is_valid_impl(store) {
530 crate::error::bail!("Frame handle is no longer valid.");
531 }
532 let (cache, registry) = store.frame_data_cache_mut_and_registry();
533 let frames = cache.lookup_or_compute(registry, self.cursor.frame());
534 // `virtual_frame_idx` counts up for ease of iteration
535 // behavior, while the frames are stored in outer-to-inner
536 // (i.e., caller to callee) order, so we need to reverse here.
537 Ok(&frames[frames.len() - 1 - self.virtual_frame_idx])
538 }
539
540 fn raw_instance<'a>(&self, store: &mut StoreOpaque) -> Result<&'a crate::vm::Instance> {
541 let frame_data = self.frame_data(store)?;
542
543 // Read out the vmctx slot.
544
545 // SAFETY: vmctx is always at offset 0 in the slot. (See
546 // crates/cranelift/src/func_environ.rs in
547 // `update_stack_slot_vmctx()`.) The frame/activation is
548 // still valid because we verified this in `frame_data` above.
549 let vmctx: usize =
550 unsafe { *(frame_data.slot_addr(self.cursor.frame().fp()) as *mut usize) };
551 let vmctx: *mut VMContext = core::ptr::with_exposed_provenance_mut(vmctx);
552 let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot");
553 // SAFETY: the stored vmctx value is a valid instance in this
554 // store; we only visit frames from this store in the
555 // backtrace.
556 let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) };
557 // SAFETY: the instance pointer read above is valid.
558 Ok(unsafe { instance.as_ref() })
559 }
560
561 /// Get the instance associated with the current frame.
562 pub fn instance(&self, mut store: impl AsContextMut) -> Result<Instance> {
563 let store = store.as_context_mut();
564 let instance = self.raw_instance(store.0.as_store_opaque())?;
565 let id = instance.id();
566 Ok(Instance::from_wasmtime(id, store.0.as_store_opaque()))
567 }
568
569 /// Get the module associated with the current frame, if any
570 /// (i.e., not a container instance for a host-created entity).
571 pub fn module<'a, T: 'static>(
572 &self,
573 store: impl Into<StoreContextMut<'a, T>>,
574 ) -> Result<Option<&'a Module>> {
575 let store = store.into();
576 let instance = self.raw_instance(store.0.as_store_opaque())?;
577 Ok(instance.runtime_module())
578 }
579
580 /// Get the raw function index associated with the current frame, and the
581 /// module-relative PC as an offset within the module binary, if
582 /// this is a Wasm function directly from the given `Module`
583 /// (rather than a trampoline).
584 pub fn wasm_function_index_and_pc(
585 &self,
586 mut store: impl AsContextMut,
587 ) -> Result<Option<(DefinedFuncIndex, ModulePC)>> {
588 let mut store = store.as_context_mut();
589 let frame_data = self.frame_data(store.0.as_store_opaque())?;
590 let FuncKey::DefinedWasmFunction(module, func) = frame_data.func_key else {
591 return Ok(None);
592 };
593 let wasm_pc = frame_data.wasm_pc;
594 debug_assert_eq!(
595 module,
596 self.module(&mut store)?
597 .expect("module should be defined if this is a defined function")
598 .env_module()
599 .module_index
600 );
601 Ok(Some((func, wasm_pc)))
602 }
603
604 /// Get the number of locals in this frame.
605 pub fn num_locals(&self, mut store: impl AsContextMut) -> Result<u32> {
606 let store = store.as_context_mut();
607 let frame_data = self.frame_data(store.0.as_store_opaque())?;
608 Ok(u32::try_from(frame_data.locals.len()).unwrap())
609 }
610
611 /// Get the depth of the operand stack in this frame.
612 pub fn num_stacks(&self, mut store: impl AsContextMut) -> Result<u32> {
613 let store = store.as_context_mut();
614 let frame_data = self.frame_data(store.0.as_store_opaque())?;
615 Ok(u32::try_from(frame_data.stack.len()).unwrap())
616 }
617
618 /// Get the type and value of the given local in this frame.
619 ///
620 /// # Panics
621 ///
622 /// Panics if the index is out-of-range (greater than
623 /// `num_locals()`).
624 pub fn local(&self, mut store: impl AsContextMut, index: u32) -> Result<Val> {
625 let store = store.as_context_mut();
626 let frame_data = self.frame_data(store.0.as_store_opaque())?;
627 let (offset, ty) = frame_data.locals[usize::try_from(index).unwrap()];
628 let slot_addr = frame_data.slot_addr(self.cursor.frame().fp());
629 // SAFETY: compiler produced metadata to describe this local
630 // slot and stored a value of the correct type into it. Slot
631 // address is valid because we checked liveness of the
632 // activation/frame via `frame_data` above.
633 Ok(unsafe { read_value(store.0.as_store_opaque(), slot_addr, offset, ty) })
634 }
635
636 /// Get the type and value of the given operand-stack value in
637 /// this frame.
638 ///
639 /// Index 0 corresponds to the bottom-of-stack, and higher indices
640 /// from there are more recently pushed values. In other words,
641 /// index order reads the Wasm virtual machine's abstract stack
642 /// state left-to-right.
643 pub fn stack(&self, mut store: impl AsContextMut, index: u32) -> Result<Val> {
644 let store = store.as_context_mut();
645 let frame_data = self.frame_data(store.0.as_store_opaque())?;
646 let (offset, ty) = frame_data.stack[usize::try_from(index).unwrap()];
647 let slot_addr = frame_data.slot_addr(self.cursor.frame().fp());
648 // SAFETY: compiler produced metadata to describe this
649 // operand-stack slot and stored a value of the correct type
650 // into it. Slot address is valid because we checked liveness
651 // of the activation/frame via `frame_data` above.
652 Ok(unsafe { read_value(store.0.as_store_opaque(), slot_addr, offset, ty) })
653 }
654}
655
656/// A cache from `StoreCodePC`s for modules' private code within a
657/// store to pre-computed layout data for the virtual stack frame(s)
658/// present at that physical PC.
659pub(crate) struct FrameDataCache {
660 /// For a given physical PC, the list of virtual frames, from
661 /// inner (most recently called/inlined) to outer.
662 by_pc: BTreeMap<StoreCodePC, Vec<FrameData>>,
663}
664
665impl FrameDataCache {
666 pub(crate) fn new() -> FrameDataCache {
667 FrameDataCache {
668 by_pc: BTreeMap::new(),
669 }
670 }
671
672 /// Look up (or compute) the list of `FrameData`s from a physical
673 /// `Frame`.
674 fn lookup_or_compute<'a>(
675 &'a mut self,
676 registry: &ModuleRegistry,
677 frame: Frame,
678 ) -> &'a [FrameData] {
679 let pc = StoreCodePC::from_raw(frame.pc());
680 match self.by_pc.entry(pc) {
681 Entry::Occupied(frames) => frames.into_mut(),
682 Entry::Vacant(v) => {
683 // Although inlining can mix modules, `module` is the
684 // module that actually contains the physical PC
685 // (i.e., the outermost function that inlined the
686 // others).
687 let (store_code, frames) = VirtualFrame::decode(registry, frame.pc());
688 let frames = frames
689 .into_iter()
690 .map(|frame| FrameData::compute(frame, store_code))
691 .collect::<Vec<_>>();
692 v.insert(frames)
693 }
694 }
695 }
696}
697
698/// Internal data pre-computed for one stack frame.
699///
700/// This represents one frame as produced by the progpoint lookup
701/// (Wasm PC, frame descriptor index, stack shape).
702struct VirtualFrame {
703 /// The module-relative Wasm PC for this frame.
704 wasm_pc: ModulePC,
705 /// The frame descriptor for this frame.
706 frame_descriptor: FrameTableDescriptorIndex,
707 /// The stack shape for this frame.
708 stack_shape: FrameStackShape,
709}
710
711impl VirtualFrame {
712 /// Return virtual frames corresponding to a physical frame, from
713 /// outermost to innermost.
714 fn decode(registry: &ModuleRegistry, pc: usize) -> (&StoreCode, Vec<VirtualFrame>) {
715 let (store_code, pc) = registry
716 .store_code_by_pc(pc)
717 .expect("Wasm frame PC does not correspond to a module");
718 let table = store_code.code_memory().frame_table().unwrap();
719 let pc = u32::try_from(pc).expect("PC offset too large");
720 // Functions without any program points (e.g. the module
721 // start function, or trampolines) have no virtual frames.
722 let frames = table
723 .find_program_point(pc, FrameInstPos::Post)
724 .into_iter()
725 .flatten()
726 .map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame {
727 wasm_pc,
728 frame_descriptor,
729 stack_shape,
730 })
731 .collect();
732
733 (store_code, frames)
734 }
735}
736
737/// Data computed when we visit a given frame.
738struct FrameData {
739 slot_to_fp_offset: usize,
740 func_key: FuncKey,
741 wasm_pc: ModulePC,
742 /// Shape of locals in this frame.
743 ///
744 /// We need to store this locally because `FrameView` cannot
745 /// borrow the store: it needs a mut borrow, and an iterator
746 /// cannot yield the same mut borrow multiple times because it
747 /// cannot control the lifetime of the values it yields (the
748 /// signature of `next()` does not bound the return value to the
749 /// `&mut self` arg).
750 locals: Vec<(FrameStateSlotOffset, FrameValType)>,
751 /// Shape of the stack slots at this program point in this frame.
752 ///
753 /// In addition to the borrowing-related reason above, we also
754 /// materialize this because we want to provide O(1) access to the
755 /// stack by depth, and the frame slot descriptor stores info in a
756 /// linked-list (actually DAG, with dedup'ing) way.
757 stack: Vec<(FrameStateSlotOffset, FrameValType)>,
758}
759
760impl FrameData {
761 fn compute(frame: VirtualFrame, store_code: &StoreCode) -> Self {
762 let frame_table = store_code.code_memory().frame_table().unwrap();
763 // Parse the frame descriptor.
764 let (data, slot_to_fp_offset) = frame_table
765 .frame_descriptor(frame.frame_descriptor)
766 .unwrap();
767 let frame_state_slot = FrameStateSlot::parse(data).unwrap();
768 let slot_to_fp_offset = usize::try_from(slot_to_fp_offset).unwrap();
769
770 // Materialize the stack shape so we have O(1) access to its
771 // elements, and so we don't need to keep the borrow to the
772 // module alive.
773 let mut stack = frame_state_slot
774 .stack(frame.stack_shape)
775 .collect::<Vec<_>>();
776 stack.reverse(); // Put top-of-stack last.
777
778 // Materialize the local offsets/types so we don't need to
779 // keep the borrow to the module alive.
780 let locals = frame_state_slot.locals().collect::<Vec<_>>();
781
782 FrameData {
783 slot_to_fp_offset,
784 func_key: frame_state_slot.func_key(),
785 wasm_pc: frame.wasm_pc,
786 stack,
787 locals,
788 }
789 }
790
791 fn slot_addr(&self, fp: usize) -> *mut u8 {
792 let fp: *mut u8 = core::ptr::with_exposed_provenance_mut(fp);
793 fp.wrapping_sub(self.slot_to_fp_offset)
794 }
795}
796
797/// Read the value at the given offset.
798///
799/// # Safety
800///
801/// The `offset` and `ty` must correspond to a valid value written
802/// to the frame by generated code of the correct type. This will
803/// be the case if this information comes from the frame tables
804/// (as long as the frontend that generates the tables and
805/// instrumentation is correct, and as long as the tables are
806/// preserved through serialization).
807unsafe fn read_value(
808 store: &mut StoreOpaque,
809 slot_base: *const u8,
810 offset: FrameStateSlotOffset,
811 ty: FrameValType,
812) -> Val {
813 let address = unsafe { slot_base.offset(isize::try_from(offset.offset()).unwrap()) };
814
815 // SAFETY: each case reads a value from memory that should be valid
816 // according to our safety condition. State-slot values are packed without
817 // alignment padding, so these loads must accept unaligned addresses.
818 match ty {
819 FrameValType::I32 => {
820 let value = unsafe { (address as *const i32).read_unaligned() };
821 Val::I32(value)
822 }
823 FrameValType::I64 => {
824 let value = unsafe { (address as *const i64).read_unaligned() };
825 Val::I64(value)
826 }
827 FrameValType::F32 => {
828 let value = unsafe { (address as *const u32).read_unaligned() };
829 Val::F32(value)
830 }
831 FrameValType::F64 => {
832 let value = unsafe { (address as *const u64).read_unaligned() };
833 Val::F64(value)
834 }
835 FrameValType::V128 => {
836 // Vectors are always stored as little-endian.
837 let value =
838 unsafe { u128::from_le_bytes((address as *const [u8; 16]).read_unaligned()) };
839 Val::V128(value.into())
840 }
841 FrameValType::AnyRef => {
842 let mut nogc = AutoAssertNoGc::new(store);
843 let value = unsafe { (address as *const u32).read_unaligned() };
844 let value = AnyRef::_from_raw(&mut nogc, value);
845 Val::AnyRef(value)
846 }
847 FrameValType::ExnRef => {
848 let mut nogc = AutoAssertNoGc::new(store);
849 let value = unsafe { (address as *const u32).read_unaligned() };
850 let value = ExnRef::_from_raw(&mut nogc, value);
851 Val::ExnRef(value)
852 }
853 FrameValType::ExternRef => {
854 let mut nogc = AutoAssertNoGc::new(store);
855 let value = unsafe { (address as *const u32).read_unaligned() };
856 let value = ExternRef::_from_raw(&mut nogc, value);
857 Val::ExternRef(value)
858 }
859 FrameValType::FuncRef => {
860 let value = unsafe { (address as *const *mut c_void).read_unaligned() };
861 let value = unsafe { Func::_from_raw(store, value) };
862 Val::FuncRef(value)
863 }
864 FrameValType::ContRef => {
865 unimplemented!("contref values are not implemented in the host API yet")
866 }
867 }
868}
869
870/// Compute raw pointers to all GC refs in the given frame.
871// Note: ideally this would be an impl Iterator, but this is quite
872// awkward because of the locally computed data (FrameStateSlot::parse
873// structured result) within the closure borrowed by a nested closure.
874#[cfg(feature = "gc")]
875pub(crate) fn gc_refs_in_frame<'a>(ft: FrameTable<'a>, pc: u32, fp: *mut usize) -> Vec<*mut u32> {
876 let fp = fp.cast::<u8>();
877 let mut ret = vec![];
878 if let Some(frames) = ft.find_program_point(pc, FrameInstPos::Post) {
879 for (_wasm_pc, frame_desc, stack_shape) in frames {
880 let (frame_desc_data, slot_to_fp_offset) = ft.frame_descriptor(frame_desc).unwrap();
881 let frame_base = unsafe { fp.offset(-isize::try_from(slot_to_fp_offset).unwrap()) };
882 let frame_desc = FrameStateSlot::parse(frame_desc_data).unwrap();
883 for (offset, ty) in frame_desc.stack_and_locals(stack_shape) {
884 match ty {
885 FrameValType::AnyRef | FrameValType::ExnRef | FrameValType::ExternRef => {
886 let slot = unsafe {
887 frame_base
888 .offset(isize::try_from(offset.offset()).unwrap())
889 .cast::<u32>()
890 };
891 ret.push(slot);
892 }
893 FrameValType::ContRef | FrameValType::FuncRef => {}
894 FrameValType::I32
895 | FrameValType::I64
896 | FrameValType::F32
897 | FrameValType::F64
898 | FrameValType::V128 => {}
899 }
900 }
901 }
902 }
903 ret
904}
905
906/// One debug event that occurs when running Wasm code on a store with
907/// a debug handler attached.
908#[derive(Debug)]
909pub enum DebugEvent<'a> {
910 /// A [`wasmtime::Error`](crate::Error) was raised by a hostcall.
911 HostcallError(&'a crate::Error),
912 /// An exception is thrown by wasm.
913 ///
914 /// Note that the exception may be caught by wasm if there's an appropriate
915 /// handler on the stack, but the stack hasn't been searched yet. The
916 /// debugger can inject its own exception or overwrite this exception if
917 /// desired.
918 Exception(OwnedRooted<ExnRef>),
919 /// A Wasm trap occurred.
920 Trap(Trap),
921 /// A breakpoint was reached.
922 Breakpoint,
923 /// An epoch yield occurred.
924 EpochYield,
925 /// Guest code is about to write watched bytes of a linear memory; see
926 /// [`BreakpointState::watch`]. The write happens
927 /// once the handler returns.
928 Watchpoint(WatchpointHit),
929}
930
931/// A guest write to watched bytes of a linear memory, reported before the
932/// write happens.
933#[derive(Clone, Copy, Debug)]
934pub struct WatchpointHit {
935 /// The memory being written.
936 pub memory: crate::Memory,
937 /// The address of the write's first byte.
938 pub address: u64,
939 /// The number of bytes written, of which at least one is watched.
940 pub len: u64,
941 /// For a store of up to 16 bytes, the bits being stored (in
942 /// little-endian order, so the first byte is the least significant).
943 /// `None` for bulk memory operations.
944 pub value: Option<u128>,
945}
946
947impl PartialEq for WatchpointHit {
948 fn eq(&self, other: &Self) -> bool {
949 self.memory.same(&other.memory)
950 && (self.address, self.len, self.value) == (other.address, other.len, other.value)
951 }
952}
953
954impl Eq for WatchpointHit {}
955
956/// A handler for debug events.
957///
958/// This is an async callback that is invoked directly within the
959/// context of a debug event that occurs, i.e., with the Wasm code
960/// still on the stack. The callback can thus observe that stack, up
961/// to the most recent entry to Wasm.[^1]
962///
963/// Because this callback receives a `StoreContextMut`, it has full
964/// access to any state that any other hostcall has, including the
965/// `T`. In that way, it is like an epoch-deadline callback or a
966/// call-hook callback. It also "freezes" the entire store for the
967/// duration of the debugger callback future.
968///
969/// In the future, we expect to provide an "externally async" API on
970/// the `Store` that allows receiving a stream of debug events and
971/// accessing the store mutably while frozen; that will need to
972/// integrate with [`Store::run_concurrent`] to properly timeslice and
973/// scope the mutable access to the store, and has not been built
974/// yet. In the meantime, it should be possible to build a fully
975/// functional debugger with this async-callback API by channeling
976/// debug events out, and requests to read the store back in, over
977/// message-passing channels between the callback and an external
978/// debugger main loop.
979///
980/// Note that the `handle` hook may use its mutable store access to
981/// invoke another Wasm. Debug events will also be caught and will
982/// cause further `handle` invocations during this recursive
983/// invocation. It is up to the debugger to handle any implications of
984/// this reentrancy (e.g., implications on a duplex channel protocol
985/// with an event/continue handshake) if it does so.
986///
987/// Note also that this trait has `Clone` as a supertrait, and the
988/// handler is cloned at every invocation as an artifact of the
989/// internal ownership structure of Wasmtime: the handler itself is
990/// owned by the store, but also receives a mutable borrow to the
991/// whole store, so we need to clone it out to invoke it. It is
992/// recommended that this trait be implemented by a type that is cheap
993/// to clone: for example, a single `Arc` handle to debugger state.
994///
995/// [^1]: Providing visibility further than the most recent entry to
996/// Wasm is not directly possible because it could see into
997/// another async stack, and the stack that polls the future
998/// running a particular Wasm invocation could change after each
999/// suspend point in the handler.
1000///
1001/// [`Store::run_concurrent`]: crate::Store::run_concurrent
1002pub trait DebugHandler: Clone + Send + Sync + 'static {
1003 /// The data expected on the store that this handler is attached
1004 /// to.
1005 type Data;
1006
1007 /// Handle a debug event.
1008 fn handle(
1009 &self,
1010 store: StoreContextMut<'_, Self::Data>,
1011 event: DebugEvent<'_>,
1012 ) -> impl Future<Output = ()> + Send;
1013}
1014
1015/// Breakpoint state for modules within a store.
1016#[derive(Default)]
1017pub(crate) struct Breakpoints {
1018 /// Single-step mode.
1019 single_step: bool,
1020 /// Breakpoints added individually. Maps from the actual
1021 /// (possibly slipped-forward) breakpoint key to a reference
1022 /// count. Multiple requested PCs may map to the same actual
1023 /// breakpoint when they are slipped forward.
1024 breakpoints: BTreeMap<BreakpointKey, usize>,
1025 /// When a requested breakpoint PC does not exactly match an
1026 /// opcode boundary, we "slip" it forward to the next available
1027 /// PC. This map records the redirect from the requested key to
1028 /// the actual key so that `remove_breakpoint` can undo it.
1029 breakpoint_redirects: BTreeMap<BreakpointKey, BreakpointKey>,
1030}
1031
1032/// A breakpoint.
1033pub struct Breakpoint {
1034 /// Reference to the module in which we are setting the breakpoint.
1035 pub module: Module,
1036 /// Module-relative Wasm PC offset.
1037 pub pc: ModulePC,
1038}
1039
1040#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1041struct BreakpointKey(CompiledModuleId, ModulePC);
1042
1043impl BreakpointKey {
1044 fn from_raw(module: &Module, pc: ModulePC) -> BreakpointKey {
1045 BreakpointKey(module.id(), pc)
1046 }
1047
1048 fn get(&self, registry: &ModuleRegistry) -> Breakpoint {
1049 let module = registry
1050 .module_by_compiled_id(self.0)
1051 .expect("Module should not have been removed from Store")
1052 .clone();
1053 Breakpoint { module, pc: self.1 }
1054 }
1055}
1056
1057/// A store's breakpoint state: its breakpoints, single-stepping, and
1058/// watchpoints, borrowed for editing.
1059///
1060/// Obtained with [`Store::edit_breakpoints`] (or the same method on
1061/// [`StoreContextMut`] and [`Caller`]).
1062///
1063/// Changes to breakpoints and single-stepping are batched for
1064/// efficiency, because code-patching has some overhead (e.g. syscalls
1065/// to write-protect/re-publish code).
1066pub struct BreakpointState<'a> {
1067 store: &'a mut StoreOpaque,
1068 /// Modules that have been edited.
1069 ///
1070 /// Invariant: each of these modules' CodeMemory objects is
1071 /// *unpublished* when in the dirty set.
1072 dirty_modules: BTreeSet<StoreCodePC>,
1073}
1074
1075impl Breakpoints {
1076 pub(crate) fn breakpoints<'a>(
1077 &'a self,
1078 registry: &'a ModuleRegistry,
1079 ) -> impl Iterator<Item = Breakpoint> + 'a {
1080 self.breakpoints.keys().map(|key| key.get(registry))
1081 }
1082
1083 pub(crate) fn is_single_step(&self) -> bool {
1084 self.single_step
1085 }
1086
1087 /// Internal helper to patch a new module for
1088 /// single-stepping. When a module is newly registered in a
1089 /// `Store`, we need to patch all breakpoints into the copy for
1090 /// this `Store` if single-stepping is currently enabled.
1091 pub(crate) fn patch_new_module(&self, code: &mut StoreCode, module: &Module) -> Result<()> {
1092 // Apply single-step state if single-stepping is enabled. Note
1093 // that no other individual breakpoints will exist yet (as
1094 // this is a newly registered module).
1095 if self.single_step {
1096 let mem = code.code_memory_mut().unwrap();
1097 mem.unpublish()?;
1098 BreakpointState::apply_single_step(mem, module, true, |_key| false)?;
1099 mem.publish()?;
1100 }
1101 Ok(())
1102 }
1103}
1104
1105impl<'a> BreakpointState<'a> {
1106 /// Errors if `module` does not belong to the same engine as the store
1107 /// being edited.
1108 ///
1109 /// The module registry rejects foreign modules on its own, but breakpoint
1110 /// bookkeeping is updated before a module reaches the registry, and
1111 /// removing a breakpoint that was never added does not touch the registry
1112 /// at all. So this editing session checks up front as well.
1113 fn check_engine(&self, module: &Module) -> Result<()> {
1114 crate::ensure!(
1115 crate::Engine::same(self.store.engine(), module.engine()),
1116 "cross-`Engine` breakpoint editing is not supported"
1117 );
1118 Ok(())
1119 }
1120
1121 fn get_code_memory<'b>(
1122 breakpoints: &Breakpoints,
1123 registry: &'b mut ModuleRegistry,
1124 dirty_modules: &mut BTreeSet<StoreCodePC>,
1125 module: &Module,
1126 ) -> Result<&'b mut CodeMemory> {
1127 let store_code_pc =
1128 registry.store_code_base_or_register(module, RegisterBreakpointState(breakpoints))?;
1129 let code_memory = registry
1130 .store_code_mut(store_code_pc)
1131 .expect("Just checked presence above")
1132 .code_memory_mut()
1133 .expect("Must have unique ownership of StoreCode in guest-debug mode");
1134 if dirty_modules.insert(store_code_pc) {
1135 code_memory.unpublish()?;
1136 }
1137 Ok(code_memory)
1138 }
1139
1140 fn patch<'b>(
1141 patches: impl Iterator<Item = FrameTableBreakpointData<'b>> + 'b,
1142 mem: &mut CodeMemory,
1143 enable: bool,
1144 ) {
1145 let mem = mem.text_mut();
1146 for patch in patches {
1147 let data = if enable { patch.enable } else { patch.disable };
1148 let mem = &mut mem[patch.offset..patch.offset + data.len()];
1149 log::trace!(
1150 "patch: offset 0x{:x} with enable={enable}: data {data:?} replacing {mem:?}",
1151 patch.offset
1152 );
1153 mem.copy_from_slice(data);
1154 }
1155 }
1156
1157 /// Add a breakpoint in the given module at the given PC in that
1158 /// module.
1159 ///
1160 /// If the requested PC does not fall exactly on an opcode
1161 /// boundary, the breakpoint is "slipped" forward to the next
1162 /// available opcode PC.
1163 ///
1164 /// No effect if the breakpoint is already set.
1165 ///
1166 /// # Errors
1167 ///
1168 /// Returns an error if `module` was not compiled by the same engine as
1169 /// the store being edited.
1170 pub fn add_breakpoint(&mut self, module: &Module, pc: ModulePC) -> Result<()> {
1171 self.check_engine(module)?;
1172 let frame_table = module
1173 .frame_table()
1174 .expect("Frame table must be present when guest-debug is enabled");
1175 let actual_pc = frame_table.nearest_breakpoint(pc).unwrap_or(pc);
1176 let requested_key = BreakpointKey::from_raw(module, pc);
1177 let actual_key = BreakpointKey::from_raw(module, actual_pc);
1178
1179 let (state, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1180 if actual_pc != pc {
1181 log::trace!("slipping breakpoint from {requested_key:?} to {actual_key:?}");
1182 state.breakpoint_redirects.insert(requested_key, actual_key);
1183 }
1184
1185 let refcount = state.breakpoints.entry(actual_key).or_insert(0);
1186 *refcount += 1;
1187 if *refcount == 1 {
1188 // First reference: actually patch the code.
1189 let mem = Self::get_code_memory(state, registry, &mut self.dirty_modules, module)?;
1190 let patches = frame_table.lookup_breakpoint_patches_by_pc(actual_pc);
1191 Self::patch(patches, mem, true);
1192 }
1193 Ok(())
1194 }
1195
1196 /// Remove a breakpoint in the given module at the given PC in
1197 /// that module.
1198 ///
1199 /// No effect if the breakpoint was not set.
1200 ///
1201 /// # Errors
1202 ///
1203 /// Returns an error if `module` was not compiled by the same engine as
1204 /// the store being edited.
1205 pub fn remove_breakpoint(&mut self, module: &Module, pc: ModulePC) -> Result<()> {
1206 self.check_engine(module)?;
1207 let (state, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1208 let requested_key = BreakpointKey::from_raw(module, pc);
1209 let actual_key = state
1210 .breakpoint_redirects
1211 .remove(&requested_key)
1212 .unwrap_or(requested_key);
1213 let actual_pc = actual_key.1;
1214
1215 if let Some(refcount) = state.breakpoints.get_mut(&actual_key) {
1216 *refcount -= 1;
1217 if *refcount == 0 {
1218 state.breakpoints.remove(&actual_key);
1219 if !state.single_step {
1220 let mem =
1221 Self::get_code_memory(state, registry, &mut self.dirty_modules, module)?;
1222 let frame_table = module
1223 .frame_table()
1224 .expect("Frame table must be present when guest-debug is enabled");
1225 let patches = frame_table.lookup_breakpoint_patches_by_pc(actual_pc);
1226 Self::patch(patches, mem, false);
1227 }
1228 }
1229 }
1230 Ok(())
1231 }
1232
1233 fn apply_single_step<F: Fn(&BreakpointKey) -> bool>(
1234 mem: &mut CodeMemory,
1235 module: &Module,
1236 enabled: bool,
1237 key_enabled: F,
1238 ) -> Result<()> {
1239 let table = module
1240 .frame_table()
1241 .expect("Frame table must be present when guest-debug is enabled");
1242 // A component's modules share one code image, and so its frame table: leave the
1243 // other modules' code to them, or this module's breakpoints decide theirs.
1244 let index = module.compiled_module().module_index();
1245 for (patch_module, wasm_pc, patch) in table.breakpoint_patches() {
1246 if patch_module != index {
1247 continue;
1248 }
1249 let key = BreakpointKey::from_raw(&module, wasm_pc);
1250 let this_enabled = enabled || key_enabled(&key);
1251 log::trace!(
1252 "single_step: enabled {enabled} key {key:?} -> this_enabled {this_enabled}"
1253 );
1254 Self::patch(core::iter::once(patch), mem, this_enabled);
1255 }
1256 Ok(())
1257 }
1258
1259 /// Turn on or off single-step mode.
1260 ///
1261 /// In single-step mode, a breakpoint event is emitted at every
1262 /// Wasm PC.
1263 pub fn single_step(&mut self, enabled: bool) -> Result<()> {
1264 let (state, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1265 log::trace!(
1266 "single_step({enabled}) with breakpoint set {:?}",
1267 state.breakpoints
1268 );
1269 if state.single_step == enabled {
1270 // No change to current state; don't go through the effort of re-patching and
1271 // re-publishing code.
1272 return Ok(());
1273 }
1274 let modules = registry
1275 .all_modules()
1276 .map(|(_, m)| m.clone())
1277 .collect::<Vec<_>>();
1278 for module in modules {
1279 let mem = Self::get_code_memory(state, registry, &mut self.dirty_modules, &module)?;
1280 Self::apply_single_step(mem, &module, enabled, |key| {
1281 state.breakpoints.contains_key(key)
1282 })?;
1283 }
1284
1285 state.single_step = enabled;
1286
1287 Ok(())
1288 }
1289
1290 /// Watches guest writes to the bytes in `range` of `memory`. A guest
1291 /// store or bulk memory operation that writes a watched byte raises
1292 /// [`DebugEvent::Watchpoint`] before writing.
1293 ///
1294 /// Whether a byte is watched is a per-byte property: watching an
1295 /// already-watched byte has no further effect. See
1296 /// [`BreakpointState::unwatch`].
1297 ///
1298 /// # Errors
1299 ///
1300 /// Returns an error if `range` extends beyond the memory's current size.
1301 ///
1302 /// # Panics
1303 ///
1304 /// Panics if `memory` does not belong to this store.
1305 pub fn watch(&mut self, memory: crate::Memory, range: core::ops::Range<u64>) -> Result<()> {
1306 self.store.debug_set_watch(memory, range, true)
1307 }
1308
1309 /// Stops watching guest writes to the bytes in `range` of `memory`.
1310 ///
1311 /// Whether a byte is watched is a per-byte property, so this unwatches
1312 /// every byte in `range`, regardless of how many (possibly overlapping)
1313 /// calls to [`BreakpointState::watch`] watched it.
1314 ///
1315 /// # Errors
1316 ///
1317 /// Returns an error if `range` extends beyond the memory's current size.
1318 ///
1319 /// # Panics
1320 ///
1321 /// Panics if `memory` does not belong to this store.
1322 pub fn unwatch(&mut self, memory: crate::Memory, range: core::ops::Range<u64>) -> Result<()> {
1323 self.store.debug_set_watch(memory, range, false)
1324 }
1325}
1326
1327impl<'a> Drop for BreakpointState<'a> {
1328 fn drop(&mut self) {
1329 let (_, registry, _) = self.store.breakpoints_and_registry_and_engine_mut();
1330 for &store_code_base in &self.dirty_modules {
1331 let store_code = registry.store_code_mut(store_code_base).unwrap();
1332 if let Err(e) = store_code
1333 .code_memory_mut()
1334 .expect("Must have unique ownership of StoreCode in guest-debug mode")
1335 .publish()
1336 {
1337 abort_on_republish_error(e);
1338 }
1339 }
1340 }
1341}
1342
1343/// Abort when we cannot re-publish executable code.
1344///
1345/// Note that this puts us in quite a conundrum. Typically we will
1346/// have been editing breakpoints from within a hostcall context
1347/// (e.g. inside a debugger hook while execution is paused) with JIT
1348/// code on the stack. Wasmtime's usual path to return errors is back
1349/// through that JIT code: we do not panic-unwind across the JIT code,
1350/// we return into the exit trampoline and that then re-enters the
1351/// raise libcall to use a Cranelift exception-throw to cross most of
1352/// the JIT frames to the entry trampoline. When even trampolines are
1353/// no longer executable, we have no way out. Even an ordinary
1354/// `panic!` cannot work, because we catch panics and carry them
1355/// across JIT code using that trampoline-based error path. Our only
1356/// way out is to directly abort the whole process.
1357///
1358/// This is not without precedent: other engines have similar failure
1359/// paths. For example, SpiderMonkey directly aborts the process when
1360/// failing to re-apply executable permissions (see [1]).
1361///
1362/// Note that we don't really expect to ever hit this case in
1363/// practice: it's unlikely that `mprotect` applying `PROT_EXEC` would
1364/// fail due to, e.g., resource exhaustion in the kernel, because we
1365/// will have the same net number of virtual memory areas before and
1366/// after the permissions change. Nevertheless, we have to account for
1367/// the possibility of error.
1368///
1369/// [1]: https://searchfox.org/firefox-main/rev/7496c8515212669451d7e775a00c2be07da38ca5/js/src/jit/AutoWritableJitCode.h#26-56
1370#[cfg(feature = "std")]
1371fn abort_on_republish_error(e: crate::Error) -> ! {
1372 log::error!(
1373 "Failed to re-publish executable code: {e:?}. Wasmtime cannot return through JIT code on the stack and cannot even panic; aborting the process."
1374 );
1375 std::process::abort();
1376}
1377
1378/// In the `no_std` case, we don't have a concept of a "process
1379/// abort", so rely on `panic!`. Typically an embedded scenario that
1380/// uses `no_std` will build with `panic=abort` so the effect is the
1381/// same. If it doesn't, there is truly nothing we can do here so
1382/// let's panic anyway; the panic propagation through the trampolines
1383/// will at least deterministically crash.
1384#[cfg(not(feature = "std"))]
1385fn abort_on_republish_error(e: crate::Error) -> ! {
1386 panic!("Failed to re-publish executable code: {e:?}");
1387}