一、断点管理
1.1 添加断点
在GDB中,添加断点使用break命令,break可以简写为b。
1、按照文件名与行号添加断点
(gdb) b src.cpp:10
上面命令表示在src.cpp文件的第10行添加断点。
如果当前GDB已经定位或者运行到某个源文件,想要直接在该源文件中添加断点,可以直接使用b+行号的方式:
(gdb) b 10
这种方式表示在当前源文件的第 10 行添加断点。
2、按照函数名添加断点
如果希望程序进入某个函数时暂停,可以直接使用函数名添加断点:
(gdb) b function_name
表示在function_name函数入口处添加断点。当程序调用function_name函数时,会在函数开始处暂停。对于C++成员函数,也可以使用类名加函数名的方式添加断点:
(gdb) b ClassName::functionName
当使用函数名进行添加断点的时候,需要注意在cpp中函数重载的情况,此时会在多个相同函数名的地方都设置上断点。
1.2 正则表达式断点
除了使用函数名或文件行号添加断点外,GDB还支持使用正则表达式批量添加断点,对应命令是rbreak,可以简写为rb。
rb [正则表达式]
例如代码中有三个类,包含person、student、teacher,三个类中都有含有work名称成员函数,想要对这些work函数都打上断点
例如,有如下 C++ 代码:
#include <iostream>
using namespace std;class Person
{
public:void work(){cout << "Person work" << endl;}
};class Student
{
public:void work(){cout << "Student work" << endl;}
};class Teacher
{
public:void work(){cout << "Teacher work" << endl;}
};int main()
{Person p;Student s;Teacher t;p.work();s.work();t.work();return 0;
}
使用命令:
rb work
可以直接在所有包含work名字的函数上打上断点。
1.3 条件断点
条件断点是普通断点的增强形式。普通断点只要程序运行到指定位置就会暂停,而条件断点只有在满足指定条件时才会暂停。条件断点常用于循环调试场景。例如,程序循环执行很多次,但我们只关心某个变量等于某个特定值时的状态。如果每次循环都停下来,会非常麻烦,这时就可以使用条件断点。
条件断点的基本格式如下:
b src.cpp:num if [condition]
例如下面是一个测试程序:
#include <iostream>
using namespace std;int main()
{for (int i = 0; i < 10; i++){int value = i * 2;cout << "i = " << i << ", value = " << value << endl;}return 0;
}
如果想在 i == 5 时停下来,可以在循环体内部设置条件断点:
(gdb) b main.cpp:8 if i == 5
然后运行程序:
(gdb) r
程序不会在每一次循环都暂停,而是等到i的值为5时才暂停。暂停后可以查看变量值:
(gdb) p i
(gdb) p value
输出可能类似:
$1 = 5
$2 = 10
条件表达式中也可以使用其他比较关系,例如:
(gdb) b main.cpp:8 if i > 5
表示当 i > 5 时暂停。
(gdb) b main.cpp:8 if value == 12
表示当 value 的值等于 12 时暂停。
对于指针变量,也可以判断是否为空:
(gdb) b main.cpp:20 if ptr == nullptr
或者:
(gdb) b main.cpp:20 if ptr == 0
条件断点也可以作用在函数上:
(gdb) b add if a == 10
表示只有调用 add 函数,并且参数 a 的值等于 10 时,才会在 add 函数入口处暂停。
如果已经设置了普通断点,也可以后续给断点添加条件。首先查看断点编号:
(gdb) i b
假设断点编号为1,可以使用condition命令添加条件:
(gdb) condition 1 i == 5
如果想取消该断点的条件,可以执行:
(gdb) condition 1
这样断点 1 就会恢复为普通断点。
1.4 临时断点
临时断点是一种只生效一次的断点。当程序第一次运行到该断点位置并暂停后,这个断点就会自动删除,后续程序再次运行到同一位置时不会再暂停。
临时断点和普通断点的格式完全相同,唯一不同的就是临时断点只作用一次。
tb src.cpp:num
1.5 查看断点信息
在GDB中,可以使用info breakpoints(i b)命令查看当前已经设置的断点信息

