Debugging Wasm in Firefox DevTools
This page answers one task: debug and profile a WebAssembly module in Firefox — useful when a bug reproduces only in Firefox, when you want SpiderMonkey’s view of performance, or when Firefox is simply your browser of choice.
Prerequisites
- [ ] Firefox (current release) with DevTools.
- [ ] A module built with debug information — DWARF for C, C++ and Rust, or a source map — for source-level work.
- [ ] The page served from a local dev server, so DevTools can fetch sources.
What Firefox offers for WebAssembly
Firefox’s debugger treats WebAssembly modules as sources. Without debug information, a module appears as disassembled WebAssembly text, one listing per module, where you can set breakpoints on instructions, step, and inspect locals and the operand stack. With debug information, the debugger maps instructions back to the original source files and shows those instead, so you step through Rust or C lines rather than Wasm instructions.
Firefox historically supported two kinds of mapping: source maps, a JSON format that maps byte offsets to source positions (produced by
Emscripten’s -gsource-map), and DWARF embedded in custom sections. Source maps give line-level stepping and file navigation; DWARF
additionally describes variables and types. The Firefox Profiler, a separate tool reached from the Performance panel, records and
analyses performance with full support for Wasm frames.
Step 1 — build with debug information
For Emscripten, choose the format explicitly:
# source map: small, line-level stepping
emcc -O1 -gsource-map --source-map-base http://localhost:8080/ src/*.c -o dist/app.js
# DWARF: full variable information
emcc -O1 -g src/*.c -o dist/app.js
--source-map-base tells the module where to find the .wasm.map file; it must be reachable from the page. For Rust, a dev build with
wasm-pack keeps DWARF: wasm-pack build --dev --target web. Keep optimization low (-O1 or opt-level = 1) while debugging so
variables are not optimized away.
Step 2 — find the module in the Debugger
Open DevTools (F12), choose the Debugger panel, and look in the Sources tree. Modules appear under a wasm:// entry; with debug
information, the original source files appear under their paths. Open one, click a line number to set a breakpoint, and trigger the
code from the page.
When execution pauses, the right-hand pane shows the call stack — Wasm and JavaScript frames interleaved — and the Scopes section shows variables. For a Wasm frame without DWARF, scopes list the function’s locals by index and the current operand stack values; with DWARF, they show named variables.
// a quick way to reach the paused frame: call the export from the console
const { instance } = window.__app; // expose the instance during development
instance.exports.process(ptr, len); // pauses at your breakpoint
Step 3 — inspect memory and values
The Scopes pane shows locals, but for data in linear memory — buffers, structs behind pointers — evaluate expressions in the console while paused. The memory object is reachable from the instance’s exports:
// while paused: show 64 bytes at the pointer held in a local
const mem = new Uint8Array(window.__app.instance.exports.memory.buffer);
mem.slice(ptr, ptr + 64);
Watch expressions in the Debugger panel re-evaluate on every pause, which is handy for keeping an eye on a buffer region while stepping through a loop. For a richer memory view, Chrome’s memory inspector is more capable, as described in inspecting Wasm memory in Chrome DevTools.
Step 4 — profile with the Firefox Profiler
Open the Performance panel, start recording, perform the slow interaction, and stop; the recording opens in the Firefox Profiler. The Call Tree tab shows Wasm functions by name, and the Flame Graph tab aggregates call stacks:
Running Time (ms) Self (ms) Function
412 3 ▼ onClick main.js
398 2 ▼ process wasm-function: app.wasm
371 288 ▼ convolve wasm-function: app.wasm
83 83 simd_dot wasm-function: app.wasm
The Profiler also shows SpiderMonkey’s compiler activity — baseline and Ion compilation of Wasm functions — on its own markers, which makes it easy to see whether a slow first call was compilation. Profiles can be uploaded to a sharing URL for colleagues, which is convenient for comparing a Firefox-specific slowdown with a Chrome trace.
Step 5 — reproduce engine-specific bugs
Most Wasm bugs reproduce in every browser, because module semantics are precisely specified. The ones that do not are worth recognising.
Feature support differs: a module using a newer proposal may fail to compile in one engine; check for CompileError mentioning an
unknown opcode. Resource limits differ: maximum memory, maximum table size, and stack depth before a “too much recursion” error vary by
engine. And JavaScript-side behaviour differs: glue code relying on an API one browser implements differently.
When something fails only in Firefox, compare the module’s features against Firefox’s support first, then look at the JavaScript glue’s behaviour, and only then suspect the engine. The approach in detecting proposal support at runtime turns feature mismatches into a graceful fallback rather than a crash.
A debugging session that crosses the boundary
Most real bugs in Wasm apps involve both sides of the boundary, and the Firefox debugger handles that well because it shows JavaScript and Wasm frames in one call stack. A typical session runs like this. A user reports that exporting a document produces garbled text in Firefox. You set a breakpoint in the JavaScript function that reads the module’s output and reproduce the export. When it pauses, the console shows the bytes the module returned — already garbled — so the problem is upstream. You step into the export call, which drops into the module’s source, and walk through the function that encodes text. A watch expression on the output buffer shows the bytes as they are written, and the bug appears: the code writes UTF-16 code units into a buffer the JavaScript side decodes as UTF-8.
The fix is on one side, but finding it required seeing both. That is the general pattern: start where the symptom is visible, establish whether the data is already wrong when it crosses the boundary, and step to the side where it went wrong. The encoding details that cause this particular class of bug are covered in encoding strings across the Wasm boundary.
Firefox’s conditional breakpoints and logpoints help keep such sessions short. A logpoint — right-click a line, Add log — prints an expression every time a line runs without pausing, which is an easy way to trace values through a Wasm loop without a rebuild.
Expected output
Paused at a breakpoint in a Rust function with DWARF, the Debugger shows:
Call stack
apply_filter src/filter.rs:42
__wbg_apply_filter app_bg.wasm
onClick main.js:58
Scopes
Block
strength: 0.75
row: 118
pixels: &mut [u8] (len 8294400)
Gotchas
- Sources tree shows only
wasm://. No debug information, or the source map’s base URL is wrong. Check the network request for the.mapfile. - Breakpoints in source files do not hit. The source map points at paths that do not match; open the module’s mapped sources from the tree rather than local files.
- Variables shown as optimized out. Build with lower optimization while debugging.
- The Profiler shows
wasm-function[N]. Thenamesection was stripped. Rebuild with names.
Performance note
Debug builds are larger and slower: the DWARF build of the example was 5.2 MB against 640 KB stripped, and its unoptimized code ran the filter four times slower. Debugging and profiling call for different builds — debug information and low optimization for stepping, optimized code with names for profiling.
Frequently Asked Questions
Does Firefox support the DWARF extension Chrome uses? Chrome’s C/C++ DevTools Support is a Chrome extension. Firefox has its own built-in handling; feature parity varies by release, so for complex C++ type inspection Chrome is often stronger.
Can I debug Wasm in a Firefox worker? Yes. Workers appear in the Debugger’s thread list; select one to see its sources and set breakpoints.
Is there a way to break on traps?
Enable Pause on exceptions; a Wasm trap surfaces as a RuntimeError and the debugger stops at the faulting instruction.
Can I edit and re-run without reloading? No — a compiled module cannot be patched in place. Rebuild and reload; keep the dev build fast so that loop stays short.
Should I profile in Firefox if my users mostly use Chrome? Profile where your users are first. Firefox profiling is valuable for Firefox-specific complaints and as a second opinion on whether a hot spot is in your code or in one engine’s handling of it.
Related
- Debugging Wasm with DWARF and source maps — the formats in depth.
- Profiling Wasm with the Chrome Performance panel — the Chrome equivalent.
- How SpiderMonkey compiles Wasm — what the compiler markers mean.
- Reading Wasm stack traces — when a debugger is not attached.
← Back to Debugging & Profiling Wasm Modules