#[cfg(feature = "coredump")]
use super::coredump::WasmCoreDump;
#[cfg(feature = "gc")]
use crate::ThrownException;
use crate::prelude::*;
use crate::store::StoreOpaque;
use crate::{AsContext, Module};
use core::fmt;
use wasmtime_environ::{FilePos, demangle_function_name, demangle_function_name_or_index};
pub use wasmtime_environ::Trap;
#[cold] pub(crate) fn from_runtime_box(
store: &mut StoreOpaque,
runtime_trap: Box<crate::runtime::vm::Trap>,
) -> Error {
let crate::runtime::vm::Trap {
reason,
backtrace,
coredumpstack,
} = *runtime_trap;
let (mut error, pc) = match reason {
#[cfg(feature = "gc")]
crate::runtime::vm::TrapReason::Exception => (ThrownException.into(), None),
crate::runtime::vm::TrapReason::User(error) => (error, None),
crate::runtime::vm::TrapReason::Jit {
pc,
faulting_addr,
trap,
} => {
let mut err: Error = trap.into();
if let Some(fault) = faulting_addr.and_then(|addr| store.wasm_fault(pc, addr)) {
err = err.context(fault);
}
(err, Some(pc))
}
crate::runtime::vm::TrapReason::Wasm(trap_code) => (trap_code.into(), None),
};
if let Some(bt) = backtrace {
let bt = WasmBacktrace::from_captured(store, bt, pc);
if !bt.wasm_trace.is_empty() {
error = error.context(bt);
}
}
let _ = &coredumpstack;
#[cfg(feature = "coredump")]
if let Some(coredump) = coredumpstack {
let bt = WasmBacktrace::from_captured(store, coredump.bt, pc);
let cd = WasmCoreDump::new(store, bt);
error = error.context(cd);
}
error
}
#[derive(Debug)]
pub struct WasmBacktrace {
wasm_trace: Vec<FrameInfo>,
hint_wasm_backtrace_details_env: bool,
_runtime_trace: crate::runtime::vm::Backtrace,
}
impl WasmBacktrace {
pub fn capture(store: impl AsContext) -> WasmBacktrace {
let store = store.as_context();
if store.engine().config().wasm_backtrace {
Self::force_capture(store)
} else {
WasmBacktrace {
wasm_trace: Vec::new(),
hint_wasm_backtrace_details_env: false,
_runtime_trace: crate::runtime::vm::Backtrace::empty(),
}
}
}
pub fn force_capture(store: impl AsContext) -> WasmBacktrace {
let store = store.as_context();
Self::from_captured(store.0, crate::runtime::vm::Backtrace::new(store.0), None)
}
fn from_captured(
store: &StoreOpaque,
runtime_trace: crate::runtime::vm::Backtrace,
trap_pc: Option<usize>,
) -> Self {
let mut wasm_trace = Vec::<FrameInfo>::with_capacity(runtime_trace.frames().len());
let mut hint_wasm_backtrace_details_env = false;
let wasm_backtrace_details_env_used =
store.engine().config().wasm_backtrace_details_env_used;
for frame in runtime_trace.frames() {
debug_assert!(frame.pc() != 0);
let pc_to_lookup = if Some(frame.pc()) == trap_pc {
frame.pc()
} else {
frame.pc() - 1
};
if let Some((info, module)) = store.modules().lookup_frame_info(pc_to_lookup) {
wasm_trace.push(info);
let has_unparsed_debuginfo =
module.module().compiled_module().has_unparsed_debuginfo();
if has_unparsed_debuginfo
&& wasm_backtrace_details_env_used
&& cfg!(feature = "addr2line")
{
hint_wasm_backtrace_details_env = true;
}
}
}
Self {
wasm_trace,
_runtime_trace: runtime_trace,
hint_wasm_backtrace_details_env,
}
}
pub fn frames(&self) -> &[FrameInfo] {
self.wasm_trace.as_slice()
}
}
impl fmt::Display for WasmBacktrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "error while executing at wasm backtrace:")?;
let mut needs_newline = false;
for (i, frame) in self.wasm_trace.iter().enumerate() {
if needs_newline {
writeln!(f, "")?;
} else {
needs_newline = true;
}
let name = frame.module().name().unwrap_or("<unknown>");
write!(f, " {i:>3}: ")?;
if let Some(offset) = frame.module_offset() {
write!(f, "{offset:#8x} - ")?;
}
let write_raw_func_name = |f: &mut fmt::Formatter<'_>| {
demangle_function_name_or_index(f, frame.func_name(), frame.func_index() as usize)
};
if frame.symbols().is_empty() {
write!(f, "{name}!")?;
write_raw_func_name(f)?;
} else {
for (i, symbol) in frame.symbols().iter().enumerate() {
if i > 0 {
if needs_newline {
writeln!(f, "")?;
} else {
needs_newline = true;
}
write!(f, " - ")?;
} else {
}
match symbol.name() {
Some(name) => demangle_function_name(f, name)?,
None if i == 0 => write_raw_func_name(f)?,
None => write!(f, "<inlined function>")?,
}
if let Some(file) = symbol.file() {
writeln!(f, "")?;
write!(f, " at {file}")?;
if let Some(line) = symbol.line() {
write!(f, ":{line}")?;
if let Some(col) = symbol.column() {
write!(f, ":{col}")?;
}
}
}
}
}
}
if self.hint_wasm_backtrace_details_env {
write!(
f,
"\nnote: using the `WASMTIME_BACKTRACE_DETAILS=1` \
environment variable may show more debugging information"
)?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct FrameInfo {
module: Module,
func_index: u32,
func_name: Option<String>,
func_start: FilePos,
instr: Option<FilePos>,
symbols: Vec<FrameSymbol>,
}
impl FrameInfo {
pub(crate) fn new(module: Module, text_offset: usize) -> Option<FrameInfo> {
let compiled_module = module.compiled_module();
let index = compiled_module.func_by_text_offset(text_offset)?;
let func_start = compiled_module.func_start_srcloc(index);
let instr =
wasmtime_environ::lookup_file_pos(module.engine_code().address_map_data(), text_offset);
let index = compiled_module.module().func_index(index);
let func_index = index.as_u32();
let func_name = compiled_module.func_name(index).map(|s| s.to_string());
debug_assert!(
instr.is_some() || !compiled_module.has_address_map(),
"failed to find instruction for {text_offset:#x}"
);
let mut symbols = Vec::new();
let _ = &mut symbols;
#[cfg(feature = "addr2line")]
if let Some(s) = &compiled_module.symbolize_context().ok().and_then(|c| c) {
if let Some(offset) = instr.and_then(|i| i.file_offset()) {
let to_lookup = u64::from(offset) - s.code_section_offset();
if let Ok(mut frames) = s.addr2line().find_frames(to_lookup).skip_all_loads() {
while let Ok(Some(frame)) = frames.next() {
symbols.push(FrameSymbol {
name: frame
.function
.as_ref()
.and_then(|l| l.raw_name().ok())
.map(|s| s.to_string()),
file: frame
.location
.as_ref()
.and_then(|l| l.file)
.map(|s| s.to_string()),
line: frame.location.as_ref().and_then(|l| l.line),
column: frame.location.as_ref().and_then(|l| l.column),
});
}
}
}
}
Some(FrameInfo {
module,
func_index,
func_name,
instr,
func_start,
symbols,
})
}
pub fn func_index(&self) -> u32 {
self.func_index
}
pub fn module(&self) -> &Module {
&self.module
}
pub fn func_name(&self) -> Option<&str> {
self.func_name.as_deref()
}
pub fn module_offset(&self) -> Option<usize> {
Some(self.instr?.file_offset()? as usize)
}
pub fn func_offset(&self) -> Option<usize> {
let instr_offset = self.instr?.file_offset()?;
Some((instr_offset - self.func_start.file_offset()?) as usize)
}
pub fn symbols(&self) -> &[FrameSymbol] {
&self.symbols
}
}
#[derive(Debug)]
pub struct FrameSymbol {
name: Option<String>,
file: Option<String>,
line: Option<u32>,
column: Option<u32>,
}
impl FrameSymbol {
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn file(&self) -> Option<&str> {
self.file.as_deref()
}
pub fn line(&self) -> Option<u32> {
self.line
}
pub fn column(&self) -> Option<u32> {
self.column
}
}