The Sands of Code: Reverse Debugging in GDB

Today I had the opportunity to use an amazing feature in GDB -- "reverse debugging". Actually, 'amazing' is probably an understatement.

Think of the following scenarios in a GDB debugging session (and forgive the dramatic prose):

  1. You're battling against an elusive little bug, the kind that appears only some times when you're running your code, crashes it, and goes its merry way, only to strike again a few compile-run cycles later. So how do you get rid of that bug? The usual answer: you pain-stakingly go through session upon GDB session, hoping to catch that bug in action and find out what's causing it. Isn't there supposed to be an easier way?

  2. You're in a middle of a particularly time-consuming debugging session, having set up the perfect combination of breakpoints and watchpoints after hunting around to find the approximate location of the bug's origin. So you run the code, and then carefully start issuing continue and next commands, navigating the code, examining variables along the way. Then you realize, somehow, that you've let the code run just a little too far and missed your chance. What now, you start over?

Back to the Future

Reverse debugging in GDB could be a neat solution to the problems above. By allowing you to re-simulate a particular instance of execution of your program any number of times, it gives you the ability to catch bugs easily and test them to your heart's content. Here's how you can handle problems #1 and #2 described above using this technique:

  1. Instead of carefully setting up and plodding through multiple GDB sessions in the hope that the bug will appear, you can simply set a few important breakpoints in the suspicious area of code, and keep issuing continue, and if Mr. Bug doesn't show up, run it again. Keep doing this until the bug rears its ugly head, at which point you issue a reverse-continue, which takes you back to the last breakpoint hit. This reversal restores everything to the state it was in before you'd issued your last continue -- but now you know the bug is going to appear shortly, so you set more breakpoints before the next one and narrow down your problem to a smaller area. Things don't look so good for the cornered prick.

  2. The answer to this should be pretty obvious now. Whenever you realize you've come too far in the code, you can go back using reverse-continue (or some other reverse command). Just make sure you set "recording" on (using record) at the point till which you want to able to reverse execution, preferrably right at the start of the program (it's pretty easy, take a look).

You can even do set exec-direction reverse to make all commands like continue, next, and step go backwards. To get back to normal, forward running code, use set exec-direction forward.