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