GDB 调试指南

**GDB(GNU Debugger)**是 Linux 环境下常用的命令行调试工具,主要用于调试 C/C++ 程序。使用 GDB 调试程序,可以抽象成一个闭环:

  1. 准备可调试程序

  2. 让程序在关键位置停下

  3. 查看程序现场

  4. 推断问题原因

  5. 继续执行或调整断点验证判断

  6. 修改代码并重新验证


一、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 行时暂停。

程序暂停后,可以用单步命令观察执行过程。常用的是nextstep,区别是:

命令 含义
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;
}

这个程序有两个问题:

  1. print_array 中数组越界;
  2. 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 查看所有线程调用栈
相关推荐
流年如夢1 小时前
算法效率:复杂度原理解析
c语言·数据结构·算法
cpp_25013 小时前
P1024 [NOIP 2001 提高组] 一元三次方程求解
数据结构·c++·算法·题解·二分答案·洛谷·csp
田梓燊10 小时前
力扣:23.合并 K 个升序链表
算法·leetcode·链表
re林檎10 小时前
算法札记——4.27
算法
数据牧羊人的成长笔记11 小时前
逻辑回归与Softmax回归
算法·回归·逻辑回归
郑州光合科技余经理11 小时前
同城O2O海外版二次开发实战:从支付网关到配送算法
开发语言·前端·后端·算法·架构·uni-app·php
张健115640964813 小时前
使用信号量限制并发数量
开发语言·c++
jc062013 小时前
6.1云原生之Docker
c++·docker·云原生
上弦月-编程13 小时前
递归实现C语言菱形图案打印
c语言