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