1. GDB入门:调试,从认识GDB开始
说实话,我刚开始做嵌入式开发那会儿,调试基本靠printf。代码里到处塞打印语句,编译、下载、看串口输出,改一行代码就得重复一遍流程。直到有位老同事看我折腾了半天,丢过来一句:「你试试GDB。」
嗯,从那以后,我再也没回到printf调试的老路上去。
1.1 什么是GDB
GDB,全称GNU Project Debugger。说白了,就是一个让你能「看穿」程序运行状态的工具。
你想想看,程序跑起来之后,变量值是多少?函数调用到了哪一层?内存里到底存了什么?这些平时看不到的东西,GDB都能让你实时观察。它就像程序的CT机,哪里有问题,扫一眼就知道了。
我个人习惯把GDB分成两种用法:
- 本地调试:在PC上直接调试本机的程序
- 远程调试:调试嵌入式设备、服务器上的程序
做嵌入式的人,大部分时间都在用远程调试。我在项目中遇到过最典型的情况——板子跑着跑着就死机了,串口打印最后一条信息停在某个函数入口。这时候用GDB连上去,看调用栈,问题往往一目了然。
1.2 GDB能做什么
我总结了一下,GDB的核心能力就这五条:
| 能力 | 说明 | 我常用的场景 |
|---|---|---|
| 启动程序 | 指定参数、环境变量来运行程序 | 调试带命令行参数的工具 |
| 设置断点 | 在指定行、函数、条件处暂停 | 定位崩溃前的最后状态 |
| 查看变量 | 打印变量值、内存内容、寄存器 | 检查指针是否越界 |
| 单步执行 | 一行一行或一条指令一条指令地跑 | 追踪逻辑分支走向 |
| 分析堆栈 | 查看函数调用链、回溯崩溃点 | 排查段错误、死锁 |
其中堆栈分析这块,是我们这门课的核心。为什么?因为90%的疑难杂症,最后都落在调用栈上。我曾经花了两天查一个内存踩踏问题,最后用GDB的backtrace命令,十分钟就定位到了。
一句话总结:GDB能让你在程序运行时「暂停时间」,然后仔细检查每一处细节。
1.3 GDB的安装与启动
安装GDB其实很简单。不同平台略有差异,我列个表:
| 平台 | 安装命令 |
|---|---|
| Ubuntu/Debian | sudo apt install gdb |
| CentOS/RHEL | sudo yum install gdb |
| macOS | brew install gdb |
| Windows | 安装MinGW或WSL后使用 |
装完之后,验证一下:
$ gdb --version
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
...
看到版本号,就说明装好了。
启动GDB有两种方式:
- 直接调试可执行文件:
gdb ./my_program - 附加到正在运行的进程:
gdb -p 1234(1234是进程PID)
我个人更常用第一种。但如果你遇到程序已经跑起来、突然卡住的情况,第二种方式就派上用场了。我记得有一次,一个服务进程CPU飙到100%,我直接gdb -p附上去,看到它在死循环里打转——嗯,一个循环条件写错了。
小技巧:编译程序时记得加-g选项,这样GDB才能看到源码和符号信息。不加的话,你看到的全是地址,没法读。
1.4 GDB的基本命令概览
GDB的命令很多,但常用的其实就十几个。我刚开始学的时候,记不住那么多,就只记了下面这些:
| 命令 | 缩写 | 作用 |
|---|---|---|
run |
r |
运行程序 |
break |
b |
设置断点 |
next |
n |
单步跳过(不进入函数) |
step |
s |
单步进入(进入函数内部) |
continue |
c |
继续运行到下一个断点 |
print |
p |
打印变量值 |
backtrace |
bt |
查看函数调用栈 |
info |
i |
查看各种信息(如info registers) |
quit |
q |
退出GDB |
来一个最简单的例子。假设我们有这么一段代码:
// test.c
#include <stdio.h>
void func() {
int a = 10;
printf("a = %d\n", a);
}
int main() {
func();
return 0;
}
编译:gcc -g -o test test.c
启动GDB:
$ gdb ./test
(gdb) break func # 在func函数入口设断点
(gdb) run # 运行程序
(gdb) next # 单步执行
(gdb) print a # 打印变量a的值
(gdb) continue # 继续运行
(gdb) quit # 退出
看到没?就这么几行命令,你就能控制程序的每一步执行,还能随时查看变量。我当时第一次用的时候,感觉就像开了上帝视角。
注意:GDB默认使用AT&T风格的汇编语法。如果你更习惯Intel风格,可以在GDB里执行set disassembly-flavor intel。我个人偏好Intel风格,看着更直观。
知识体系总览
下面这张图,是我梳理的GDB入门知识结构。你可以把它当作整个课程的地图:
这张图把GDB入门的知识点串起来了。你可以看到,堆栈分析是我们这门课的核心,后面的章节都会围绕它展开。
好了,第一章就到这里。记住,调试不是靠猜,而是靠工具。GDB就是你手里最趁手的工具之一。下一章,我们开始真正动手——编译带调试信息的程序,然后跑第一个GDB会话。
课后小练习:写一个简单的C程序,包含至少两个函数调用。用gcc -g编译,然后用GDB启动,试试break、run、next、print这四个命令。感受一下「控制程序执行」是什么体验。
可可精品资料库