第一章:GDB入门——从零开始认识调试利器
说实话,我刚开始做嵌入式开发那会儿,调试代码全靠printf大法。代码里到处塞打印语句,编译下载看串口输出,改完再重复一遍……直到有一次,一个野指针问题折腾了我整整三天。后来一位老同事走过来,敲了几个命令,十分钟就定位到了问题。他用的就是GDB。从那天起,我就再也没离开过这个工具。
1.1 GDB是什么
GDB,全称GNU Debugger,是GNU项目下的一个调试器。说白了,它就是一个能让你「看穿」程序运行过程的工具。
你想想看,平时我们写代码,编译完直接运行。程序跑飞了、崩溃了、结果不对了,你只能看到最终现象。但GDB不一样,它能让程序在任意位置停下来,让你查看变量、寄存器、内存,甚至修改运行状态。
核心理解:GDB就像程序的「CT扫描仪」。普通调试是看表面症状,GDB能让你看到程序内部的每一个细节。
1.2 GDB能做什么
我在项目中用过GDB解决过太多问题了。简单列一下它的核心能力:
- 设置断点——让程序在指定行停下来。我习惯在可疑函数入口先设个断点,看看参数对不对。
- 单步执行——一行一行地跑代码。遇到函数调用,可以跟进去,也可以跳过。
- 查看变量——随时查看任意变量的当前值。还能监控变量什么时候被修改。
- 查看堆栈——程序崩溃时,看一眼调用堆栈,就知道是从哪条路走到这里的。
- 修改内存——嗯,这个我常用。临时改个变量值,不用重新编译就能测试边界情况。
- 反汇编——看C代码对应的汇编指令。调试启动代码或者优化问题时特别有用。
我的经验:GDB最强大的地方在于调试「偶发bug」。那种跑十次才出现一次的问题,用printf基本没戏。但用GDB的条件断点,可以精确捕获触发条件。
1.3 GDB的安装与启动
安装GDB其实很简单。不同平台略有差异:
| 平台 | 安装命令 |
|---|---|
| Ubuntu/Debian | sudo apt-get install gdb |
| CentOS/RHEL | sudo yum install gdb |
| macOS | brew install gdb(需要额外签名步骤) |
| Windows | 安装MinGW或Cygwin,自带GDB |
安装完后,验证一下:
$ gdb --version
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
...
启动GDB有两种方式。我一般这么用:
// 方式一:直接启动,再加载程序
$ gdb
(gdb) file ./my_program
// 方式二:启动时直接加载(我更喜欢这种方式)
$ gdb ./my_program
注意:编译程序时一定要加 -g 选项!不加的话,GDB能看到的东西非常有限。我曾经帮同事调试,折腾半天发现他编译没加-g……嗯,从那以后我每次写Makefile都会检查这个选项。
1.4 GDB的基本工作流程
GDB的工作流程,说白了就是「停-看-改-跑」四个字。我画了一张图,帮你理解这个过程:
具体来说,一个典型的调试会话是这样的:
- 启动GDB:加载你的程序。我习惯用
gdb ./program一步到位。 - 设置断点:告诉GDB在哪里停下来。比如
break main在main函数入口停,break 42在第42行停。 - 运行程序:输入
run开始执行。程序会在第一个断点处停下。 - 查看状态:用
print看变量,用backtrace看堆栈,用info locals看所有局部变量。 - 继续或单步:
next执行下一行(不进入函数),step进入函数内部,continue继续跑到下一个断点。 - 修改变量:用
set var x=5临时改值,测试不同情况。
来看一个完整的例子。假设我们有这么一段代码:
// test.c
#include <stdio.h>
int add(int a, int b) {
int result = a + b;
return result;
}
int main() {
int x = 10;
int y = 20;
int sum = add(x, y);
printf("sum = %d\n", sum);
return 0;
}
编译并调试:
$ gcc -g -o test test.c
$ gdb ./test
(gdb) break main
Breakpoint 1 at 0x4004f6: file test.c, line 9.
(gdb) run
Starting program: /home/user/test
Breakpoint 1, main () at test.c:9
9 int x = 10;
(gdb) next
10 int y = 20;
(gdb) print x
$1 = 10
(gdb) next
11 int sum = add(x, y);
(gdb) step
add (a=10, b=20) at test.c:4
4 int result = a + b;
(gdb) print a
$2 = 10
(gdb) print b
$3 = 20
(gdb) next
5 return result;
(gdb) print result
$4 = 30
(gdb) continue
Continuing.
sum = 30
[Inferior 1 (process 12345) exited normally]
(gdb) quit
我的习惯:调试时我一般先 break main,然后 run。这样程序从入口就开始受控。遇到函数调用不确定时,先用 step 跟进去看看,确认没问题了再用 next 跳过。
避坑指南:我曾经在调试一个多线程程序时,发现断点总是触发不到。查了半天,原来是断点设在了会被编译器优化掉的代码行上。记住,加了 -O2 或更高优化等级时,GDB的行号可能不准。调试阶段建议用 -O0 -g。
嗯,到这里GDB的基本概念和流程就讲完了。你可能会觉得,这些命令看起来也不复杂嘛。没错,GDB的入门门槛其实很低。但真正用好它,需要在实际项目中不断积累经验。后面几章,我会带你深入每个功能的具体用法。
可可精品资料库