use crate::store::StoreOpaque;
use crate::{AsContext, Module};
use anyhow::Error;
use std::fmt;
use wasmtime_environ::{EntityRef, FilePos};
use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
pub use wasmtime_environ::Trap;
pub(crate) unsafe fn raise(error: anyhow::Error) -> ! {
let needs_backtrace = error.downcast_ref::<WasmBacktrace>().is_none();
wasmtime_runtime::raise_user_trap(error, needs_backtrace)
}
#[cold] pub(crate) fn from_runtime_box(
store: &StoreOpaque,
runtime_trap: Box<wasmtime_runtime::Trap>,
) -> Error {
let wasmtime_runtime::Trap { reason, backtrace } = *runtime_trap;
let (error, pc) = match reason {
wasmtime_runtime::TrapReason::User {
error,
needs_backtrace,
} => {
debug_assert!(
needs_backtrace == backtrace.is_some() || !store.engine().config().wasm_backtrace
);
(error, None)
}
wasmtime_runtime::TrapReason::Jit(pc) => {
let code = store
.modules()
.lookup_trap_code(pc)
.unwrap_or(Trap::StackOverflow);
(code.into(), Some(pc))
}
wasmtime_runtime::TrapReason::Wasm(trap_code) => (trap_code.into(), None),
};
match backtrace {
Some(bt) => {
let bt = WasmBacktrace::from_captured(store, bt, pc);
if bt.wasm_trace.is_empty() {
error
} else {
error.context(bt)
}
}
None => error,
}
}
#[derive(Debug)]
pub struct WasmBacktrace {
wasm_trace: Vec<FrameInfo>,
hint_wasm_backtrace_details_env: bool,
#[allow(dead_code)]
runtime_trace: wasmtime_runtime::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: wasmtime_runtime::Backtrace::empty(),
}
}
}
pub fn force_capture(store: impl AsContext) -> WasmBacktrace {
let store = store.as_context();
Self::from_captured(store.0, wasmtime_runtime::Backtrace::new(), None)
}
fn from_captured(
store: &StoreOpaque,
runtime_trace: wasmtime_runtime::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.compiled_module().has_unparsed_debuginfo();
if has_unparsed_debuginfo && wasm_backtrace_details_env_used {
hint_wasm_backtrace_details_env = true;
}
}
}
Self {
wasm_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, " {:>3}: ", i)?;
if let Some(offset) = frame.module_offset() {
write!(f, "{:#6x} - ", offset)?;
}
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 {
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_name: Option<String>,
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 module = module.compiled_module();
let (index, _func_offset) = module.func_by_text_offset(text_offset)?;
let info = module.wasm_func_info(index);
let instr =
wasmtime_environ::lookup_file_pos(module.code_memory().address_map_data(), text_offset);
debug_assert!(
instr.is_some() || !module.has_address_map(),
"failed to find instruction for {:#x}",
text_offset
);
let mut symbols = Vec::new();
if let Some(s) = &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) {
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),
});
}
}
}
}
let index = module.module().func_index(index);
Some(FrameInfo {
module_name: module.module().name.clone(),
func_index: index.index() as u32,
func_name: module.func_name(index).map(|s| s.to_string()),
instr,
func_start: info.start_srcloc,
symbols,
})
}
pub fn func_index(&self) -> u32 {
self.func_index
}
pub fn module_name(&self) -> Option<&str> {
self.module_name.as_deref()
}
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
}
}