作为 Linux 下 C/C++ 开发的核心工具,gdb 调试器是排查代码 bug、理解程序运行流程的必备技能。很多新手面对黑屏命令行调试望而却步,甚至资深开发者也可能只掌握基础用法。而 cgdb 作为 gdb 的增强版,更是解决了纯命令行调试看不到代码的痛点。本文将结合实战案例,从基础配置到高级技巧,全面拆解 gdb/cgdb 的使用方法,让你彻底掌握 Linux 下的调试精髓!下面就让我们正式开始吧!
在开发过程中,我们难免会遇到代码逻辑错误、变量取值异常、程序崩溃等问题。printf 打印调试虽然简单,但存在诸多局限:需要手动添加打印语句、重新编译,无法实时观察变量变化,也难以定位崩溃点。而 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",表明包含调试信息在终端中输入以下命令启动 gdb 并加载可执行程序:
gdb 可执行程序名
# 示例:gdb mycmd 启动成功后,终端会显示 gdb 版本信息,并进入 gdb 命令行模式,提示符为(gdb)。
有两种常用退出方式:
quit或q命令:(gdb) quit;Ctrl + D。 调试时需要对照源代码查看执行位置,list命令(简写l)可以显示程序源代码。
常用用法:
(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断点是调试的核心功能,用于指定程序暂停执行的位置,方便观察变量状态和程序流程。
常用断点设置方式:
(gdb) b 行号(示例:b 20 在第 20 行设置断点)(gdb) b 函数名(示例:b Sum 在 Sum 函数开头设置断点)(gdb) b 文件名:行号(示例:b mycmd.c:10) 使用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输出说明:
常用删除方式:
(gdb) delete breakpoints 或 (gdb) d(gdb) delete breakpoints 断点编号(示例:d 1 删除 1 号断点)无需删除断点时,可临时禁用或启用:
(gdb) disable breakpoints(gdb) enable breakpoints(gdb) disable breakpoints 断点编号(gdb) enable breakpoints 断点编号设置断点后,需要通过执行命令控制程序运行,逐步排查问题。
在 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);单步执行是调试的核心操作,分为两种模式:
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++) 程序暂停在断点或单步执行后,使用continue或c命令让程序继续执行到下一个断点或程序结束:
(gdb) c
Continuing.
running done, result is: [1-100]=5050
[Inferior 1 (process 12345) exited normally] 在函数内部调试时,使用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函数返回值 使用until 行号命令让程序执行到指定行,无需设置断点,适合快速跳转到目标位置:
(gdb) until 16 # 执行到第16行
Sum (s=1, e=100) at mycmd.c:16
16 return result;调试的核心目的是观察变量取值是否符合预期,gdb 提供了丰富的变量操作命令。
使用print或p命令查看变量、表达式的值,支持直接计算表达式。
常用用法:
(gdb) p 变量名(示例:p result 查看 result 变量值);(gdb) p 表达式(示例:p start+end 计算并显示 start+end 的值);实战示例:
(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 调试时如果发现变量取值异常,可以使用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 # 结果正常,验证问题根源 使用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的最新值 当程序崩溃时(如段错误),最关键的是定位崩溃发生的函数调用链。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输出说明:
通过调用栈可以快速定位崩溃的根源:main 调用 func1,func1 调用 func2 并传入空指针,func2 解引用空指针导致崩溃。
使用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) b 行号/函数名 if 条件
示例:b 9 if i == 30(在第 9 行设置断点,仅当 i=30 时暂停)。(gdb) condition 断点编号 条件
示例:condition 2 i == 30(给 2 号断点添加条件 i=30)。假设 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 watch命令用于监视变量或表达式的值,当值发生变化时,程序会自动暂停并提示旧值和新值,适合排查变量被意外修改的问题。
watch 变量名:监视变量值的读写变化(读写触发);rwatch 变量名:仅监视变量被读时触发;awatch 变量名:仅监视变量被修改时触发。(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 变量每次的变化情况,快速定位异常修改。
在多线程程序中,gdb 支持线程切换、设置线程专属断点等功能,核心命令如下:
info threads;thread 线程编号;b 行号 thread 线程编号;set scheduler-locking on(避免其他线程干扰)。gdb 虽然强大,但纯命令行模式看不到代码,需要频繁使用 list 命令查看,效率较低。cgdb 作为 gdb 的前端工具,支持分屏显示代码和 gdb 命令行,操作方式与 gdb 完全兼容,上手成本极低。
sudo yum install -y cgdbsudo apt-get install -y cgdbcgdb 的命令与 gdb 完全一致,额外增加了分屏控制快捷键:
cgdb 可执行程序名(示例:cgdb mycmd);Esc键进入代码屏,按i键回到命令行屏;j/k键上下滚动;quit或Ctrl + D。启动 cgdb 后,界面分为上下两部分:
实战体验:
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。
No debugging symbols found in 可执行程序;-g选项,程序无调试信息;-g选项(gcc -g 源文件 -o 可执行程序)。Breakpoint 1 at 0xXXXX: file 文件名.c, line 行号. (2 locations);-O2优化选项),导致行号对应不上;-O相关选项),仅保留-g选项。No symbol "变量名" in current context.No such file or directory。file 文件名:行号格式设置断点,或编译时指定源文件路径。调试是编程的核心技能之一,熟练使用 gdb/cgdb 不仅能解决问题,更能帮助你深入理解程序运行机制。建议结合实际项目反复练习,将这些命令内化为肌肉记忆,成为高效的 Linux 开发者!