目录
[一、gdb 调试基础:从环境准备到核心概念](#一、gdb 调试基础:从环境准备到核心概念)
[1.1 为什么需要 gdb 调试?](#1.1 为什么需要 gdb 调试?)
[1.2 gdb 调试的前提:编译调试版本](#1.2 gdb 调试的前提:编译调试版本)
[1.3 gdb 的启动与退出](#1.3 gdb 的启动与退出)
[启动 gdb](#启动 gdb)
[退出 gdb](#退出 gdb)
[二、gdb 核心命令:从基础到实战](#二、gdb 核心命令:从基础到实战)
[2.1 查看源代码(list/l)](#2.1 查看源代码(list/l))
[2.2 断点操作:调试的核心(break/b)](#2.2 断点操作:调试的核心(break/b))
[2.2.1 设置断点](#2.2.1 设置断点)
[2.2.2 查看断点信息](#2.2.2 查看断点信息)
[2.2.3 删除断点](#2.2.3 删除断点)
[2.2.4 禁用 / 启用断点](#2.2.4 禁用 / 启用断点)
[2.3 程序执行控制:运行、单步与继续](#2.3 程序执行控制:运行、单步与继续)
[2.3.1 运行程序(run/r)](#2.3.1 运行程序(run/r))
[2.3.2 单步执行:逐过程与逐语句](#2.3.2 单步执行:逐过程与逐语句)
[2.3.3 继续执行(continue/c)](#2.3.3 继续执行(continue/c))
[2.3.4 执行到函数返回(finish)](#2.3.4 执行到函数返回(finish))
[2.3.5 执行到指定行(until)](#2.3.5 执行到指定行(until))
[2.4 变量操作:查看、修改与监视](#2.4 变量操作:查看、修改与监视)
[2.4.1 查看变量值(print/p)](#2.4.1 查看变量值(print/p))
[2.4.2 修改变量值(set var)](#2.4.2 修改变量值(set var))
[2.4.3 跟踪显示变量(display/undisplay)](#2.4.3 跟踪显示变量(display/undisplay))
[2.5 堆栈查看:定位崩溃位置(backtrace/bt)](#2.5 堆栈查看:定位崩溃位置(backtrace/bt))
[2.6 查看局部变量(info locals)](#2.6 查看局部变量(info locals))
[三、gdb 高级技巧:条件断点、变量监视与更多](#三、gdb 高级技巧:条件断点、变量监视与更多)
[3.1 条件断点:精准定位特定场景](#3.1 条件断点:精准定位特定场景)
[3.1.1 两种设置方式](#3.1.1 两种设置方式)
[3.1.2 实战示例:循环中定位特定值](#3.1.2 实战示例:循环中定位特定值)
[3.2 变量监视(watch):捕捉变量变化](#3.2 变量监视(watch):捕捉变量变化)
[3.2.1 三种监视模式](#3.2.1 三种监视模式)
[3.2.2 实战示例:监视 result 变量变化](#3.2.2 实战示例:监视 result 变量变化)
[3.3 多线程调试:thread 命令](#3.3 多线程调试:thread 命令)
[四、cgdb:gdb 的可视化增强版](#四、cgdb:gdb 的可视化增强版)
[4.1 cgdb 安装](#4.1 cgdb 安装)
[CentOS 系统:](#CentOS 系统:)
[Ubuntu 系统:](#Ubuntu 系统:)
[4.2 cgdb 核心操作](#4.2 cgdb 核心操作)
[4.3 cgdb 优势展示](#4.3 cgdb 优势展示)
[5.1 无法启动 gdb 调试](#5.1 无法启动 gdb 调试)
[5.2 断点设置失败](#5.2 断点设置失败)
[5.3 变量无法查看](#5.3 变量无法查看)
[5.4 多文件调试路径问题](#5.4 多文件调试路径问题)
前言
作为 Linux 下 C/C++ 开发的核心工具,gdb 调试器是排查代码 bug、理解程序运行流程的必备技能。很多新手面对黑屏命令行调试望而却步,甚至资深开发者也可能只掌握基础用法。而 cgdb 作为 gdb 的增强版,更是解决了纯命令行调试看不到代码的痛点。本文将结合实战案例,从基础配置到高级技巧,全面拆解 gdb/cgdb 的使用方法,让你彻底掌握 Linux 下的调试精髓!下面就让我们正式开始吧!
一、gdb 调试基础:从环境准备到核心概念
1.1 为什么需要 gdb 调试?
在开发过程中,我们难免会遇到代码逻辑错误、变量取值异常、程序崩溃等问题。printf 打印调试虽然简单,但存在诸多局限:需要手动添加打印语句、重新编译,无法实时观察变量变化,也难以定位崩溃点。而 gdb 调试器可以直接加载可执行程序,支持断点设置、单步执行、变量监视、堆栈查看等功能,让你像 "上帝视角" 一样看透程序运行的每一个细节。

1.2 gdb 调试的前提:编译调试版本
Linux 下 gcc/g++ 默认生成的是 release 版本程序,不包含调试信息,无法使用 gdb 调试。因此,必须在编译时添加**-g**选项,生成包含调试信息的 debug 版本。
示例代码(mycmd.c):
cpp
#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;
}
编译命令对比:
bash
# 默认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 并加载可执行程序:
bash
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 行)
实战示例:
cpp
(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命令查看所有断点的详细信息:
bash
(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命令启动程序,程序会执行到第一个断点处暂停:
bash
(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)
执行当前行代码,如果是函数调用,进入函数内部,停在函数第一行
实战示例:
bash
(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命令让程序继续执行到下一个断点或程序结束:
bash
(gdb) c
Continuing.
running done, result is: [1-100]=5050
[Inferior 1 (process 12345) exited normally]
2.3.4 执行到函数返回(finish)
在函数内部调试时,使用finish命令执行到当前函数返回,并显示返回值:
bash
(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 行号命令让程序执行到指定行,无需设置断点,适合快速跳转到目标位置:
bash
(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...),可通过编号快速查看历史值。
实战示例:
bash
(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 导致结果异常:
cpp
#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 值:
bash
(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 的跟踪)。
实战示例:
bash
(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 命令定位:
bash
(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 命令,高效便捷:
bash
(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 的值:
bash
(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 变量变化
bash
(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 系统:
bash
sudo yum install -y cgdb
Ubuntu 系统:
bash
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 命令。
实战体验:
bash
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 不仅能解决问题,更能帮助你深入理解程序运行机制。建议结合实际项目反复练习,将这些命令内化为肌肉记忆,成为高效的 Linux 开发者!