· 6 min read ·

What Chrome's WASM Debugger Actually Gives You

Source: eli-bendersky

Eli Bendersky recently posted a practical walkthrough of debugging WASM in Chrome DevTools while working on a Scheme compiler that targets WebAssembly. It is a useful tutorial, but the experience of actually using the debugger raises some broader questions about where WASM tooling stands right now. The short version: Chrome’s debugger is genuinely capable, and also genuinely limited in ways that matter depending on what you are trying to do.

The Baseline: WAT and Breakpoints

The most accessible layer of WASM debugging in Chrome is the WAT (WebAssembly Text Format) view. When you open DevTools, navigate to Sources, and expand the wasm:// tree, Chrome disassembles the binary on the fly and presents the human-readable text format. You can set breakpoints directly on WAT instructions, step through them, and inspect the operand stack.

For simple generated WASM, this is often enough. If you are tracing through a small hand-written module or trying to verify that a code generator is emitting the right instruction sequence, stepping through WAT is workable. The operand stack view shows you every value as execution proceeds, which is something you do not get with most native debuggers without extra effort.

Eli’s example uses the WASM GC proposal, which adds reference types, structs, and arrays to the WASM type system. This is significant because the GC proposal has been shipping in Chrome since version 119 and in Firefox since version 120, and it changes what you can do in WASM substantially. The gc-print-scheme-pairs sample he references builds Scheme-style cons cells as WASM GC structs and prints them recursively. That is a real program, not a toy, and seeing it debugged at the WAT level is instructive.

DWARF: The More Useful Layer

WAT-level debugging is readable, but it is not source-level debugging. For that, Chrome relies on DWARF debug information embedded in the WASM binary.

DWARF support in WASM binaries works similarly to how it works in native ELF or Mach-O binaries: the compiler emits .debug_info, .debug_line, .debug_abbrev, and related sections into the WASM file as custom sections. Chrome DevTools, with the help of the C/C++ DevTools Support (DWARF) extension, reads these sections and maps WASM addresses back to source locations. The extension is actually doing the heavy lifting here: it uses a WASM port of LLVM’s DWARF parsing library and communicates with DevTools over the Chrome DevTools Protocol.

When DWARF is present, you get source-level stepping, local variable names, type information, and inline function frames. Emscripten generates DWARF by default when you pass -g to the compiler. Rust’s wasm32-unknown-unknown target does the same when you build in debug mode. The difference in debugging experience with and without DWARF is significant: without it you are reading assembly, with it you are reading your original source.

The limitation is that DWARF in WASM has gaps. Complex types, especially WASM GC reference types, do not always map cleanly into DWARF’s type system, which was designed with C-family languages in mind. The DWARF spec and WASM toolchains are catching up, but it is an ongoing process. For a Scheme compiler or any language with a garbage-collected heap, the variable inspector may show you a reference opaque value where you wanted to see the actual s-expression tree.

The Source Map Alternative

DWARF is not the only path. WASM supports source maps through a custom section called sourceMappingURL, pointing to a .wasm.map file that maps WASM offsets to source positions. This is the same Source Map v3 format used by JavaScript bundlers, extended to cover WASM byte offsets.

Source maps are simpler than DWARF. They handle line and column mapping but carry no type information, no local variable names, no inlining data. For a language that compiles through a high-level IR or for cases where you primarily need to know which source line caused a crash, source maps are sufficient and have broader tooling support. For stepping through a complex generated function and inspecting intermediate values, you want DWARF.

Some toolchains emit both. AssemblyScript, for example, emits source maps pointing back to the TypeScript source. Emscripten can emit source maps alongside DWARF. When both are present, Chrome will prefer DWARF.

What the Stack Inspector Shows

One thing the DevTools WASM debugger gets right that is easy to undervalue is the operand stack view. WASM is a stack machine, and at any point during execution the stack holds a well-defined sequence of typed values. Chrome shows this in the Scope panel when paused at a WAT-level breakpoint.

This is more useful than it sounds. A lot of subtle bugs in generated WASM come from stack imbalances: a code path that leaves an extra value on the stack, or that pops one too many. The static validator in the WASM runtime catches some of these at load time, but logic errors in the generated code can still produce wrong results without violating the type constraints. Watching the stack evolve instruction by instruction is the fastest way to find these.

For WASM GC code specifically, the typed references show up in the stack view with their declared types. You will see (ref $Pair) or similar rather than a raw i32, which at minimum confirms that the type system is working as intended.

Serving Requirements and the Local Server Constraint

Eli mentions in passing that you cannot open WASM files directly from the filesystem; you need a local HTTP server because browsers restrict certain APIs for file:// origins. This is a minor friction point but worth noting because it catches people off guard. Python’s http.server module works fine:

python3 -m http.server 8080

Alternatively, tools like serve or static-server (which Eli uses) handle it in one command. The important thing is that the server must send the correct Content-Type: application/wasm header for WASM files, and most static file servers do this correctly for .wasm extensions by default.

For development workflows where you are iterating quickly on a WASM code generator, it is worth setting this up as part of your build script rather than running the server manually each time.

Where the Tooling Falls Short

The WASM debugger in Chrome is good enough to be genuinely useful, but there are real limitations.

First, there is no reverse debugging. You cannot step backward through execution. In a complex recursive WASM function, if you step past the point of interest, you reload and try again. This is the same situation as native debugging without rr or WinDbg’s time-travel feature, but it is worth stating because the operand stack model makes it especially easy to miss a specific stack state.

Second, the DWARF extension is a browser extension, not a built-in. Users need to install it separately, and it has had stability issues when Chrome’s DevTools protocol changes. The Chrome team has been moving toward integrating this capability more directly, but as of early 2026 the extension install step is still part of the workflow for source-level debugging of C, C++, or Rust WASM.

Third, memory inspection is limited. WASM linear memory shows up as a raw byte array in the Memory tab, and while Chrome will let you navigate it, there is no heap visualizer analogous to what you get with native debuggers targeting V8’s own heap. For WASM GC heaps, you are essentially on your own beyond what the typed reference display gives you.

The Broader Context

What makes Eli’s post interesting is the specific combination of WASM GC and a Scheme compiler. The GC proposal is still relatively new in production, and most WASM debugging documentation targets Emscripten-compiled C/C++ where the toolchain is mature. Debugging a language implementation that uses WASM GC structs as its runtime representation is pushing into territory where the tooling has less prior art.

The experience he describes, where the Chrome debugger is capable enough to be useful but requires some care to set up, matches what I would expect. The WAT view plus careful use of the operand stack inspector is a workable workflow for this kind of work. It is not as comfortable as a native debugger with full DWARF support and a known heap layout, but it is substantially better than nothing, and better than what WASM debugging looked like even two years ago.

If you are building anything that compiles to WASM and you have not looked at the Chrome DevTools debugger recently, it is worth spending an hour with Eli’s walkthrough and the official Chrome documentation. The gap between “I have no idea what my generated WASM is doing” and “I can step through it and watch the stack” is significant, and the setup cost is low.

Was this interesting?