Aussie AI
Interactive Debuggers
-
Book Excerpt from "Generative AI in C++"
-
by David Spuler, Ph.D.
Interactive Debuggers
I used to be a big fan of gdb
and dbx
on various Unix platforms,
but lately I'm addicted to Microsoft Visual Studio's interactive debugging tools
in the Windows GUI.
Everyone uses debuggers differently,
and some programmers even hate using IDE debuggers to step through code,
but I find it invaluable.
Here are some of my own thoughts on how to use interactive debugging tools to find bugs:
Breakpoints. I use breakpoints a lot.
I have standard breakpoints set inside my assertion and self-testing failure code,
so that my code automatically stops when that triggers.
Similarly, if I have an unexpected problem, I set a breakpoint right before the
offending error message.
Another trick with breakpoints is to define a C++ function called “breakpoint
” and
call it from various other places.
Stepping Through Code. I find stepping through the C++ code useful for both debugging and new code development. You need to get proficient with the different stepwise actions, such as Step, Step Over, Step Into, and Step Out (go to the end of this function). Sometimes stepping gets annoying, in which case I also find that I'm often having to set breakpoints a few lines of code down from where I'm currently at, and hitting “Continue”.
Restart: I find that I'm always doing “Restart” when debugging. It's very helpful to restart stepping, and also if you add a minor code change, you can then Restart to rebuild and rerun from the beginning. This is most useful if you have a unit test that triggers the error.
Watches: You can watch the value of a variable, or an expression, as it changes throughout execution. This is an extremely useful feature to have. Set some watches as you step through the code.
Edit and Continue. This is an IDE feature where you can edit the value of a variable, or even edit the C++ code in the middle of a run, and the compiler will incrementally re-compile your small changes into the executable, and keep going from where it is (i.e. rather than re-starting from the beginning). Lots of programmers like this feature, and as a former compiler engineer, I offer kudos to the engineers who have made it actually work(!), but sorry, I cannot stand this feature as a user of the debugger. I wish I could turn it off and never be prompted about it again.
Postmortem debugging.
On Linux or other Unix platforms, if you have a “core
” file as part of your user's error report,
it's helpful to run gdb
to get a stack trace using the “where
” command.
And you can also sometimes get other useful context about the values of variables.
One important point about this is that you need a copy of the actual version of the executable
that shipped to the user.
Trying to postmortem debug a core dump file using your developer's version of the executable doesn't work too well.
Also, you ideally need a version of that shipped Unix executable that still has debugging symbols, and hasn't
had its debug info removed with the “strip
” tool,
so the software release process needs to save copies of both of those.
Command-Line Debuggers:
A symbolic debugger such as gdb
on Linux can also be used to debug the program interactively.
The programmer runs the program from within the debugger and sets breakpoints
to control execution. The “where
” command is useful to show the function call stack.
The values of variables can be examined at run-time, and function
calls can be monitored carefully. When used properly these tools are a highly effective
method of finding an error. However, a programmer should not fall into the trap of using
a symbolic debugger to test a program because this form of testing is not easily
reproducible. Instead, symbolic debuggers should be used mainly when a particular failure has been
identified.
Debugging with Keyboard Signals:
An interesting but rarely used debugging procedure is to trap keyboard interrupts such
as those from the <ctrl-c>
keyboard shortcut,
which causes a SIGINT
signal. These interrupts can be caught in C++ so as
to cause the execution of a “signal handler” function via <signal.h>
and this code can then output helpful debugging information (or do whatever you need),
such as a report on the current
heap state or the function call stack.
Trapping Fatal Signals.
Another tip is that on Linux or Unix you can trap fatal signals
such as SIGSEGV
, SIGILL
, and SIGFPE
.
You can set a breakpoint in there,
and there's also a supportability benefit.
The signal handler can't ignore or correct a fatal signal,
but it can print a nice message for the user before dumping core.
• Next: • Up: Table of Contents |
The new AI programming book by Aussie AI co-founders:
Get your copy from Amazon: Generative AI in C++ |