GDB 入门:调试器中的瑞士军刀

说实话,我第一次接触 GDB 是在大学做操作系统实验的时候。那时候写了个内核模块,一运行就死机,连个错误提示都没有。急得我团团转。后来师兄甩过来一句:「用 GDB 啊。」嗯,从那天起,我就再也没离开过这个工具。

GDB,全称 GNU Debugger。说白了,就是一个让你能「看」进程序内部的神器。你写的 C/C++ 代码跑起来出了奇怪的问题,不用靠猜,不用靠 printf 大法,直接用 GDB 把程序停下来,看看变量值、看看堆栈、看看内存——一切清清楚楚。

GDB 是什么?

GDB 是一个可调试多种语言的源码级调试器。它支持 C、C++、Go、Rust、Fortran 等等。我个人的习惯是,只要编译时加了 -g 选项,GDB 就能把机器码和源代码对应起来。你想想看,这意味着什么?意味着你可以在源代码行上设断点,单步执行,甚至修改变量的值。

它的核心能力其实就三条:

  • 控制执行:让程序跑、停下来、一步一步走
  • 观察状态:看变量、寄存器、内存、堆栈
  • 修改状态:改变量值、改内存、甚至改指令

我在项目中遇到过最典型的场景:一个服务跑着跑着就 segfault 了,日志里啥也没有。用 GDB 挂上去,等它崩,然后 bt 一下,调用链清清楚楚——原来是某个指针被 free 了两次。这种问题,靠肉眼读代码,读三天也未必找得到。

一句话总结:GDB 就是程序员的「CT 扫描仪」。你不需要拆开程序,就能看到它内部的一切。

GDB 的历史:一个四十年的故事

GDB 是 Richard Stallman 在 1986 年开始写的。没错,就是那个 GNU 项目的发起人。他当时写 GCC 编译器,发现缺一个配套的调试器,于是自己动手写了 GDB。

你可能觉得,一个四十年前的工具,现在还能用?嗯,不仅能用,而且依然是业界标准。为什么?因为它的设计太扎实了。GDB 采用了一种「目标-前端」分离的架构:

  • 前端:处理用户交互(命令行、GUI、IDE 集成)
  • 目标:处理底层调试逻辑(ptrace、硬件调试寄存器)

这种设计让它能适应各种平台。从 x86 到 ARM,从 Linux 到 macOS,甚至嵌入式系统,GDB 都能搞定。我记得有一次调试一个跑在 MIPS 架构路由器上的程序,GDB 照样稳稳地工作。

版本 发布时间 重要特性
GDB 1.0 1986 基本调试功能
GDB 4.0 1990 支持 C++
GDB 7.0 2009 Python 脚本扩展
GDB 10.0 2020 支持 Rust、改进的调试信息

安装 GDB:三分钟搞定

安装 GDB 其实很简单。不同系统的方法我列一下:

# Ubuntu / Debian
sudo apt-get install gdb

# CentOS / RHEL
sudo yum install gdb

# macOS(用 Homebrew)
brew install gdb

# 从源码编译(如果你非要这么干)
wget https://ftp.gnu.org/gnu/gdb/gdb-14.1.tar.gz
tar -xzf gdb-14.1.tar.gz
cd gdb-14.1
./configure
make -j$(nproc)
sudo make install

我个人建议用包管理器安装,省事。从源码编译虽然能拿到最新版,但编译过程挺耗时的。除非你需要某些特定补丁,否则没必要。

小提示:安装完后,在终端输入 gdb --version,如果看到版本号,就说明装好了。我每次在新机器上干活,第一件事就是敲这个命令。

基本使用流程:从编译到调试

好,现在 GDB 装好了。怎么用?我带你走一遍最基础的流程。

第一步:编译带调试信息的程序

这是关键。编译时必须加 -g 选项:

gcc -g -o myprogram myprogram.c

不加 -g,GDB 就看不到源代码信息,只能看到汇编。我曾经犯过这个错,调试了半天全是地址,那叫一个痛苦。

第二步:启动 GDB

gdb ./myprogram

你会看到 GDB 的提示符 (gdb)。这时候程序还没跑,只是被加载进来了。

第三步:设置断点

(gdb) break main
(gdb) break 42          # 在第42行设断点
(gdb) break my_function # 在函数入口设断点

第四步:运行程序

(gdb) run

程序会一直跑到第一个断点处停下来。这时候你可以:

  • print x — 看变量 x 的值
  • next — 执行下一行(不进入函数)
  • step — 执行下一行(进入函数)
  • continue — 继续运行到下一个断点
  • list — 查看当前附近的源代码

第五步:退出

(gdb) quit

如果程序还在运行,GDB 会问你是否要终止。输入 y 就行。

注意:如果程序崩溃了(比如 segfault),GDB 会自动停在崩溃的位置。这时候千万别急着退出,先敲 bt 看调用栈,这是定位崩溃最直接的方法。

一个完整的例子

假设我们有这么个程序:

// bug.c
#include <stdio.h>

void crash_me() {
    int *p = NULL;
    *p = 42;  // 这里会崩
}

int main() {
    printf("Hello\n");
    crash_me();
    printf("World\n");
    return 0;
}

编译并调试:

$ gcc -g -o bug bug.c
$ gdb ./bug
(gdb) run
Starting program: ./bug
Hello

Program received signal SIGSEGV, Segmentation fault.
0x0000555555555159 in crash_me () at bug.c:5
5           *p = 42;
(gdb) bt
#0  0x0000555555555159 in crash_me () at bug.c:5
#1  0x0000555555555173 in main () at bug.c:10
(gdb) print p
$1 = (int *) 0x0
(gdb) quit

看到了吗?GDB 直接告诉我们:p 是空指针,在第 5 行解引用时崩了。调用栈显示是从 main 的 10 行调过来的。问题一目了然。

知识体系总览

下面这张图,是我自己整理的 GDB 入门知识结构。你跟着这个脉络学,不会乱:

GDB 入门 控制执行 run / continue next / step break / watch 观察状态 print / display backtrace / info x / examine 修改状态 set variable jump / return call 准备工作:gcc -g 编译 + gdb 启动 三大核心能力:停下来 → 看状态 → 改状态

这张图把 GDB 入门要掌握的东西都串起来了。你照着这个结构学,先搞定「控制执行」,再学「观察状态」,最后才是「修改状态」。别一上来就想着改内存,先把断点和单步走顺了再说。

我的建议:刚开始用 GDB,别急着记命令。先记住三个:break、run、print。这三个能解决 80% 的问题。剩下的命令,遇到具体场景再查,用多了自然就记住了。

好了,GDB 入门就讲到这里。记住,调试不是魔法,是技术。GDB 就是你手里最趁手的工具。多用、多练,慢慢你就会发现——那些曾经让你抓狂的 bug,其实都有迹可循。

可可精品资料库