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