WebAssembly Debugging

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.

Source maps were first introduced to help debug transpiled JavaScript. In cases where the code is derived from another language such as CoffeeScript or minified and/or combined with Browserify or webpack, the resulting file can be frustrating to directly debug.

Source maps define a format for mapping between the original input files and the resulting JavaScript instructions, so that browser debugging can be performed against a view of the original input files.

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:

Makefile for generating source map

(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.py call. This is done using the -u argument, 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(

wasmBuffer,

window.location.href);

Putting it all together

Debugging C source in Firefox.

Here’s a demo of source map debugging of WebAssembly code that can be inspected against Node, Chrome, and Firefox. The full example repository is here.