该命令会列出当前所有断点的信息,包括断点编号、断点类型、是否启用、断点地址以及断点所在位置。其中各字段含义如下:
| 字段 | 含义 |
|---|---|
Num |
断点编号,后续删除、禁用、启用断点时会用到 |
Type |
断点类型,例如 breakpoint 表示普通断点 |
Disp |
断点命中后的处理方式 |
Enb |
断点是否启用,y 表示启用,n 表示禁用 |
Address |
断点对应的程序地址 |
What |
断点所在的函数、文件和行号 |
其中,Disp 字段比较常见的值有:
Disp 值 |
含义 |
|---|---|
keep |
断点命中后继续保留,普通断点通常是这个值 |
del |
断点命中后自动删除,临时断点通常是这个值 |
dis |
断点命中后自动禁用 |
如果只想查看某一个断点的信息,可以在命令后面加上断点编号:
i b [id]
1.6 删除断点/禁用断点
在调试过程中,随着断点数量增多,有些断点可能不再需要。此时可以选择删除断点,也可以选择临时禁用断点。
删除一个断点使用delete(d)命令进行删除,只使用delete命令,不跟断点号,是删除所有断点
d [id]
禁用某一个断点是用disable命令进行禁用
disable [id]
enable [id]
二、断点处预设置命令
在使用GDB调试程序时,程序停在断点之后,调试者经常会重复执行一些命令,例如查看变量值、打印链表节点、查看调用栈等。如果每次命中断点后都手动输入这些命令,会比较麻烦。GDB提供了断点预设置命令功能,可以为某个断点提前绑定一组命令。当程序运行到该断点并暂停时,GDB会自动执行这些命令,从而避免重复操作。
断点预设置命令使用commands命令,基本格式如下:
(gdb) commands [断点编号]
> 命令1
> 命令2
> 命令3
> end
其中,end表示命令列表结束。例如:
(gdb) commands 1
> p *curr
> p prev
> end
表示当编号为1的断点被命中时,自动执行:
p *curr
p prev
下面举一个具体的示例:
#include <iostream>
using namespace std;int main()
{for (int i = 0; i < 5; i++){int value = i * 10;cout << "loop" << endl;}return 0;
}
在循环体中设置断点:
(gdb) b main.cpp:8
假设这个断点编号为1,可以为它设置预执行命令:
(gdb) commands 1
> p i
> p value
> end
然后运行程序:
(gdb) run
当程序每次运行到该断点时,GDB都会自动打印i和value的值。
不过这种写法有一个特点:程序每次命中断点后仍然会停下来,需要手动执行continue才会继续运行。如果只是想把断点当作“打印日志”来使用,不希望程序每次都停下来,可以在命令列表最后添加continue:
(gdb) commands 1
> p i
> p value
> continue
> end
这样每次命中断点后,GDB会自动打印变量,然后继续运行程序。
需要注意的是,默认情况下,GDB每次命中断点时都会显示类似下面的信息:
Breakpoint 1, main () at main.cpp:8
如果不想显示这些断点提示信息,可以在命令列表开头添加silent:
(gdb) commands 1
> silent
> p i
> p value
> continue
> end
这样程序命中断点时,就不会输出默认的断点提示信息,只会执行我们自定义的命令。
这里有一个小技巧:
GDB 中也提供了printf命令,用于格式化打印信息。它的用法和C语言中的printf类似,但是格式上有一点不同:GDB中的printf不需要写括号。
C 语言中的写法是:
printf("prev node data is %d\n", prev_node->data);
GDB 中应该写成:
(gdb) printf "prev node data is %d\n", prev_node->data
注意,GDB 的 printf 格式如下:
(gdb) printf "格式字符串", 参数1, 参数2
例如:
(gdb) printf "i = %d, value = %d\n", i, value
在断点预设置命令中,printf很适合用于循环打印:
(gdb) commands 1
> silent
> printf "i = %d, value = %d\n", i, value
> continue
> end
这样程序运行时会自动输出:
i = 0, value = 0
i = 1, value = 10
i = 2, value = 20
i = 3, value = 30
i = 4, value = 40
这种方式相当于在不修改源代码的情况下,临时给程序添加了一些调试打印。
如果想清除某个断点已经设置好的命令,可以重新执行commands,然后直接用end结束,不添加任何命令。
例如,清除编号为1的断点命令:
(gdb) commands 1
> end
这样断点 1 仍然存在,但是它命中后不会再自动执行之前设置的命令。
三、保存断点信息文件
在使用GDB调试程序时,我们可能已经设置好了很多断点,例如普通断点、条件断点、临时断点,甚至还为某些断点设置了预定义命令。如果此时需要处理其他事情,必须先中断当前调试工作,那么直接退出GDB会导致这些断点信息丢失。
为了避免下次重新调试时再次手动添加断点,GDB提供了保存断点信息的功能,可以将当前断点配置保存到文件中。下次重新进入GDB后,再将该文件导入,就可以恢复之前设置好的断点。
3.1 保存断点信息
保存断点信息使用save breakpoints命令,基本格式如下:
(gdb) save breakpoints 文件名
例如:
(gdb) save breakpoints breakpoints.gdb
该命令会将当前已经设置的断点信息保存到breakpoints.gdb文件中。
保存的内容不仅包括普通断点,也包括条件断点以及断点处设置的预定义命令。例如,假设当前设置了如下断点:
(gdb) b main.cpp:10
(gdb) b main.cpp:20 if i == 5(gdb) commands 2
> silent
> printf "i = %d\n", i
> continue
> end
然后执行:
(gdb) save breakpoints breakpoints.gdb
GDB 就会把这些断点配置保存到breakpoints.gdb文件中。
3.2 查看导入保存的断点文件
save breakpoints 生成的文件本质上是一个GDB命令脚本,里面保存的是重新创建断点所需的GDB命令。
可以直接使用 cat 查看:
cat breakpoints.gdb
文件内容可能类似:
break main.cpp:10
break main.cpp:20 if i == 5
commandssilentprintf "i = %d\n", icontinue
end
因此,这个文件不仅可以用于恢复断点,也可以根据需要手动编辑。例如,可以删除不需要的断点,或者修改某个断点的位置。
下次重新进入 GDB 后,可以使用source命令导入之前保存的断点文件。
例如先启动GDB:
gdb ./main
然后在 GDB 中执行:
(gdb) source breakpoints.gdb
执行完成后,GDB会读取breakpoints.gdb文件中的命令,并重新添加之前保存的断点。
可以使用下面命令查看断点是否恢复成功:
(gdb) i b
