Debugging WebAssembly with modern tools
Interested in helping improve DevTools? Sign up to participate in Google User Research here.
#The road so far
A year ago, Chrome announced initial support for native WebAssembly debugging in Chrome DevTools.
We demonstrated basic stepping support and talked about opportunities usage of DWARF information instead of source maps opens for us in the future:
- Resolving variable names
- Pretty-printing types
- Evaluating expressions in source languages
- …and much more!
Today, we're excited to showcase the promised features come into life and the progress Emscripten and Chrome DevTools teams have made over this year, in particular, for C and C++ apps.
Before we start, please keep in mind that this is still a beta version of the new experience, you need to use the latest version of all tools at your own risk, and if you run into any issues, please report them to https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.
Let's start with the same simple C example as the last time:
#include <stdlib.h>
void assert_less(int x, int y) {
if (x >= y) {
abort();
}
}
int main() {
assert_less(10, 20);
assert_less(30, 20);
}
To compile it, we use latest Emscripten and pass a -g
flag, just like in the original post, to include debug information:
emcc -g temp.c -o temp.html
Now we can serve the generated page from a localhost HTTP server (for example, with serve), and open it in the latest Chrome Canary.
This time we'll also need a helper extension that integrates with Chrome DevTools and helps it make sense of all the debugging information encoded in the WebAssembly file. Please install it by going to this link: goo.gle/wasm-debugging-extension
You'll also want to enable WebAssembly debugging in the DevTools Experiments. Open Chrome DevTools, click the gear (⚙) icon in the top right corner of DevTools pane, go to the Experiments panel and tick WebAssembly Debugging: Enable DWARF support.
When you close the Settings, DevTools will suggest to reload itself to apply settings, so let's do just that. That's it for the one-off setup.
Now we can go back to the Sources panel, enable Pause on exceptions (⏸ icon), then check Pause on caught exceptions and reload the page. You should see the DevTools paused on an exception:
By default, it stops on an Emscripten-generated glue code, but on the right you can see a Call Stack view representing the stacktrace of the error, and can navigate to the original C line that invoked abort
:
Now, if you look in the Scope view, you can see the original names and values of variables in the C/C++ code, and no longer have to figure out what mangled names like $localN
mean and how they relate to the source code you've written.
This applies not only to primitive values like integers, but to compound types like structures, classes, arrays, etc., too!
#Rich type support
Let's take a look at a more complicated example to show those. This time, we'll draw a Mandelbrot fractal with the following C++ code:
#include <SDL2/SDL.h>
#include <complex>
int main() {
// Init SDL.
int width = 600, height = 600;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window;
SDL_Renderer* renderer;
SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
&renderer);
// Generate a palette with random colors.
enum { MAX_ITER_COUNT = 256 };
SDL_Color palette[MAX_ITER_COUNT];
srand(time(0));
for (int i = 0; i < MAX_ITER_COUNT; ++i) {
palette[i] = {
.r = (uint8_t)rand(),
.g = (uint8_t)rand(),
.b = (uint8_t)rand(),
.a = 255,
};
}
// Calculate and draw the Mandelbrot set.
std::complex<double> center(0.5, 0.5);
double scale = 4.0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
std::complex<double> point((double)x / width, (double)y / height);
std::complex<double> c = (point - center) * scale;
std::complex<double> z(0, 0);
int i = 0;
for (; i < MAX_ITER_COUNT - 1; i++) {
z = z * z + c;
if (abs(z) > 2.0)
break;
}
SDL_Color color = palette[i];
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_RenderDrawPoint(renderer, x, y);
}
}
// Render everything we've drawn to the canvas.
SDL_RenderPresent(renderer);