use crate::{
AnyRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, Instance, Module, OwnedRooted,
StoreContext, StoreContextMut, Val,
store::{AutoAssertNoGc, StoreOpaque},
vm::{CurrentActivationBacktrace, VMContext},
};
use alloc::vec;
use alloc::vec::Vec;
use core::{ffi::c_void, ptr::NonNull};
#[cfg(feature = "gc")]
use wasmtime_environ::FrameTable;
use wasmtime_environ::{
DefinedFuncIndex, FrameInstPos, FrameStackShape, FrameStateSlot, FrameStateSlotOffset,
FrameTableDescriptorIndex, FrameValType, FuncKey, Trap,
};
use wasmtime_unwinder::Frame;
use super::store::AsStoreOpaque;
impl<'a, T> StoreContextMut<'a, T> {
pub fn debug_frames(self) -> Option<DebugFrameCursor<'a, T>> {
if !self.engine().tunables().debug_guest {
return None;
}
let iter = unsafe { CurrentActivationBacktrace::new(self) };
let mut view = DebugFrameCursor {
iter,
is_trapping_frame: false,
frames: vec![],
current: None,
};
view.move_to_parent(); Some(view)
}
}
pub struct DebugFrameCursor<'a, T: 'static> {
iter: CurrentActivationBacktrace<'a, T>,
is_trapping_frame: bool,
frames: Vec<VirtualFrame>,
current: Option<FrameData>,
}
impl<'a, T: 'static> DebugFrameCursor<'a, T> {
pub fn move_to_parent(&mut self) {
self.current = None;
if self.frames.is_empty() {
let Some(next_frame) = self.iter.next() else {
return;
};
self.frames = VirtualFrame::decode(
self.iter.store.0.as_store_opaque(),
next_frame,
self.is_trapping_frame,
);
debug_assert!(!self.frames.is_empty());
self.is_trapping_frame = false;
}
self.current = self.frames.pop().map(|vf| FrameData::compute(vf));
}
pub fn done(&self) -> bool {
self.current.is_none()
}
fn frame_data(&self) -> &FrameData {
self.current.as_ref().expect("No current frame")
}
fn raw_instance(&self) -> &crate::vm::Instance {
let vmctx: *mut VMContext = unsafe { *(self.frame_data().slot_addr as *mut _) };
let vmctx = NonNull::new(vmctx).expect("null vmctx in debug state slot");
let instance = unsafe { crate::vm::Instance::from_vmctx(vmctx) };
unsafe { instance.as_ref() }
}
pub fn instance(&mut self) -> Instance {
let instance = self.raw_instance();
Instance::from_wasmtime(instance.id(), self.iter.store.0.as_store_opaque())
}
pub fn module(&self) -> Option<&Module> {
let instance = self.raw_instance();
instance.runtime_module()
}
pub fn wasm_function_index_and_pc(&self) -> Option<(DefinedFuncIndex, u32)> {
let data = self.frame_data();
let FuncKey::DefinedWasmFunction(module, func) = data.func_key else {
return None;
};
debug_assert_eq!(
module,
self.module()
.expect("module should be defined if this is a defined function")
.env_module()
.module_index
);
Some((func, data.wasm_pc))
}
pub fn num_locals(&self) -> u32 {
u32::try_from(self.frame_data().locals.len()).unwrap()
}
pub fn num_stacks(&self) -> u32 {
u32::try_from(self.frame_data().stack.len()).unwrap()
}
pub fn local(&mut self, index: u32) -> Val {
let data = self.frame_data();
let (offset, ty) = data.locals[usize::try_from(index).unwrap()];
let slot_addr = data.slot_addr;
unsafe { read_value(&mut self.iter.store.0, slot_addr, offset, ty) }
}
pub fn stack(&mut self, index: u32) -> Val {
let data = self.frame_data();
let (offset, ty) = data.stack[usize::try_from(index).unwrap()];
let slot_addr = data.slot_addr;
unsafe { read_value(&mut self.iter.store.0, slot_addr, offset, ty) }
}
}
struct VirtualFrame {
fp: *const u8,
module: Module,
wasm_pc: u32,
frame_descriptor: FrameTableDescriptorIndex,
stack_shape: FrameStackShape,
}
impl VirtualFrame {
fn decode(store: &mut StoreOpaque, frame: Frame, is_trapping_frame: bool) -> Vec<VirtualFrame> {
let (module_with_code, pc) = store
.modules()
.module_and_code_by_pc(frame.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 pos = if is_trapping_frame {
FrameInstPos::Pre
} else {
FrameInstPos::Post
};
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");
program_points
.map(|(wasm_pc, frame_descriptor, stack_shape)| VirtualFrame {
fp: core::ptr::with_exposed_provenance(frame.fp()),
module: module.clone(),
wasm_pc,
frame_descriptor,
stack_shape,
})
.collect()
}
}
struct FrameData {
slot_addr: *const u8,
func_key: FuncKey,
wasm_pc: u32,
locals: Vec<(FrameStateSlotOffset, FrameValType)>,
stack: Vec<(FrameStateSlotOffset, FrameValType)>,
}
impl FrameData {
fn compute(frame: VirtualFrame) -> Self {
let frame_table = frame.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_addr = frame
.fp
.wrapping_sub(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_addr,
func_key: frame_state_slot.func_key(),
wasm_pc: frame.wasm_pc,
stack,
locals,
}
}
}
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 { *(address as *const u128) };
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
}
impl<'a, T: 'static> AsContext for DebugFrameCursor<'a, T> {
type Data = T;
fn as_context(&self) -> StoreContext<'_, Self::Data> {
StoreContext(self.iter.store.0)
}
}
impl<'a, T: 'static> AsContextMut for DebugFrameCursor<'a, T> {
fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> {
StoreContextMut(self.iter.store.0)
}
}
#[derive(Debug)]
pub enum DebugEvent<'a> {
HostcallError(&'a anyhow::Error),
CaughtExceptionThrown(OwnedRooted<ExnRef>),
UncaughtExceptionThrown(OwnedRooted<ExnRef>),
Trap(Trap),
}
pub trait DebugHandler: Clone + Send + Sync + 'static {
type Data;
fn handle(
&self,
store: StoreContextMut<'_, Self::Data>,
event: DebugEvent<'_>,
) -> impl Future<Output = ()> + Send;
}