跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

Linux 调试器 GDB/Cgdb 使用指南

Linux 下 C/C++ 开发常用调试工具 GDB 和 CGDB 的使用详解。涵盖编译添加 -g 选项生成调试信息,启动与退出命令。核心功能包括查看源代码、设置断点(行号/函数/条件)、程序执行控制(运行/单步/继续)、变量操作(查看/修改/监视)、堆栈查看及多线程调试。CGDB 作为可视化增强版提供分屏显示代码与命令行。常见问题涉及无法启动、断点失败、变量不可见及多文件路径处理。掌握这些技能有助于排查逻辑错误、崩溃定位及理解程序运行机制。

abccba发布于 2026/3/25更新于 2026/8/1753 浏览
Linux 调试器 GDB/Cgdb 使用指南

前言

作为 Linux 下 C/C++ 开发的核心工具,gdb 调试器是排查代码 bug、理解程序运行流程的必备技能。很多新手面对黑屏命令行调试望而却步,甚至资深开发者也可能只掌握基础用法。而 cgdb 作为 gdb 的增强版,更是解决了纯命令行调试看不到代码的痛点。本文将结合实战案例,从基础配置到高级技巧,全面拆解 gdb/cgdb 的使用方法。

一、gdb 调试基础:从环境准备到核心概念

1.1 为什么需要 gdb 调试?

在开发过程中,我们难免会遇到代码逻辑错误、变量取值异常、程序崩溃等问题。printf 打印调试虽然简单,但存在诸多局限:需要手动添加打印语句、重新编译,无法实时观察变量变化,也难以定位崩溃点。而 gdb 调试器可以直接加载可执行程序,支持断点设置、单步执行、变量监视、堆栈查看等功能,让你像 "上帝视角" 一样看透程序运行的每一个细节。

1.2 gdb 调试的前提:编译调试版本

Linux 下 gcc/g++ 默认生成的是 release 版本程序,不包含调试信息,无法使用 gdb 调试。因此,必须在编译时添加 -g 选项,生成包含调试信息的 debug 版本。

示例代码(mycmd.c):

#include <stdio.h>
int Sum(int s, int e) {
    int result = 0;
    for(int i = s; i <= e; i++) {
        result += i;
    }
    return result;
}
int main() {
    int start = 1;
    int end = 100;
    printf("I will begin\n");
    int n = Sum(start, end);
    printf("running done, result is: [%d-%d]=%d\n", start, end, n);
    return 0;
}

编译命令对比:

# 默认 release 版本,不支持 gdb 调试
gcc mycmd.c -o mycmd
file mycmd # 查看程序信息,输出"not stripped"但无 debug_info

# debug 版本,添加-g 选项,支持 gdb 调试
gcc mycmd.c -o mycmd -g
file mycmd # 输出"with debug_info, not stripped",表明包含调试信息

1.3 gdb 的启动与退出

启动 gdb

在终端中输入以下命令启动 gdb 并加载可执行程序:

gdb 可执行程序名 # 示例:gdb mycmd

启动成功后,终端会显示 gdb 版本信息,并进入 gdb 命令行模式,提示符为 (gdb)。

退出 gdb

有两种常用退出方式:

输入 quit 或 q 命令:(gdb) quit; 使用快捷键 Ctrl + D。

二、gdb 核心命令:从基础到实战

2.1 查看源代码(list/l)

调试时需要对照源代码查看执行位置,list 命令(简写 l)可以显示程序源代码。

常用用法:

显示当前位置附近的代码(默认每次 10 行):(gdb) l 或 (gdb) list 显示指定行号附近的代码:(gdb) l 行号(示例:l 10 显示第 10 行附近代码) 显示指定函数的代码:(gdb) l 函数名(示例:l main 显示 main 函数代码) 显示指定文件的指定行代码:(gdb) l 文件名:行号(示例:l mycmd.c:1 显示 mycmd.c 第 1 行)

实战示例:

(gdb) l main # 查看 main 函数代码
14 15 int main()
16 {
17     int start = 1;
18     int end = 100;
19     printf("I will begin\n");
20     int n = Sum(start, end);
21     printf("running done, result is: [%d-%d]=%d\n", start, end, n);
22     return 0;
23 }
24

2.2 断点操作:调试的核心(break/b)

断点是调试的核心功能,用于指定程序暂停执行的位置,方便观察变量状态和程序流程。

2.2.1 设置断点

常用断点设置方式:

按行号设置断点:(gdb) b 行号(示例:b 20 在第 20 行设置断点) 按函数名设置断点:(gdb) b 函数名(示例:b Sum 在 Sum 函数开头设置断点) 按文件 + 行号设置断点(多文件项目):(gdb) b 文件名:行号(示例:b mycmd.c:10)

