Using GDB Breakpoint Command Lists
Breakpoint command lists are a handy feature of our most loved GNU debugger. Still, I seldom read about them or see them in use, even by proficient users. Thus, I'd like to give some usage examples here.
Published by Philipp Trommler. This article has also been translated to: de.
The basics are quite easy: After defining at least one breakpoint you can enter
commands
into the GDB command prompt to set a set of commands that shall be
executed every time the breakpoint defined last is hit (to set the commands for
a different breakpoint just append its number to the commands
command).
Thereafter, you can specify any GDB commands you could also enter into the GDB
console and leave the commands
environment with end
when you're finished.
Within the commands
environment you have access to all variables that could be
accessed if you would have already been dropped into the prompt by the
breakpoint, i.e. global variables, function arguments and local variables. If
you want to skip the current breakpoint, you have to include a continue
before
your commands
environment ends. Likewise, if you want to be dropped into the
GDB prompt for the current breakpoint, you must reach the terminating end
of
your commands
environment without passing a continue
. The behavior is analog
for next
and step
.
In my experience, the commands
feature is best paired with the silent
command, which silences the typical information GDB prints whenever hitting a
breakpoint. This makes the output way more readable in cases where the
breakpoint is hit regularly. Further, you can of course use the common shorthand
symbols within the commands
environment, e.g. c
for continue
(still, I've
omitted them for this post to lower the entry barrier).
But now for some examples. Consider the following problem: You have a long
running program where, from time to time, a parameter of a function is not what
you would expect it to be and you have no idea where this unusual argument is
coming from nor how to trigger the situation. This issue is reproduced in an
abstracted form in the simple program below in the function foo
. Every now and
then it receives a NULL
pointer instead of a valid string. Of course, in the
right circumstances this would trigger a SEGFAULT
or similar anyway so you'd
need no breakpoint, but let's just assume it wouldn't for the sake of the
example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
You can compile the program with gcc -Wall -Wextra -Werror -std=c18 -g -o main
main.c
and even run it without problems. To analyze the problem, we can start
the GDB with gdb main
. Then, within the GDB prompt we can proceed with:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
This will create a breakpoint for foo
and execute the following commands each
time the breakpoint is hit:
- It will silence the normal GDB breakpoint output.
- If the function argument
str
is aNULL
pointer it will print the current backtrace and drop us back into the GDB prompt. - Else, it will continue running the program.
And indeed, after executing run
, we'll find ourselves in the GDB prompt at
exactly the point where we've passed NULL
into foo
:
1 2 3 4 5 |
|
Clever minds might argue now that you could have achieved nearly the same result
by creating a conditional breakpoint with break foo if 0x0 == str
and indeed
that's true. The following, however, cannot be replicated that easily: Imagine
you want to inject certain, maybe faulty, values into a running program in order
to trigger a specific behaviour, e.g. during prototyping. Just as you can check
and print values within a commands
block, you can also set them.
Let's change the foo
function to print the string it's been given:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
The resulting program can still be compiled as before but running it will result
in a segmentation fault when we're trying to puts
a NULL
pointer. But, again
for the sake of the example, imagine this would be a far more complex program in
which you want to change some data on the fly to trigger a certain behaviour. To
achieve that you can simply use GDB's set var
command in the commands
environment. Let's change the str
argument to "bar"
for each invocation of
foo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
And as you can see, we get the expected five outputs and the SEGFAULT
is gone.
You might have already guessed that these are just very basic examples of how to
use the commands
and you can probably already think of more elaborate command
lists that can boost your debugging productivity a lot. And indeed, by combining
them with the other GDB commands you might already know, they get really
powerful. At least since I know this feature of GDB my whole style of debugging
has changed to include it regularly.
Filed under Debugging. Tags: programming, gdb.
Want to comment on this article? Write me at blog [at] philipp-trommler [dot] me!