The art of debugging with GDB (2) --Breakpoint Command Lists

印成天
2023-12-01

In article The art of debugging with GDB (1) --Breakpoints, We have talked about some basic features about Beakpoints. Now we go on learning some advanced tricks about Beakpoints.

Breakpoint Command Lists (commands)

It’s possible to let GDB automatically perform a set of commands each time it reaches a breakpoint.

You set command lists using the commands command:

commands breakpoint-identifier

specific-commands

end

breakpoint-identifier: the identifier for the breakpoint you want to add the commands to.
specific-commands: a newline-separated list of any valid GDB commands.
end: to signify that you’re done entering 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:

/* 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);
}

Now we try the “breakpoints command list” to let it automatically do something when it reaches the specified breakpoint.

$ 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)

Add silent command to the command lists

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)

Add continue command to the command lists

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)

Use define command to make a macro

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.

Command lists are very useful, but you can also combine them with conditional breaking, and that’s powerful.

Modify or cancel the command set

The command set for a given breakpoint can be modified dynamically, or simply canceled by redefining an empty set:

(gdb) commands 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>end

The help message of commands given by GDB

(gdb) help commands
Set commands to be executed when the given breakpoints are hit.
Give a space-separated breakpoint list as argument after "commands".
A list element can be a breakpoint number (e.g. `5') or a range of numbers
(e.g. `5-7').
With no argument, the targeted breakpoint is the last one set.
The commands themselves follow starting on the next line.
Type a line containing "end" to indicate the end of them.
Give "silent" as the first line to make the breakpoint silent;
then no output is printed when it is hit, except what the commands print.
 类似资料: