2. 断点基础:设置断点(break)、查看断点(info breakpoints)、删除与禁用断点
调试这件事,说白了就是让程序在关键时刻停下来,让你看看它到底在干什么。而断点,就是那个「关键时刻」的标记。
我记得刚入行那会儿,调试全靠 printf 大法。代码里到处是 printf("here1")、printf("here2")…… 后来被 mentor 骂了一顿:「你这是在写代码还是在写日志?」从那以后,我才老老实实学 GDB。嗯,断点这东西,一旦用熟了,你就再也回不去了。
2.1 设置断点:break 命令
设置断点最常用的命令就是 break,简写为 b。我个人习惯用简写,毕竟调试的时候谁也不想打那么多字。
基本用法有这几种:
| 命令 | 说明 | 示例 |
|---|---|---|
break 行号 |
在指定行设置断点 | b 42 |
break 函数名 |
在函数入口处设置断点 | b main |
break 文件名:行号 |
在指定文件的某行设置断点 | b server.c:128 |
break 文件名:函数名 |
在指定文件的函数入口设置断点 | b utils.c:parse_request |
break +偏移量 |
从当前行往后偏移若干行 | b +5 |
举个例子,假设我们有这么一段代码:
// server.c
1: #include <stdio.h>
2: #include <stdlib.h>
3:
4: int handle_request(int fd) {
5: char buffer[1024];
6: int n = read(fd, buffer, sizeof(buffer));
7: if (n < 0) {
8: return -1;
9: }
10: // 处理请求...
11: return 0;
12: }
13:
14: int main() {
15: int sock = create_socket();
16: while (1) {
17: int client = accept(sock, NULL, NULL);
18: handle_request(client);
19: }
20: return 0;
21: }
在 GDB 里可以这样设断点:
(gdb) b 17 # 在 accept 调用处停下
(gdb) b handle_request # 在函数入口处停下
(gdb) b server.c:8 # 在错误处理处停下
break 函数名 thread 线程号 来让断点只对某个线程生效。我在排查一个并发 bug 时用过这招,效果拔群。
2.2 条件断点:让断点更聪明
有时候你不想每次循环都停下来,只想在某个条件满足时才停。这时候就需要条件断点了。
(gdb) b 17 if client == -1
(gdb) b handle_request if fd > 100
我曾经调试过一个内存泄漏问题,程序里有个循环处理了上万个请求。如果每个请求都停下来,那得等到猴年马月。我加了个条件断点 b free_pool if pool->used > 1024,只在内存池使用量超过阈值时才停。嗯,一下子就定位到了问题。
malloc,结果程序跑着跑着就崩了…… 别问我怎么知道的。
2.3 查看断点:info breakpoints
断点设多了,难免会忘记哪些设了、设在哪里。这时候 info breakpoints(简写 i b)就派上用场了。
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x4005a2 in main at server.c:17
2 breakpoint keep y 0x4005c0 in handle_request at server.c:5
3 breakpoint keep n 0x4005d0 in handle_request at server.c:8
stop only if fd < 0
输出里每一列的含义:
- Num:断点编号,后续操作都用这个编号
- Type:断点类型,一般是 breakpoint
- Disp:命中后的行为,keep 表示保留,del 表示删除
- Enb:是否启用,y 表示启用,n 表示禁用
- Address:断点所在的地址
- What:断点位置描述
你想想看,如果断点编号是 3,但 Enb 列显示的是 n,说明这个断点被禁用了。这在调试时很有用——你暂时不想停在这里,但又不想删掉它。
2.4 删除与禁用断点
断点设多了,该清理的时候就得清理。GDB 提供了两种方式:删除和禁用。
删除断点:delete
(gdb) delete 1 # 删除编号为 1 的断点
(gdb) delete 1 2 3 # 删除多个断点
(gdb) delete # 删除所有断点(会提示确认)
禁用/启用断点:disable / enable
(gdb) disable 2 # 禁用编号为 2 的断点
(gdb) enable 2 # 重新启用
(gdb) disable # 禁用所有断点
(gdb) enable # 启用所有断点
禁用和删除的区别在于:禁用的断点还在,只是暂时不生效;删除了就真的没了,想用还得重新设。我个人习惯是:
- 如果这个断点后面还可能用到 → 用 disable
- 如果这个断点已经没用了 → 用 delete
- 如果调试结束后想保留断点配置 → 用
save breakpoints bp.txt保存,下次用source bp.txt恢复
2.5 知识体系总览
下面这张图帮你理清断点操作的核心脉络:
2.6 实战小贴士
最后分享几个我平时用得比较多的技巧:
- 临时断点:用
tbreak设置临时断点,命中一次后自动删除。适合只想停一次的场景。 - 硬件断点:用
hbreak设置硬件断点,适合调试内核代码或 ROM 中的代码。 - 断点命令列表:用
commands 编号给断点绑定一组命令,命中后自动执行。比如自动打印变量值然后继续运行。
(gdb) commands 1
> print fd
> continue
> end
这样每次命中断点 1 时,GDB 会自动打印 fd 的值,然后继续运行。省得你一次次手动敲命令。
断点这东西,说白了就是调试的「眼睛」。设得好,问题一眼就能看出来;设得乱,反而把自己绕进去。多练练,慢慢就有感觉了。