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