2. GDB基本命令
调试嵌入式程序,说白了就是跟代码的「灵魂」对话。GDB 就是那个翻译官。我刚开始用 GDB 时,其实就靠五个命令活下来的——run、break、next/step、print、quit。别小看这五个,它们能解决 80% 的调试问题。
核心思想:调试不是乱试,而是有节奏地「停、看、走」。先让程序停在可疑位置,再观察变量状态,最后决定下一步怎么走。
2.1 启动程序:run 命令
你编译好了一个程序,比如叫 test.elf。怎么用 GDB 跑起来?
(gdb) file test.elf # 加载可执行文件
(gdb) run # 启动程序
(gdb) run arg1 arg2 # 带参数启动
我个人习惯先设好断点再 run,不然程序一口气跑完,你啥也看不到。嗯,这里有个坑——如果程序跑飞了或者死循环了,按 Ctrl+C 可以中断回来。
小技巧:嵌入式调试时,经常需要给目标板传参数。比如 run --baud 115200 /dev/ttyUSB0,GDB 会把后面的字符串原封不动传给 main 函数的 argc/argv。
2.2 设置断点:break 命令
断点就是让程序「停下来让你检查」的位置。我见过新手一口气设十几个断点,结果自己都忘了停在哪。其实断点贵精不贵多。
(gdb) break main # 在 main 函数入口停
(gdb) break 42 # 在当前文件的第42行停
(gdb) break file.c:100 # 在 file.c 的第100行停
(gdb) break func_name # 在某个函数入口停
我曾经在一个电机驱动项目里,发现电流波形偶尔异常。设断点在 PWM 更新函数上,每次停住就看占空比寄存器——结果发现是某个中断优先级配错了,导致更新被延迟。要是没有断点,这种偶发问题根本抓不住。
注意:在 ROM 或 Flash 上设断点,硬件需要支持「硬件断点」。ARM Cortex-M 系列通常只有 4-6 个硬件断点,设多了会报错。软件断点只能在 RAM 里用。
2.3 单步执行:next 与 step
这两个命令长得像,但性格完全不同。
| 命令 | 行为 | 适用场景 |
|---|---|---|
next (n) |
单步执行,不进入函数内部 | 你确定这个函数没问题,只想看外层逻辑 |
step (s) |
单步执行,进入函数内部 | 你想钻进函数里看每一行怎么跑的 |
举个例子:
int a = foo(); // 如果这里用 next,直接跳到下一行
// 如果这里用 step,会钻进 foo() 函数里
int b = bar();
我建议新手多用 step,别怕麻烦。你想想看,调试的本质就是「确认每一行代码都按你预期执行」。有一次我调一个 SPI 驱动,用 next 跳过了初始化函数,结果发现寄存器值不对。回头用 step 钻进去,才发现是时钟分频算错了——这种问题,用 next 永远发现不了。
我的习惯:先 next 快速过一遍主流程,发现可疑变量时,重新跑一遍用 step 钻进去细看。这叫「粗调 + 精调」。
2.4 查看变量:print 命令
程序停住了,接下来就是「看」。print 是最直接的观察方式。
(gdb) print var_name # 打印变量值
(gdb) print &var_name # 打印变量地址
(gdb) print *ptr # 打印指针指向的内容
(gdb) print array[3] # 打印数组元素
(gdb) print /x var # 以十六进制显示
(gdb) print /t var # 以二进制显示
我记得有一次调一个网络协议栈,发现 checksum 总是不对。用 print /x buffer 看了原始数据,再用 print /x checksum_calc 看计算结果——发现是字节序搞反了。要是没有 print,这种问题得猜半天。
实用技巧:嵌入式里经常看寄存器,可以用 print /x *(uint32_t*)0x40021000 直接读内存地址。我习惯把常用寄存器地址记成宏,比如 #define RCC_APB2ENR (*(volatile uint32_t*)0x40021018),然后直接 print RCC_APB2ENR。
2.5 退出:quit 命令
调试完了,或者想重来,用 quit 或 q 退出 GDB。
(gdb) quit
如果程序还在运行,GDB 会问你是否要终止。一般选 yes。我偶尔会忘记退出,直接关终端——其实也没事,GDB 会自动清理。
注意:在嵌入式远程调试(比如用 gdbserver)时,quit 只会退出本地 GDB 客户端,目标板上的 gdbserver 可能还在跑。记得手动停掉 gdbserver,不然下次连接会报端口被占用。
2.6 知识体系总览
下面这张图,是我自己梳理的 GDB 基本命令关系。你一看就明白:调试就是「启动→设断点→单步→观察→退出」的循环。
说白了,调试就是这五个命令的排列组合。你不需要记住几百个 GDB 命令,先把这五个用熟。我在带新人时,第一周只让他们用这五个命令,不许碰别的。等他们能流畅地「停→看→走→停→看→走」,再教高级功能。
一句话总结:run 让程序跑起来,break 让它停下来,next/step 让它一步一步走,print 让你看清它心里想什么,quit 让你体面地结束这场对话。