This method configures whether native unwind information (e.g. `.eh_frame` on Linux) is generated or not. This helps integrate with third-party stack capturing tools, such as the system unwinder or the `backtrace` crate. It does not affect whether Wasmtime can capture stack traces in Wasm code that it is running or not. Unwind info is always enabled on Windows, since the Windows ABI requires it. This configuration option defaults to true. Additionally, we deprecate `Config::wasm_backtrace` since we can always cheaply capture stack traces ever since https://github.com/bytecodealliance/wasmtime/pull/4431. Fixes https://github.com/bytecodealliance/wasmtime/issues/4554
757 lines
22 KiB
Rust
757 lines
22 KiB
Rust
use anyhow::Result;
|
|
use std::panic::{self, AssertUnwindSafe};
|
|
use std::process::Command;
|
|
use wasmtime::*;
|
|
|
|
#[test]
|
|
fn test_trap_return() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let wat = r#"
|
|
(module
|
|
(func $hello (import "" "hello"))
|
|
(func (export "run") (call $hello))
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let hello_type = FuncType::new(None, None);
|
|
let hello_func = Func::new(&mut store, hello_type, |_, _, _| Err(Trap::new("test 123")));
|
|
|
|
let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
|
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
|
|
let e = run_func
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
assert!(e.to_string().contains("test 123"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn test_trap_trace() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let wat = r#"
|
|
(module $hello_mod
|
|
(func (export "run") (call $hello))
|
|
(func $hello (unreachable))
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
|
|
let e = run_func
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
|
|
let trace = e.trace().expect("backtrace is available");
|
|
assert_eq!(trace.len(), 2);
|
|
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
|
assert_eq!(trace[0].func_index(), 1);
|
|
assert_eq!(trace[0].func_name(), Some("hello"));
|
|
assert_eq!(trace[0].func_offset(), Some(1));
|
|
assert_eq!(trace[0].module_offset(), Some(0x26));
|
|
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
|
|
assert_eq!(trace[1].func_index(), 0);
|
|
assert_eq!(trace[1].func_name(), None);
|
|
assert_eq!(trace[1].func_offset(), Some(1));
|
|
assert_eq!(trace[1].module_offset(), Some(0x21));
|
|
assert!(
|
|
e.to_string().contains("unreachable"),
|
|
"wrong message: {}",
|
|
e.to_string()
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[allow(deprecated)]
|
|
fn test_trap_backtrace_disabled() -> Result<()> {
|
|
let mut config = Config::default();
|
|
config.wasm_backtrace(false);
|
|
let engine = Engine::new(&config).unwrap();
|
|
let mut store = Store::<()>::new(&engine, ());
|
|
let wat = r#"
|
|
(module $hello_mod
|
|
(func (export "run") (call $hello))
|
|
(func $hello (unreachable))
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
|
|
let e = run_func
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
|
|
assert!(e.trace().is_none(), "backtraces should be disabled");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn test_trap_trace_cb() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let wat = r#"
|
|
(module $hello_mod
|
|
(import "" "throw" (func $throw))
|
|
(func (export "run") (call $hello))
|
|
(func $hello (call $throw))
|
|
)
|
|
"#;
|
|
|
|
let fn_type = FuncType::new(None, None);
|
|
let fn_func = Func::new(&mut store, fn_type, |_, _, _| Err(Trap::new("cb throw")));
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[fn_func.into()])?;
|
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
|
|
let e = run_func
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
|
|
let trace = e.trace().expect("backtrace is available");
|
|
assert_eq!(trace.len(), 2);
|
|
assert_eq!(trace[0].module_name().unwrap(), "hello_mod");
|
|
assert_eq!(trace[0].func_index(), 2);
|
|
assert_eq!(trace[1].module_name().unwrap(), "hello_mod");
|
|
assert_eq!(trace[1].func_index(), 1);
|
|
assert!(e.to_string().contains("cb throw"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn test_trap_stack_overflow() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let wat = r#"
|
|
(module $rec_mod
|
|
(func $run (export "run") (call $run))
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
|
|
let e = run_func
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
|
|
let trace = e.trace().expect("backtrace is available");
|
|
assert!(trace.len() >= 32);
|
|
for i in 0..trace.len() {
|
|
assert_eq!(trace[i].module_name().unwrap(), "rec_mod");
|
|
assert_eq!(trace[i].func_index(), 0);
|
|
assert_eq!(trace[i].func_name(), Some("run"));
|
|
}
|
|
assert!(e.to_string().contains("call stack exhausted"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn trap_display_pretty() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let wat = r#"
|
|
(module $m
|
|
(func $die unreachable)
|
|
(func call $die)
|
|
(func $foo call 1)
|
|
(func (export "bar") call $foo)
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "bar")?;
|
|
|
|
let e = run_func
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
assert_eq!(
|
|
e.to_string(),
|
|
"\
|
|
wasm trap: wasm `unreachable` instruction executed
|
|
wasm backtrace:
|
|
0: 0x23 - m!die
|
|
1: 0x27 - m!<wasm function 1>
|
|
2: 0x2c - m!foo
|
|
3: 0x31 - m!<wasm function 3>
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn trap_display_multi_module() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let wat = r#"
|
|
(module $a
|
|
(func $die unreachable)
|
|
(func call $die)
|
|
(func $foo call 1)
|
|
(func (export "bar") call $foo)
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let bar = instance.get_export(&mut store, "bar").unwrap();
|
|
|
|
let wat = r#"
|
|
(module $b
|
|
(import "" "" (func $bar))
|
|
(func $middle call $bar)
|
|
(func (export "bar2") call $middle)
|
|
)
|
|
"#;
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[bar])?;
|
|
let bar2 = instance.get_typed_func::<(), (), _>(&mut store, "bar2")?;
|
|
|
|
let e = bar2
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
assert_eq!(
|
|
e.to_string(),
|
|
"\
|
|
wasm trap: wasm `unreachable` instruction executed
|
|
wasm backtrace:
|
|
0: 0x23 - a!die
|
|
1: 0x27 - a!<wasm function 1>
|
|
2: 0x2c - a!foo
|
|
3: 0x31 - a!<wasm function 3>
|
|
4: 0x29 - b!middle
|
|
5: 0x2e - b!<wasm function 2>
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn trap_start_function_import() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let binary = wat::parse_str(
|
|
r#"
|
|
(module $a
|
|
(import "" "" (func $foo))
|
|
(start $foo)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let module = Module::new(store.engine(), &binary)?;
|
|
let sig = FuncType::new(None, None);
|
|
let func = Func::new(&mut store, sig, |_, _, _| Err(Trap::new("user trap")));
|
|
let err = Instance::new(&mut store, &module, &[func.into()])
|
|
.err()
|
|
.unwrap();
|
|
assert!(err
|
|
.downcast_ref::<Trap>()
|
|
.unwrap()
|
|
.to_string()
|
|
.contains("user trap"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn rust_panic_import() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let binary = wat::parse_str(
|
|
r#"
|
|
(module $a
|
|
(import "" "" (func $foo))
|
|
(import "" "" (func $bar))
|
|
(func (export "foo") call $foo)
|
|
(func (export "bar") call $bar)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let module = Module::new(store.engine(), &binary)?;
|
|
let sig = FuncType::new(None, None);
|
|
let func = Func::new(&mut store, sig, |_, _, _| panic!("this is a panic"));
|
|
let func2 = Func::wrap(&mut store, || panic!("this is another panic"));
|
|
let instance = Instance::new(&mut store, &module, &[func.into(), func2.into()])?;
|
|
let func = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
|
|
let err =
|
|
panic::catch_unwind(AssertUnwindSafe(|| drop(func.call(&mut store, ())))).unwrap_err();
|
|
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
|
|
|
|
let func = instance.get_typed_func::<(), (), _>(&mut store, "bar")?;
|
|
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(func.call(&mut store, ()));
|
|
}))
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err.downcast_ref::<&'static str>(),
|
|
Some(&"this is another panic")
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
// Test that we properly save/restore our trampolines' saved Wasm registers
|
|
// (used when capturing backtraces) before we resume panics.
|
|
#[test]
|
|
fn rust_catch_panic_import() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
|
|
let binary = wat::parse_str(
|
|
r#"
|
|
(module $a
|
|
(import "" "panic" (func $panic))
|
|
(import "" "catch panic" (func $catch_panic))
|
|
(func (export "panic") call $panic)
|
|
(func (export "run")
|
|
call $catch_panic
|
|
call $catch_panic
|
|
unreachable
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let module = Module::new(store.engine(), &binary)?;
|
|
let num_panics = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
|
|
let sig = FuncType::new(None, None);
|
|
let panic = Func::new(&mut store, sig, {
|
|
let num_panics = num_panics.clone();
|
|
move |_, _, _| {
|
|
num_panics.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
panic!("this is a panic");
|
|
}
|
|
});
|
|
let catch_panic = Func::wrap(&mut store, |mut caller: Caller<'_, _>| {
|
|
panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(
|
|
caller
|
|
.get_export("panic")
|
|
.unwrap()
|
|
.into_func()
|
|
.unwrap()
|
|
.call(&mut caller, &[], &mut []),
|
|
);
|
|
}))
|
|
.unwrap_err();
|
|
});
|
|
|
|
let instance = Instance::new(&mut store, &module, &[panic.into(), catch_panic.into()])?;
|
|
let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
let trap = run.call(&mut store, ()).unwrap_err();
|
|
let trace = trap.trace().unwrap();
|
|
assert_eq!(trace.len(), 1);
|
|
assert_eq!(trace[0].func_index(), 3);
|
|
assert_eq!(num_panics.load(std::sync::atomic::Ordering::SeqCst), 2);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn rust_panic_start_function() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let binary = wat::parse_str(
|
|
r#"
|
|
(module $a
|
|
(import "" "" (func $foo))
|
|
(start $foo)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let module = Module::new(store.engine(), &binary)?;
|
|
let sig = FuncType::new(None, None);
|
|
let func = Func::new(&mut store, sig, |_, _, _| panic!("this is a panic"));
|
|
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(Instance::new(&mut store, &module, &[func.into()]));
|
|
}))
|
|
.unwrap_err();
|
|
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
|
|
|
|
let func = Func::wrap(&mut store, || panic!("this is another panic"));
|
|
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(Instance::new(&mut store, &module, &[func.into()]));
|
|
}))
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err.downcast_ref::<&'static str>(),
|
|
Some(&"this is another panic")
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn mismatched_arguments() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let binary = wat::parse_str(
|
|
r#"
|
|
(module $a
|
|
(func (export "foo") (param i32))
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let module = Module::new(store.engine(), &binary)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let func = instance.get_func(&mut store, "foo").unwrap();
|
|
assert_eq!(
|
|
func.call(&mut store, &[], &mut []).unwrap_err().to_string(),
|
|
"expected 1 arguments, got 0"
|
|
);
|
|
assert_eq!(
|
|
func.call(&mut store, &[Val::F32(0)], &mut [])
|
|
.unwrap_err()
|
|
.to_string(),
|
|
"argument type mismatch: found f32 but expected i32",
|
|
);
|
|
assert_eq!(
|
|
func.call(&mut store, &[Val::I32(0), Val::I32(1)], &mut [])
|
|
.unwrap_err()
|
|
.to_string(),
|
|
"expected 1 arguments, got 2"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn call_signature_mismatch() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let binary = wat::parse_str(
|
|
r#"
|
|
(module $a
|
|
(func $foo
|
|
i32.const 0
|
|
call_indirect)
|
|
(func $bar (param i32))
|
|
(start $foo)
|
|
|
|
(table 1 anyfunc)
|
|
(elem (i32.const 0) 1)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let module = Module::new(store.engine(), &binary)?;
|
|
let err = Instance::new(&mut store, &module, &[])
|
|
.err()
|
|
.unwrap()
|
|
.downcast::<Trap>()
|
|
.unwrap();
|
|
assert!(err
|
|
.to_string()
|
|
.contains("wasm trap: indirect call type mismatch"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn start_trap_pretty() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let wat = r#"
|
|
(module $m
|
|
(func $die unreachable)
|
|
(func call $die)
|
|
(func $foo call 1)
|
|
(func $start call $foo)
|
|
(start $start)
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let e = match Instance::new(&mut store, &module, &[]) {
|
|
Ok(_) => panic!("expected failure"),
|
|
Err(e) => e.downcast::<Trap>()?,
|
|
};
|
|
|
|
assert_eq!(
|
|
e.to_string(),
|
|
"\
|
|
wasm trap: wasm `unreachable` instruction executed
|
|
wasm backtrace:
|
|
0: 0x1d - m!die
|
|
1: 0x21 - m!<wasm function 1>
|
|
2: 0x26 - m!foo
|
|
3: 0x2b - m!start
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn present_after_module_drop() -> Result<()> {
|
|
let mut store = Store::<()>::default();
|
|
let module = Module::new(store.engine(), r#"(func (export "foo") unreachable)"#)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let func = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
|
|
|
|
println!("asserting before we drop modules");
|
|
assert_trap(func.call(&mut store, ()).unwrap_err());
|
|
drop((instance, module));
|
|
|
|
println!("asserting after drop");
|
|
assert_trap(func.call(&mut store, ()).unwrap_err());
|
|
return Ok(());
|
|
|
|
fn assert_trap(t: Trap) {
|
|
println!("{}", t);
|
|
let trace = t.trace().expect("backtrace is available");
|
|
assert_eq!(trace.len(), 1);
|
|
assert_eq!(trace[0].func_index(), 0);
|
|
}
|
|
}
|
|
|
|
fn assert_trap_code(wat: &str, code: wasmtime::TrapCode) {
|
|
let mut store = Store::<()>::default();
|
|
let module = Module::new(store.engine(), wat).unwrap();
|
|
|
|
let err = match Instance::new(&mut store, &module, &[]) {
|
|
Ok(_) => unreachable!(),
|
|
Err(e) => e,
|
|
};
|
|
let trap = err.downcast_ref::<Trap>().unwrap();
|
|
assert_eq!(trap.trap_code(), Some(code));
|
|
}
|
|
|
|
#[test]
|
|
fn heap_out_of_bounds_trap() {
|
|
assert_trap_code(
|
|
r#"
|
|
(module
|
|
(memory 0)
|
|
(func $start (drop (i32.load (i32.const 1000000))))
|
|
(start $start)
|
|
)
|
|
"#,
|
|
TrapCode::MemoryOutOfBounds,
|
|
);
|
|
|
|
assert_trap_code(
|
|
r#"
|
|
(module
|
|
(memory 0)
|
|
(func $start (drop (i32.load memory.size)))
|
|
(start $start)
|
|
)
|
|
"#,
|
|
TrapCode::MemoryOutOfBounds,
|
|
);
|
|
}
|
|
|
|
fn rustc(src: &str) -> Vec<u8> {
|
|
let td = tempfile::TempDir::new().unwrap();
|
|
let output = td.path().join("foo.wasm");
|
|
let input = td.path().join("input.rs");
|
|
std::fs::write(&input, src).unwrap();
|
|
let result = Command::new("rustc")
|
|
.arg(&input)
|
|
.arg("-o")
|
|
.arg(&output)
|
|
.arg("--target")
|
|
.arg("wasm32-wasi")
|
|
.arg("-g")
|
|
.output()
|
|
.unwrap();
|
|
if result.status.success() {
|
|
return std::fs::read(&output).unwrap();
|
|
}
|
|
panic!(
|
|
"rustc failed: {}\n{}",
|
|
result.status,
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn parse_dwarf_info() -> Result<()> {
|
|
let wasm = rustc(
|
|
"
|
|
fn main() {
|
|
panic!();
|
|
}
|
|
",
|
|
);
|
|
let mut config = Config::new();
|
|
config.wasm_backtrace_details(WasmBacktraceDetails::Enable);
|
|
let engine = Engine::new(&config)?;
|
|
let module = Module::new(&engine, &wasm)?;
|
|
let mut linker = Linker::new(&engine);
|
|
wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
|
|
let mut store = Store::new(
|
|
&engine,
|
|
wasmtime_wasi::sync::WasiCtxBuilder::new()
|
|
.inherit_stdio()
|
|
.build(),
|
|
);
|
|
linker.module(&mut store, "", &module)?;
|
|
let run = linker.get_default(&mut store, "")?;
|
|
let trap = run
|
|
.call(&mut store, &[], &mut [])
|
|
.unwrap_err()
|
|
.downcast::<Trap>()?;
|
|
|
|
let mut found = false;
|
|
for frame in trap.trace().expect("backtrace is available") {
|
|
for symbol in frame.symbols() {
|
|
if let Some(file) = symbol.file() {
|
|
if file.ends_with("input.rs") {
|
|
found = true;
|
|
assert!(symbol.name().unwrap().contains("main"));
|
|
assert_eq!(symbol.line(), Some(3));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert!(found);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn no_hint_even_with_dwarf_info() -> Result<()> {
|
|
let mut config = Config::new();
|
|
config.wasm_backtrace_details(WasmBacktraceDetails::Disable);
|
|
let engine = Engine::new(&config)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let module = Module::new(
|
|
&engine,
|
|
r#"
|
|
(module
|
|
(@custom ".debug_info" (after last) "")
|
|
(func $start
|
|
unreachable)
|
|
(start $start)
|
|
)
|
|
"#,
|
|
)?;
|
|
let trap = Instance::new(&mut store, &module, &[])
|
|
.err()
|
|
.unwrap()
|
|
.downcast::<Trap>()?;
|
|
assert_eq!(
|
|
trap.to_string(),
|
|
"\
|
|
wasm trap: wasm `unreachable` instruction executed
|
|
wasm backtrace:
|
|
0: 0x1a - <unknown>!start
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn hint_with_dwarf_info() -> Result<()> {
|
|
// Skip this test if the env var is already configure, but in CI we're sure
|
|
// to run tests without this env var configured.
|
|
if std::env::var("WASMTIME_BACKTRACE_DETAILS").is_ok() {
|
|
return Ok(());
|
|
}
|
|
let mut store = Store::<()>::default();
|
|
let module = Module::new(
|
|
store.engine(),
|
|
r#"
|
|
(module
|
|
(@custom ".debug_info" (after last) "")
|
|
(func $start
|
|
unreachable)
|
|
(start $start)
|
|
)
|
|
"#,
|
|
)?;
|
|
let trap = Instance::new(&mut store, &module, &[])
|
|
.err()
|
|
.unwrap()
|
|
.downcast::<Trap>()?;
|
|
assert_eq!(
|
|
trap.to_string(),
|
|
"\
|
|
wasm trap: wasm `unreachable` instruction executed
|
|
wasm backtrace:
|
|
0: 0x1a - <unknown>!start
|
|
note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn multithreaded_traps() -> Result<()> {
|
|
// Compile and run unreachable on a thread, then moves over the whole store to another thread,
|
|
// and make sure traps are still correctly caught after notifying the store of the move.
|
|
let mut store = Store::<()>::default();
|
|
let module = Module::new(
|
|
store.engine(),
|
|
r#"(module (func (export "run") unreachable))"#,
|
|
)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
|
|
assert!(instance
|
|
.get_typed_func::<(), (), _>(&mut store, "run")?
|
|
.call(&mut store, ())
|
|
.is_err());
|
|
|
|
let handle = std::thread::spawn(move || {
|
|
assert!(instance
|
|
.get_typed_func::<(), (), _>(&mut store, "run")
|
|
.unwrap()
|
|
.call(&mut store, ())
|
|
.is_err());
|
|
});
|
|
|
|
handle.join().expect("couldn't join thread");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "macos", target_arch = "aarch64"), ignore)] // TODO #2808 system libunwind is broken on aarch64
|
|
fn traps_without_address_map() -> Result<()> {
|
|
let mut config = Config::new();
|
|
config.generate_address_map(false);
|
|
let engine = Engine::new(&config)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let wat = r#"
|
|
(module $hello_mod
|
|
(func (export "run") (call $hello))
|
|
(func $hello (unreachable))
|
|
)
|
|
"#;
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
|
|
let e = run_func
|
|
.call(&mut store, ())
|
|
.err()
|
|
.expect("error calling function");
|
|
|
|
let trace = e.trace().expect("backtrace is available");
|
|
assert_eq!(trace.len(), 2);
|
|
assert_eq!(trace[0].func_name(), Some("hello"));
|
|
assert_eq!(trace[0].func_index(), 1);
|
|
assert_eq!(trace[0].module_offset(), None);
|
|
assert_eq!(trace[1].func_name(), None);
|
|
assert_eq!(trace[1].func_index(), 0);
|
|
assert_eq!(trace[1].module_offset(), None);
|
|
Ok(())
|
|
}
|