2、GDB基础回顾:启动GDB、设置断点、单步执行、查看变量

好,咱们正式开始。在深入指针越界排查之前,我觉得有必要先热个身。GDB 这东西,说白了就是 C/C++ 程序员的「后悔药」和「放大镜」。你写代码时脑子再清醒,跑起来之后到底发生了什么,还得靠它来还原现场。

我个人习惯,不管多简单的 bug,第一反应都是「上 GDB 看看」。嗯,这招帮我省了无数加班的夜晚。今天咱们就把最核心的几个操作再过一遍——启动、断点、单步、看变量。别嫌基础,越界排查时,每一步都可能决定你能不能抓到那只「鬼」。

2.1 启动GDB:两种姿势,你都得会

启动 GDB 其实就两种常见场景。第一种,程序还没跑,你想从头开始调试。第二种,程序已经跑飞了,你想看看它死在哪。

2.1.1 直接启动(最常用)

gdb ./my_program

这行命令一敲,GDB 就加载了你的可执行文件。注意,编译时一定要加 -g 选项,否则你看到的全是汇编,那画面太美我不敢看。

警告: 生产环境编译通常不加 -g,但调试版本必须加。我曾经在客户现场调试一个崩溃,结果发现二进制是 strip 过的,符号表全没了……那叫一个尴尬。

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 次?手都按酸了。条件断点就是帮你省掉这些重复劳动。

小技巧: 条件断点里的表达式可以是任何合法的 C 表达式。我曾经用它来监控某个全局变量何时被修改——在变量地址上打一个写断点(watch),然后条件触发时自动暂停。

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基础操作

说了这么多,咱们用一张图把核心流程串起来。你调试时,基本就是在这个循环里打转:启动 -> 打断点 -> 单步 -> 看变量 -> 发现问题 -> 修代码 -> 重新启动。

GDB基础调试流程 启动GDB 设置断点 运行 / 单步执行 查看变量 / 内存 发现问题,重新断点

这张图看着简单,但实际调试中,你可能会在这个循环里来回转好几圈。别急,慢慢来。指针越界这种 bug,往往需要你反复确认变量的值和内存的状态,才能最终锁定。

我的建议: 刚开始用 GDB 时,别贪多。先把启动、断点、单步、查看变量这四个基本功练熟。等你闭着眼睛都能打出 b、n、s、p 的时候,再学更高级的技巧。基础不牢,地动山摇。

好了,GDB 基础就回顾到这里。下一节咱们会真正进入指针越界的实战排查,到时候这些命令会反复出现。你最好现在就在自己的机器上敲一遍,找找手感。嗯,光看是学不会的,动手才是王道。

可可精品资料库