Running GDB in the Browser

GDB inside a Linux in a x86 Virtual Machine using Web Assembly running in the browser. Crazy?

Running GDB in the Browser

GDB is a very powerful debugging tool. Recently, I added GDB support for the Wokwi Arduino Simulator, by implementing the gdbserver protocol.

However, using GDB in Wokwi wasn't easy: you had to download and install a copy of avr-gdb on your computer, and then also download and set up a special proxy that would bridge the simulator running in the browser with GDB.

In short - the user experience was far from ideal. The Wokwi Simulator is running in the browser, so it only made sense to try and get GDB to run in the browser too, giving our users that magical click on a link and get right into GDB experience.

In this blog post, I'll share with you three different approaches I tried, and the surprising solution: running GDB using a tiny Linux system running in a Virtual Machine (VM) inside the browser. Let's get started!

First Attempt: WebAssembly

My first thought was: GDB is written in C/C++, so I may be able to compile it into a WebAssembly binary, which can run in any modern browser. This seemed like the most straightforward solution, and I also expected it to provide decent performance.

I set up a Docker container with Emscripten, a WebAssembly compiler toolchain, downloaded the latest GDB source archive, and... spent the next evening on a very long call with my friend Benny Meisels.

We tackled all the compiler issues one by one, and 7 patch files later, we got it to compile!

However, it turned out, that getting GDB to compile was just a tip of the iceberg:

First of all, the binary we got was huge - about 90MB!
Second, it required enabling experimental WebAssembly thread support.
It would fail to load in Chrome, and when running in Node.js, it'd immediately quit:

While I was happy at the initial success, it seemed like there's still a very long way to go if I wanted to make it work in the browser.

But feel free to prove me wrong. I shared the project on GitHub, so you can continue hacking on it: https://github.com/wokwi/wasm-avr-gdb/

Second Attempt: Cloud GDB

Then I thought: if I can't run GDB in the browser, the second best thing would be to run it together with the proxy server inside a container in the cloud, and create a small frontend app that would glue the pieces together and let the user interact with the cloud GDB instance.

There are a few challenges with this approach:

  1. I have to provision one container per user, since each user needs a fresh copy of GDB. This container has to live as long as the user is connected, so the number of containers can quickly grow and consume a lot of RAM.
  2. GDB can execute shell commands, so I have to look into securing those containers and make sure they are totally isolated.

Luckily, I didn't have to deal with these issues myself - I found a service which already does what I want. Gitpod is a service that let's you run dev environments in the cloud, and the process can totally be scripted.

From the user perspective, using Gitpod is pretty straightforward: just navigate the browser to gitpod.io/#repo-url-here, wait a few seconds, and you have a fully functional dev environment in your browser.

I set up a Gitpod configuration that installs the Node.js proxy and avr-gdb, and then opens a minimal webpage with instructions how to connect it with Wokwi:

The Node.js proxy instructions page, running on Gitpod

The fun part - instead of coding my own frontend for GDB, I used GoTTY, a program that convert CLI tools (such as GDB) into web applications. It does its magic by setting up a web socket that transfers output from the CLI program to the browser, and input from the browser into the CLI program.

Unlike my previous attempt, I got the Gitpod setup to work within a few hours. However, it had some major limitations:

  1. Gitpod requires you to sign in with a GitHub account before you can use it.
  2. The workspace has a startup time of 10-30 seconds. The users will have to wait every time they start a new GDB session.
  3. This can break if Gitpod changes something in the future. And if it can break, it will break.

So in my opinion, this still wasn't a good enough solution, but at least it was working and usable.

GDB inside GitPod using GoTTY

