1 //! Debugging API. 2 3 use crate::{ 4 AnyRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, Instance, Module, OwnedRooted, 5 StoreContext, StoreContextMut, Val, 6 store::{AutoAssertNoGc, StoreOpaque}, 7 vm::{CurrentActivationBacktrace, VMContext}, 8 }; 9 use alloc::vec; 10 use alloc::vec::Vec; 11 use core::{ffi::c_void, ptr::NonNull}; 12 #[cfg(feature = "gc")] 13 use wasmtime_environ::FrameTable; 14 use wasmtime_environ::{ 15 DefinedFuncIndex, FrameInstPos, FrameStackShape, FrameStateSlot, FrameStateSlotOffset, 16 FrameTableDescriptorIndex, FrameValType, FuncKey, Trap, 17 }; 18 use wasmtime_unwinder::Frame; 19 20 use super::store::AsStoreOpaque; 21 22 impl<'a, T> StoreContextMut<'a, T> { 23 /// Provide an object that captures Wasm stack state, including 24 /// Wasm VM-level values (locals and operand stack). 25 /// 26 /// This object views all activations for the current store that 27 /// are on the stack. An activation is a contiguous sequence of 28 /// Wasm frames (called functions) that were called from host code 29 /// and called back out to host code. If there are activations 30 /// from multiple stores on the stack, for example if Wasm code in 31 /// one store calls out to host code which invokes another Wasm 32 /// function in another store, then the other stores are "opaque" 33 /// to our view here in the same way that host code is. 34 /// 35 /// Returns `None` if debug instrumentation is not enabled for 36 /// the engine containing this store. 37 pub fn debug_frames(self) -> Option<DebugFrameCursor<'a, T>> { 38 if !self.engine().tunables().debug_guest { 39 return None; 40 } 41 42 // SAFETY: This takes a mutable borrow of `self` (the 43 // `StoreOpaque`), which owns all active stacks in the 44 // store. We do not provide any API that could mutate the 45 // frames that we are walking on the `DebugFrameCursor`. 46 let iter = unsafe { CurrentActivationBacktrace::new(self) }; 47 let mut view = DebugFrameCursor { 48 iter, 49 is_trapping_frame: false, 50 frames: vec![], 51 current: None, 52 }; 53 view.move_to_parent(); // Load the first frame. 54 Some(view) 55 } 56 } 57 58 /// A view of an active stack frame, with the ability to move up the 59 /// stack. 60 /// 61 /// See the documentation on `Store::stack_value` for more information 62 /// about which frames this view will show. 63 pub struct DebugFrameCursor<'a, T: 'static> { 64 /// Iterator over frames. 65 /// 66 /// This iterator owns the store while the view exists (accessible 67 /// as `iter.store`). 68 iter: CurrentActivationBacktrace<'a, T>, 69 70 /// Is the next frame to be visited by the iterator a trapping 71 /// frame? 72 /// 73 /// This alters how we interpret `pc`: for a trap, we look at the 74 /// instruction that *starts* at `pc`, while for all frames 75 /// further up the stack (i.e., at a callsite), we look at the 76 /// instruction that *ends* at `pc`. 77 is_trapping_frame: bool, 78 79 /// Virtual frame queue: decoded from `iter`, not yet 80 /// yielded. Innermost frame on top (last). 81 /// 82 /// This is only non-empty when there is more than one virtual 83 /// frame in a physical frame (i.e., for inlining); thus, its size 84 /// is bounded by our inlining depth. 85 frames: Vec<VirtualFrame>, 86 87 /// Currently focused virtual frame. 88 current: Option<FrameData>, 89 } 90 91 impl<'a, T: 'static> DebugFrameCursor<'a, T> { 92 /// Move up to the next frame in the activation. 93 pub fn move_to_parent(&mut self) { 94 // If there are no virtual frames to yield, take and decode 95 // the next physical frame. 96 // 97 // Note that `if` rather than `while` here, and the assert 98 // that we get some virtual frames back, enforce the invariant 99 // that each physical frame decodes to at least one virtual 100 // frame (i.e., there are no physical frames for interstitial 101 // functions or other things that we completely ignore). If 102 // this ever changes, we can remove the assert and convert 103 // this to a loop that polls until it finds virtual frames. 104 self.current = None; 105 if self.frames.is_empty() { 106 let Some(next_frame) = self.iter.next() else { 107 return; 108 }; 109 self.frames = VirtualFrame::decode( 110 self.iter.store.0.as_store_opaque(), 111 next_frame, 112 self.is_trapping_frame, 113 ); 114 debug_assert!(!self.frames.is_empty()); 115 self.is_trapping_frame = false; 116 } 117 118 // Take a frame and focus it as the current one. 119 self.current = self.frames.pop().map(|vf| FrameData::compute(vf)); 120 } 121 122 /// Has the iterator reached the end of the activation? 123 pub fn done(&self) -> bool { 124 self.current.is_none() 125 } 126 127 fn frame_data(&self) -> &FrameData { 128 self.current.as_ref().expect("No current frame") 129 } 130 131 fn raw_instance(&self) -> &crate::vm::Instance { 132 // Read out the vmctx slot. 133 134 // SAFETY: vmctx is always at offset 0 in the slot. 135 // (See crates/cranelift/src/func_environ.rs in `update_stack_slot_vmctx()`.) 136 let vmctx: *mut VMContext = unsafe { *(self.frame_data().slot_addr as *mut _) }; 137 let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot"); 138 // SAFETY: the stored vmctx value is a valid instance in this 139 // store; we only visit frames from this store in the 140 // backtrace. 141 let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) }; 142 // SAFETY: the instance pointer read above is valid. 143 unsafe { instance.as_ref() } 144 } 145 146 /// Get the instance associated with the current frame. 147 pub fn instance(&mut self) -> Instance { 148 let instance = self.raw_instance(); 149 Instance::from_wasmtime(instance.id(), self.iter.store.0.as_store_opaque()) 150 } 151 152 /// Get the module associated with the current frame, if any 153 /// (i.e., not a container instance for a host-created entity). 154 pub fn module(&self) -> Option<&Module> { 155 let instance = self.raw_instance(); 156 instance.runtime_module() 157 } 158 159 /// Get the raw function index associated with the current frame, and the 160 /// PC as an offset within its code section, if it is a Wasm 161 /// function directly from the given `Module` (rather than a 162 /// trampoline). 163 pub fn wasm_function_index_and_pc(&self) -> Option<(DefinedFuncIndex, u32)> { 164 let data = self.frame_data(); 165 let FuncKey::DefinedWasmFunction(module, func) = data.func_key else { 166 return None; 167 }; 168 debug_assert_eq!( 169 module, 170 self.module() 171 .expect("module should be defined if this is a defined function") 172 .env_module() 173 .module_index 174 ); 175 Some((func, data.wasm_pc)) 176 } 177 178 /// Get the number of locals in this frame. 179 pub fn num_locals(&self) -> u32 { 180 u32::try_from(self.frame_data().locals.len()).unwrap() 181 } 182 183 /// Get the depth of the operand stack in this frame. 184 pub fn num_stacks(&self) -> u32 { 185 u32::try_from(self.frame_data().stack.len()).unwrap() 186 } 187 188 /// Get the type and value of the given local in this frame. 189 /// 190 /// # Panics 191 /// 192 /// Panics if the index is out-of-range (greater than 193 /// `num_locals()`). 194 pub fn local(&mut self, index: u32) -> Val { 195 let data = self.frame_data(); 196 let (offset, ty) = data.locals[usize::try_from(index).unwrap()]; 197 let slot_addr = data.slot_addr; 198 // SAFETY: compiler produced metadata to describe this local 199 // slot and stored a value of the correct type into it. 200 unsafe { read_value(&mut self.iter.store.0, slot_addr, offset, ty) } 201 } 202 203 /// Get the type and value of the given operand-stack value in 204 /// this frame. 205 /// 206 /// Index 0 corresponds to the bottom-of-stack, and higher indices 207 /// from there are more recently pushed values. In other words, 208 /// index order reads the Wasm virtual machine's abstract stack 209 /// state left-to-right. 210 pub fn stack(&mut self, index: u32) -> Val { 211 let data = self.frame_data(); 212 let (offset, ty) = data.stack[usize::try_from(index).unwrap()]; 213 let slot_addr = data.slot_addr; 214 // SAFETY: compiler produced metadata to describe this 215 // operand-stack slot and stored a value of the correct type 216 // into it. 217 unsafe { read_value(&mut self.iter.store.0, slot_addr, offset, ty) } 218 } 219 } 220 221 /// Internal data pre-computed for one stack frame. 222 /// 223 /// This combines physical frame info (pc, fp) with the module this PC 224 /// maps to (yielding a frame table) and one frame as produced by the 225 /// progpoint lookup (Wasm PC, frame descriptor index, stack shape). 226 struct VirtualFrame { 227 /// The frame pointer. 228 fp: *const u8, 229 /// The resolved module handle for the physical PC. 230 /// 231 /// The module for each inlined frame within the physical frame is 232 /// resolved from the vmctx reachable for each such frame; this 233 /// module isused only for looking up the frame table. 234 module: Module, 235 /// The Wasm PC for this frame. 236 wasm_pc: u32, 237 /// The frame descriptor for this frame. 238 frame_descriptor: FrameTableDescriptorIndex, 239 /// The stack shape for this frame. 240 stack_shape: FrameStackShape, 241 } 242 243 impl VirtualFrame { 244 /// Return virtual frames corresponding to a physical frame, from 245 /// outermost to innermost. 246 fn decode(store: &mut StoreOpaque, frame: Frame, is_trapping_frame: bool) -> Vec<VirtualFrame> { 247 let (module_with_code, pc) = store 248 .modules() 249 .module_and_code_by_pc(frame.pc()) 250 .expect("Wasm frame PC does not correspond to a module"); 251 let module = module_with_code.module(); 252 let table = module.frame_table().unwrap(); 253 let pc = u32::try_from(pc).expect("PC offset too large"); 254 let pos = if is_trapping_frame { 255 FrameInstPos::Pre 256 } else { 257 FrameInstPos::Post 258 }; 259 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"); 260 261 program_points 262 .map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame { 263 fp: core::ptr::with_exposed_provenance(frame.fp()), 264 module: module.clone(), 265 wasm_pc, 266 frame_descriptor, 267 stack_shape, 268 }) 269 .collect() 270 } 271 } 272 273 /// Data computed when we visit a given frame. 274 struct FrameData { 275 slot_addr: *const u8, 276 func_key: FuncKey, 277 wasm_pc: u32, 278 /// Shape of locals in this frame. 279 /// 280 /// We need to store this locally because `FrameView` cannot 281 /// borrow the store: it needs a mut borrow, and an iterator 282 /// cannot yield the same mut borrow multiple times because it 283 /// cannot control the lifetime of the values it yields (the 284 /// signature of `next()` does not bound the return value to the 285 /// `&mut self` arg). 286 locals: Vec<(FrameStateSlotOffset, FrameValType)>, 287 /// Shape of the stack slots at this program point in this frame. 288 /// 289 /// In addition to the borrowing-related reason above, we also 290 /// materialize this because we want to provide O(1) access to the 291 /// stack by depth, and the frame slot descriptor stores info in a 292 /// linked-list (actually DAG, with dedup'ing) way. 293 stack: Vec<(FrameStateSlotOffset, FrameValType)>, 294 } 295 296 impl FrameData { 297 fn compute(frame: VirtualFrame) -> Self { 298 let frame_table = frame.module.frame_table().unwrap(); 299 // Parse the frame descriptor. 300 let (data, slot_to_fp_offset) = frame_table 301 .frame_descriptor(frame.frame_descriptor) 302 .unwrap(); 303 let frame_state_slot = FrameStateSlot::parse(data).unwrap(); 304 let slot_addr = frame 305 .fp 306 .wrapping_sub(usize::try_from(slot_to_fp_offset).unwrap()); 307 308 // Materialize the stack shape so we have O(1) access to its 309 // elements, and so we don't need to keep the borrow to the 310 // module alive. 311 let mut stack = frame_state_slot 312 .stack(frame.stack_shape) 313 .collect::<Vec<_>>(); 314 stack.reverse(); // Put top-of-stack last. 315 316 // Materialize the local offsets/types so we don't need to 317 // keep the borrow to the module alive. 318 let locals = frame_state_slot.locals().collect::<Vec<_>>(); 319 320 FrameData { 321 slot_addr, 322 func_key: frame_state_slot.func_key(), 323 wasm_pc: frame.wasm_pc, 324 stack, 325 locals, 326 } 327 } 328 } 329 330 /// Read the value at the given offset. 331 /// 332 /// # Safety 333 /// 334 /// The `offset` and `ty` must correspond to a valid value written 335 /// to the frame by generated code of the correct type. This will 336 /// be the case if this information comes from the frame tables 337 /// (as long as the frontend that generates the tables and 338 /// instrumentation is correct, and as long as the tables are 339 /// preserved through serialization). 340 unsafe fn read_value( 341 store: &mut StoreOpaque, 342 slot_base: *const u8, 343 offset: FrameStateSlotOffset, 344 ty: FrameValType, 345 ) -> Val { 346 let address = unsafe { slot_base.offset(isize::try_from(offset.offset()).unwrap()) }; 347 348 // SAFETY: each case reads a value from memory that should be 349 // valid according to our safety condition. 350 match ty { 351 FrameValType::I32 => { 352 let value = unsafe { *(address as *const i32) }; 353 Val::I32(value) 354 } 355 FrameValType::I64 => { 356 let value = unsafe { *(address as *const i64) }; 357 Val::I64(value) 358 } 359 FrameValType::F32 => { 360 let value = unsafe { *(address as *const u32) }; 361 Val::F32(value) 362 } 363 FrameValType::F64 => { 364 let value = unsafe { *(address as *const u64) }; 365 Val::F64(value) 366 } 367 FrameValType::V128 => { 368 let value = unsafe { *(address as *const u128) }; 369 Val::V128(value.into()) 370 } 371 FrameValType::AnyRef => { 372 let mut nogc = AutoAssertNoGc::new(store); 373 let value = unsafe { *(address as *const u32) }; 374 let value = AnyRef::_from_raw(&mut nogc, value); 375 Val::AnyRef(value) 376 } 377 FrameValType::ExnRef => { 378 let mut nogc = AutoAssertNoGc::new(store); 379 let value = unsafe { *(address as *const u32) }; 380 let value = ExnRef::_from_raw(&mut nogc, value); 381 Val::ExnRef(value) 382 } 383 FrameValType::ExternRef => { 384 let mut nogc = AutoAssertNoGc::new(store); 385 let value = unsafe { *(address as *const u32) }; 386 let value = ExternRef::_from_raw(&mut nogc, value); 387 Val::ExternRef(value) 388 } 389 FrameValType::FuncRef => { 390 let value = unsafe { *(address as *const *mut c_void) }; 391 let value = unsafe { Func::_from_raw(store, value) }; 392 Val::FuncRef(value) 393 } 394 FrameValType::ContRef => { 395 unimplemented!("contref values are not implemented in the host API yet") 396 } 397 } 398 } 399 400 /// Compute raw pointers to all GC refs in the given frame. 401 // Note: ideally this would be an impl Iterator, but this is quite 402 // awkward because of the locally computed data (FrameStateSlot::parse 403 // structured result) within the closure borrowed by a nested closure. 404 #[cfg(feature = "gc")] 405 pub(crate) fn gc_refs_in_frame<'a>(ft: FrameTable<'a>, pc: u32, fp: *mut usize) -> Vec<*mut u32> { 406 let fp = fp.cast::<u8>(); 407 let mut ret = vec![]; 408 if let Some(frames) = ft.find_program_point(pc, FrameInstPos::Post) { 409 for (_wasm_pc, frame_desc, stack_shape) in frames { 410 let (frame_desc_data, slot_to_fp_offset) = ft.frame_descriptor(frame_desc).unwrap(); 411 let frame_base = unsafe { fp.offset(-isize::try_from(slot_to_fp_offset).unwrap()) }; 412 let frame_desc = FrameStateSlot::parse(frame_desc_data).unwrap(); 413 for (offset, ty) in frame_desc.stack_and_locals(stack_shape) { 414 match ty { 415 FrameValType::AnyRef | FrameValType::ExnRef | FrameValType::ExternRef => { 416 let slot = unsafe { 417 frame_base 418 .offset(isize::try_from(offset.offset()).unwrap()) 419 .cast::<u32>() 420 }; 421 ret.push(slot); 422 } 423 FrameValType::ContRef | FrameValType::FuncRef => {} 424 FrameValType::I32 425 | FrameValType::I64 426 | FrameValType::F32 427 | FrameValType::F64 428 | FrameValType::V128 => {} 429 } 430 } 431 } 432 } 433 ret 434 } 435 436 impl<'a, T: 'static> AsContext for DebugFrameCursor<'a, T> { 437 type Data = T; 438 fn as_context(&self) -> StoreContext<'_, Self::Data> { 439 StoreContext(self.iter.store.0) 440 } 441 } 442 impl<'a, T: 'static> AsContextMut for DebugFrameCursor<'a, T> { 443 fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> { 444 StoreContextMut(self.iter.store.0) 445 } 446 } 447 448 /// One debug event that occurs when running Wasm code on a store with 449 /// a debug handler attached. 450 #[derive(Debug)] 451 pub enum DebugEvent<'a> { 452 /// An `anyhow::Error` was raised by a hostcall. 453 HostcallError(&'a anyhow::Error), 454 /// An exception is thrown and caught by Wasm. The current state 455 /// is at the throw-point. 456 CaughtExceptionThrown(OwnedRooted<ExnRef>), 457 /// An exception was not caught and is escaping to the host. 458 UncaughtExceptionThrown(OwnedRooted<ExnRef>), 459 /// A Wasm trap occurred. 460 Trap(Trap), 461 } 462 463 /// A handler for debug events. 464 /// 465 /// This is an async callback that is invoked directly within the 466 /// context of a debug event that occurs, i.e., with the Wasm code 467 /// still on the stack. The callback can thus observe that stack, up 468 /// to the most recent entry to Wasm.[^1] 469 /// 470 /// Because this callback receives a `StoreContextMut`, it has full 471 /// access to any state that any other hostcall has, including the 472 /// `T`. In that way, it is like an epoch-deadline callback or a 473 /// call-hook callback. It also "freezes" the entire store for the 474 /// duration of the debugger callback future. 475 /// 476 /// In the future, we expect to provide an "externally async" API on 477 /// the `Store` that allows receiving a stream of debug events and 478 /// accessing the store mutably while frozen; that will need to 479 /// integrate with [`Store::run_concurrent`] to properly timeslice and 480 /// scope the mutable access to the store, and has not been built 481 /// yet. In the meantime, it should be possible to build a fully 482 /// functional debugger with this async-callback API by channeling 483 /// debug events out, and requests to read the store back in, over 484 /// message-passing channels between the callback and an external 485 /// debugger main loop. 486 /// 487 /// Note that the `handle` hook may use its mutable store access to 488 /// invoke another Wasm. Debug events will also be caught and will 489 /// cause further `handle` invocations during this recursive 490 /// invocation. It is up to the debugger to handle any implications of 491 /// this reentrancy (e.g., implications on a duplex channel protocol 492 /// with an event/continue handshake) if it does so. 493 /// 494 /// Note also that this trait has `Clone` as a supertrait, and the 495 /// handler is cloned at every invocation as an artifact of the 496 /// internal ownership structure of Wasmtime: the handler itself is 497 /// owned by the store, but also receives a mutable borrow to the 498 /// whole store, so we need to clone it out to invoke it. It is 499 /// recommended that this trait be implemented by a type that is cheap 500 /// to clone: for example, a single `Arc` handle to debugger state. 501 /// 502 /// [^1]: Providing visibility further than the most recent entry to 503 /// Wasm is not directly possible because it could see into 504 /// another async stack, and the stack that polls the future 505 /// running a particular Wasm invocation could change after each 506 /// suspend point in the handler. 507 pub trait DebugHandler: Clone + Send + Sync + 'static { 508 /// The data expected on the store that this handler is attached 509 /// to. 510 type Data; 511 512 /// Handle a debug event. 513 fn handle( 514 &self, 515 store: StoreContextMut<'_, Self::Data>, 516 event: DebugEvent<'_>, 517 ) -> impl Future<Output = ()> + Send; 518 } 519