use super::store::AsStoreOpaque;
use crate::code::StoreCode;
use crate::module::RegisterBreakpointState;
use crate::store::StoreId;
use crate::vm::{Activation, Backtrace};
use crate::{
AnyRef, AsContextMut, CodeMemory, ExnRef, Extern, ExternRef, Func, Instance, Module,
OwnedRooted, StoreContext, StoreContextMut, Val,
code::StoreCodePC,
module::ModuleRegistry,
store::{AutoAssertNoGc, StoreOpaque},
vm::{CompiledModuleId, VMContext},
};
use crate::{Caller, Result, Store};
use alloc::collections::{BTreeMap, BTreeSet, btree_map::Entry};
use alloc::vec;
use alloc::vec::Vec;
use core::{ffi::c_void, ptr::NonNull};
#[cfg(feature = "gc")]
use wasmtime_environ::FrameTable;
pub use wasmtime_environ::ModulePC;
use wasmtime_environ::{
DefinedFuncIndex, EntityIndex, FrameInstPos, FrameStackShape, FrameStateSlot,
FrameStateSlotOffset, FrameTableBreakpointData, FrameTableDescriptorIndex, FrameValType,
FuncIndex, FuncKey, GlobalIndex, MemoryIndex, TableIndex, TagIndex, Trap,
};
use wasmtime_unwinder::{Frame, FrameCursor};
impl<T> Store<T> {
pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
self.as_store_opaque().debug_exit_frames()
}
pub fn edit_breakpoints<'a>(&'a mut self) -> Option<BreakpointEdit<'a>> {
self.as_store_opaque().edit_breakpoints()
}
pub fn debug_all_instances(&mut self) -> Vec<Instance> {
self.as_store_opaque().debug_all_instances()
}
pub fn debug_all_modules(&mut self) -> Vec<Module> {
self.as_store_opaque().debug_all_modules()
}
}
impl<'a, T> StoreContextMut<'a, T> {
pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
self.0.as_store_opaque().debug_exit_frames()
}
pub fn edit_breakpoints(self) -> Option<BreakpointEdit<'a>> {
self.0.as_store_opaque().edit_breakpoints()
}
pub fn debug_all_instances(self) -> Vec<Instance> {
self.0.as_store_opaque().debug_all_instances()
}
pub fn debug_all_modules(self) -> Vec<Module> {
self.0.as_store_opaque().debug_all_modules()
}
}
impl<'a, T> Caller<'a, T> {
pub fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
self.store.0.as_store_opaque().debug_exit_frames()
}
pub fn edit_breakpoints<'b>(&'b mut self) -> Option<BreakpointEdit<'b>> {
self.store.0.as_store_opaque().edit_breakpoints()
}
pub fn debug_all_instances(&mut self) -> Vec<Instance> {
self.store.0.as_store_opaque().debug_all_instances()
}
pub fn debug_all_modules(&mut self) -> Vec<Module> {
self.store.0.as_store_opaque().debug_all_modules()
}
}
impl StoreOpaque {
fn debug_exit_frames(&mut self) -> impl Iterator<Item = FrameHandle> {
let activations = if self.engine().tunables().debug_guest {
Backtrace::activations(self)
} else {
vec![]
};
activations
.into_iter()
.filter_map(|act| unsafe { FrameHandle::exit_frame(self, act) })
}
fn edit_breakpoints<'a>(&'a mut self) -> Option<BreakpointEdit<'a>> {
if !self.engine().tunables().debug_guest {
return None;
}
let (breakpoints, registry) = self.breakpoints_and_registry_mut();
Some(breakpoints.edit(registry))
}
fn debug_all_instances(&mut self) -> Vec<Instance> {
if !self.engine().tunables().debug_guest {
return vec![];
}
self.all_instances().collect()
}
fn debug_all_modules(&self) -> Vec<Module> {
if !self.engine().tunables().debug_guest {
return vec![];
}
self.modules().all_modules().cloned().collect()
}
}
impl Instance {
pub fn debug_global(
&self,
mut store: impl AsContextMut,
global_index: u32,
) -> Option<crate::Global> {
self.debug_export(
store.as_context_mut().0,
GlobalIndex::from_bits(global_index).into(),
)
.and_then(|s| s.into_global())
}
pub fn debug_memory(
&self,
mut store: impl AsContextMut,
memory_index: u32,
) -> Option<crate::Memory> {
self.debug_export(
store.as_context_mut().0,
MemoryIndex::from_bits(memory_index).into(),
)
.and_then(|s| s.into_memory())
}
pub fn debug_shared_memory(
&self,
mut store: impl AsContextMut,
memory_index: u32,
) -> Option<crate::SharedMemory> {
self.debug_export(
store.as_context_mut().0,
MemoryIndex::from_bits(memory_index).into(),
)
.and_then(|s| s.into_shared_memory())
}
pub fn debug_table(
&self,
mut store: impl AsContextMut,
table_index: u32,
) -> Option<crate::Table> {
self.debug_export(
store.as_context_mut().0,
TableIndex::from_bits(table_index).into(),
)
.and_then(|s| s.into_table())
}
pub fn debug_function(
&self,
mut store: impl AsContextMut,
function_index: u32,
) -> Option<crate::Func> {
self.debug_export(
store.as_context_mut().0,
FuncIndex::from_bits(function_index).into(),
)
.and_then(|s| s.into_func())
}
pub fn debug_tag(&self, mut store: impl AsContextMut, tag_index: u32) -> Option<crate::Tag> {
self.debug_export(
store.as_context_mut().0,
TagIndex::from_bits(tag_index).into(),
)
.and_then(|s| s.into_tag())
}
fn debug_export(&self, store: &mut StoreOpaque, index: EntityIndex) -> Option<Extern> {
if !store.engine().tunables().debug_guest {
return None;
}
let env_module = self._module(store).env_module();
if !env_module.is_valid(index) {
return None;
}
let store_id = store.id();
let (instance, registry) = store.instance_and_module_registry_mut(self.id());
let export = unsafe { instance.get_export_by_index_mut(registry, store_id, index) };
Some(Extern::from_wasmtime_export(export, store.engine()))
}
}
impl<'a, T> StoreContext<'a, T> {
pub fn breakpoints(self) -> Option<impl Iterator<Item = Breakpoint> + 'a> {
if !self.engine().tunables().debug_guest {
return None;
}
let (breakpoints, registry) = self.0.breakpoints_and_registry();
Some(breakpoints.breakpoints(registry))
}
pub fn is_single_step(&self) -> bool {
let (breakpoints, _) = self.0.breakpoints_and_registry();
breakpoints.is_single_step()
}
}
#[derive(Clone)]
pub struct FrameHandle {
cursor: FrameCursor,
virtual_frame_idx: usize,
store_id: StoreId,
store_version: u64,
}
impl FrameHandle {
unsafe fn exit_frame(store: &mut StoreOpaque, activation: Activation) -> Option<FrameHandle> {
let mut cursor = unsafe { activation.cursor() };
while !cursor.done() {
let (cache, registry) = store.frame_data_cache_mut_and_registry();
let frames = cache.lookup_or_compute(registry, cursor.frame());
if frames.len() > 0 {
return Some(FrameHandle {
cursor,
virtual_frame_idx: 0,
store_id: store.id(),
store_version: store.vm_store_context().execution_version,
});
}
unsafe {
cursor.advance(store.unwinder());
}
}
None
}
pub fn is_valid(&self, mut store: impl AsContextMut) -> bool {
let store = store.as_context_mut();
self.is_valid_impl(store.0.as_store_opaque())
}
fn is_valid_impl(&self, store: &StoreOpaque) -> bool {
let id = store.id();
let version = store.vm_store_context().execution_version;
self.store_id == id && self.store_version == version
}
pub fn parent(&self, mut store: impl AsContextMut) -> Result<Option<FrameHandle>> {
let mut store = store.as_context_mut();
if !self.is_valid(&mut store) {
crate::error::bail!("Frame handle is no longer valid.");
}
let mut parent = self.clone();
parent.virtual_frame_idx += 1;
while !parent.cursor.done() {
let (cache, registry) = store
.0
.as_store_opaque()
.frame_data_cache_mut_and_registry();
let frames = cache.lookup_or_compute(registry, parent.cursor.frame());
if parent.virtual_frame_idx < frames.len() {
return Ok(Some(parent));
}
parent.virtual_frame_idx = 0;
unsafe {
parent.cursor.advance(store.0.as_store_opaque().unwinder());
}
}
Ok(None)
}
fn frame_data<'a>(&self, store: &'a mut StoreOpaque) -> Result<&'a FrameData> {
if !self.is_valid_impl(store) {
crate::error::bail!("Frame handle is no longer valid.");
}
let (cache, registry) = store.frame_data_cache_mut_and_registry();
let frames = cache.lookup_or_compute(registry, self.cursor.frame());
Ok(&frames[frames.len() - 1 - self.virtual_frame_idx])
}
fn raw_instance<'a>(&self, store: &mut StoreOpaque) -> Result<&'a crate::vm::Instance> {
let frame_data = self.frame_data(store)?;
let vmctx: usize =
unsafe { *(frame_data.slot_addr(self.cursor.frame().fp()) as *mut usize) };
let vmctx: *mut VMContext = core::ptr::with_exposed_provenance_mut(vmctx);
let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot");
let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) };
Ok(unsafe { instance.as_ref() })
}
pub fn instance(&self, mut store: impl AsContextMut) -> Result<Instance> {
let store = store.as_context_mut();
let instance = self.raw_instance(store.0.as_store_opaque())?;
let id = instance.id();
Ok(Instance::from_wasmtime(id, store.0.as_store_opaque()))
}
pub fn module<'a, T: 'static>(
&self,
store: impl Into<StoreContextMut<'a, T>>,
) -> Result<Option<&'a Module>> {
let store = store.into();
let instance = self.raw_instance(store.0.as_store_opaque())?;
Ok(instance.runtime_module())
}
pub fn wasm_function_index_and_pc(
&self,
mut store: impl AsContextMut,
) -> Result<Option<(DefinedFuncIndex, ModulePC)>> {
let mut store = store.as_context_mut();
let frame_data = self.frame_data(store.0.as_store_opaque())?;
let FuncKey::DefinedWasmFunction(module, func) = frame_data.func_key else {
return Ok(None);
};
let wasm_pc = frame_data.wasm_pc;
debug_assert_eq!(
module,
self.module(&mut store)?
.expect("module should be defined if this is a defined function")
.env_module()
.module_index
);
Ok(Some((func, wasm_pc)))
}
pub fn num_locals(&self, mut store: impl AsContextMut) -> Result<u32> {
let store = store.as_context_mut();
let frame_data = self.frame_data(store.0.as_store_opaque())?;
Ok(u32::try_from(frame_data.locals.len()).unwrap())
}
pub fn num_stacks(&self, mut store: impl AsContextMut) -> Result<u32> {
let store = store.as_context_mut();
let frame_data = self.frame_data(store.0.as_store_opaque())?;
Ok(u32::try_from(frame_data.stack.len()).unwrap())
}
pub fn local(&self, mut store: impl AsContextMut, index: u32) -> Result<Val> {
let store = store.as_context_mut();
let frame_data = self.frame_data(store.0.as_store_opaque())?;
let (offset, ty) = frame_data.locals[usize::try_from(index).unwrap()];
let slot_addr = frame_data.slot_addr(self.cursor.frame().fp());
Ok(unsafe { read_value(store.0.as_store_opaque(), slot_addr, offset, ty) })
}
pub fn stack(&self, mut store: impl AsContextMut, index: u32) -> Result<Val> {
let store = store.as_context_mut();
let frame_data = self.frame_data(store.0.as_store_opaque())?;
let (offset, ty) = frame_data.stack[usize::try_from(index).unwrap()];
let slot_addr = frame_data.slot_addr(self.cursor.frame().fp());
Ok(unsafe { read_value(store.0.as_store_opaque(), slot_addr, offset, ty) })
}
}
pub(crate) struct FrameDataCache {
by_pc: BTreeMap<StoreCodePC, Vec<FrameData>>,
}
impl FrameDataCache {
pub(crate) fn new() -> FrameDataCache {
FrameDataCache {
by_pc: BTreeMap::new(),
}
}
fn lookup_or_compute<'a>(
&'a mut self,
registry: &ModuleRegistry,
frame: Frame,
) -> &'a [FrameData] {
let pc = StoreCodePC::from_raw(frame.pc());
match self.by_pc.entry(pc) {
Entry::Occupied(frames) => frames.into_mut(),
Entry::Vacant(v) => {
let (module, frames) = VirtualFrame::decode(registry, frame.pc());
let frames = frames
.into_iter()
.map(|frame| FrameData::compute(frame, &module))
.collect::<Vec<_>>();
v.insert(frames)
}
}
}
}
struct VirtualFrame {
wasm_pc: ModulePC,
frame_descriptor: FrameTableDescriptorIndex,
stack_shape: FrameStackShape,
}
impl VirtualFrame {
fn decode(registry: &ModuleRegistry, pc: usize) -> (Module, Vec<VirtualFrame>) {
let (module_with_code, pc) = registry
.module_and_code_by_pc(pc)
.expect("Wasm frame PC does not correspond to a module");
let module = module_with_code.module();
let table = module.frame_table().unwrap();
let pc = u32::try_from(pc).expect("PC offset too large");
let program_points = table.find_program_point(pc, FrameInstPos::Post)
.expect("There must be a program point record in every frame when debug instrumentation is enabled");
(
module.clone(),
program_points
.map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame {
wasm_pc,
frame_descriptor,
stack_shape,
})
.collect(),
)
}
}
struct FrameData {
slot_to_fp_offset: usize,
func_key: FuncKey,
wasm_pc: ModulePC,
locals: Vec<(FrameStateSlotOffset, FrameValType)>,
stack: Vec<(FrameStateSlotOffset, FrameValType)>,
}
impl FrameData {
fn compute(frame: VirtualFrame, module: &Module) -> Self {
let frame_table = module.frame_table().unwrap();
let (data, slot_to_fp_offset) = frame_table
.frame_descriptor(frame.frame_descriptor)
.unwrap();
let frame_state_slot = FrameStateSlot::parse(data).unwrap();
let slot_to_fp_offset = usize::try_from(slot_to_fp_offset).unwrap();
let mut stack = frame_state_slot
.stack(frame.stack_shape)
.collect::<Vec<_>>();
stack.reverse();
let locals = frame_state_slot.locals().collect::<Vec<_>>();
FrameData {
slot_to_fp_offset,
func_key: frame_state_slot.func_key(),
wasm_pc: frame.wasm_pc,
stack,
locals,
}
}
fn slot_addr(&self, fp: usize) -> *mut u8 {
let fp: *mut u8 = core::ptr::with_exposed_provenance_mut(fp);
fp.wrapping_sub(self.slot_to_fp_offset)
}
}
unsafe fn read_value(
store: &mut StoreOpaque,
slot_base: *const u8,
offset: FrameStateSlotOffset,
ty: FrameValType,
) -> Val {
let address = unsafe { slot_base.offset(isize::try_from(offset.offset()).unwrap()) };
match ty {
FrameValType::I32 => {
let value = unsafe { *(address as *const i32) };
Val::I32(value)
}
FrameValType::I64 => {
let value = unsafe { *(address as *const i64) };
Val::I64(value)
}
FrameValType::F32 => {
let value = unsafe { *(address as *const u32) };
Val::F32(value)
}
FrameValType::F64 => {
let value = unsafe { *(address as *const u64) };
Val::F64(value)
}
FrameValType::V128 => {
let value = unsafe { u128::from_le_bytes(*(address as *const [u8; 16])) };
Val::V128(value.into())
}
FrameValType::AnyRef => {
let mut nogc = AutoAssertNoGc::new(store);
let value = unsafe { *(address as *const u32) };
let value = AnyRef::_from_raw(&mut nogc, value);
Val::AnyRef(value)
}
FrameValType::ExnRef => {
let mut nogc = AutoAssertNoGc::new(store);
let value = unsafe { *(address as *const u32) };
let value = ExnRef::_from_raw(&mut nogc, value);
Val::ExnRef(value)
}
FrameValType::ExternRef => {
let mut nogc = AutoAssertNoGc::new(store);
let value = unsafe { *(address as *const u32) };
let value = ExternRef::_from_raw(&mut nogc, value);
Val::ExternRef(value)
}
FrameValType::FuncRef => {
let value = unsafe { *(address as *const *mut c_void) };
let value = unsafe { Func::_from_raw(store, value) };
Val::FuncRef(value)
}
FrameValType::ContRef => {
unimplemented!("contref values are not implemented in the host API yet")
}
}
}
#[cfg(feature = "gc")]
pub(crate) fn gc_refs_in_frame<'a>(ft: FrameTable<'a>, pc: u32, fp: *mut usize) -> Vec<*mut u32> {
let fp = fp.cast::<u8>();
let mut ret = vec![];
if let Some(frames) = ft.find_program_point(pc, FrameInstPos::Post) {
for (_wasm_pc, frame_desc, stack_shape) in frames {
let (frame_desc_data, slot_to_fp_offset) = ft.frame_descriptor(frame_desc).unwrap();
let frame_base = unsafe { fp.offset(-isize::try_from(slot_to_fp_offset).unwrap()) };
let frame_desc = FrameStateSlot::parse(frame_desc_data).unwrap();
for (offset, ty) in frame_desc.stack_and_locals(stack_shape) {
match ty {
FrameValType::AnyRef | FrameValType::ExnRef | FrameValType::ExternRef => {
let slot = unsafe {
frame_base
.offset(isize::try_from(offset.offset()).unwrap())
.cast::<u32>()
};
ret.push(slot);
}
FrameValType::ContRef | FrameValType::FuncRef => {}
FrameValType::I32
| FrameValType::I64
| FrameValType::F32
| FrameValType::F64
| FrameValType::V128 => {}
}
}
}
}
ret
}
#[derive(Debug)]
pub enum DebugEvent<'a> {
HostcallError(&'a crate::Error),
CaughtExceptionThrown(OwnedRooted<ExnRef>),
UncaughtExceptionThrown(OwnedRooted<ExnRef>),
Trap(Trap),
Breakpoint,
EpochYield,
}
pub trait DebugHandler: Clone + Send + Sync + 'static {
type Data;
fn handle(
&self,
store: StoreContextMut<'_, Self::Data>,
event: DebugEvent<'_>,
) -> impl Future<Output = ()> + Send;
}
#[derive(Default)]
pub(crate) struct BreakpointState {
single_step: bool,
breakpoints: BTreeMap<BreakpointKey, usize>,
breakpoint_redirects: BTreeMap<BreakpointKey, BreakpointKey>,
}
pub struct Breakpoint {
pub module: Module,
pub pc: ModulePC,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct BreakpointKey(CompiledModuleId, ModulePC);
impl BreakpointKey {
fn from_raw(module: &Module, pc: ModulePC) -> BreakpointKey {
BreakpointKey(module.id(), pc)
}
fn get(&self, registry: &ModuleRegistry) -> Breakpoint {
let module = registry
.module_by_compiled_id(self.0)
.expect("Module should not have been removed from Store")
.clone();
Breakpoint { module, pc: self.1 }
}
}
pub struct BreakpointEdit<'a> {
state: &'a mut BreakpointState,
registry: &'a mut ModuleRegistry,
dirty_modules: BTreeSet<StoreCodePC>,
}
impl BreakpointState {
pub(crate) fn edit<'a>(&'a mut self, registry: &'a mut ModuleRegistry) -> BreakpointEdit<'a> {
BreakpointEdit {
state: self,
registry,
dirty_modules: BTreeSet::new(),
}
}
pub(crate) fn breakpoints<'a>(
&'a self,
registry: &'a ModuleRegistry,
) -> impl Iterator<Item = Breakpoint> + 'a {
self.breakpoints.keys().map(|key| key.get(registry))
}
pub(crate) fn is_single_step(&self) -> bool {
self.single_step
}
pub(crate) fn patch_new_module(&self, code: &mut StoreCode, module: &Module) -> Result<()> {
if self.single_step {
let mem = code.code_memory_mut().unwrap();
mem.unpublish()?;
BreakpointEdit::apply_single_step(mem, module, true, |_key| false)?;
mem.publish()?;
}
Ok(())
}
}
impl<'a> BreakpointEdit<'a> {
fn get_code_memory<'b>(
breakpoints: &BreakpointState,
registry: &'b mut ModuleRegistry,
dirty_modules: &mut BTreeSet<StoreCodePC>,
module: &Module,
) -> Result<&'b mut CodeMemory> {
let store_code_pc =
registry.store_code_base_or_register(module, RegisterBreakpointState(breakpoints))?;
let code_memory = registry
.store_code_mut(store_code_pc)
.expect("Just checked presence above")
.code_memory_mut()
.expect("Must have unique ownership of StoreCode in guest-debug mode");
if dirty_modules.insert(store_code_pc) {
code_memory.unpublish()?;
}
Ok(code_memory)
}
fn patch<'b>(
patches: impl Iterator<Item = FrameTableBreakpointData<'b>> + 'b,
mem: &mut CodeMemory,
enable: bool,
) {
let mem = mem.text_mut();
for patch in patches {
let data = if enable { patch.enable } else { patch.disable };
let mem = &mut mem[patch.offset..patch.offset + data.len()];
log::trace!(
"patch: offset 0x{:x} with enable={enable}: data {data:?} replacing {mem:?}",
patch.offset
);
mem.copy_from_slice(data);
}
}
pub fn add_breakpoint(&mut self, module: &Module, pc: ModulePC) -> Result<()> {
let frame_table = module
.frame_table()
.expect("Frame table must be present when guest-debug is enabled");
let actual_pc = frame_table.nearest_breakpoint(pc).unwrap_or(pc);
let requested_key = BreakpointKey::from_raw(module, pc);
let actual_key = BreakpointKey::from_raw(module, actual_pc);
if actual_pc != pc {
log::trace!("slipping breakpoint from {requested_key:?} to {actual_key:?}");
self.state
.breakpoint_redirects
.insert(requested_key, actual_key);
}
let refcount = self.state.breakpoints.entry(actual_key).or_insert(0);
*refcount += 1;
if *refcount == 1 {
let mem =
Self::get_code_memory(self.state, self.registry, &mut self.dirty_modules, module)?;
let patches = frame_table.lookup_breakpoint_patches_by_pc(actual_pc);
Self::patch(patches, mem, true);
}
Ok(())
}
pub fn remove_breakpoint(&mut self, module: &Module, pc: ModulePC) -> Result<()> {
let requested_key = BreakpointKey::from_raw(module, pc);
let actual_key = self
.state
.breakpoint_redirects
.remove(&requested_key)
.unwrap_or(requested_key);
let actual_pc = actual_key.1;
if let Some(refcount) = self.state.breakpoints.get_mut(&actual_key) {
*refcount -= 1;
if *refcount == 0 {
self.state.breakpoints.remove(&actual_key);
if !self.state.single_step {
let mem = Self::get_code_memory(
self.state,
self.registry,
&mut self.dirty_modules,
module,
)?;
let frame_table = module
.frame_table()
.expect("Frame table must be present when guest-debug is enabled");
let patches = frame_table.lookup_breakpoint_patches_by_pc(actual_pc);
Self::patch(patches, mem, false);
}
}
}
Ok(())
}
fn apply_single_step<F: Fn(&BreakpointKey) -> bool>(
mem: &mut CodeMemory,
module: &Module,
enabled: bool,
key_enabled: F,
) -> Result<()> {
let table = module
.frame_table()
.expect("Frame table must be present when guest-debug is enabled");
for (wasm_pc, patch) in table.breakpoint_patches() {
let key = BreakpointKey::from_raw(&module, wasm_pc);
let this_enabled = enabled || key_enabled(&key);
log::trace!(
"single_step: enabled {enabled} key {key:?} -> this_enabled {this_enabled}"
);
Self::patch(core::iter::once(patch), mem, this_enabled);
}
Ok(())
}
pub fn single_step(&mut self, enabled: bool) -> Result<()> {
log::trace!(
"single_step({enabled}) with breakpoint set {:?}",
self.state.breakpoints
);
if self.state.single_step == enabled {
return Ok(());
}
let modules = self.registry.all_modules().cloned().collect::<Vec<_>>();
for module in modules {
let mem =
Self::get_code_memory(self.state, self.registry, &mut self.dirty_modules, &module)?;
Self::apply_single_step(mem, &module, enabled, |key| {
self.state.breakpoints.contains_key(key)
})?;
}
self.state.single_step = enabled;
Ok(())
}
}
impl<'a> Drop for BreakpointEdit<'a> {
fn drop(&mut self) {
for &store_code_base in &self.dirty_modules {
let store_code = self.registry.store_code_mut(store_code_base).unwrap();
if let Err(e) = store_code
.code_memory_mut()
.expect("Must have unique ownership of StoreCode in guest-debug mode")
.publish()
{
abort_on_republish_error(e);
}
}
}
}
#[cfg(feature = "std")]
fn abort_on_republish_error(e: crate::Error) -> ! {
log::error!(
"Failed to re-publish executable code: {e:?}. Wasmtime cannot return through JIT code on the stack and cannot even panic; aborting the process."
);
std::process::abort();
}
#[cfg(not(feature = "std"))]
fn abort_on_republish_error(e: crate::Error) -> ! {
panic!("Failed to re-publish executable code: {e:?}");
}