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