The current state of interactive debugging for WebAssembly and useful tips on how to do better.
Debugging WebAssembly, as with any code, is critical for both developers and implementers. In the case of WebAssembly, most developers I’ve met rely on
println debugging because of a lack of documentation for alternatives. WebAssembly already supports step-through debugging of compiled code with integration and references to the original source, but using this tooling remains a hassle and lacks automation.
This is an attempt to walk through the state of interactive debugging for WebAssembly.
The state of the world
Step-through debugging of binaries has traditionally been facilitated by the DWARF debugging standard. DWARF was originally intended for Linux ELF binaries, but has found its way into the LLVM and other compiler backends more generally, and variants get used for Mach-O and other file formats as well.
When using Clang / LLVM compilation targeting a Wasm binary, the
-g “Generate debug information” flag will add an additional module to the compiled assembly with DWARF format debugging information.
While DWARF works well for programs like GDB, it isn’t directly usable for WebAssembly in any current execution engines. However, the Chrome and Firefox execution engines can make use of debugging information, as both are able to link a SourceMap to executing Wasm.
Debugging WebAssembly using source maps
We can use the same format to debug WebAssembly in the browser with an imported view of the original source.
There are two steps needed to generate a source map from a WebAssembly file with debug symbols. First, we’ll extract the DWARF section to its own file, and then we’ll use the
wasm-sourcemap.py utility from Emscripten to convert the DWARF symbols into the mapping.
A makefile for these steps looks like:
(The full code of this example is here)
To use this makefile template with your own project, copy the file, replace line 2 with your targets, and then execute
make in your terminal.
Now you have a generated mapping from your original source file to the generated WebAssembly. How do you actually debug it?
The Wasm file generated by the
wasm-sourcemap.py tool includes a custom module (or S-expression) named
sourceMappingURL. The contents of this section is the URI we entered in the wasm-sourcemap command:
"./rot13.wasm.map", in this example.
When Chrome or Firefox encounters execution of a Wasm instantiation with such a module (with debugging inspector open), it will attempt to fetch and link the source map.
The remaining caveat is that WebAssembly is instantiated using
.instantiate(Buffer) — the bytes of the Wasm module (or a streaming promise from a
fetch call) are provided as input. This means in practice that a relative URL like the one above will not function. The browser has lost the providence of the Wasm file itself, so is unable to understand what the map is being loaded relative to.
You can fix this in two ways:
- If you know where your local / development web server will exist where the source map file can be fetched from, you can embed an absolute URL of the form “http(s)://…” into the Wasm file directly in the
wasm-sourcemap.pycall. This is done using the
-uargument, for instance
wasm-sourcemap.py ... -u https://localhost:8080/out.wasm.map
- Or you can use the wasm-sourcemap module to rewrite the Wasm buffer before instantiation to update the referenced source map to be relative to where the files have ended up at time of load.
const wasmmap = require('wasm-sourcemap');
wasmBuffer = wasmmap.SetSourceMapURLRelativeTo(
Putting it all together
Rust compiled directly to Wasm using the
wasm32-wasi targets does not yet generate DWARF headers. It’s on the timeline the Rust team has for 2019, and hopefully progress will be relatively fast.
Ultimately, having strong debugging and development tooling is critical to building reliable and secure programs. At Oasis Labs, we’re excited to see the tooling grow around the emerging WebAssembly standard to provide a capable and easy to work with runtime.