Debugging Golang With Delve (Replay and Rewind)

Introduction

The easiest way to debug has always been to use print statements, at least for me. Print imporant points of the program and observe what's going on. Over the years, I improved on my print debugging. Instead of printing "HERE", "HEREE", and "YOOHOO" I switched to single letters. Talk about progress.

For complex functions I increasingly began to use "tracing"—permanent print statements that are toggled by a variable or a flag. I love having tracing; it's a convenient way to get a sense of what the program is doing. Tracing is especially useful if you come back to a project after a long time. Setup up a simple test, run it with tracing enabled and see the flow.

Learning Delve

Finally, I dug into a real debugger. The debugger I am working with is Delve, probably the most common Go debugger. At first, I didn't find any advantages over the old approach. The new process was slower and didn't provide any more information than my usual cluster of prints.

However, I became comfortable with Delve quick and got to know the shortcuts and advance commands. The one feature everybody is familiar with are breakpoints but there are others that make debugging with Delve a joy.

A Couple of Commands

Delve provides commands that provide insight that wouldn't be possible with print statements. And then it provides two commands that I always use. They are simple and because of that they are most useful. They are a better version of the prints I was used to.

display

display prints the value of an expression every time the program stops (at every breakpoint, step, etc.). I use it to print the state I am interested in throughout the whole program.

Example

Print variable s on every stop:

display -a s
on

on is similar to display but prints only on the given breakpoint. I use it to print the state I want to see only at a certain point in the program.

Example

Print n.Type on breakpoint 'a':

on a print n.Type

Replay and Rewind

One feature I love are replays and how easy they are to use. They enable replaying a program execution over and over again and it always being the same. Replays can be used to find rarely occuring bugs such as race conditions, but best of all, they enable rewinding.

Rewinding allows going back to previous breakpoints. With rewinding, I can step over my program however I see fit and dive into it. I can step into a function, print its local variables, step out, and jump back and do it again. For me, rewinding is the next level of debugging.

Enable with Mozilla rr

Delve supports multiple backends but all the backends that support replays need to be installed separately. I use the open source Mozilla rr.

To enable replays and rewinds, install the Mozilla rr. Then, when you wish to replay and rewind, use the 'rr' backend like so:

dlv test --backend rr [package]

# real example: run a specific printer test that can be rewound
dlv test --backend rr ./printer/ -- -test.run 'TestFprint/^"\^a"$'

Inside Delve, rewind using rewind or the rw shortcut.

Hints

Name your breakpoints

When setting breakpoints, give them a meaningful name. I default to using the letters on the home row ("a", "s", "d", etc.) as I set only a couple of breakpoints at a time and can easily remember them all.

Set breakpoints from code

I prefer to set breakpoints in Delve, but you can also set breakpoints from inside the code by using runtime.Breakpoint().

Learn using help

Delve has good enough help to easily learn more, use it.

Examples
help
help trace

Closing Thoughts

Debugging can speed up the process of finding bugs, especially those more complex. While I still use my "YOOHOO" prints here and there, I now turn to Delve if there is anything proper that I need to debug. I find that tests paired with Delve replay and rewind form a great development tool, whether for debugging, developing new features, or refactoring.


You can see this page's source written in Touch and the config used.