3、GDB断点基础:设置断点(break)、查看断点(info break)、删除断点(delete)、启用/禁用断点(enable/disable)
断点,说白了就是调试器里的「暂停键」。你让程序跑着,它跑到某一行就自动停下来,等你检查变量、看堆栈、分析逻辑。我刚开始学GDB时,觉得断点不就是个暂停嘛,有啥好学的?后来在Qt项目里追一个信号槽死循环,才发现断点用得好不好,直接决定你是一小时解决问题,还是一整天原地打转。
这一节,咱们就把断点的基本功打扎实。你想想看,如果连断点都不会设,后面的条件断点、观察点、捕获点就更别谈了。
3.1 设置断点:break 命令
break 是GDB里最常用的命令,缩写是 b。我个人习惯直接用 b,少打几个字,调试时效率更高。
基本用法有这几种:
| 命令 | 说明 | 示例 |
|---|---|---|
b 行号 |
在指定行设置断点 | b 42 |
b 函数名 |
在函数入口设置断点 | b main |
b 文件名:行号 |
在指定文件的某行设断点 | b main.cpp:42 |
b 文件名:函数名 |
在指定文件的函数入口设断点 | b main.cpp:main |
b +偏移量 |
从当前行往后偏移N行 | b +5 |
举个例子,假设你有这么一段代码:
// main.cpp
#include <iostream>
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv); // 第8行
QPushButton btn("Click me"); // 第10行
btn.show(); // 第11行
return app.exec(); // 第13行
}
在GDB里,你可以这样设断点:
(gdb) b 10
Breakpoint 1 at 0x4007f0: file main.cpp, line 10.
(gdb) b main
Breakpoint 2 at 0x4007e0: file main.cpp, line 8.
(gdb) b main.cpp:13
Breakpoint 3 at 0x400810: file main.cpp, line 13.
b QPushButton::setText 这种形式来断住某个Qt方法。但要注意,如果函数是内联的或者被编译器优化掉了,可能断不住。这时候我会加 -O0 编译选项重新编译。
3.2 查看断点:info break
断点设多了,难免会忘记自己设了哪些。这时候 info break 就派上用场了。缩写是 i b。
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004007f0 in main at main.cpp:10
2 breakpoint keep y 0x00000000004007e0 in main at main.cpp:8
3 breakpoint keep y 0x0000000000400810 in main at main.cpp:13
每一列的含义:
- Num:断点编号,后面删除、启用禁用都靠它
- Type:断点类型,breakpoint 是普通断点,还有 watchpoint、catchpoint 等
- Disp:处置方式,keep 表示保留,del 表示命中后自动删除
- Enb:是否启用,y 表示启用,n 表示禁用
- Address:断点的内存地址
- What:断点所在位置
我记得有一次追一个Qt信号槽的bug,一口气设了十几个断点。结果跑起来后,程序到处停,根本分不清哪个是哪个。后来我养成了一个习惯:每设一个断点,就用 comment 给它加个备注。
(gdb) b main.cpp:42
Breakpoint 4 at 0x400850: file main.cpp, line 42.
(gdb) comment 4
> 这里是处理按钮点击后的回调函数入口
>
这样再看 info break 时,最后一列会显示你的备注,一目了然。
3.3 删除断点:delete 命令
断点不用了,就该删掉。用 delete 命令,缩写是 d。
(gdb) d 1 # 删除编号为1的断点
(gdb) d 1 2 3 # 删除编号1、2、3的断点
(gdb) d # 删除所有断点(会提示确认)
d 没加编号,结果把所有断点都删了。当时那个项目编译一次要5分钟,我整个人都麻了。所以建议你:删除时一定带上编号。如果真想清空所有断点,用 d 后GDB会问你是否确认,看清楚再回答。
3.4 启用/禁用断点:enable / disable
有时候你不想删断点,只是暂时不想让它生效。比如你设了一个断点在循环里,每次循环都停,烦得很。这时候用 disable 把它禁掉,等需要时再 enable 启用。
(gdb) disable 2 # 禁用编号2的断点
(gdb) enable 2 # 启用编号2的断点
(gdb) disable 1 2 3 # 禁用多个
(gdb) enable 1 2 3 # 启用多个
禁用后,info break 里对应的 Enb 列会变成 n:
(gdb) disable 2
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004007f0 in main at main.cpp:10
2 breakpoint keep n 0x00000000004007e0 in main at main.cpp:8
3 breakpoint keep y 0x0000000000400810 in main at main.cpp:13
嗯,这里要注意:禁用断点后,程序跑到那一行不会停,但断点本身还在,编号也不会变。你随时可以重新启用。
3.5 实战场景:在Qt信号槽中设置断点
Qt的信号槽机制,说白了就是函数调用。你可以在槽函数里设断点,也可以在发射信号的代码行设断点。但有个坑:如果你在 emit 那一行设断点,程序停住时,信号还没真正发出去。如果你想在信号发出后、槽函数执行前停住,那就得在槽函数入口设断点。
举个例子:
// widget.cpp
void Widget::onButtonClicked()
{
emit mySignal("hello"); // 第15行
}
void Widget::onMySignal(const QString &msg)
{
qDebug() << msg; // 第20行
}
如果你在15行设断点,停住时信号还没发。如果你在20行设断点,停住时信号已经发了,槽函数正在执行。我个人习惯是在槽函数入口设断点,因为我想看的是槽函数接收到的参数对不对。
3.6 本章知识体系
下面这张图帮你梳理了断点操作的核心流程:
这张图把断点的生命周期串起来了:先设置,再查看,然后根据需要进行删除、禁用或启用。你调试时就在这四个操作之间来回切换,熟练了以后,效率会高很多。
disable 禁掉。等程序跑到发射信号的地方,我再手动 enable 那个断点。这样既不会漏掉,也不会被无关的断点干扰。
好了,断点基础就这些。下一节咱们聊聊条件断点——那个才是真正让你「指哪打哪」的利器。