2、GDB基础回顾:启动GDB、设置断点、单步执行、查看变量
好,咱们正式开始。在深入指针越界排查之前,我觉得有必要先热个身。GDB 这东西,说白了就是 C/C++ 程序员的「后悔药」和「放大镜」。你写代码时脑子再清醒,跑起来之后到底发生了什么,还得靠它来还原现场。
我个人习惯,不管多简单的 bug,第一反应都是「上 GDB 看看」。嗯,这招帮我省了无数加班的夜晚。今天咱们就把最核心的几个操作再过一遍——启动、断点、单步、看变量。别嫌基础,越界排查时,每一步都可能决定你能不能抓到那只「鬼」。
2.1 启动GDB:两种姿势,你都得会
启动 GDB 其实就两种常见场景。第一种,程序还没跑,你想从头开始调试。第二种,程序已经跑飞了,你想看看它死在哪。
2.1.1 直接启动(最常用)
gdb ./my_program
这行命令一敲,GDB 就加载了你的可执行文件。注意,编译时一定要加 -g 选项,否则你看到的全是汇编,那画面太美我不敢看。
2.1.2 附加到正在运行的进程
有时候程序已经跑起来了,突然卡住或者死循环。这时候你可以用 attach 方式。
gdb -p <PID>
或者先启动 GDB,再 attach:
(gdb) attach <PID>
我记得有一次线上服务内存持续增长,我直接 attach 上去,用 bt 一看调用栈,立马定位到是一个指针没释放。嗯,这种场景 attach 比重新启动调试快太多了。
2.2 设置断点:让程序在你想停的地方停下来
断点就是 GDB 的灵魂。没有断点,你只能眼睁睁看着程序跑完,然后一脸懵。
2.2.1 在函数入口打断点
(gdb) break main
(gdb) b main # 简写
这是最基础的。程序从 main 函数开始执行时就会停住。
2.2.2 在指定行号打断点
(gdb) break pointer.c:42
(gdb) b pointer.c:42 # 简写
我个人习惯用行号断点,尤其是排查指针越界时,我会在可疑的赋值语句前后各打一个断点,然后单步观察指针值的变化。
2.2.3 条件断点(高级技巧)
有时候你只关心某个循环的第 1000 次迭代,或者某个变量等于特定值的时候。这时候条件断点就派上用场了。
(gdb) break pointer.c:42 if i == 1000
(gdb) b pointer.c:42 if ptr == 0x0
你想想看,如果没有条件断点,你得手动按多少次 c(continue)才能到第 1000 次?手都按酸了。条件断点就是帮你省掉这些重复劳动。
2.3 单步执行:一步一步看程序怎么走
断点打好了,接下来就是「走一步看一步」。GDB 提供了几种单步方式,各有各的用处。
| 命令 | 全称 | 作用 |
|---|---|---|
n |
next | 单步执行,遇到函数调用不进入,直接跳过 |
s |
step | 单步执行,遇到函数调用会进入函数内部 |
finish |
finish | 执行完当前函数,返回到调用处 |
until |
until | 一直执行到指定行号(常用于跳出循环) |
举个例子,假设你在 main 里调用了 foo():
- 用
n,GDB 会直接执行完foo(),停在下一行。 - 用
s,GDB 会钻进foo()内部,让你看它里面干了什么。
我个人习惯是:如果我知道 foo() 没问题,就用 n 跳过;如果怀疑 bug 就在 foo() 里面,就用 s 进去看。说白了,这就是个「信任 vs 怀疑」的选择题。
2.4 查看变量:看看内存里到底放了什么
断点停了,单步走了,接下来最关键的一步——看变量。指针越界排查的核心就在这里:你得亲眼看看指针指向了哪里,那块内存里到底存了什么。
2.4.1 打印变量值
(gdb) print ptr
(gdb) p ptr # 简写
(gdb) p *ptr # 打印指针指向的内容
这应该是最常用的命令了。但注意,print 默认会格式化输出,对于指针,它会显示地址和类型。
2.4.2 查看内存内容
有时候光看变量值不够,你想直接看内存里的原始字节。这时候用 x 命令(examine memory)。
(gdb) x/10xb ptr # 以十六进制格式,显示 ptr 指向的 10 个字节
(gdb) x/10wd ptr # 以十进制格式,显示 ptr 指向的 10 个整数
格式说明:
x:十六进制d:十进制c:字符s:字符串b:单字节w:四字节(32位)g:八字节(64位)
我记得有一次排查堆内存越界,我怀疑某个数组写越界了,就用 x/20xb 看了目标地址前后的内存,结果发现紧挨着的另一个变量的值被改写了。嗯,铁证如山。
2.4.3 显示局部变量
(gdb) info locals
这个命令会一次性显示当前函数的所有局部变量。省得你一个一个 p 过去。我特别喜欢在循环里用这个,配合条件断点,效率翻倍。
2.5 本章小结:一张图看懂GDB基础操作
说了这么多,咱们用一张图把核心流程串起来。你调试时,基本就是在这个循环里打转:启动 -> 打断点 -> 单步 -> 看变量 -> 发现问题 -> 修代码 -> 重新启动。
这张图看着简单,但实际调试中,你可能会在这个循环里来回转好几圈。别急,慢慢来。指针越界这种 bug,往往需要你反复确认变量的值和内存的状态,才能最终锁定。
b、n、s、p 的时候,再学更高级的技巧。基础不牢,地动山摇。
好了,GDB 基础就回顾到这里。下一节咱们会真正进入指针越界的实战排查,到时候这些命令会反复出现。你最好现在就在自己的机器上敲一遍,找找手感。嗯,光看是学不会的,动手才是王道。