2.2.2 查看断点信息

使用 info break 或 info b 命令查看所有断点的详细信息:

(gdb) b 20 # 设置断点
Breakpoint 1 at 0x11c3: file mycmd.c, line 20.
(gdb) info b # 查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555551c3 in main at mycmd.c:20

输出说明:

Num:断点编号(后续操作断点的标识) Type:断点类型(默认 breakpoint) Disp:断点触发后的行为(keep 表示保留) Enb:是否启用(y 表示启用,n 表示禁用) Address:断点对应的内存地址 What:断点位置信息

2.2.3 删除断点

常用删除方式:

删除所有断点:(gdb) delete breakpoints 或 (gdb) d 删除指定编号的断点:(gdb) delete breakpoints 断点编号(示例:d 1 删除 1 号断点)

2.2.4 禁用 / 启用断点

无需删除断点时,可临时禁用或启用:

禁用所有断点:(gdb) disable breakpoints 启用所有断点:(gdb) enable breakpoints 禁用指定断点:(gdb) disable breakpoints 断点编号 启用指定断点:(gdb) enable breakpoints 断点编号

2.3 程序执行控制:运行、单步与继续

设置断点后,需要通过执行命令控制程序运行,逐步排查问题。

2.3.1 运行程序(run/r)

在 gdb 中输入 run 或 r 命令启动程序,程序会执行到第一个断点处暂停:

(gdb) r
Starting program: /home/whb/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:20
20     int n = Sum(start, end);
2.3.2 单步执行:逐过程与逐语句

单步执行是调试的核心操作,分为两种模式:

逐过程(不进入函数):next 或 n(类似 VSCode 的 F10) 执行当前行代码,如果是函数调用,不进入函数内部,直接执行完函数并返回结果 逐语句(进入函数):step 或 s(类似 VSCode 的 F11) 执行当前行代码,如果是函数调用,进入函数内部,停在函数第一行

实战示例:

