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