第一章:GDB入门——调试,从认识GDB开始
大家好,我是你们的老朋友。做了十几年嵌入式开发,调试过无数个让人头疼的bug。今天咱们聊聊GDB——这个我几乎每天都在用的工具。
说实话,刚入行那会儿,我调试代码全靠printf大法。代码里到处是printf,编译、下载、看串口输出,改完再重复一遍。直到有一次,一个偶发性的指针越界问题,我打了整整两天的printf都没定位到。后来一位老同事走过来,敲了几行GDB命令,十分钟就找到了问题根源。从那天起,我就再也没离开过GDB。
一句话总结:GDB是GNU项目下的源码级调试器,是C/C++程序员最锋利的调试工具。
1.1 GDB是什么?
GDB,全称GNU Debugger。说白了,就是一个让你能“看穿”程序运行过程的工具。
你想想看,平时我们写代码,编译完直接运行。程序跑飞了、崩溃了、结果不对了,你只能靠猜。GDB能做什么?它能让你:
- 让程序在任意一行停下来(断点)
- 停下来后,查看变量的值、内存的内容
- 单步执行,一行一行看代码怎么跑的
- 程序崩溃时,告诉你崩溃在哪一行、调用栈是什么
- 甚至能修改运行中的变量值,临时“修复”问题
我个人习惯把GDB比作“程序的CT机”。printf就像X光片,能看到大概;GDB就像CT扫描,每一层、每一个细节都清清楚楚。
1.2 GDB能做什么?
我整理了一下,GDB的核心能力其实就这几大类:
| 功能类别 | 具体能力 | 我常用的场景 |
|---|---|---|
| 断点调试 | 设置行断点、函数断点、条件断点 | 怀疑某行代码有问题,停在那看变量 |
| 单步执行 | step into、step over、continue | 跟踪函数调用,看参数传递是否正确 |
| 变量查看 | print、display、watch | 监控某个变量何时被修改 |
| 内存分析 | x命令查看内存、查看寄存器 | 排查指针越界、内存泄漏 |
| 崩溃分析 | backtrace、frame、core dump分析 | 程序段错误时,快速定位崩溃点 |
| 多线程/多进程 | 切换线程、查看线程栈 | 调试并发问题、死锁问题 |
我的小建议:刚开始学GDB,别贪多。先把断点、单步、变量查看这三个基本功练熟,能解决80%的日常调试问题。
1.3 GDB的安装与启动
安装GDB其实很简单。不同平台稍微有点区别:
Linux系统:
# Ubuntu/Debian
sudo apt-get install gdb
# CentOS/RHEL
sudo yum install gdb
# 验证安装
gdb --version
macOS系统:
# 使用Homebrew
brew install gdb
# 注意:macOS上需要额外签名,否则可能无法调试
Windows系统:
我建议用MinGW或者WSL。Windows原生GDB体验不太好,我个人更推荐在WSL的Linux环境下调试。
注意:编译程序时,一定要加-g选项!不加-g,GDB就看不到源码和变量名,只能看到汇编代码。我曾经帮一个同事排查问题,他编译时忘了加-g,结果GDB里全是地址和汇编,调试难度直接翻倍。
启动GDB有两种方式:
# 方式一:直接启动,再加载程序
gdb
(gdb) file ./myprogram
# 方式二:启动时直接加载程序(我更喜欢这种方式)
gdb ./myprogram
1.4 第一个GDB调试会话
光说不练假把式。咱们写个简单的程序,走一遍完整的调试流程。
先看代码:
// demo.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;
}
编译并启动GDB:
gcc -g -o demo demo.c
gdb ./demo
进入GDB后,你会看到类似这样的提示符:
(gdb)
好,现在开始我们的第一个调试会话:
# 1. 在main函数入口设置断点
(gdb) break main
Breakpoint 1 at 0x4004f6: file demo.c, line 9.
# 2. 运行程序
(gdb) run
Starting program: /home/user/demo
Breakpoint 1, main () at demo.c:9
9 int x = 10;
# 3. 单步执行到下一行
(gdb) next
10 int y = 20;
# 4. 查看变量x的值
(gdb) print x
$1 = 10
# 5. 继续单步
(gdb) next
11 int sum = add(x, y);
# 6. 查看x和y的值
(gdb) print x
$2 = 10
(gdb) print y
$3 = 20
# 7. 单步进入add函数
(gdb) step
add (a=10, b=20) at demo.c:3
3 int result = a + b;
# 8. 查看函数参数
(gdb) print a
$4 = 10
(gdb) print b
$5 = 20
# 9. 单步执行完add函数
(gdb) next
4 return result;
(gdb) print result
$6 = 30
# 10. 继续执行到程序结束
(gdb) continue
Continuing.
sum = 30
[Inferior 1 (process 12345) exited normally]
看到了吗?整个流程非常清晰。每一步你都知道程序在干什么,变量是什么值。这就是GDB的魅力。
核心要点:
break:设置断点,让程序停在你关心的位置run:启动程序,遇到断点会停下next:单步执行,不进入函数内部step:单步执行,会进入函数内部print:查看变量或表达式的值continue:继续执行,直到下一个断点或程序结束
避坑指南:我曾经犯过一个低级错误——在调试时忘了加-g编译。结果GDB里全是0x7fffffff这样的地址,根本看不到变量名。后来我养成了习惯,写Makefile时默认加上-g,调试完再换成-O2优化。
嗯,到这里,GDB的入门内容就差不多了。你可能会问:就这几个命令,能解决复杂问题吗?别急,这只是开胃菜。后面的章节,我会带你一步步深入,从断点的高级用法,到多线程调试,再到core dump分析。每一个技巧,都是我在实际项目中踩过坑、流过汗换来的经验。
记住一句话:调试不是浪费时间,而是节省时间。用GDB调试,一次定位问题,比打一百次printf都管用。