(gdb) r # 启动程序到断点
Breakpoint 1, main () at mycmd.c:20
20     int n = Sum(start, end);
(gdb) s # 逐语句执行,进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:5
5   {
(gdb) n # 逐过程执行,执行下一行
6   int result = 0;
(gdb) n # 继续逐过程执行
7   for(int i = s; i <= e; i++)
2.3.3 继续执行(continue/c)

程序暂停在断点或单步执行后,使用 continue 或 c 命令让程序继续执行到下一个断点或程序结束:

(gdb) c
Continuing.
running done, result is: [1-100]=5050
[Inferior 1 (process 12345) exited normally]
2.3.4 执行到函数返回(finish)

在函数内部调试时,使用 finish 命令执行到当前函数返回,并显示返回值:

(gdb) s # 进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:5
5   {
(gdb) finish # 执行到 Sum 函数返回
Run till exit from #0 Sum (s=1, e=100) at mycmd.c:5
0x00005555555551d2 in main () at mycmd.c:20
20     int n = Sum(start, end);
Value returned is $1 = 5050 # 显示 Sum 函数返回值
2.3.5 执行到指定行(until)

使用 until 行号 命令让程序执行到指定行,无需设置断点,适合快速跳转到目标位置:

(gdb) until 16 # 执行到第 16 行
Sum (s=1, e=100) at mycmd.c:16
16     return result;

2.4 变量操作:查看、修改与监视

调试的核心目的是观察变量取值是否符合预期,gdb 提供了丰富的变量操作命令。

2.4.1 查看变量值(print/p)

使用 print 或 p 命令查看变量、表达式的值,支持直接计算表达式。

常用用法:

查看变量值:(gdb) p 变量名(示例:p result 查看 result 变量值) 查看表达式值:(gdb) p 表达式(示例:p start+end 计算并显示 start+end 的值) 连续查看变量(自动递增序号):多次使用 p 命令,变量值会被编号($1, $2...),可通过编号快速查看历史值。

实战示例:

(gdb) s # 进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:5
5   {
(gdb) n
6   int result = 0;
(gdb) p result # 查看 result 变量值
$1 = 0
(gdb) n
7   for(int i = s; i <= e; i++)
(gdb) p i # 查看 i 变量值
$2 = 1
(gdb) p s+e # 查看表达式值
$3 = 101
2.4.2 修改变量值(set var)

调试时如果发现变量取值异常,可以使用 set var 变量名=值 命令直接修改变量值,验证是否是该变量导致的问题。

实战示例:假设我们修改 Sum 函数,添加一个 flag 变量控制返回值,故意设置 flag=0 导致结果异常:

#include <stdio.h>
int flag = 0; // 故意错误,导致返回值为 0
int Sum(int s, int e) {
    int result = 0;
    for(int i = s; i <= e; i++) {
        result += i;
    }
    return result*flag; // 乘以 flag,结果为 0
}
int main() {
    int start = 1;
    int end = 100;
    printf("I will begin\n");
    int n = Sum(start, end);
    printf("running done, result is: [%d-%d]=%d\n", start, end, n);
    return 0;
}

调试时修改 flag 值:

(gdb) r # 启动程序
Starting program: /home/whb/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:24
24     int n = Sum(start, end);
(gdb) s # 进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:9
9   {
(gdb) n
10      int result = 0;
(gdb) n
11      for(int i = s; i <= e; i++)
(gdb) until 16 # 执行到 return 语句前
Sum (s=1, e=100) at mycmd.c:16
16      return result*flag;
(gdb) p result # 查看 result 值,正确应为 5050
$1 = 5050
(gdb) p flag # 查看 flag 值,发现为 0
$2 = 0
(gdb) set var flag=1 # 修改 flag 值为 1
(gdb) p flag # 验证修改结果
$3 = 1
(gdb) n # 执行 return 语句
17      }
(gdb) n
main () at mycmd.c:25
25      printf("running done, result is: [%d-%d]=%d\n", start, end, n);
(gdb) n
running done, result is: [1-100]=5050 # 结果正常,验证问题根源
2.4.3 跟踪显示变量(display/undisplay)

使用 print 命令需要手动重复输入,而 display 命令可以 设置变量跟踪,每次程序暂停时自动显示变量值,适合需要持续观察的变量。

常用用法:

跟踪变量:(gdb) display 变量名(示例:display i 跟踪循环变量 i) 查看跟踪列表:(gdb) info display 取消跟踪:(gdb) undisplay 跟踪编号(示例:undisplay 1 取消编号为 1 的跟踪)。

实战示例:

(gdb) s # 进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:5
5   {
(gdb) n
6   int result = 0;
(gdb) n
7   for(int i = s; i <= e; i++)
(gdb) display i # 跟踪 i 变量
1: i = 1
(gdb) n
9   result += i;
1: i = 1
(gdb) n
7   for(int i = s; i <= e; i++)
1: i = 1
(gdb) n
9   result += i;
1: i = 2 # 自动显示 i 的最新值

2.5 堆栈查看:定位崩溃位置(backtrace/bt)

当程序崩溃时(如段错误),最关键的是定位崩溃发生的函数调用链。backtrace 或 bt 命令可以显示当前的函数调用栈,包括每个函数的调用位置和参数。

实战场景:假设程序崩溃,使用 bt 命令定位:

(gdb) r # 启动程序,程序崩溃
Starting program: /home/whb/test/crash_program
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555189 in func2 (p=0x0) at crash.c:8
8       *p = 10; # 空指针解引用导致崩溃
(gdb) bt # 查看函数调用栈
#0 0x0000555555555189 in func2 (p=0x0) at crash.c:8
#1 0x000055555555519e in func1 () at crash.c:13
#2 0x00005555555551b6 in main () at crash.c:19

输出说明:

#0:当前崩溃的函数(func2),位置在 crash.c 第 8 行,参数 p=0x0(空指针); #1:调用 func2 的函数(func1),位置在 crash.c 第 13 行; #2:调用 func1 的函数(main),位置在 crash.c 第 19 行。

通过调用栈可以快速定位崩溃的根源:main 调用 func1,func1 调用 func2 并传入空指针,func2 解引用空指针导致崩溃。

2.6 查看局部变量(info locals)

使用 info locals 或 i locals 命令查看当前函数的所有局部变量及其值,无需逐个输入 print 命令,高效便捷:

(gdb) s # 进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:5
5   {
(gdb) n
6   int result = 0;
(gdb) info locals # 查看当前函数局部变量
result = 0
i = 1

三、gdb 高级技巧:条件断点、变量监视与更多

掌握基础命令后,高级技巧能让调试效率翻倍,尤其适合复杂场景下的问题排查。

3.1 条件断点:精准定位特定场景

普通断点会在每次执行到该位置时暂停,而条件断点只在满足指定条件时才暂停,适合循环、分支等需要特定触发条件的场景。

3.1.1 两种设置方式

新增断点 时直接添加条件:(gdb) b 行号/函数名 if 条件 示例:b 9 if i == 30(在第 9 行设置断点,仅当 i=30 时暂停)。 给 已有断点 添加条件:(gdb) condition 断点编号 条件 示例:condition 2 i == 30(给 2 号断点添加条件 i=30)。

3.1.2 实战示例:循环中定位特定值

假设 Sum 函数循环计算 1 到 100 的和,我们需要在 i=30 时观察 result 的值:

(gdb) r # 启动程序
Breakpoint 1, main () at mycmd.c:20
20     int n = Sum(start, end);
(gdb) s # 进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:5
5   {
(gdb) n
6   int result = 0;
(gdb) n
7   for(int i = s; i <= e; i++)
(gdb) b 9 if i == 30 # 新增条件断点,i=30 时暂停
Breakpoint 2 at 0x555555555186: file mycmd.c, line 9.
(gdb) c # 继续执行
Continuing.
Breakpoint 2, Sum (s=1, e=100) at mycmd.c:9
9   result += i;
(gdb) p i # 验证 i=30
$1 = 30
(gdb) p result # 查看此时 result 的值(1+2+...+29=435)
$2 = 435

3.2 变量监视(watch):捕捉变量变化

watch 命令用于监视变量或表达式的值,当值发生变化时,程序会自动暂停并提示旧值和新值,适合排查变量被意外修改的问题。

3.2.1 三种监视模式

watch 变量名:监视变量值的 读写变化(读写触发); rwatch 变量名:仅监视变量 被读时 触发; awatch 变量名:仅监视变量 被修改时 触发。

3.2.2 实战示例:监视 result 变量变化
(gdb) r # 启动程序
Breakpoint 1, main () at mycmd.c:20
20     int n = Sum(start, end);
(gdb) s # 进入 Sum 函数
Sum (s=1, e=100) at mycmd.c:5
5   {
(gdb) n
6   int result = 0;
(gdb) watch result # 监视 result 变量
Hardware watchpoint 2: result
(gdb) c # 继续执行
Continuing.
Hardware watchpoint 2: result
Old value = 0
New value = 1 # 第一次变化:result=0→1
Sum (s=1, e=100) at mycmd.c:7
7   for(int i = s; i <= e; i++)
(gdb) c
Continuing.
Hardware watchpoint 2: result
Old value = 1
New value = 3 # 第二次变化:result=1→3
Sum (s=1, e=100) at mycmd.c:7
7   for(int i = s; i <= e; i++)

通过 watch 命令,我们可以清晰看到 result 变量每次的变化情况,快速定位异常修改。

3.3 多线程调试:thread 命令

在多线程程序中,gdb 支持线程切换、设置线程专属断点等功能,核心命令如下:

查看所有线程:**info threads**; 切换到指定线程:**thread 线程编号**; 设置线程专属断点:**b 行号 thread 线程编号**; 仅当前线程执行:set scheduler-locking on(避免其他线程干扰)。

四、cgdb:gdb 的可视化增强版

gdb 虽然强大,但纯命令行模式看不到代码,需要频繁使用 list 命令查看,效率较低。cgdb 作为 gdb 的前端工具,支持分屏显示代码和 gdb 命令行,操作方式与 gdb 完全兼容,上手成本极低。

4.1 cgdb 安装

CentOS 系统:
sudo yum install -y cgdb
Ubuntu 系统:
sudo apt-get install -y cgdb

4.2 cgdb 核心操作

cgdb 的命令与 gdb 完全一致,额外增加了分屏控制快捷键:

启动 cgdb:**cgdb 可执行程序名**(示例:cgdb mycmd); 切换代码屏 / 命令行屏:按 Esc 键进入代码屏,按 i 键回到命令行屏; 代码屏滚动:使用 方向键或 j/k 键 上下滚动; 退出 cgdb:与 gdb 一致,quit 或 Ctrl + D。

4.3 cgdb 优势展示

启动 cgdb 后,界面分为上下两部分:

上半部分:实时显示源代码,当前执行行高亮显示; 下半部分:gdb 命令行,支持所有 gdb 命令。

实战体验:

cgdb mycmd # 启动 cgdb
(cgdb) r # 运行程序
Starting program: /home/whb/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:20
20     int n = Sum(start, end);

此时上半部分代码屏会高亮显示第 20 行,无需输入 list 命令即可看到当前执行位置,调试过程中代码会自动跟随执行行滚动,体验远超纯 gdb。

五、常见问题与避坑指南

5.1 无法启动 gdb 调试

问题:No debugging symbols found in 可执行程序; 原因:编译时未添加 -g 选项,程序无调试信息; 解决:重新编译,添加 -g 选项(gcc -g 源文件 -o 可执行程序)。

5.2 断点设置失败

问题:Breakpoint 1 at 0xXXXX: file 文件名.c, line 行号. (2 locations); 原因:可能是代码被优化(如使用 -O2 优化选项),导致行号对应不上; 解决:编译时关闭优化(去掉 -O 相关选项),仅保留 -g 选项。

5.3 变量无法查看

问题:No symbol "变量名" in current context. 原因:变量超出作用域,或编译优化导致变量被消除。 解决:确保在变量作用域内查看,关闭编译优化。

5.4 多文件调试路径问题

问题:多文件项目中,设置其他文件断点时提示 No such file or directory。 解决:使用 file 文件名:行号 格式设置断点,或编译时指定源文件路径。

总结

调试是编程的核心技能之一,熟练使用 gdb/cgdb 不仅能解决问题,更能帮助你深入理解程序运行机制。建议结合实际项目反复练习,将这些命令内化为肌肉记忆。

目录

  1. 前言
  2. 一、gdb 调试基础:从环境准备到核心概念
  3. 1.1 为什么需要 gdb 调试?
  4. 1.2 gdb 调试的前提:编译调试版本
  5. 默认 release 版本,不支持 gdb 调试
  6. debug 版本,添加-g 选项,支持 gdb 调试
  7. 1.3 gdb 的启动与退出
  8. 启动 gdb
  9. 退出 gdb
  10. 二、gdb 核心命令:从基础到实战
  11. 2.1 查看源代码(list/l)
  12. 2.2 断点操作:调试的核心(break/b)
  13. 2.2.1 设置断点
  14. 2.2.2 查看断点信息
  15. 2.2.3 删除断点
  16. 2.2.4 禁用 / 启用断点
  17. 2.3 程序执行控制:运行、单步与继续
  18. 2.3.1 运行程序(run/r)
  19. 2.3.2 单步执行:逐过程与逐语句
  20. 2.3.3 继续执行(continue/c)
  21. 2.3.4 执行到函数返回(finish)
  22. 2.3.5 执行到指定行(until)
  23. 2.4 变量操作:查看、修改与监视
  24. 2.4.1 查看变量值(print/p)
  25. 2.4.2 修改变量值(set var)
  26. 2.4.3 跟踪显示变量(display/undisplay)
  27. 2.5 堆栈查看:定位崩溃位置(backtrace/bt)
  28. 2.6 查看局部变量(info locals)
  29. 三、gdb 高级技巧:条件断点、变量监视与更多
  30. 3.1 条件断点:精准定位特定场景
  31. 3.1.1 两种设置方式
  32. 3.1.2 实战示例:循环中定位特定值
  33. 3.2 变量监视(watch):捕捉变量变化
  34. 3.2.1 三种监视模式
  35. 3.2.2 实战示例:监视 result 变量变化
  36. 3.3 多线程调试:thread 命令
  37. 四、cgdb:gdb 的可视化增强版
  38. 4.1 cgdb 安装
  39. CentOS 系统:
  40. Ubuntu 系统:
  41. 4.2 cgdb 核心操作
  42. 4.3 cgdb 优势展示
  43. 五、常见问题与避坑指南
  44. 5.1 无法启动 gdb 调试
  45. 5.2 断点设置失败
  46. 5.3 变量无法查看
  47. 5.4 多文件调试路径问题
  48. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 线程池单例模式、线程安全与重入及死锁详解
  • Openclaw 开源仿生机械爪原理与应用解析
  • LeetCode 179 最大数 贪心算法解析
  • Spring Boot RESTful API 开发与测试
  • Redis Hash 类型及相关指令详解
  • AIGC 工作流商业化实战:如何将代码转化为可交易的技术资产
  • Faster-Whisper 本地部署及实时语音转文本方案
  • 网络安全面试115问:从信息收集到应急响应,按模块整理
  • 前端加密实战:encrypt-labs 靶场环境与解密技巧
  • C++ 继承入门:从概念定义到默认成员函数详解
  • OpenClaw 本地极简部署与 QQ 机器人接入教程
  • Flutter for OpenHarmony 集成 dart_openai 实现 AIGC 功能
  • C++ 继承机制详解:从基础到多继承与组合
  • CentOS 下五种 Python 定时发送邮件方案实战
  • AI Copilot 概念及多领域应用场景
  • 网站漏洞挖掘思路与常见渗透测试方法
  • 基于 Java SSM 的网上挂号系统设计与实现
  • 基于 SSM 框架的 Web 房屋销售管理系统设计与实现
  • GitHub Copilot 学生认证指南:免费获取 Pro 版权益
  • 深入理解数据结构中的时间与空间复杂度

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online