What is Time Travel Debugging?
Time travel debugging (aka reverse debugging) enables developers to record all program activities at runtime (every memory access, every computation, and every call to the operating system), and then rewind and replay to inspect the program state. This huge amount of data can be described using a powerful metaphor: the ability to travel backward in time (and forward again) to inspect the program state. Typically, in order to optimize for performance, the developers will fine tune the time travel debugger to collect only the information necessary for accurate replay of the program under inspection.
Time travel debuggers vs traditional debuggers
Traditional debuggers let you step line-by-line, forward, through a program and watch for a bug. This a approach can work well for very simple bugs, where the crash happens on the same line as, or immediately after, the error.
Sometimes, the location of an error and a developer’s knowledge of the code may suggest one or more possible locations to start looking for the root cause, so that when you restart the program in the debugger and run it forward, you can start looking at the logic around these locations to see where it goes off track.
However, this guesswork approach is much harder to apply in the case of hard to reproduce bugs, where a developer may have little/no information available about why the bug came about. Time travel debuggers are the single most helpful solution for these types of failures as a programmer can replay and walk through the program’s execution backward, as well as forward, in order to home in on a point of interest. This enables them to find the root cause from two ends of the program instead of one.
How does time travel debugging compare with runtime debugging?
Runtime debugging is the common practice of forwards-only debugging. It is the mixture of breakpoints, watchpoints and other general runtime-debugger things that serve as bookmarks to help you find bugs in your software.You can run your program until it hits a breakpoint and analyze any issues that appear in that particular section of code.
In some cases, this works fine, but more often than not, you do not know what section of code caused the error. This is a major problem when programs are large and makes it time-consuming to run them again and again. With time travel debugging, a developer only has to run their program once in order to capture a complete record of the program’s execution - including any error that appeared during runtime - in a recording. A recording not only captures the bug itself, but more importantly, the sequence of events that led up to and caused it.
How does time travel debugging work?
Developers have adopted different methods for time travel debugging and there is much discussion about how it works (see for example the StackExchange thread How does reverse debugging work?). Taking Undo as an example, Undo’s time travel debugger called UDB, provides the ability to instruct the process to go to any previous point in the execution history. It relies on the fact that many operations in a computer are actually deterministic and uses these as a way of identifying all sources of non-determinism that appear in compiled code (for more information, see introduction to reverse debugging). This allows developers to address the most time-consuming aspects of debugging today’s multilevel, multicomponent, multithreaded and multi-process applications.
How does time travel debugging affect performance?
Some slowdown must be expected when using time travel debugging (see for example the points raised on StackExchange in the thread Why is reverse debugging rarely used?). However, this varies hugely depending on the method of time travel debugging used. The time travel debugging function of GDB, for example, can slow a program down 50,000x, which is far too slow to be considered by most developers. UDB, typically causes a slowdown of 2-3x (though for large complex code systems, this can be greater). However, it is important to remember that you will have to restart your program multiple times when using a conventional debugger whereas with time travel debuggers such as UDB, you can replay backward and forward as often as you need without having to restart.
Can you use time travel debugging in any programming language?
Yes. Most Time Travel Debuggers for compiled code are based on the GNU debugger, GDB, and therefore support all languages compatible with GDB (e.g. C/C++, Go and even Fortran). For other languages, there are a growing number approaches to time travel debugging tools, such as:
Time travel debugging for Python (PyTrace)
Reverse debugging for .Net and C# (RevDeBug)
Time travel debugging for web applications (Replay.io)
Reverse debugging for Python (RevPDB)
Time Travel Debugging for Elm (Elm TTD)