Debugging Rust-Generated WebAssembly

This section contains tips for debugging Rust-generated WebAssembly.

Building with Debug Symbols

⚡ When debugging, always make sure you are building with debug symbols!

If you don't have debug symbols enabled, then the "name" custom section won'tbe present in the compiled .wasm binary, and stack traces will have functionnames like wasm-function[42] rather than the Rust name of the function, likewasm_game_of_life::Universe::live_neighbor_count.

When using a "debug" build (aka wasm-pack build —debug or cargo build)debug symbols are enabled by default.

With a "release" build, debug symbols are not enabled by default. To enabledebug symbols, ensure that you debug = true in the [profile.release] sectionof your Cargo.toml:

  1. [profile.release]
  2. debug = true

Logging with the console APIs

Logging is one of the most effective tools we have for proving and disprovinghypotheses about why our programs are buggy. On the Web, the console.logfunction is theway to log messages to the browser's developer tools console.

We can use the web-sys crate to get access to the console loggingfunctions:

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. extern crate web_sys;
  4. web_sys::console::log_1(&"Hello, world!".into());
  5. #}

Alternatively, the console.errorfunction hasthe same signature as console.log, but developer tools tend to also captureand display a stack trace alongside the logged message when console.error isused.

References

Logging Panics

The console_error_panic_hook crate logs unexpected panics to the developerconsole via console.error. Rather than getting cryptic,difficult-to-debug RuntimeError: unreachable executed error messages, thisgives you Rust's formatted panic message.

All you need to do is install the hook by callingconsole_error_panic_hook::set_once() in an initialization function or commoncode path:

  1. # #![allow(unused_variables)]
  2. #fn main() {
  3. #[wasm_bindgen]
  4. pub fn init_panic_hook() {
  5. console_error_panic_hook::set_once();
  6. }
  7. #}

Using a Debugger

Unfortunately, the debugging story for WebAssembly is still immature. On mostUnix systems, DWARF is used to encode the information that a debuggerneeds to provide source-level inspection of a running program. There is analternative format that encodes similar information on Windows. Currently, thereis no equivalent for WebAssembly. Therefore, debuggers currently provide limitedutility, and we end up stepping through raw WebAssembly instructions emitted bythe compiler, rather than the Rust source text we authored.

There is a sub-charter of the W3C WebAssembly group for debugging, so expect this story to improve in the future!

Nonetheless, debuggers are still useful for inspecting the JavaScript thatinteracts with our WebAssembly, and inspecting raw wasm state.

References

Avoid the Need to Debug WebAssembly in the First Place

If the bug is specific to interactions with JavaScript or Web APIs, then writetests with wasm-bindgen-test.

If a bug does not involve interaction with JavaScript or Web APIs, then try toreproduce it as a normal Rust #[test] function, where you can leverage yourOS's mature native tooling when debugging. Use testing crates likequickcheck and its test case shrinkers to mechanically reducetest cases. Ultimately, you will have an easier time finding and fixing bugs ifyou can isolate them in a smaller test cases that don't require interacting withJavaScript.

Note that in order to run native #[test]s without compiler and linker errors,you will need to ensure that "rlib" is included in the [lib.crate-type]array in your Cargo.toml file.

  1. [lib]
  2. crate-type ["cdylib", "rlib"]