If you wish to experience it yourself, just follow this link. I could spend more time polishing the experience (so that you won't have to manually copy the WebSocket link into Wokwi, and also load the debugging symbols to GDB), but I decided to move on and explore a different approach:

Winner: Linux VM in the Browser!

Compiling GDB to Web Assembly proved to be a challenging task, but what if I could somehow run a Linux GDB binary in the browser?

This would allow me to use any GDB version without needing to tackle compilation issues or adapt the code to a browser environment. Just take an existing binary and run it without any modifications!

I then remembered JSLinux. As the name suggest, it is a Linux version that runs in the browser. When I first seen it, about 10 years ago, I was totally overawed.

I started playing around with the Alpine X86 VM, and the performance seemed acceptable. GDB doesn't usually do heavy computations, so I was optimistic I can get something usable out of this setup.

The only problem was: JSLinux isn't really documented, there is no information about how to build the virtual machines, and the only source code I code find was dated 9 years ago. So I went to look for alternatives.

Then I found it: v86

v86 is a 32-bit x86 emulator, written in WebAssembly. In fact, it even translates the x86 machine code into WebAssembly, so that it runs faster.

It's open source, well-documented, and there are several online demo VMs you can play with. It also supports snapshots, meaning that you can save the state of the machine after it loaded, and not have to wait for Linux to boot every time (see the Arch Linux demo).

I played a bit with the demos, and even uploaded a static build of GDB which worked just fine. But then, I still needed to figure out how to build my own Linux image with a more recent version of GDB with AVR support.

The v86 readme pointed me at browservm, a docker container that automates the creation of Linux images for v86. It uses Buildroot, a tool that can generate compact Linux images for embedded systems.

I wasn't familiar with Buildroot, but luckily the browservm documentation proved very useful and explained how to customize the build. It turns out buildroot comes with a built-it GDB package you can enable through their menuconfig system:

Adding GDB is as simple as checking a box!

That evening, I managed to get the first working version of avr-gdb that ran in the browser! 🥳

To sweeten the deal, the whole Linux + GDB image was just 6.25MB, and even if you add the size of v86 (about 2.3MB), that's still a reasonable download. Reminding you, the WebAssembly binary was 90MB!

But the work was not done yet:

  1. The GDB version was 7.11.1, which is nearly 5-years old
  2. It couldn't communicate with the Wokwi simulator
  3. The startup time of the VM was 15-30 seconds
Initial success! old GDB, slow boot

Tying Up Loose Ends

I spent the next day upgrading Buildroot the latest version, figuring out the configuration to build the latest GDB (10.1), finding a kernel version that worked correctly with v86, and patching Buildroot to build the GDB with AVR target (instead of x86).

Then I made GDB talk with Wokwi. The browser VM interacts with the user through the simulated serial port (/dev/ttyS0) that is connected to a virtual terminal using the excellent Term.js library.

I added a second serial port and a bit of JavaScript glue to transport the gdbserver messages to Wokwi through a MessageChannel port. And this is how you can ask GDB to debug over the serial port: gdb -ex "target remote /dev/ttyS1".

GDB is much more useful when you have the debugging symbols and the source code for the program your are debugging. But how can you get them into the VM?

Fortunately, v86 supports a 9P2000, a simple remote filesystem protocol that allows the VM to read files provided by the JS code. Wokwi sends the binary ELF file with symbol and program sources through the same MessageChannel, and another piece of glue code makes uploads them to the VM.

At this point, there was still one major usability issue: the VM takes about 15-30 seconds to boot! Web users are spoiled. They want their web apps to load in seconds, not half a minute.

v86 can start the VM from a snapshot, skipping the whole boot process. There is one downside to it: you need to download ~20MB instead of just ~6.25MB if you download the Linux image and boot it. So you save on boot time, but then spend more time downloading the snapshot.

I ended up combining both methods: when you start Web GDB for the first time, it boots Linux and then takes a snapshot, storing it to the browser cache. Then, the next time, the snapshot is loaded from cache and the VM starts almost instantly.

Hooray! GDB in the Browser

We did it. GDB loads and runs in the browser, the performance is good enough, and there's even TUI (Text User Interface) support!

Now it's your turn to check it out: open any project on Wokwi (e.g. this Simon game), click on the code editor, and press F1. In the prompt that opens, type "GDB":

Choose the "debug build" option (the release build is harder to debug, but it's useful if your program uses the FastLED library). Web GDB will load in a new browser tab (you have to be a bit patient), and you should get the familiar GDB prompt:

0x00000000 in __vectors ()
(gdb) 

At this point, you can write continue to start the program, or better - check out the Arduino/AVR GDB Cheatsheet to see all the things GDB can do for you!

Web GDB + TUI debugging Simon

Resources