Welcome back to this series about using the GNU debugger (GDB) to print information in a way that is similar to using print statements in your code. The first article introduced you to using GDB for printf-style debugging, and the second article showed how to save commands and output. This final article demonstrates the power of GDB to interact with C and C++ functions and automate GDB behavior.
Calling program-defined output routines
Our example program contains a function named
print_tree that outputs a constructed tree. Suppose that you wish to use that function to examine the tree on which the
insert function will operate each time it is called. This can be done by setting a breakpoint on
insert and then specifying GDB commands to execute each time the breakpoint is hit. But before looking at how that's done, let's first look at how ordinary GDB breakpoints work.
Setting a breakpoint
A breakpoint can be set using the break command. We can use it to set a breakpoint at the start of the
insert function, as follows:
(gdb) break insert Breakpoint 1 at 0x40127a: file tree.c, line 40.
If you refer back to the source code from Part 1, you'll see that line 40 is the first executable line of the function. If you run the program, GDB stops at the breakpoint, showing the values of the arguments to the function in addition to the line at which GDB has stopped:
(gdb) run Starting program: /home/kev/ctests/tree Breakpoint 1, insert (tree=0x0, data=0x40203f "dog") at tree.c:40 40 if (tree == NULL)
There are many interesting things you could do now, such as examining a stack trace via the backtrace command, or perhaps printing other values using GDB's print command. However, I wish to demonstrate the call and continue commands that we'll use in the next section.
Call and continue
In the following example, I issue the
continue command to ask GDB to continue past that breakpoint seven times, stopping again the eighth time it is hit. By default, the
continue command causes the program to execute to the next breakpoint. Providing a numeric argument to this command tells GDB to continue that number of times without stopping at the intermediate breakpoints. After the
continue command stops, the
call command calls
(gdb) continue 8 Will ignore next 7 crossings of breakpoint 1. Continuing. Breakpoint 1, insert (tree=0x4052a0, data=0x402055 "gecko") at tree.c:40 40 if (tree == NULL) (gdb) call print_tree(tree) cat dog javelina wolf (gdb)
GDB's printf command
GDB also has a printf command, which I've used here:
(gdb) printf "tree is %lx and data is %s\n", tree, data tree is 4052a0 and data is gecko
printf command prints to GDB's console, not to the program output. For this example, we will probably find it more useful to call the program's
printf() function from the standard C library. It will print to the program's output, which is also where the program's
print_tree function prints its output:
(gdb) call printf("tree is %lx and data is %s\n", tree, data) tree is 4052a0 and data is gecko $1 = 33 (gdb)
This output differs from GDB's built-in
printf command by printing an additional line (
$1 = 33). What's happening here is that GDB is calling
printf() to output the expected result. The
printf() function returns an integer representing the number of characters printed. This return value is printed to the GDB console and saved to the value history. If you want to suppress the printing of the return value (as well as its appearance in the value history), cast the return value of
void, like this:
(gdb) call (void) printf("tree is %lx and data is %s\n", tree, data) tree is 4052a0 and data is gecko
Attaching commands to a breakpoint
We are now ready to use GDB's commands command to attach a list of commands to a previously set breakpoint or list of breakpoints. When no breakpoint number or list is provided,
commands adds commands to the most recently defined breakpoint. Assuming that to be the case for the breakpoint set in the previous section, we can use the
commands command as follows:
(gdb) commands Type commands for breakpoint(s) 1, one per line. End with a line saying just "end". >silent >call (void) printf("Entering insert(tree=%lx, data=%s)\n", tree, data) >if (tree != 0) >call (void) printf("Tree is...\n") >call (void) print_tree(tree) >end >continue >end (gdb)
The first command associated with the breakpoint is silent. It tells GDB not to print the usual messages that are printed when stopping at a breakpoint.
Next is a
call command. It invokes
printf(), which prints a message showing that the
insert() function has been entered along with the values of
Next comes an if command. It checks whether the value of
tree is non-zero (that is, non-
NULL). If this condition is true, then the two
call commands are executed, because there is data in
tree. If not, those
call commands are skipped. The
end command terminates the block of commands for the
continue command comes next. It causes GDB to resume execution until either another breakpoint is hit or the program terminates.
end command terminates the list of commands to attach to the breakpoint.
With the breakpoint and its associated commands in place, running the program produces the following output:
(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/kev/ctests/tree Entering insert(tree=0, data=dog) Entering insert(tree=4056e0, data=cat) Tree is... dog Entering insert(tree=0, data=cat) Entering insert(tree=4056e0, data=wolf) Tree is... cat dog ... Entering insert(tree=405970, data=scorpion) Tree is... gecko javelina Entering insert(tree=0, data=scorpion) cat coyote dog gecko javelina scorpion wolf cat coyote dog gecko javelina scorpion wolf [Inferior 1 (process 326307) exited normally]
Saving the insert function breakpoint
Let's use the
info breakpoints command to look at the breakpoint for
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x000000000040127a in insert at tree.c:40 breakpoint already hit 19 times silent call (void) printf("Entering insert(tree=%lx, data=%s)\n", tree, data) if (tree != 0) call (void) printf("Tree is...\n") call (void) print_tree(tree) end continue (gdb)
Observe that the
info breakpoints output shows the number of times that the breakpoint has been hit. In this program, we see that
insert was called 19 times. Although it's not especially relevant for the current discussion, knowing how many times a particular function was called might be useful for optimization or performance analysis.
Let's save this breakpoint to a file named
(gdb) save breakpoints my-insert-breakpoint Saved to file 'my-insert-breakpoint'.
my-insert-breakpoint file now contains GDB commands that, when run, will recreate the
insert() breakpoint plus its associated commands for use in a future GDB session:
break tree.c:insert commands silent call (void) printf("Entering insert(tree=%lx, data=%s)\n", tree, data) if (tree != 0) call (void) printf("Tree is...\n") call (void) print_tree(tree) end continue end
Running a program with insert and dprintf breakpoints
I now have two files with saved breakpoints, one named
my-dprintf-breakpoints and the other named
my-insert-breakpoint. Let's start GDB, load the
breakpoint commands listed in the files, and then run the program with output redirected to
$ gdb -q ./tree Reading symbols from ./tree... (gdb) source my-dprintf-breakpoints Dprintf 1 at 0x401281: file tree.c, line 41. Dprintf 2 at 0x4012b9: file tree.c, line 47. Dprintf 3 at 0x4012de: file tree.c, line 49. (gdb) source my-insert-breakpoint Breakpoint 4 at 0x40127a: file tree.c, line 40. (gdb) set dprintf-style call (gdb) run >my-program-output Starting program: /home/kev/ctests/tree >my-program-output [Inferior 1 (process 327130) exited normally] (gdb) quit
Note that the
set dprintf-style call command had not been automatically added to either of the files loaded via the
source command. It might make sense to manually add it to the
my-dprintf-breakpoints file. Alternately, it could be placed into another file—let's call it
file tree source my-dprintf-breakpoints source my-insert-breakpoint set dprintf-style call
tree-debugging-commands, first specifies the program to debug via the file command. In earlier examples, we caused
tree to be loaded by mentioning it on the
gdb command line; here, however, we don't list it on the command line, but instead cause it to be loaded via the
The remaining commands in
tree-debugging-commands should be familiar by now. Commands contained in the
my-insert-breakpoints files are executed, followed by the
set dprintf-style call command. Recall that this command causes
dprintf breakpoints to call
printf() in the program being debugged (instead of using GDB's internal
With that file in place, we can run GDB as follows:
$ gdb -q -x tree-debugging-commands Dprintf 1 at 0x401281: file tree.c, line 41. Dprintf 2 at 0x4012b9: file tree.c, line 47. Dprintf 3 at 0x4012de: file tree.c, line 49. Breakpoint 4 at 0x40127a: file tree.c, line 40. (gdb) run >my-program-output Starting program: /home/kev/ctests/tree >my-program-output [Inferior 1 (process 351102) exited normally] (gdb) quit
It should also be possible to achieve the same effect, but without needing to interact with GDB, by using the following command:
$ gdb -q -x tree-debugging-commands -ex 'run >my-program-output' -ex quit
As noted in Part 1 of this series, the
-q option suppresses the GDB banner, copyright, and help information when GDB starts up. The
-x option, in this case, causes GDB to load and execute the commands from the file
-ex options cause the command following the option to be run. So, in this case, after loading and running the commands in
run command is issued from the first
-ex option; moreover, the output from the run is redirected to the file
my-program-output. The command following the second
-ex option is
quit; this causes GDB to quit without ever showing a prompt.
quit commands could also be placed in the command file,
tree-debugging-commands. If this were done, the command line would be shortened to look like this:
$ gdb -q -x tree-debugging-commands
A bugfix for dprintf breakpoints
While writing this article, I discovered a bug in GDB that caused
dprintf breakpoints to be (essentially) disabled when running a program from the command line or from within a GDB script. This bug has been fixed in the upstream GDB sources. On Fedora Linux,
gdb-11.1-5 (and later) contain this fix. If you are using a version of GDB without this fix, you will need to issue the
run command from the GDB prompt.
Go further with GDB
I hope this series has been useful to developers familiar with debugging their code using print statements, but who previously had little or no familiarity with GDB. I also hope that it whets your appetite for doing more with GDB.
This article demonstrated how to set a breakpoint and run until it is hit, a very common use of GDB. Once GDB is stopped at a breakpoint, you can enter a variety of GDB commands to reveal more about the state of the program at that point. If you want to go further with GDB commands, I recommend the following:
- The GDB developer's GNU Debugger tutorial, Part 1 is a general guide to debugging with the GNU Debugger.
- Debugging with GDB, the GDB manual, is the authoritative reference for using GDB.