Using the C/C++ DevTools Support Extension
This page answers one task: debug a WebAssembly module compiled from C, C++ or Rust inside Chrome DevTools at the source level — set
breakpoints in .cpp or .rs files, see local variables with their real types, and read call stacks with function names and lines.
Prerequisites
- [ ] Chrome or Edge, current release.
- [ ] The C/C++ DevTools Support (DWARF) extension from the Chrome Web Store.
- [ ] A module built with DWARF debug information (
-gfor clang and emcc; a dev profile for Rust). - [ ] Source files reachable from the browser or mapped from local disk.
What the extension adds
Without help, Chrome DevTools can debug WebAssembly at the instruction level: it disassembles the module, lets you set breakpoints on
instructions, and shows locals by index ($var0, $var1). That is enough to see that something went wrong and too little to see what.
DWARF — the debug format native C and C++ toolchains have used for decades — describes how instructions map to source lines and how
variables, structs and classes are laid out in memory. LLVM emits DWARF into WebAssembly custom sections just as it does for native
binaries.
The extension is the piece that reads that DWARF inside DevTools. It plugs into the Sources panel, maps breakpoints set in source files to
code offsets, shows the source file when execution pauses, and — most valuably — evaluates variables in terms of their source types. A
std::vector<Point> appears as a vector of points with their fields, not as a pointer and a length you decode by hand.
Step 1 — install and enable
Install C/C++ DevTools Support (DWARF) from the Chrome Web Store. Then open DevTools settings (F1), go to Experiments, and make sure WebAssembly Debugging: Enable DWARF support is enabled if your Chrome version still lists it there. Reload DevTools. The extension works with any WebAssembly module containing DWARF, regardless of which language or toolchain produced it.
Step 2 — build with DWARF
# C or C++ with Emscripten
emcc -g -O0 src/*.cpp -o dist/app.js
# C with clang directly
clang --target=wasm32-wasip1 -g -O0 src/main.c -o dist/app.wasm
# Rust with wasm-pack (dev profile keeps DWARF)
wasm-pack build --dev --target web
-O0 makes stepping predictable; at higher levels, variables are optimized away and lines execute out of order. Use -O1 as a
compromise when -O0 is too slow to reproduce the bug.
DWARF makes modules large — often several times the size of the code. Emscripten can split it into a separate file that only DevTools downloads:
emcc -g -gseparate-dwarf=dist/app.debug.wasm src/*.cpp -o dist/app.js
The main module then contains a small external_debug_info section pointing at app.debug.wasm; the extension fetches it when needed,
and normal users never download it. Rust builds can achieve the same with wasm-split or by keeping an unstripped copy beside the stripped
one, as described in debugging Wasm with DWARF and source maps.
Step 3 — make the source files findable
DWARF records the paths the compiler saw. If the page is served from the same machine that compiled the code and those paths are absolute, the extension can often load the sources directly. When they are not — the build ran in a container, in CI, or with path remapping — map the recorded paths to local ones in the extension’s options (right-click its icon, Options):
Path substitutions
/build/src → /home/dev/projects/app/src
/rustc/eeb90cda1 → /home/dev/.rustup/toolchains/1.81.0-x86_64-unknown-linux-gnu/lib/rustlib/src/rust
Check what the module recorded with llvm-dwarfdump --debug-line dist/app.wasm | head and map the prefixes accordingly. Getting this right
is what turns grey, unbound breakpoints into working ones.
Step 4 — set breakpoints and inspect variables
Open the Sources panel; the original source files appear under a file:// tree. Set a breakpoint on a line and trigger the code from the
page. When it pauses:
- The Call Stack shows C++ or Rust frames with function names and line numbers, interleaved with JavaScript frames above the export.
- The Scope pane lists local variables with their types; expand structs, classes, vectors and strings.
- Hovering a variable in the editor shows its value.
- The Console evaluates expressions in the source language’s terms for simple cases — member access, array indexing.
Scope
Local
this: Image * {width: 1920, height: 1080, pixels: std::vector (size 8294400)}
row: int 118
kernel: const float[5] {0.06, 0.24, 0.40, 0.24, 0.06}
Stepping commands (step over, into, out) work at source-line granularity, and you can step from a JavaScript frame into the Wasm export and out again.
Step 5 — inspect raw memory behind a pointer
For buffers and objects behind pointers, the Memory Inspector shows linear memory as hex and decoded values. In the Scope pane, right-click a pointer variable and choose Reveal in Memory Inspector; the inspector opens at that address with the bytes highlighted. That is the quickest way to check whether a buffer holds what you expect, to compare a struct’s raw bytes with its declared layout, or to see the bytes around an object when hunting corruption, as in tracking down memory corruption in Wasm.
Using it with Rust
The extension was built with C and C++ in mind, but it reads any DWARF, and Rust modules work reasonably well: function names, source lines,
stepping and primitive locals are reliable. Rust-specific types — enums with data, Option, Vec, String — are displayed through their
underlying representation rather than with Rust-aware formatting, so a Vec<u8> appears as a struct with a pointer, capacity and length. That
is still far more useful than raw locals, and for complex values the console can evaluate field accesses. If you also debug Rust in VS Code,
the same DWARF powers debugging Wasm from VS Code,
which offers a similar experience in the editor.
A workflow that keeps debug builds useful
Source-level debugging only helps if a debug build is close at hand when a bug appears, so it is worth setting up the project so that one
always is. Keep a dedicated debug build target that produces DWARF at -O1, with the separate-DWARF option for large modules, and make it
part of the normal development command. Keep path mapping stable by building in the same directory layout locally and in CI, or by
recording the extension’s substitutions in the repository’s developer documentation. And make it easy to point a local page at a debug
build of a module without rebuilding everything else — a query parameter or a local override that swaps the module URL works well.
For bugs reported from production, the release build’s stripped module gives you only function offsets. If you also archive the unstripped module, or the separate DWARF file, for every release, you can load the matching debug information locally, map the production module’s URL to it, and step through the exact code users ran. That archive costs little storage and turns the hardest class of bug — “only happens in production, only in the release build” — into an ordinary debugging session. Symbolicating crash reports from the same archive is covered in symbolicating Wasm stack traces in production.
Expected output
Paused in a C++ method, the Sources panel shows image.cpp with the current line highlighted, the Call Stack reads:
Image::blur(float) image.cpp:142
apply_filter bindings.cpp:31
(anonymous) app.js:2087
onClick main.js:58
and the Scope pane shows this, radius and loop variables with their declared types.
Gotchas
- Breakpoints stay grey. Path mapping is wrong, or the module has no DWARF. Check
llvm-dwarfdumpoutput and the path substitutions. - Very slow first pause. The extension parses DWARF on first use, which takes seconds for large modules. Subsequent pauses are fast.
<optimized out>everywhere. Build with-O0or-O1while debugging.- Separate DWARF file not found. The
external_debug_infoURL must be reachable from the page; check the Network panel for the request.
Performance note
For a C++ image library, the DWARF build was 11.8 MB against a 1.4 MB release module; with -gseparate-dwarf, the shipped module stayed at
1.5 MB and the debug file was fetched only when DevTools opened. Initial DWARF indexing took about 3 seconds on first pause, a one-off cost
per session.
Frequently Asked Questions
Does the extension work in Edge? Yes — Edge uses the same DevTools and installs Chrome Web Store extensions.
Can I use it on a production site?
Only if the production module contains or links DWARF. With -gseparate-dwarf, you can host debug files privately and debug production
builds locally by mapping their URLs.
Does it support source maps too? Chrome’s built-in support handles Wasm source maps for line mapping; the extension adds DWARF’s variable information. DWARF is the better choice for C, C++ and Rust.
What about Firefox? Firefox has its own built-in support; see debugging Wasm in Firefox DevTools.
Related
- Inspecting Wasm memory in Chrome DevTools — the Memory Inspector in depth.
- Binding C++ libraries with Embind — C++ code you will often be stepping through.
- Reading Wasm stack traces — when you only have a crash report.
- Debugging Wasm in Node.js with the Inspector — the same extension through
chrome://inspect.
← Back to Debugging & Profiling Wasm Modules