Debugging WASM in Chrome DevTools

Eli Bendersky Tools

Summary

A guide to using Chrome DevTools for debugging WebAssembly code, including setting breakpoints and catching exceptions.

<p>When I was working on the <a class="reference external" href="https://eli.thegreenplace.net/2026/compiling-scheme-to-webassembly/">WASM backend for my Scheme compiler</a>, I ran into several tricky situations with debugging generated WASM code. It turned out that Chrome has a very capable WASM debugger in its DevTools, so in this brief post I want to share how it can be used.</p> <div class="section" id="the-setup-and-harness"> <h2>The setup and harness</h2> <p>I'll be using an example from my <a class="reference external" href="https://github.com/eliben/wasm-wat-samples">wasm-wat-samples project</a> for this post. In fact, everything is already in place in the <a class="reference external" href="https://github.com/eliben/wasm-wat-samples/tree/main/gc-print-scheme-pairs">gc-print-scheme-pairs</a> sample. This sample shows how to construct Scheme-like s-exprs in WASM using gc references and print them out recursively. The sample supports nested pairs of integers, booleans and symbols.</p> <p>To see this in action, we have to first compile the WAT file to WASM, for example using <a class="reference external" href="https://github.com/eliben/watgo">watgo</a>:</p> <div class="highlight"><pre><span></span>$ cd gc-print-scheme-pairs $ watgo parse gc-print-scheme-pairs.wat -o gc-print-scheme-pairs.wasm </pre></div> <p>The <tt class="docutils literal"><span class="pre">browser-loader.html</span></tt> file in that directory already expects to load <tt class="docutils literal"><span class="pre">gc-print-scheme-pairs.wasm</span></tt>. But we can't just open it directly from the file-system; since it loads WASM, this file needs to be served with a local HTTP server. I personally use <a class="reference external" href="https://github.com/eliben/static-server/">static-server</a> for this, but you can use anything else - like Python's built-in <tt class="docutils literal">http.server</tt>:</p> <div class="highlight"><pre><span></span>$ static-server 2026/04/10 08:55:20.244096 Serving directory &quot;.&quot; on http://127.0.0.1:8080 ... </pre></div> <p>Now it can be opened in the browser by following the printed link and selecting the <tt class="docutils literal"><span class="pre">browser-loader.html</span></tt> file.</p> </div> <div class="section" id="the-debugging-process"> <h2>The debugging process</h2> <p>Open the Chrome DevTools, and in <em>Sources</em>, open the <em>Page</em> view on the left. It should have one entry under <em>wasm</em>, which will show the decompiled WAT code for our module. Note: this code is disassembled from the binary WASM, so it will lose some WAT syntactic sugar (like folded instructions):</p> <img alt="Screenshot showing where WASM source is in DevTools" class="align-center" src="https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot1.png" /> <p>You can set a breakpoint by clicking on the address column to the left of the code, and then refresh the page. The DevTools debugger will run the program again and stop at the breakpoint:</p> <img alt="Screenshot showing debugger stopping on the breakpoint line" class="align-center" src="https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot2.png" /> <p>Here you can step over, into, see local values and call stack, etc - a real debugger!</p> </div> <div class="section" id="debugging-unexpected-exceptions"> <h2>Debugging unexpected exceptions</h2> <p>The most important use case for me while developing the compiler was debugging unexpected exceptions (coming from instructions like <tt class="docutils literal">ref.cast</tt>). Notice the checkboxes saying &quot;Pause on ... exceptions&quot; on the right-hand side of the previous screenshot. With these selected, the DevTools debugger will automatically stop on an exception and show where it is coming from. Let's modify the <tt class="docutils literal"><span class="pre">gc-print-scheme-pairs.wat</span></tt> sample to see this in action. The <tt class="docutils literal">$emit_value</tt> function performs a set of <tt class="docutils literal">ref.test</tt> checks to see which kind of reference it's dealing with before casting; let's add this line at the very start:</p> <div class="highlight"><pre><span></span>(call $emit_bool (ref.cast (ref $Bool) (local.get $v))) </pre></div> <p>It's clearly wrong to assume that <tt class="docutils literal">$v</tt> is a bool reference without first testing it; this is just for demonstration purposes.</p> <p>Without setting any breakpoints, recompiling this code with <tt class="docutils literal">watgo</tt> and reloading the page, we get:</p> <img alt="Screenshot showing debugger stopping on an exception" class="align-center" src="https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot3.png" /> <p>The debugger stopped at the instruction causing the exception; moreover, in the <em>Scope</em> pane on the right we can see that the actual type of <tt class="docutils literal">$v</tt> is <tt class="docutils literal">(ref $Pair)</tt>, so it's immediately clear what's going on.</p> <p>I've found this capability extremely valuable when writing (or emitting from a compiler) non-trivial chunks of WASM code using gc types and instructions.</p> </div> <div class="section" id="debugger-vs-printfs-in-wasm"> <h2>Debugger vs. printfs in wasm</h2> <p>&quot;Should I use a debugger or just printfs&quot; is a common topic of debate among programmers. While I'm usually in the &quot;printf debugging&quot; camp, I'm not dogmatic, and will certainly reach for a debugger when the situation calls for it.</p> <p>Specifically, when investigating reference exceptions in WASM, two strong factors tilt the decision towards using a debugger:</p> <ol class="arabic"> <li><p class="first">In general, WASM's printf capabilities aren't great. We can import print-like functions from the host (and - in fact - our sample does just that), but they're not very flexible and dealing with strings in WASM is painful in general. This is compounded even more when working with gc types, because these aren't even visible to the host (they're opaque references). If we want to do printf debugging of gc values, we have to build <em>a lot</em> of scaffolding first.</p> </li> <li><p class="first">Exception debugging - in general - is much easier with a supportive debugger in hand. Our <tt class="docutils literal">ref.cast</tt> exception from the example above could have happened <em>anywhere</em> in the code. Imagine having to debug a very large WASM program (emitted by a compiler) to find the source of a failed <tt class="docutils literal">ref.cast</tt>; the debugger takes you right to the spot!</p> <p>In fact, even for C programming, I've always found <tt class="docutils literal">gdb</tt> most useful for pinpointing the source of segmentation faults and similar crashes.</p> </li> </ol> </div>
Original Article
View Cached Full Text

Cached at: 05/16/26, 03:36 AM

# Debugging WASM in Chrome DevTools Source: [https://eli.thegreenplace.net/2026/debugging-wasm-in-chrome-devtools](https://eli.thegreenplace.net/2026/debugging-wasm-in-chrome-devtools) When I was working on the[WASM backend for my Scheme compiler](https://eli.thegreenplace.net/2026/compiling-scheme-to-webassembly/), I ran into several tricky situations with debugging generated WASM code\. It turned out that Chrome has a very capable WASM debugger in its DevTools, so in this brief post I want to share how it can be used\. ## The setup and harness I'll be using an example from my[wasm\-wat\-samples project](https://github.com/eliben/wasm-wat-samples)for this post\. In fact, everything is already in place in the[gc\-print\-scheme\-pairs](https://github.com/eliben/wasm-wat-samples/tree/main/gc-print-scheme-pairs)sample\. This sample shows how to construct Scheme\-like s\-exprs in WASM using gc references and print them out recursively\. The sample supports nested pairs of integers, booleans and symbols\. To see this in action, we have to first compile the WAT file to WASM, for example using[watgo](https://github.com/eliben/watgo): ``` $ cd gc-print-scheme-pairs $ watgo parse gc-print-scheme-pairs.wat -o gc-print-scheme-pairs.wasm ``` Thebrowser\-loader\.htmlfile in that directory already expects to loadgc\-print\-scheme\-pairs\.wasm\. But we can't just open it directly from the file\-system; since it loads WASM, this file needs to be served with a local HTTP server\. I personally use[static\-server](https://github.com/eliben/static-server/)for this, but you can use anything else \- like Python's built\-inhttp\.server: ``` $ static-server 2026/04/10 08:55:20.244096 Serving directory "." on http://127.0.0.1:8080 ... ``` Now it can be opened in the browser by following the printed link and selecting thebrowser\-loader\.htmlfile\. ## The debugging process Open the Chrome DevTools, and in*Sources*, open the*Page*view on the left\. It should have one entry under*wasm*, which will show the decompiled WAT code for our module\. Note: this code is disassembled from the binary WASM, so it will lose some WAT syntactic sugar \(like folded instructions\): ![Screenshot showing where WASM source is in DevTools](https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot1.png) You can set a breakpoint by clicking on the address column to the left of the code, and then refresh the page\. The DevTools debugger will run the program again and stop at the breakpoint: ![Screenshot showing debugger stopping on the breakpoint line](https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot2.png) Here you can step over, into, see local values and call stack, etc \- a real debugger\! ## Debugging unexpected exceptions The most important use case for me while developing the compiler was debugging unexpected exceptions \(coming from instructions likeref\.cast\)\. Notice the checkboxes saying "Pause on \.\.\. exceptions" on the right\-hand side of the previous screenshot\. With these selected, the DevTools debugger will automatically stop on an exception and show where it is coming from\. Let's modify thegc\-print\-scheme\-pairs\.watsample to see this in action\. The$emit\_valuefunction performs a set ofref\.testchecks to see which kind of reference it's dealing with before casting; let's add this line at the very start: ``` (call $emit_bool (ref.cast (ref $Bool) (local.get $v))) ``` It's clearly wrong to assume that$vis a bool reference without first testing it; this is just for demonstration purposes\. Without setting any breakpoints, recompiling this code withwatgoand reloading the page, we get: ![Screenshot showing debugger stopping on an exception](https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot3.png) The debugger stopped at the instruction causing the exception; moreover, in the*Scope*pane on the right we can see that the actual type of$vis\(ref $Pair\), so it's immediately clear what's going on\. I've found this capability extremely valuable when writing \(or emitting from a compiler\) non\-trivial chunks of WASM code using gc types and instructions\. ## Debugger vs\. printfs in wasm "Should I use a debugger or just printfs" is a common topic of debate among programmers\. While I'm usually in the "printf debugging" camp, I'm not dogmatic, and will certainly reach for a debugger when the situation calls for it\. Specifically, when investigating reference exceptions in WASM, two strong factors tilt the decision towards using a debugger: 1. In general, WASM's printf capabilities aren't great\. We can import print\-like functions from the host \(and \- in fact \- our sample does just that\), but they're not very flexible and dealing with strings in WASM is painful in general\. This is compounded even more when working with gc types, because these aren't even visible to the host \(they're opaque references\)\. If we want to do printf debugging of gc values, we have to build*a lot*of scaffolding first\. 2. Exception debugging \- in general \- is much easier with a supportive debugger in hand\. Ourref\.castexception from the example above could have happened*anywhere*in the code\. Imagine having to debug a very large WASM program \(emitted by a compiler\) to find the source of a failedref\.cast; the debugger takes you right to the spot\! In fact, even for C programming, I've always foundgdbmost useful for pinpointing the source of segmentation faults and similar crashes\.

Similar Articles

weblings: Compiling Rust to WASM from inside WASM

Lobsters Hottest

Weblings is a Rust compiler toolchain compiled to WebAssembly, enabling compilation and execution of Rust code directly in the browser via a web UI with a playground and Rustlings exercises.

GC and Exceptions in Wasmtime

Hacker News Top

Wasmtime 47 enables Wasm GC and exceptions proposals by default, making it easier for high-level languages with garbage collection and exceptions to target WebAssembly efficiently.

Writing a Debugger from Scratch

Hacker News Top

This article kicks off a series on building a debugger from scratch in Rust, starting with attaching to a Windows process using OS debugging APIs.

Debug web apps with browser use in Codex

YouTube AI Channels

Codex's Browser Use feature adds Chrome DevTools Protocol support, enabling developers to deeply debug web applications by inspecting network traffic, performance analysis, console logs, and other advanced features.