Erlang Debug VMs: What, Why and How
Erlang can load and run C libraries. This can be useful if you want to speed up parts of your code, or if you want to interact with some OS-specific features. These special C libraries are written specially to work with the Erlang VM, and can’t easily be run in isolation without extra code. This makes these libraries difficult to debug. In this post, I introduce Erlang debug VMs, explain how to build them, and show why they can be tremendously useful for debugging linked-in drivers and NIFs.
Once you load and run arbitrary C code, the Erlang VM becomes very easy to kill.
$ erl Erlang/OTP 19 [erts-8.2.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] Eshell V8.2.2 (abort with ^G) 1> test_nif:do_something_cool(). Segmentation fault $ :(
There are so many great tools for debugging Erlang code (dbg, redbug, erlyberly, recon) as well as excellent tools for debugging C code (gdb, valgind, afl-fuzz). However, there is a bit of a shortage of good tooling for debugging C code that gets executed by the BEAM. There’s niffy, which is a neat tool that acts like a reasonable subset of the Erlang VM, letting you load and call NIFs. Niffy and your NIF can then be run through something like afl-fuzz or your own test suite to try and uncover bugs. As far as I am aware, no such tool exists for Port Drivers.
Enter Debug VMs
I’ve gotten into the habit of building Erlang/OTP from source. This affords me more flexibility than using a package manager. One particularly nice thing about building Erlang from source is that you can compile debug VMs, modified BEAM binaries intended to be run inside of tools like GDB and Valgrind. This lets us debug any C code we’ve written that gets executed by BEAM. In addition, there is also a bonus set of GDB scripts called ETP which can pretty-print Erlang terms from inside of gdb! Debug VM binaries and ETP make the ordeal of digging through core dumps of crashed BEAM processes much easier, as you can poke around various parts of memory, and print the Erlang terms they correspond to.
How I Build My Debug VMs
If you want to build some debug VMs for yourself, the first thing you need to do is clone the Erlang/OTP git repository. (For managing software compiled from source, I highly recommend GNU Stow).
Here’s the general workflow of building and installing Erlang from source, using the git repository. Please note that these commands are somewhat symbolic; if you copy and paste them into your terminal, they may not work.
- Clone https://github.com/erlang/otp.git
export ERL_TOP="$(pwd)/otp"; cd $ERL_TOP
git checkout OTP-X.Y.Z
./configure --help | $PAGER
- Figure out which options I want from step 6.
./configure --prefix=/usr/local/stow/erlang-X.Y.Z --and-other-opts
make && make docs
The following steps are optional:
sudo make install install-docs
readlink $(which erl)to figure out the current version in use
sudo stow -D erlang-X.(Y-1).Wto unlink the old version
sudo stow erlang-X.Y.Zto link the new version
We haven’t built any debug VMs yet. Let’s do that now. There are quite a couple of different VMs you can build. The ones I use the most are the
$ cd $ERL_TOP/erts/emulator $ make debug valgrind gcov gprof lcnt icount
After this, you should have a shell script called
$ERL_TOP/bin. This is the script to invoke when you want to run a special BEAM VM. Adding that script to your path will let you invoke a debug VM whenever you like!
Now that you have built debug VMs, and have
cerl somewhere in your path, we can start the BEAM inside of gdb or valgrind using
cerl -rgdb or
cerl -valgrind. After that, you can pass arguments to the script as if you were passing them to
NOTE: If you type
cerl -gdb, the script will start Emacs to control your gdb session, which can be surprising!
I hope this inspires some of you to play around with Erlang debug VMs! There’s a lot of great stuff to be discovered! In the near future, I hope to publish another post showing off some use cases of debug VMs!