3、GDB断点管理:断点类型、列表、删除与禁用
调试这件事,说白了就是让程序在关键时刻停下来,让你看看它到底在干什么。而断点,就是我们叫停程序的「哨卡」。GDB里的断点管理,我用了十几年,今天把压箱底的经验都掏出来给你。
3.1 普通断点:最基础的「停车位」
普通断点,就是最直接的断点。你告诉GDB:「嘿,跑到第42行的时候停一下。」它就会照做。
(gdb) break main.c:42
Breakpoint 1 at 0x4005a0: file main.c, line 42.
(gdb) break func_name
Breakpoint 2 at 0x4006b0: file main.c, line 18.
我个人习惯用函数名加行号的方式,比如 break main.c:42。为什么?因为项目大了以后,多个文件里可能有相同的行号,带上文件名更精准。
break 文件名:函数名,比如 break main.c:main,GDB会在函数入口处停下。
我在项目中遇到过一个问题:一个函数被调用了上千次,每次都在入口处停,调试效率极低。这时候普通断点就不够用了,得用条件断点。
3.2 条件断点:精准打击的「智能哨卡」
条件断点,说白了就是带条件的普通断点。只有条件满足时,程序才会停下来。这玩意儿在循环里特别好用。
(gdb) break main.c:42 if i == 100
Breakpoint 3 at 0x4005a0: file main.c, line 42.
(gdb) break func_name if value > 0 && value < 10
Breakpoint 4 at 0x4006b0: file main.c, line 18.
你想想看,一个循环跑100万次,你只关心第100次的情况。用普通断点你得手动按100万次continue?不现实。条件断点就是干这个的。
我曾经调试一个网络协议栈,有个bug只在特定数据包长度下触发。我直接写了个条件断点:break packet_handler if pkt_len == 1500。嗯,一次命中,省了我半天时间。
3.3 临时断点:用完即走的「一次性哨卡」
临时断点,就是只生效一次的断点。程序停一次后,这个断点自动消失。用 tbreak 命令创建。
(gdb) tbreak main.c:42
Temporary breakpoint 5 at 0x4005a0: file main.c, line 42.
(gdb) continue
Continuing.
Temporary breakpoint 5, main () at main.c:42
42 int result = compute(data);
临时断点什么时候用?我个人习惯在「我就想看一眼这里」的场景下用。比如你怀疑某个函数第一次调用时有问题,但又不想事后手动删断点,用 tbreak 就对了。
info break 里永久保留。它被触发后,GDB会自动删除它。所以别指望用它做长期调试。
3.4 断点列表:info break 的妙用
调试到一半,你可能忘了自己设了多少断点。这时候 info break 就是你的救星。
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004005a0 in main at main.c:42
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004006b0 in func_name at main.c:18
stop only if i == 100
3 breakpoint keep y 0x00000000004007c0 in packet_handler at net.c:55
stop only if pkt_len == 1500
4 hw watchpoint keep y value changed
stop only if value > 0
这张表里信息很丰富:断点编号、类型、状态、位置、命中次数、条件。我每次调试前都会跑一遍 info break,确认断点没设错。
info break 2 只看编号为2的断点。
3.5 断点删除与禁用:该放手时就放手
断点设多了,有时候反而碍事。GDB提供了两种方式:删除和禁用。
删除断点
(gdb) delete 2 # 删除编号为2的断点
(gdb) delete # 删除所有断点(会提示确认)
(gdb) clear main.c:42 # 删除main.c第42行的所有断点
我个人更推荐用 delete 加编号的方式,精准、安全。用 clear 有时候会误删你不想删的断点。
禁用与启用断点
(gdb) disable 2 # 禁用编号为2的断点
(gdb) enable 2 # 启用编号为2的断点
(gdb) disable # 禁用所有断点
(gdb) enable # 启用所有断点
禁用和删除的区别,你想想看:禁用是暂时不用,但保留设置;删除是彻底移除。我调试时经常先禁用一批断点,等需要时再启用,省得重新设。
delete 删了所有断点,结果忘了某个关键断点,导致重新设了半天。后来我养成了习惯:先用 disable,确认没问题再 delete。
3.6 知识体系总览
下面这张图,是我自己总结的断点管理知识体系。你可以把它当成调试时的「地图」。
这张图把断点管理的核心内容都串起来了。从上到下,从左到右,你可以看到每种断点的定位、操作方式,以及它们在实际调试中的典型用法。
好了,断点管理这块就聊到这儿。记住:普通断点打基础,条件断点做精准,临时断点省麻烦,info break 看全局,delete/disable 善后。把这几个玩熟了,调试效率至少翻一倍。