GDB(GNU Debugger)是Linux平台下最强大的调试工具,掌握它可以让你从“代码能跑通”进化到“代码为什么能跑通/跑崩”。下

 一、GDB是什么?

GDB是GNU开源组织开发的调试器,支持C、C++、Go、Rust等多种语言。它的核心能力可以概括为四句话:

1.  启动程序:按你的要求运行目标程序。
2.  让程序暂停:在指定条件下停止执行。
3.  检查现场:程序暂停时,查看变量、内存、寄存器、调用栈等状态。
4.  修改环境:改变程序中的变量或数据,观察后续行为。

1.1 编译准备:-g选项是关键

想让GDB读懂你的程序,编译时必须加上 -g 选项,这会将符号表(变量名、行号等)保留在可执行文件中。

gcc -g myprogram.c -o myprogram   # 生成带调试信息的程序

对比一下,加 -g 编译出的文件通常比不加的大,因为包含了额外的调试信息。

 1.2 启动GDB的三种方式

GDB可以灵活地介入程序的各个生命周期:

| 启动方式 | 命令示例 | 适用场景 |
| :--- | :--- | :--- |
| **直接调试** | `gdb ./myprogram` | 从头开始调试程序。 |
| **附加进程** | `gdb -p <PID>` | 调试正在运行的进程(如卡死的服务)。 |
| **分析Core文件** | `gdb ./myprogram core` | 程序崩溃后,复盘事故现场(需先生成core文件)。 |

> 如果不想看GDB启动时的版权声明,可以加上 `-q` 或 `--silent` 参数。

---

 二、GDB入门:基础调试命令

假设我们有如下程序 test.c:

#include <stdio.h>
int main() {
    int sum = 0;
    for (int i = 1; i <= 5; i++) {
        sum += i;
    }
    printf("sum = %d\n", sum);
    return 0;
}

编译并启动调试:gcc -g test.c -o test 然后 gdb ./test

核心命令速查表

  • l (list)显示源代码。默认显示 10 行。一直按回车会继续往下显示。你可以输入 l 15 来查看第 15 行附近的代码,或者 l main 查看 main 函数。

  • b (break)打断点。程序运行到这里会立刻暂停。

    • b 20:在第 20 行打断点。

    • b func_name:在进入某个函数时打断点。

    • info b:查看目前打了哪些断点。

  • r (run)运行程序。程序会一直跑到你打的第一个断点处停下来。(如果没打断点,程序会直接跑完)。

  • n (next)单步执行(不进入函数)。也就是常说的 Step Over。执行当前行,如果当前行有函数调用,它会直接把函数执行完,停在下一行。

  • s (step)单步执行(进入函数)。也就是常说的 Step Into。如果当前行是一个函数,按 s 会一头扎进这个函数的内部去看个究竟。

  • c (continue)继续运行。让程序从当前暂停的位置继续狂奔,直到遇到下一个断点。

  • p (print)打印变量的值。(极其好用!)

    • p my_var:查看变量 my_var 当前里面装了什么。

    • p &my_var:查看这个变量的内存地址。

  • bt (backtrace)查看函数调用栈(排查段错误的神器!) 程序崩溃时,直接输入 bt,它会告诉你程序是在哪个函数的哪一行死掉的,以及它是怎么一层层被调用过来的。

  • q (quit)退出 GDB

操作流程演示:

(gdb) b main          # 在main函数入口设断点
(gdb) r               # 运行程序,停在main入口
(gdb) l               # 查看源代码
(gdb) b 5 if i == 3   # 在第5行设置条件断点(循环到i==3时才触发)
(gdb) c               # 继续运行,程序将停在i==3的那次循环
(gdb) p i             # 打印i的值,确认是3
(gdb) p sum           # 打印sum的值
(gdb) n               # 执行下一行(sum += i)
(gdb) quit            # 退出

三、断点高级玩法:让调试更精准

面对复杂的循环或多层调用,基础断点会频繁暂停,让人烦躁。高级断点技巧可以让你“指哪打哪”。

3.1 条件断点

只在满足特定条件时才暂停,非常适合调试循环中的特定迭代。

# 语法:break 位置 if 条件
(gdb) break test.c:10 if i == 100   # 循环到第100次时暂停
(gdb) break func if result < 0      # 当func函数中result变为负数时暂停

设置后,可以用 condition 命令修改断点的条件:

(gdb) condition 1 i == 500   # 将编号为1的断点条件改为 i==500

3.2 监视点(Watchpoint)

断点关注“位置”,而监视点关注“数据”。当被监视的变量值发生变化时,程序自动暂停。这是定位“变量被谁意外修改”的神器。

(gdb) watch sum       # 当sum的值发生变化时暂停
Hardware watchpoint 2: sum
(gdb) r
... 程序运行 ...
Hardware watchpoint 2: sum
Old value = 3
New value = 6         # GDB会精确告诉你变化前后的值

3.3 断点的管理

  • 临时禁用disable 1 (禁用编号为1的断点,方便后续恢复)

  • 重新启用enable 1

  • 批量删除delete (不加编号,删除所有断点)

四、变量跟踪与调用栈:洞悉程序内部

4.1 持续监视变量(display)

用 print 需要每次手动敲,而 display 可以让GDB在每次暂停时自动打印你关心的变量。

(gdb) display sum
1: sum = 0
(gdb) display i
2: i = 1
(gdb) n
... 每次执行后,都会自动显示 sum 和 i 的值 ...

取消监视:undisplay 1 (取消编号为1的显示项)

4.2 查看调用栈(backtrace)

当程序陷入深层调用或崩溃时,bt 命令可以清晰地展示函数调用链条,让你知道“我是怎么来到这里的”。

(gdb) bt
#0  add (a=3, b=5) at math.c:3
#1  0x400567 in calc () at main.c:8
#2  0x400600 in main () at main.c:15
  • frame 1:切换到 #1 对应的栈帧(calc函数),查看那层的局部变量。

4.3 查看局部变量

info locals 一次性打印当前函数的所有局部变量,非常高效。

(gdb) info locals
sum = 15
i = 5

五、多线程/多进程调试:并发问题克星

调试多线程程序时,GDB提供了专门的命令。

5.1 线程相关命令

  • 查看所有线程info threads

  • 切换线程thread <线程ID>

  • 让指定线程执行命令thread apply <线程ID> <命令>,例如 thread apply 2 bt 查看线程2的调用栈。

  • 查看所有线程的栈thread apply all bt (崩溃后第一时间执行此命令,非常有价值)

  • 锁住调度set scheduler-locking on,防止单步调试时切换到其他线程,避免混乱。

5.2 进程调试

对于 fork() 创建的子进程,可以设置GDB跟随哪个进程:

(gdb) set follow-fork-mode child   # 调试子进程
(gdb) set follow-fork-mode parent  # 调试父进程(默认)

六、事后分析:Core Dump调试

程序崩溃后生成的核心转储文件(core dump)是“案发现场的尸检报告”,能让你在崩溃发生后还原现场。

6.1 生成Core文件

首先确保系统允许生成core文件:

ulimit -c unlimited    # 设置core文件大小无限制

然后运行程序直到崩溃,会在当前目录生成 core 或 core.PID 文件。

6.2 分析Core文件

gdb ./myprogram core   # 加载程序和core文件
(gdb) bt               # 立即查看崩溃时的调用栈,通常就能定位到出错函数
(gdb) info locals      # 查看崩溃点的局部变量
(gdb) list             # 查看崩溃点附近的源代码
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