1. GDB入门
大家好,我是你们的老朋友。今天咱们聊聊GDB——这个陪伴了我十几年的调试器。
说实话,刚入行那会儿,我对GDB是又爱又恨。爱的是它功能强大,恨的是命令行操作太不友好。但后来我明白了,越是强大的工具,越值得花时间去掌握。
1.1 GDB的历史与定位
GDB全称是GNU Debugger,诞生于1988年。那会儿我还没入行呢(笑)。它的创造者是Richard Stallman,GNU项目的发起人。
为什么要聊历史?因为理解GDB的定位,能帮你更好地用它。
GDB的核心定位就三个:
- 源码级调试:让你在源代码层面观察程序行为
- 跨语言支持:C、C++、Go、Rust... 主流语言基本都支持
- 远程调试:这是嵌入式开发的命脉
重要认知:GDB不是IDE里的那个调试按钮。它是调试器的"底层引擎"。VS Code、Eclipse、CLion这些IDE的调试功能,底层调用的都是GDB。
1.2 GDB在嵌入式与服务器开发中的核心价值
我经常跟团队里的新人说:调试器是程序员的第三只眼。尤其在下面两个场景,GDB的价值无可替代。
嵌入式开发:没有GDB,寸步难行
嵌入式开发有个特点:目标板资源极其有限。没有显示器、没有键盘、甚至没有操作系统。你想想看,printf打印调试?不存在的。
我在做STM32项目时遇到过这种情况:程序跑飞了,但不知道在哪一行。用LED闪烁定位?太原始了。用逻辑分析仪?太麻烦。
GDB远程调试一上,直接断点打在可疑位置,单步执行,寄存器值一目了然。嗯,这才是调试该有的样子。
| 调试方式 | 适用场景 | 效率 |
|---|---|---|
| printf打印 | 简单逻辑验证 | 低 |
| LED闪烁 | 硬件调试 | 极低 |
| GDB远程调试 | 复杂逻辑、性能问题 | 高 |
服务器开发:线上问题排查的利器
服务器端开发,最怕什么?线上程序崩溃,core dump一坨,但不知道原因。
我曾经接手过一个项目,服务每隔3小时就挂一次。日志看不出问题,代码review也找不到原因。最后用GDB分析core dump,发现是一个野指针在特定条件下触发的。
说白了,GDB就是你的"事后诸葛亮"。程序挂了,它还能告诉你挂在哪里、为什么挂。
1.3 GDB的安装与基本启动
安装GDB其实很简单。但不同平台有些小差异,我踩过坑,所以给你说说。
Linux平台
# Ubuntu/Debian
sudo apt-get install gdb
# CentOS/RHEL
sudo yum install gdb
# 验证安装
gdb --version
我的建议:尽量用系统包管理器安装。自己编译GDB虽然能获得最新版本,但依赖问题容易让人头疼。
macOS平台
# 使用Homebrew
brew install gdb
# 注意:macOS需要额外签名
# 否则GDB无法附加到进程
这里有个坑:macOS从Mojave开始,对调试器的权限限制很严格。我曾经折腾了一下午,才发现是签名问题。
嵌入式交叉编译环境
嵌入式开发需要交叉编译的GDB。比如你开发ARM Cortex-M,需要在x86的电脑上调试ARM的程序。
# 以ARM为例
sudo apt-get install gdb-multiarch
# 或者编译特定架构的GDB
./configure --target=arm-none-eabi
make
sudo make install
基本启动方式
启动GDB有三种常见方式:
- 直接调试可执行文件
gdb ./my_program
- 附加到正在运行的进程
gdb -p 1234 # 1234是进程PID
- 分析core dump文件
gdb ./my_program core.1234
注意:分析core dump时,可执行文件必须包含调试符号(编译时加-g选项)。否则你看到的只有一堆地址,没有源码信息。
1.4 GDB调试的核心流程
我画了一张图,帮你理解GDB调试的整体流程:
这张图展示了GDB调试的完整闭环。你想想看,从编译开始,到设置断点,再到运行和调试,每一步都有讲究。
1.5 我的第一个GDB调试会话
光说不练假把式。咱们来个最简单的例子:
// 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;
}
编译并调试:
# 编译(一定要加-g)
gcc -g -o test test.c
# 启动GDB
gdb ./test
# 在GDB内部
(gdb) break main
(gdb) run
(gdb) next
(gdb) print x
$1 = 10
(gdb) print y
$2 = 20
(gdb) step
(gdb) print result
$3 = 30
(gdb) continue
小技巧:GDB支持Tab补全。输入bre然后按Tab,会自动补全为break。这个习惯我从入行就养成了,效率提升不少。
1.6 避坑指南
最后,分享几个我踩过的坑:
- 忘记加-g选项:编译出来的程序没有调试符号,GDB只能看到地址,看不到源码。我曾经因为这个排查了半小时。
- 优化级别太高:-O2或-O3优化后,代码执行顺序可能改变,单步调试会跳来跳去。调试阶段建议用-O0。
- strip操作:发布版本通常会strip掉符号表。但调试时一定要保留未strip的版本。
嗯,第一章就到这里。记住一句话:GDB不是万能的,但没有GDB是万万不能的。