目录
- [1. 调试前的准备:Debug 模式](#1. 调试前的准备:Debug 模式)
- [2. 安装 CGDB](#2. 安装 CGDB)
- [3. GDB 常用命令](#3. GDB 常用命令)
- [3.1. list (l):查看源代码](#3.1. list (l):查看源代码)
- [3.2. 程序执行控制:run, next, step, continue, finish, until](#3.2. 程序执行控制:run, next, step, continue, finish, until)
- [3.3. 断点管理:break, info, delete, disable, enable](#3.3. 断点管理:break, info, delete, disable, enable)
- [3.4. 变量与状态查看:print, p, set var, display, undisplay, info locals](#3.4. 变量与状态查看:print, p, set var, display, undisplay, info locals)
- [4. 监视变量 (Watch)](#4. 监视变量 (Watch))
- [4.1 在 main 函数打断点并运行,进入 Sum 函数内部,让 result 完成初始化](#4.1 在 main 函数打断点并运行,进入 Sum 函数内部,让 result 完成初始化)
- [4.2 设置监视点 (Watch),继续执行 (c) 并观察变化](#4.2 设置监视点 (Watch),继续执行 (c) 并观察变化)
- [5. 条件断点](#5. 条件断点)
- [5.1 新增断点时直接添加条件](#5.1 新增断点时直接添加条件)
- [5.2 给已有断点追加条件](#5.2 给已有断点追加条件)
GDB(GNU Debugger)是 GNU 项目下的命令行级程序调试器;CGDB 是 GDB 的前端封装,底层仍然调用 GDB,提供类似 Vim 的分屏界面,解决原生 GDB 纯命令行操作不直观的痛点。
1. 调试前的准备:Debug 模式
在开始调试之前,必须确保你的程序包含调试信息。Linux 下 gcc/g++ 默认生成的是 Release 模式,不支持调试。我们需要在编译时加上 -g 选项。
示例:
#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;
}
编译对比:
默认模式(不支持调试):
$ gcc mycmd.c -o mycmd
$ file mycmd
# 输出:... not stripped (但没有 debug_info)
Debug 模式(支持调试):
$ gcc mycmd.c -o mycmd -g
$ file mycmd
# 输出:... with debug_info, not stripped

2. 安装 CGDB
cgdb在上方显示源代码,下方保留 GDB 命令行,支持语法高亮。
Ubuntu:
sudo apt-get install -y cgdb
CentOS:
sudo yum install -y cgdb
基本操作:
-
启动:
gdb ./mycmd或cgdb ./mycmd -
退出:
quit或Ctrl + D -
CGDB 分屏技巧 :按
ESC进入上方代码屏(可浏览代码),按i回到下方 GDB 命令屏。

3. GDB 常用命令
3.1. list (l):查看源代码
作用 :在命令行中显示源代码,方便对照行号打断点。默认每次显示 10 行。
操作步骤:
-
list / l(显示源码)-
操作步骤 :在
(gdb)提示符下直接输入l并回车。 -
效果:GDB 会默认以当前文件为中心,每次列出 10 行代码。如果继续敲回车,会接着上一次的位置继续往下显示 10 行。
-
示例 :
list / l 10(从第 10 行开始显示代码) -

-
-
list / l 函数名(列出指定函数源码)-
操作步骤 :输入
l main或l Sum。 -
效果:直接跳到该函数的开头,显示其上下文代码。非常适合在打断点前查看函数逻辑。
-

-
-
list / l 文件名:行号(列出指定文件的源码)-
操作步骤 :如果在多文件项目中,输入
l mycmd.c:1。 -
效果 :直接打开
mycmd.c文件并定位到第 1 行。
-
3.2. 程序执行控制:run, next, step, continue, finish, until
这是 GDB 最核心的控制流命令,决定了程序如何一步步往下走。
-
run / r(开始运行)-
操作步骤 :输入
r并回车。 -
效果:程序从头开始执行。如果之前设置了断点,程序会在第一个断点处暂停;如果没有断点,程序会直接运行到结束。
-

-
-
next / n(单步执行 - 逐过程,F10)-
操作步骤 :程序暂停时,输入
n。 -
效果 :执行当前行代码。关键特性 :如果当前行是一个函数调用(如
int n = Sum(start, end);),按n不会 进入Sum函数内部,而是把整个函数执行完,直接停在下一行。相当于 VS 中的 F10 (Step Over)。 -

-
-
step / s(单步执行 - 逐语句,F11)-
操作步骤 :程序暂停时,输入
s。 -
效果 :执行当前行代码。关键特性 :如果遇到函数调用,按
s会 进入被调用函数的内部(比如进入Sum函数的第一行)。相当于 VS 中的 F11 (Step Into)。 -

-
-
continue / c(继续执行)-
操作步骤 :输入
c。 -
效果:让暂停的程序恢复运行。它会一直往下跑,直到遇到下一个断点,或者程序结束
-

-
-
finish(跳出当前函数)-
操作步骤 :当你使用
s进入了Sum函数,但发现内部逻辑太长了不想单步看了,输入finish。 -
效果 :程序会瞬间执行完
Sum函数剩余的代码,并返回到调用它的main函数处暂停,同时打印出函数的返回值。 -

-
-
until X行号(运行至指定行)-
操作步骤 :比如当前在第 11 行的
for循环处,你想直接跳到第 15 行,输入until 15。 -
效果:程序会自动连续执行,直到到达指定行号才停下。常用于快速跳出一个循环。
-

-
3.3. 断点管理:break, info, delete, disable, enable
在大型程序中,我们需要精确控制程序在哪里停下。
-
break / b [文件名:]行号(行号打断点)-
操作步骤 :输入
b 10(在当前文件第10行打断点),或者b test.c:10(在指定文件打断点)。 -
效果 :GDB 提示
Breakpoint 1 at 0x...: file mycmd.c, line 10.。 -

-
-
break / b 函数名(函数入口打断点)-
操作步骤 :输入
b main或b Sum。 -
效果:在该函数的第一行代码处自动设置断点。
-

-
-
info breakpoints / info b(查看断点列表)-
操作步骤 :输入
info b。 -
效果:以表格形式列出当前所有的断点,包含断点编号(Num)、是否启用(Enb)、位置和命中的次数。
-

-
-
delete / d breakpoints和delete / d breakpoints n(删除断点)-
操作步骤 :输入
d breakpoints删除所有 断点。输入d 1删除编号为1的断点。 -

-
-
disable breakpoints和enable breakpoints(禁用与启用)-
操作步骤 :输入
disable breakpoints。 -
效果 :保留断点但不生效 (Enb 列变为 n),程序执行时不会在这些断点停下。调试完后输入
enable breakpoints可以重新激活它们。这比删了重打要方便得多。 -

-
3.4. 变量与状态查看:print, p, set var, display, undisplay, info locals
停下来之后,查看内存状态是找 Bug 的关键。
-
print / p 表达式和p 变量(打印)-
操作步骤 :输入
p start查看变量值;输入p start+end让 GDB 帮你计算表达式的结果。 -

-
-
set var 变量=值(强制修改变量)-
操作步骤 :输入
set var i=10。 -
效果 :在程序运行期间,强行把内存中变量
i的值修改为 10。这是验证逻辑假设的终极利器。 -


-
-
display 变量名(跟踪显示变量)-
操作步骤 :输入
display x。 -
效果 :这相当于设置了"监视"。之后你每次执行
n或s导致程序暂停时,GDB 都会自动 打印出x的当前值,不需要你每次都敲p x。 -

-
-
undisplay 编号(取消跟踪显示)-
操作步骤 :输入
undisplay 1。 -
效果 :取消编号为 1 的自动显示设置。编号可以通过
info display查看。 -

-
-
info locals(查看当前局部变量)-
操作步骤 :当程序停在函数内部时,输入
info locals(可简写为i locals)。 -
效果 :GDB 会一次性列出当前栈帧(当前函数)中所有局部变量的名字和当前的值。非常适合快速了解函数内部的全局状态。
-

-
4. 监视变量 (Watch)
在调试循环时(例如 for 循环累加),我们常想知道某个变量(如 result)到底是在哪一次循环、什么时候发生了改变 。
如果我们靠手动不断敲 p result(print)去查看,不仅费时费力,而且很容易错过变化的那一瞬间。
watch 命令可以帮助我们实时监控变量的内存变化。一旦该变量的值发生改变,GDB 就会自动暂停程序,并告诉你"旧值"和"新值"分别是什么。
示例:
4.1 在 main 函数打断点并运行,进入 Sum 函数内部,让 result 完成初始化
首先,我们需要让程序跑起来,并在进入 Sum 函数前停下来。

4.2 设置监视点 (Watch),继续执行 (c) 并观察变化
现在 result 已经初始化完毕,正是建立监视的好时机。


- 观察点编号:
18,监视变量result - Old value:修改之前 result 的值
- New value:修改完成后 result 的值
mycmd.c:5:在代码第 5 行发生这次赋值(result += i;)
总结:
-
变量异动的捕捉神器 :如果你有一些变量(比如全局的状态标志位、关键结构体成员)不应该被修改 ,但是你怀疑某段代码有 Bug,偷偷修改导致了问题,你就可以用
watch盯住它。 -
一旦变化,立刻通知 :程序会停在被修改后的第一现场,方便你通过
bt(查看调用栈) 找出是哪个函数干的"坏事"。 -
日常调试的取舍 :虽然
watch很好用,但硬件观察点数量有限(通常只有 4 个)。如果需要监视大量变量,建议使用display命令(每次单步停下时自动打印变量值)。
5. 条件断点
如果我们怀疑循环中间某个时刻出了问题(比如:我想看看当 i 等于 30 的时候,result 累加到了多少 ),如果在第 6 行 result += i; 打一个普通的断点,那么程序将会疯狂暂停 100 次 !你需要绝望地敲击 100 次 continue (c) 才能跑到最后。这不仅极其繁琐,而且极易让人崩溃。
为了解决这个问题,我们需要使用条件断点------让断点只在满足特定条件时才生效。
5.1 新增断点时直接添加条件
b [行号/函数名] if [条件]


程序在后台自动跑了前 29 次循环,完全没有打扰你 。直到 i 变成 30 的那一瞬间,条件满足,程序才如你所愿地停在第六行。此时打印变量,result 恰好是 1 到 29 的累加和 (435)。
5.2 给已有断点追加条件
condition [断点编号] [条件]

