After GDB hits a breakpoint, you'll almost always inspect a variable. If the same breakpoint gets hit repeatedly (as with a breakpoint inside a loop), you'll inspect the same variable repeatedly. Wouldn't it be nice to automate the procedure by telling GDB to automatically perform a set of commands each time it reaches a breakpoint?
In fact, you can do just this with "breakpoint command lists." We'll use GDB's printf
command to illustrate command lists. You haven't been formally introduced to it yet, but printf
basically works the same way in GDB as it does in C, but the parentheses are optional.
You set command lists using the commands
command:
commandsbreakpoint-number
...commands
... end
where breakpoint-number
is the identifier for the breakpoint you want to add the commands to, and commands
is a newline-separated list of any valid GDB commands. You enter the commands one by one, and then type end
to signify that you're done entering commands. Thereafter, whenever GDB breaks at this breakpoint, it'll execute whatever commands you gave it. Let's take a look at an example. Consider the following program:
Example Listing 2-3. fibonacci.c
#include <stdio.h> int fibonacci(int n); int main(void) { printf("Fibonacci(3) is %d.\n", fibonacci(3)); return 0; } int fibonacci(int n) { if ( n <= 0 || n == 1 ) return 1; else return fibonacci(n-1) + fibonacci(n-2); }
We'd like to see what values are passed to fibonacci()
and in what order. However, you don't want to stick printf()
statements in and recompile the code. First of all, that would be gauche in a book on debugging, wouldn't it? But more importantly, it would take time to insert code and recompile/link, and to later remove that code and recompile/link after you fix this particular bug, especially if your program is large. Moreover, it would clutter up your code with statements not related to the code, thus making it harder to read during the debugging process.
You could step
through the code and print n
with each invocation of fibonacci()
, but command lists are better, because they alleviate the need to repeatedly type the print command. Let's see.
First, set a breakpoint at the top of fibonacci()
. This breakpoint will be assigned identifier 1, since it's the first breakpoint you've set. Then set a command on breakpoint 1 to print the variable n
.
$ gdb fibonacci (gdb) break fibonacci Breakpoint 1 at 0x80483e0: file fibonacci.c, line 13. (gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >printf "fibonacci was passed %d.\n", n >end (gdb)
Now run the program and see what happens.
(gdb) run Starting program: fibonacci Breakpoint 1, fibonacci (n=3) at fibonacci.c:13 13 if ( n <= 0 || n == 1 ) fibonacci was passed 3. (gdb) continue Continuing. Breakpoint 1, fibonacci (n=2) at fibonacci.c:13 13 if ( n <= 0 || n == 1 ) fibonacci was passed 2. (gdb) continue Continuing. Breakpoint 1, fibonacci (n=1) at fibonacci.c:13 13 if ( n <= 0 || n == 1 ) fibonacci was passed 1. (gdb) continue Continuing. Breakpoint 1, fibonacci (n=0) at fibonacci.c:13 13 if ( n <= 0 || n == 1 ) fibonacci was passed 0. (gdb) continue Continuing. Breakpoint 1, fibonacci (n=1) at fibonacci.c:13 13 if ( n <= 0 || n == 1 ) fibonacci was passed 1. (gdb) continue Continuing. Fibonacci(3) is 3. Program exited normally. (gdb)
Well, that's pretty much what we expected, but the output is too verbose. After all, we already know where the breakpoint is. Fortunately, you can make GDB more quiet about triggering breakpoints using the silent
command, which needs to be the first item in the command list. Let's take a look at silent
in action. Note how we're redefining the command list by placing a new command list "over" the one we previously set:
(gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >silent >printf "fibonacci was passed %d.\n", n >end (gdb)
And here's the output:
(gdb) run Starting program: fibonacci fibonacci was passed 3. (gdb) continue Continuing. fibonacci was passed 2. (gdb) continue Continuing. fibonacci was passed 1. (gdb) continue Continuing. fibonacci was passed 0. (gdb) continue Continuing. fibonacci was passed 1. (gdb) continue Continuing. Fibonacci(3) is 3. Program exited normally. (gdb)
Nice. One last feature to demonstrate: If the last command in a commands list is Continue
, GDB will automatically continue executing the program after it completes the commands in the commands list:
(gdb) command 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >silent >printf "fibonacci was passed %d.\n", n >continue >end (gdb) run Starting program: fibonacci fibonacci was passed 3. fibonacci was passed 2. fibonacci was passed 1. fibonacci was passed 0. fibonacci was passed 1. Fibonacci(3) is 3. Program exited normally. (gdb)
You might want to do this type of thing in other programs, or at other lines of this program, so let's make a macro out of it, using GDB's define
command.
First, let's define the macro, which we'll name print_and_go
:
(gdb) define print_and_go Redefine command "print_and_go"? (y or n) y Type commands for definition of "print_and_go". End with a line saying just "end". >printf $arg0, $arg1 >continue >end
To use it as above, you would type:
(gdb) commands 1 Type commands for when breakpoint 1 is hit, one per line. End with a line saying just "end". >silent >print_and_go "fibonacci() was passed %d\n" n >end
Note that there is no comma between arguments of print_and_go
. You would then get the same output as before when you run the program, but the point is that now you can use it generally, anywhere in the code. Moreover, you can put it in your .gdbinit file for use in other programs. By the way, up to ten arguments are allowed, though there are just two in the example here.
You can get a list of all the macros by typing show user
.
Command lists are very useful, but you can also combine them with conditional breaking, and that's powerful. With this kind of conditional input/output, you might even be tempted to throw C away and simply use GDB as your programming language of choice. Just kidding, of course.
Command lists in DDD are similar to conditional breakpoints in DDD. First, set a breakpoint. Right-click the red stop sign and choose Properties. A pop-up window will appear. A large subwindow will be on the right (if you don't see the large subwindow, left-click the Edit button, which toggles the visibility of the commands window). You can type your commands right into this window. There's also a Record button. If you right-click this button, you can enter your commands into the GDB Console.