2、GDB核心概念:断点、观察点、捕获点、单步执行、继续执行、查看变量
调试器这东西,说白了就是让你能「看穿」程序运行时的内部状态。GDB 的核心能力,就围绕这几个概念展开。我个人习惯把它们分成两类:一类是「停下来」的手段(断点、观察点、捕获点),另一类是「动起来」的手段(单步、继续、查看变量)。
嗯,咱们一个一个来拆解。
2.1 断点(Breakpoint)—— 最常用的暂停键
断点就是告诉 GDB:「执行到某一行,给我停住」。这是最基础、最常用的功能。我在项目中遇到过无数次这样的场景:程序跑飞了,不知道在哪崩的,先在所有可疑函数入口打上断点,然后一步步缩小范围。
核心用法:
break main—— 在 main 函数入口打断点break 42—— 在当前文件的第 42 行打断点break file.c:100—— 在 file.c 的第 100 行打断点break func if x == 5—— 条件断点,x 等于 5 时才停
你想想看,条件断点有多实用?比如你有个循环跑 10000 次,但只在第 5000 次出问题。你总不能手动按 5000 次 continue 吧?break loop_line if i == 5000,一行搞定。
我的小技巧: 调试多线程程序时,可以用 break func thread 2 指定只在某个线程中生效。这能省掉大量干扰信息。
2.2 观察点(Watchpoint)—— 盯着变量不放
断点是盯着代码行,观察点是盯着变量。只要变量的值发生变化,GDB 就会自动停下来。我曾经花了一整个下午追一个野指针问题,最后就是用观察点抓到的。
为什么会这样?因为断点只能告诉你「执行到哪一行了」,但有时候你根本不知道是哪一行改坏了变量。观察点就不一样了——它像雷达一样,变量一变就报警。
核心用法:
watch var—— 监视 var 变量,值变化时停住rwatch var—— 监视 var 被读取时停住awatch var—— 监视 var 被读或写时都停住
注意: 观察点是有代价的。硬件观察点数量有限(通常 4 个),软件观察点会让程序跑得极慢。别一口气设太多,否则调试体验会非常痛苦。
2.3 捕获点(Catchpoint)—— 抓异常和事件
捕获点比断点更「高级」一点。它不关心代码行,而是关心事件。比如 C++ 抛异常了、程序 fork 子进程了、动态库加载了——这些都能用捕获点抓住。
我记得有一次调试一个诡异的 SIGSEGV,程序总是在某个随机时刻崩溃。用断点根本没法复现,后来我用 catch signal SIGSEGV,一抓一个准。
核心用法:
catch throw—— 捕获 C++ 异常抛出catch signal SIGSEGV—— 捕获段错误信号catch fork—— 捕获 fork 系统调用catch syscall write—— 捕获 write 系统调用
2.4 单步执行(Step & Next)—— 一步一步来
停下来了,然后呢?你得「走」起来。GDB 提供了两种走法:step 和 next。区别很简单:step 会钻进函数内部,next 会跳过函数调用。
我个人习惯是:调试自己写的函数用 step,调试别人写的库函数用 next。你想想看,要是 printf 也 step 进去,那得看到多少汇编代码?
常用命令:
step或s—— 单步进入next或n—— 单步跳过finish—— 一直执行到当前函数返回until 100—— 一直执行到第 100 行
避坑指南: 我曾经在调试时一直用 next,结果发现某个变量的值被改了,但怎么也找不到是谁改的。后来才意识到,那个修改发生在另一个线程里,next 根本不会切线程。这时候就得用 thread apply all next 或者观察点。
2.5 继续执行(Continue)—— 从暂停到继续
单步走太慢了,有时候你想直接跑到下一个断点。这时候就用 continue(简写 c)。
嗯,这里要注意:continue 不是「从头开始跑」,而是「从当前位置继续跑,直到遇到下一个断点或程序结束」。
变体用法:
continue—— 继续执行continue 5—— 忽略当前断点 5 次,第 6 次才停signal SIGINT—— 向程序发送一个信号(比如模拟 Ctrl+C)
2.6 查看变量(Print & Display)—— 看穿内存
停下来了,也走起来了,但你看不到变量的值,那调试个啥?print 命令就是干这个的。
说白了,print 就是 GDB 的「眼睛」。它能打印变量、表达式、甚至调用函数。
核心用法:
print x—— 打印变量 x 的值print &x—— 打印变量 x 的地址print *ptr—— 打印指针指向的内容print array[0]@10—— 打印数组的前 10 个元素display x—— 每次停下来都自动打印 x
我的经验: 调试结构体时,print *my_struct 会打印所有成员。但如果成员太多,可以用 print my_struct->field 只打印你关心的那个。另外,display 配合单步执行简直是绝配——每走一步,自动显示关键变量的变化。
2.7 知识体系总览
下面这张图,是我自己总结的 GDB 核心概念关系图。你可以把它当作调试时的「导航地图」。
2.8 实战小贴士
最后,分享几个我自己的调试习惯:
- 先打断点,再 run —— 别让程序直接跑完,否则你连停的机会都没有
- 条件断点比手动 continue 高效 10 倍 —— 尤其是循环次数多的时候
- 观察点不要设太多 —— 硬件观察点有限,软件观察点慢到怀疑人生
- print 可以调用函数 —— 比如
print strlen(str),但要注意副作用 - display 和 step 是黄金搭档 —— 每走一步,自动看变量,省得你反复敲 print
曾经踩过的坑: 我在调试一个嵌入式程序时,设了 6 个观察点,结果程序跑一步要等 3 秒。后来才发现,软件观察点是通过单步执行 + 比较值来实现的,设多了就是灾难。从那以后,我最多只设 2 个观察点,其他的用条件断点替代。
嗯,这一章的内容就到这。核心就是记住:断点管位置,观察点管变量,捕获点管事件,单步和继续管流程,print 和 display 管查看。把这几个概念吃透了,GDB 就算入门了。