* Provide filename/line number information in `Trap`
This commit extends the `Trap` type and `Store` to retain DWARF debug
information found in a wasm file unconditionally, if it's present. This
then enables us to print filenames and line numbers which point back to
actual source code when a trap backtrace is printed. Additionally the
`FrameInfo` type has been souped up to return filename/line number
information as well.
The implementation here is pretty simplistic currently. The meat of all
the work happens in `gimli` and `addr2line`, and otherwise wasmtime is
just schlepping around bytes of dwarf debuginfo here and there!
The general goal here is to assist with debugging when using wasmtime
because filenames and line numbers are generally orders of magnitude
better even when you already have a stack trace. Another nicety here is
that backtraces will display inlined frames (learned through debug
information), improving the experience in release mode as well.
An example of this is that with this file:
```rust
fn main() {
panic!("hello");
}
```
we get this stack trace:
```
$ rustc foo.rs --target wasm32-wasi -g
$ cargo run foo.wasm
Finished dev [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/wasmtime foo.wasm`
thread 'main' panicked at 'hello', foo.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: failed to run main module `foo.wasm`
Caused by:
0: failed to invoke command default
1: wasm trap: unreachable
wasm backtrace:
0: 0x6c1c - panic_abort::__rust_start_panic::abort::h2d60298621b1ccbf
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/panic_abort/src/lib.rs:77:17
- __rust_start_panic
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/panic_abort/src/lib.rs:32:5
1: 0x68c7 - rust_panic
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:626:9
2: 0x65a1 - std::panicking::rust_panic_with_hook::h2345fb0909b53e12
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:596:5
3: 0x1436 - std::panicking::begin_panic::{{closure}}::h106f151a6db8c8fb
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:506:9
4: 0xda8 - std::sys_common::backtrace::__rust_end_short_backtrace::he55aa13f22782798
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/backtrace.rs:153:18
5: 0x1324 - std::panicking::begin_panic::h1727e7d1d719c76f
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:505:12
6: 0xfde - foo::main::h2db1313a64510850
at /Users/acrichton/code/wasmtime/foo.rs:2:5
7: 0x11d5 - core::ops::function::FnOnce::call_once::h20ee1cc04aeff1fc
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:227:5
8: 0xddf - std::sys_common::backtrace::__rust_begin_short_backtrace::h054493e41e27e69c
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/sys_common/backtrace.rs:137:18
9: 0x1d5a - std::rt::lang_start::{{closure}}::hd83784448d3fcb42
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/rt.rs:66:18
10: 0x69d8 - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::h564d3dad35014917
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:259:13
- std::panicking::try::do_call::hdca4832ace5a8603
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:381:40
- std::panicking::try::ha8624a1a6854b456
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:345:19
- std::panic::catch_unwind::h71421f57cf2bc688
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panic.rs:382:14
- std::rt::lang_start_internal::h260050c92cd470af
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/rt.rs:51:25
11: 0x1d0c - std::rt::lang_start::h0b4bcf3c5e498224
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/rt.rs:65:5
12: 0xffc - <unknown>!__original_main
13: 0x393 - __muloti4
at /cargo/registry/src/github.com-1ecc6299db9ec823/compiler_builtins-0.1.35/src/macros.rs:269
```
This is relatively noisy by default but there's filenames and line
numbers! Additionally frame 10 can be seen to have lots of frames
inlined into it. All information is always available to the embedder but
we could try to handle the `__rust_begin_short_backtrace` and
`__rust_end_short_backtrace` markers to trim the backtrace by default as
well.
The only gotcha here is that it looks like `__muloti4` is out of place.
That's because the libc that Rust ships with doesn't have dwarf
information, although I'm not sure why we land in that function for
symbolizing it...
* Add a configuration switch for debuginfo
* Control debuginfo by default with `WASM_BACKTRACE_DETAILS`
* Try cpp_demangle on demangling as well
* Rename to WASMTIME_BACKTRACE_DETAILS
621 lines
18 KiB
Rust
621 lines
18 KiB
Rust
use anyhow::Result;
|
|
use std::panic::{self, AssertUnwindSafe};
|
|
use std::process::Command;
|
|
use wasmtime::*;
|
|
|
|
#[test]
|
|
fn test_trap_return() -> Result<()> {
|
|
let 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(&store, hello_type, |_, _, _| Err(Trap::new("test 123")));
|
|
|
|
let instance = Instance::new(&store, &module, &[hello_func.into()])?;
|
|
let run_func = instance.get_func("run").expect("expected function export");
|
|
|
|
let e = run_func
|
|
.call(&[])
|
|
.err()
|
|
.expect("error calling function")
|
|
.downcast::<Trap>()?;
|
|
|
|
assert!(e.to_string().contains("test 123"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn test_trap_trace() -> Result<()> {
|
|
let 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(&store, &module, &[])?;
|
|
let run_func = instance.get_func("run").expect("expected function export");
|
|
|
|
let e = run_func
|
|
.call(&[])
|
|
.err()
|
|
.expect("error calling function")
|
|
.downcast::<Trap>()?;
|
|
|
|
let trace = e.trace();
|
|
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(), 1);
|
|
assert_eq!(trace[0].module_offset(), 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(), 1);
|
|
assert_eq!(trace[1].module_offset(), 0x21);
|
|
assert!(
|
|
e.to_string().contains("unreachable"),
|
|
"wrong message: {}",
|
|
e.to_string()
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn test_trap_trace_cb() -> Result<()> {
|
|
let 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(&store, fn_type, |_, _, _| Err(Trap::new("cb throw")));
|
|
|
|
let module = Module::new(store.engine(), wat)?;
|
|
let instance = Instance::new(&store, &module, &[fn_func.into()])?;
|
|
let run_func = instance.get_func("run").expect("expected function export");
|
|
|
|
let e = run_func
|
|
.call(&[])
|
|
.err()
|
|
.expect("error calling function")
|
|
.downcast::<Trap>()?;
|
|
|
|
let trace = e.trace();
|
|
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 = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn test_trap_stack_overflow() -> Result<()> {
|
|
let 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(&store, &module, &[])?;
|
|
let run_func = instance.get_func("run").expect("expected function export");
|
|
|
|
let e = run_func
|
|
.call(&[])
|
|
.err()
|
|
.expect("error calling function")
|
|
.downcast::<Trap>()?;
|
|
|
|
let trace = e.trace();
|
|
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 = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn trap_display_pretty() -> Result<()> {
|
|
let 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(&store, &module, &[])?;
|
|
let run_func = instance.get_func("bar").expect("expected function export");
|
|
|
|
let e = run_func.call(&[]).err().expect("error calling function");
|
|
assert_eq!(
|
|
e.to_string(),
|
|
"\
|
|
wasm trap: unreachable
|
|
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 = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn trap_display_multi_module() -> Result<()> {
|
|
let 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(&store, &module, &[])?;
|
|
let bar = instance.get_export("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(&store, &module, &[bar])?;
|
|
let bar2 = instance.get_func("bar2").expect("expected function export");
|
|
|
|
let e = bar2.call(&[]).err().expect("error calling function");
|
|
assert_eq!(
|
|
e.to_string(),
|
|
"\
|
|
wasm trap: unreachable
|
|
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]
|
|
#[cfg_attr(all(target_os = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn trap_start_function_import() -> Result<()> {
|
|
let 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(&store, sig, |_, _, _| Err(Trap::new("user trap")));
|
|
let err = Instance::new(&store, &module, &[func.into()])
|
|
.err()
|
|
.unwrap();
|
|
assert!(err
|
|
.downcast_ref::<Trap>()
|
|
.unwrap()
|
|
.to_string()
|
|
.contains("user trap"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn rust_panic_import() -> Result<()> {
|
|
let 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(&store, sig, |_, _, _| panic!("this is a panic"));
|
|
let instance = Instance::new(
|
|
&store,
|
|
&module,
|
|
&[
|
|
func.into(),
|
|
Func::wrap(&store, || panic!("this is another panic")).into(),
|
|
],
|
|
)?;
|
|
let func = instance.get_func("foo").unwrap();
|
|
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(func.call(&[]));
|
|
}))
|
|
.unwrap_err();
|
|
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
|
|
|
|
let func = instance.get_func("bar").unwrap();
|
|
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(func.call(&[]));
|
|
}))
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err.downcast_ref::<&'static str>(),
|
|
Some(&"this is another panic")
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn rust_panic_start_function() -> Result<()> {
|
|
let 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(&store, sig, |_, _, _| panic!("this is a panic"));
|
|
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(Instance::new(&store, &module, &[func.into()]));
|
|
}))
|
|
.unwrap_err();
|
|
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
|
|
|
|
let func = Func::wrap(&store, || panic!("this is another panic"));
|
|
let err = panic::catch_unwind(AssertUnwindSafe(|| {
|
|
drop(Instance::new(&store, &module, &[func.into()]));
|
|
}))
|
|
.unwrap_err();
|
|
assert_eq!(
|
|
err.downcast_ref::<&'static str>(),
|
|
Some(&"this is another panic")
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn mismatched_arguments() -> Result<()> {
|
|
let 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(&store, &module, &[])?;
|
|
let func = instance.get_func("foo").unwrap();
|
|
assert_eq!(
|
|
func.call(&[]).unwrap_err().to_string(),
|
|
"expected 1 arguments, got 0"
|
|
);
|
|
assert_eq!(
|
|
func.call(&[Val::F32(0)]).unwrap_err().to_string(),
|
|
"argument type mismatch: found f32 but expected i32",
|
|
);
|
|
assert_eq!(
|
|
func.call(&[Val::I32(0), Val::I32(1)])
|
|
.unwrap_err()
|
|
.to_string(),
|
|
"expected 1 arguments, got 2"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(all(target_os = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn call_signature_mismatch() -> Result<()> {
|
|
let 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(&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 = "windows", target_arch = "aarch64"), ignore)] // FIXME(#1642)
|
|
#[cfg_attr(all(target_os = "windows", feature = "experimental_x64"), ignore)] // FIXME(#2079)
|
|
fn start_trap_pretty() -> Result<()> {
|
|
let 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(&store, &module, &[]) {
|
|
Ok(_) => panic!("expected failure"),
|
|
Err(e) => e.downcast::<Trap>()?,
|
|
};
|
|
|
|
assert_eq!(
|
|
e.to_string(),
|
|
"\
|
|
wasm trap: unreachable
|
|
wasm backtrace:
|
|
0: 0x1d - m!die
|
|
1: 0x21 - m!<wasm function 1>
|
|
2: 0x26 - m!foo
|
|
3: 0x2b - m!start
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn present_after_module_drop() -> Result<()> {
|
|
let store = Store::default();
|
|
let module = Module::new(store.engine(), r#"(func (export "foo") unreachable)"#)?;
|
|
let instance = Instance::new(&store, &module, &[])?;
|
|
let func = instance.get_func("foo").unwrap();
|
|
|
|
println!("asserting before we drop modules");
|
|
assert_trap(func.call(&[]).unwrap_err().downcast()?);
|
|
drop((instance, module));
|
|
|
|
println!("asserting after drop");
|
|
assert_trap(func.call(&[]).unwrap_err().downcast()?);
|
|
return Ok(());
|
|
|
|
fn assert_trap(t: Trap) {
|
|
println!("{}", t);
|
|
assert_eq!(t.trace().len(), 1);
|
|
assert_eq!(t.trace()[0].func_index(), 0);
|
|
}
|
|
}
|
|
|
|
fn assert_trap_code(wat: &str, code: wasmtime::TrapCode) {
|
|
let store = Store::default();
|
|
let module = Module::new(store.engine(), wat).unwrap();
|
|
|
|
let err = match Instance::new(&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]
|
|
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 store = Store::new(&engine);
|
|
let module = Module::new(&engine, &wasm)?;
|
|
let mut linker = Linker::new(&store);
|
|
wasmtime_wasi::Wasi::new(&store, wasmtime_wasi::WasiCtxBuilder::new().build()?)
|
|
.add_to_linker(&mut linker)?;
|
|
linker.module("", &module)?;
|
|
let run = linker.get_default("")?;
|
|
let trap = run.call(&[]).unwrap_err().downcast::<Trap>()?;
|
|
|
|
let mut found = false;
|
|
for frame in trap.trace() {
|
|
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]
|
|
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 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(&store, &module, &[])
|
|
.err()
|
|
.unwrap()
|
|
.downcast::<Trap>()?;
|
|
assert_eq!(
|
|
trap.to_string(),
|
|
"\
|
|
wasm trap: unreachable
|
|
wasm backtrace:
|
|
0: 0x1a - <unknown>!start
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
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 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(&store, &module, &[])
|
|
.err()
|
|
.unwrap()
|
|
.downcast::<Trap>()?;
|
|
assert_eq!(
|
|
trap.to_string(),
|
|
"\
|
|
wasm trap: unreachable
|
|
wasm backtrace:
|
|
0: 0x1a - <unknown>!start
|
|
note: run with `WASMTIME_BACKTRACE_DETAILS=1` environment variable to display more information
|
|
"
|
|
);
|
|
Ok(())
|
|
}
|