Philipp Trommler's Blog

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
#include <stddef.h>

void foo(const char * str)
{
    /* TODO: Do something with str */
    (void) str;
}

int main(void)
{
    const char * arr[5] = {
        "This",
        "is",
        "a",
        NULL,
        "test."
    };

    for (long unsigned i = 0; i < sizeof(arr) / sizeof(char *); ++i)
    {
        foo(arr[i]);
    }

    return 0;
}

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
(gdb) break foo
Breakpoint 1 at 0x1141: file main.c, line 7.
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>if (0x0 == str)
 >backtrace
 >else
 >continue
 >end
>end
(gdb)

This will create a breakpoint for foo and execute the following commands each time the breakpoint is hit:

  1. It will silence the normal GDB breakpoint output.
  2. If the function argument str is a NULL pointer it will print the current backtrace and drop us back into the GDB prompt.
  3. 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
(gdb) run
Starting program: /tmp/tmp.4UHFRrHldC/main
#0  foo (str=0x0) at main.c:7
#1  0x00005555555551aa in main () at main.c:21
(gdb)

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
@@ -1,9 +1,9 @@
 #include <stddef.h>
+#include <stdio.h>

 void foo(const char * str)
 {
-    /* TODO: Do something with str */
-    (void) str;
+    puts(str);
 }

 int main(void)

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
(gdb) break foo
Breakpoint 1 at 0x1155: file main.c, line 6.
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>set var str="bar"
>continue
>end
(gdb) run
Starting program: /tmp/tmp.4UHFRrHldC/main
bar
bar
bar
bar
bar
[Inferior 1 (process 21664) exited normally]
(gdb)

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!

Articles from blogs I follow around the net

One machine can go pretty far if you build things properly
via Writing - rachelbythebay, January 28, 2022

Okay, so yesterday I posted a picture of an old multi-line bulletin board system which had apparently taken over an entire bedroom with dozens of computers. I mentioned that I intended to come back to the topic to throw in my own two cents, and so here we…

Implementing a MIME database in XXXX
via Drew DeVault's blog, January 28, 2022

This is a (redacted) post from the internal blog of a new systems programming language we’re developing. The project is being kept under wraps until we’re done with it, so for this post I’ll be calling it XXXX. If you are interested in participating, send m…

LibrePCB Talk at FOSDEM 2022
via LibrePCB Blog, January 28, 2022

After the introduction to LibrePCB talk at FOSDEM 18 and the status update talk at FOSDEM 20, I’m happy to give one more update about the current project status at FOSDEM 22! The status update talk will take place on Saturday, February 5, at 12:00 CET. Due…

Generated by openring