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