1. GDB入门

1.1 GDB是什么?为什么我们需要它?

说实话,我刚入行那会儿,调试代码全靠printf大法。到处加打印,编译,运行,看输出,改代码,再编译……循环往复。直到有一次,一个野指针问题让我加了整整两天的打印,愣是没定位到。后来一位老同事走过来,敲了几个命令,十分钟就找到了问题根源。那个工具,就是GDB。

GDB,全称GNU Project Debugger,是Linux/Unix环境下最强大的调试工具,没有之一。它让你能「看」进程序的内部——看变量值、看内存、看寄存器、看调用栈。说白了,它给了你一双透视眼。

你想想看,一个程序跑起来就是个黑盒子。你只知道输入和输出,中间发生了什么?哪个变量被改错了?哪条分支走歪了?GDB能让你在任意时刻「暂停」程序,然后像翻书一样,一页一页地检查内部状态。

核心价值一句话:GDB让你从「猜问题」变成「看问题」。

1.2 GDB的安装与启动

安装GDB其实很简单。我习惯用包管理器一把梭,省心。

1.2.1 安装GDB

不同的系统,命令略有不同:

系统 安装命令
Ubuntu/Debian sudo apt-get install gdb
CentOS/RHEL sudo yum install gdb
macOS (Homebrew) brew install gdb

装完之后,在终端敲 gdb --version,能看到版本号就说明搞定了。嗯,这里要注意,macOS上可能需要额外签名,否则调试时会报权限错误。我曾经在这上面卡了半小时,后来发现是系统安全策略的问题。

1.2.2 启动GDB

启动GDB有两种常见姿势:

  1. 直接调试可执行文件:gdb ./my_program
  2. 先启动,再加载文件:进入GDB后,敲 file ./my_program

我个人更推荐第一种,一步到位。但如果你已经进了GDB,用第二种也完全OK。

还有一个关键点:编译时一定要加 -g 选项。不加的话,GDB就看不到源码和变量名,只能看到一堆地址。我见过太多新手跑来问「为什么GDB看不到变量」,结果一看,编译命令里没有 -g。

警告:编译时务必加上 -g 选项!例如:gcc -g -o my_program my_program.c。否则调试信息缺失,GDB功能大打折扣。

1.3 GDB基本命令概览

GDB的命令很多,但常用的其实就那么十几个。别被吓到,咱们先掌握最核心的。

1.3.1 启动与退出

  • gdb ./program —— 启动并加载程序
  • quit 或 q —— 退出GDB
  • help —— 查看帮助。记不住命令?敲 help 就对了

1.3.2 运行与控制

  • run 或 r —— 运行程序
  • start —— 启动程序并在main函数入口暂停
  • continue 或 c —— 继续运行,直到下一个断点
  • next 或 n —— 单步执行,不进入函数内部
  • step 或 s —— 单步执行,会进入函数内部
  • finish —— 运行到当前函数返回

这里有个小技巧:next 和 step 的区别,说白了就是「要不要钻进函数里看看」。如果你确定某个函数没问题,就用 next 跳过;如果你怀疑函数内部有鬼,就用 step 钻进去。

1.3.3 断点管理

  • break main 或 b main —— 在main函数入口设断点
  • break 42 —— 在第42行设断点
  • break file.c:100 —— 在file.c的第100行设断点
  • info breakpoints —— 查看所有断点
  • delete 1 —— 删除编号为1的断点
  • disable 1 —— 禁用编号为1的断点(不删除)

断点这东西,用好了就是神器。我曾经调试一个多线程死锁问题,就是在关键锁操作前后各设一个断点,然后观察线程执行顺序,才找到问题的。

1.3.4 变量查看与修改

  • print var 或 p var —— 打印变量var的值
  • print &var —— 打印变量var的地址
  • display var —— 每次暂停时自动显示var的值
  • set var = 42 —— 将变量var的值修改为42
  • whatis var —— 查看变量var的类型

举个例子,假设你怀疑某个循环变量越界了:

(gdb) break 25
(gdb) run
(gdb) print i
$1 = 0
(gdb) continue
(gdb) print i
$2 = 1
(gdb) set i = 100
(gdb) continue

看到了吗?你可以在运行时直接修改变量值,强制让程序走某条分支。这个能力在测试边界条件时特别有用。

1.3.5 查看调用栈

  • backtrace 或 bt —— 查看函数调用栈
  • frame N —— 切换到第N层栈帧
  • info locals —— 查看当前栈帧的局部变量

程序崩溃时,第一件事就是敲 bt。它会告诉你「我是谁?我在哪?谁叫我来的?」。我调试段错误时,90%的情况靠 bt 就能定位到问题函数。

1.4 知识体系一览

为了让你对GDB有个整体认识,我画了张图。这张图把GDB的核心功能分成了四大块:启动与配置、运行控制、断点与观察、数据查看与修改。每一块都是调试中不可或缺的。

GDB 核心功能体系 GDB 调试器 启动与配置 gdb ./program file / run -g 编译选项 运行控制 next / step continue / finish start 断点与观察 break / info break delete / disable watch 数据查看与修改 print / display set var backtrace 调试的本质: 暂停程序 → 观察状态 → 分析问题 → 修改验证

1.5 一个完整的调试小例子

光说不练假把式。咱们拿一个最简单的程序走一遍流程。

假设有这么一个bug程序 bug.c:

#include <stdio.h>

int main() {
    int sum = 0;
    for (int i = 0; i < 10; i++) {
        sum += i;
    }
    printf("Sum = %d\n", sum);
    return 0;
}

编译并启动调试:

$ gcc -g -o bug bug.c
$ gdb ./bug

进入GDB后,我们来走一遍:

(gdb) break main          # 在main函数设断点
(gdb) run                  # 运行,停在main入口
(gdb) next                 # 执行 int sum = 0;
(gdb) print sum            # 查看sum的值 → 0
(gdb) next                 # 进入循环
(gdb) print i              # 查看i的值 → 0
(gdb) display sum          # 让GDB每次暂停都自动显示sum
(gdb) next                 # 执行 sum += i;
(gdb) next                 # i++,然后判断循环条件
(gdb) print i              # 查看i → 1
(gdb) continue             # 直接跑到结束

看到了吗?每一步你都能看到变量怎么变的。这就是GDB的威力。

小技巧:如果觉得每次敲 next 太累,可以试试直接敲回车——GDB会重复执行上一条命令。这个习惯我用了十年,省了不少键盘寿命。

1.6 避坑指南

最后,分享几个我踩过的坑:

  • 忘了加 -g:这是最最常见的错误。没有调试信息,GDB就像瞎子。
  • 优化级别太高:用 -O2 或 -O3 编译后,变量可能被优化掉,你根本看不到。调试时建议用 -O0。
  • strip过的程序:生产环境里的程序经常被strip过,符号表没了。这时候GDB只能看到地址,看不到函数名和变量名。
  • 权限问题:调试某些系统级程序时,可能需要root权限。记得加 sudo。

嗯,这一章的内容就到这里。GDB入门其实不难,关键是动手练。找个简单的程序,把上面这些命令挨个敲一遍,比看十遍文档都管用。