
**GDB(GNU Debugger)**是 Linux 环境下常用的命令行调试工具,主要用于调试 C/C++ 程序。使用 GDB 调试程序,可以抽象成一个闭环:
准备可调试程序
让程序在关键位置停下
查看程序现场
推断问题原因
继续执行或调整断点验证判断
修改代码并重新验证
一、GDB 基本概念
1.1 生成可调试程序
GDB 依赖编译器生成的调试信息。调试 C 程序时,一般这样编译:
cpp
gcc -g main.c -o main
调试 C++ 程序时,一般这样编译:
cpp
g++ -g main.cpp -o main
其中 -g 表示在可执行文件中加入调试信息。调试信息包括:源代码文件名、行号信息、函数名、变量名、类型信息等
如果不加 -g,GDB 仍然可以运行程序,但调试时可能只能看到地址信息,很难对应到源代码。
调试阶段可使用:
cpp
gcc -g -O0 -Wall main.c -o main
含义如下:
| 选项 | 作用 |
|---|---|
| -g | 生成调试信息 |
| -O0 | 关闭优化,使执行过程更接近源代码 |
| -Wall | 打开常见警告,提前发现潜在问题 |
1.2 断点与单步执行
断点的作用是让程序运行到指定位置时暂停。例如:
cpp
break main.c:20
表示程序运行到 main.c 第 20 行时暂停。
程序暂停后,可以用单步命令观察执行过程。常用的是next 和step,区别是:
| 命令 | 含义 |
|---|---|
| next | 执行下一行,但不进入函数内部 |
| step | 执行下一行,如果遇到函数调用则进入函数内部 |
1.3 查看变量、调用栈
程序暂停后,可以查看变量值。
cpp
print x 简写: p x
cpp
p x //可以查看普通变量
p arr[0] //查看数组元素
p stu.age //查看结构体成员
p *p //查看指针指向的内容
调用栈记录了函数调用关系。backtrace 查看调用栈,简写为 bt
例如输出:
cpp
#0 crash () at main.c:12
#1 main () at main.c:19
表示main 函数调用了 crash 函数;程序在 crash 函数中出错。
调用栈中的每一层叫一个栈帧。切换栈帧:
cpp
frame 0
frame 1
进入不同栈帧后,可以查看对应函数里的参数和局部变量。
二、GDB 标准调试流程
假设有程序 main.c,调试步骤如下
2.1 编译程序
cpp
gcc -g -O0 -Wall main.c -o main
2.2 启动 GDB 并设置断点
cpp
gdb ./main //启动 GDB
break main //在 main 函数设置断点
break function_name //在可疑函数设置断点
2.3 运行程序查看现场
cpp
run //程序运行到断点处暂停
list //查看当前代码位置
info locals //查看局部变量
info args //查看函数参数
print x //查看某个变量
2.4 程序继续执行
cpp
next //执行下一行,不进入函数
step //执行下一行,进入函数
continue //继续运行到下一个断点
finish //执行完当前函数并返回
如果发现变量值正常,就继续往后执行。
如果发现变量值异常,就往前找变量第一次变错的位置。
如果不知道变量在哪里被修改,可以使用:
cpp
watch x
让程序在变量 x 被修改时自动暂停
2.5 用一个错误程序为例
cpp
#include <stdio.h>
void print_array(int *arr, int n) {
for (int i = 0; i <= n; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
}
void crash() {
int *p = NULL;
*p = 100;
}
int main() {
int nums[3] = {10, 20, 30};
print_array(nums, 3);
crash();
return 0;
}
这个程序有两个问题:
- print_array 中数组越界;
- crash 中空指针解引用。
编译并启动 GDB
cpp
gcc -g -O0 -Wall main.c -o main
gdb ./main
run

程序崩溃,看到:
cpp
crash () at main.c:11
11 *p = 100;
说明程序在 crash 函数中崩溃
运行 bt 查看调用栈

输出:
cpp
#0 crash () at main.c:11
#1 main () at main.c:18
说明main 调用了 crash;crash 中发生了段错误。
查看崩溃现场变量
frame 0 切换到崩溃现场
cpp
info locals
p p
查看局部变量 - 查看指针 p

输出:
cpp
$2 = (int *) 0x0
0x0 表示空指针。执行:*p = 100; 就是向空地址写入数据,所以发生段错误。
数组越界问题
先设置断点:
cpp
break print_array
run
程序停在 print_array 函数入口
cpp
info args
info args 查看函数参数,输出:
cpp
(gdb) info args
arr = 0x61fe44
n = 3
数组长度是 3,合法下标应该是0, 1, 2
查看循环变量并单步执行
cpp
display i
next
next
next
next
当发现 i 变成 3 时,程序仍然访问arr[i],也就是访问:arr[3],数组越界
原代码应该改成:
cpp
for (int i = 0; i < n; i++)
三、不同问题类型的排查
可以根据不同问题类型采用不同套路
3.1 程序崩溃
出现现象:
cpp
Segmentation fault
可能的原因是:空指针解引用、数组越界、野指针访问、重复释放内存、使用已经释放的内存等
此时重点看调用栈和现场变量,流程为
run
→ 程序崩溃
→ bt 查看调用栈
→ frame 0 查看崩溃现场
→ info locals 查看局部变量
→ info args 查看函数参数
→ print 查看可疑变量
需要分析:哪一行崩溃、哪个指针是 NULL、哪个数组下标越界、函数参数是否异常、调用者传入的数据是否正确......
3.2 程序结果不对
程序没有崩溃,但结果不符合预期,这类问题一般是逻辑错误。
此时要找到变量第一次变错的位置:
找到影响结果的关键函数
→ 设置断点
→ 单步执行
→ 观察关键变量
→ 找到变量第一次异常的位置
3.3 变量被意外修改
如果发现变量已经错了,但不知道是谁改的,可以用:
cpp
watch x
当 x 被修改时,程序会自动暂停
cpp
watch arr[2] //观察数组元素
watch *p //观察指针指向的数据
设置 watch
→ continue
→ 程序在变量被修改时暂停
→ bt 查看是谁改的
如果循环执行很多次,可使用条件断点,不要手动一步一步执行。
例如,只想在 i == 1000 时暂停
cpp
break main.c:30 if i == 1000
只想在变量异常时暂停
cpp
break main.c:30 if value < 0
3.4 程序运行异常
如果程序一直运行不结束,可以先按 Ctrl+C 让程序暂停,然后 bt 查看当前调用栈
如果是多线程程序,查看所有线程:
cpp
info threads
thread apply all bt
如果程序在服务器上已经崩溃,可以通过 core 文件分析崩溃现场
先开启 core dump:
cpp
ulimit -c unlimited
./main
如果程序崩溃,可能生成 core 文件,使用 GDB 分析
cpp
gdb ./main core
如果程序已经在运行,不能重新启动,可以用 GDB 附加到进程上。
先查看进程号:
cpp
ps aux | grep main
假设进程号是 12345:
cpp
gdb -p 12345
调试结束后,不想终止程序,可以执行:
cpp
detach
quit
detach 表示让 GDB 和进程分离,程序继续运行。
四、GDB 命令分类
程序启动与退出
| 命令 | 作用 |
|---|---|
| gdb ./main | 启动 GDB 调试可执行文件 |
| gdb -p PID | 调试正在运行的进程 |
| run / r | 运行程序 |
| run arg1 arg2 | 带参数运行程序 |
| set args arg1 arg2 | 设置运行参数 |
| show args | 查看运行参数 |
| quit / q | 退出 GDB |
| detach | 与进程分离 |
断点控制
| 命令 | 作用 |
|---|---|
| break main / b main | 在函数入口设置断点 |
| b main.c:20 | 在指定行设置断点 |
| b main.c:20 if x == 5 | 设置条件断点 |
| info breakpoints | 查看所有断点 |
| delete 1 | 删除编号为 1 的断点 |
| delete | 删除所有断点 |
| disable 1 | 暂时禁用断点 1 |
| enable 1 | 启用断点 1 |
执行控制
| 命令 | 作用 |
|---|---|
| next / n | 下一行,不进入函数 |
| step / s | 下一行,遇到函数会进入 |
| continue / c | 继续运行 |
| finish | 执行完当前函数并返回 |
| Ctrl+C | 暂停正在运行的程序 |
查看变量和表达式
| 命令 | 作用 |
|---|---|
| print x / p x | 查看变量 |
| p arr[0] | 查看数组元素 |
| p arr[0]@5 | 从 arr[0] 开始连续查看 5 个元素 |
| p *p | 查看指针指向的值 |
| p &x | 查看变量地址 |
| info locals | 查看局部变量 |
| info args | 查看函数参数 |
| display x | 每次暂停自动显示变量 |
| undisplay 1 | 取消自动显示 |
| set var x = 10 | 临时修改变量值 |
调用栈分析
| 命令 | 作用 |
|---|---|
| backtrace / bt | 查看调用栈 |
| frame 0 | 切换到第 0 层栈帧 |
| frame 1 | 切换到第 1 层栈帧 |
| up | 切换到上一层调用者 |
| down | 切换到下一层被调用者 |
变量追踪
| 命令 | 作用 |
|---|---|
| watch x | 当 x 被修改时暂停 |
| watch arr[2] | 当 arr[2] 被修改时暂停 |
| watch *p | 当指针指向内容被修改时暂停 |
多线程调试
| 命令 | 作用 |
|---|---|
| info threads | 查看所有线程 |
| thread 2 | 切换到线程 2 |
| thread apply all bt | 查看所有线程调用栈 |