目录
- 1. 调试前的准备:Debug 模式
- 2. 安装 CGDB
- 3. GDB 常用命令
- 3.1. list (l):查看源代码
- 3.2. 程序执行控制:run, next, step, continue, finish, until
- 3.3. 断点管理:break, info, delete, disable, enable
- 3.4. 变量与状态查看:print, p, set var, display, undisplay, info locals
- 4. 监视变量 (Watch)
- 4.1 在 main 函数打断点并运行,进入 Sum 函数内部,让 result 完成初始化
- 4.2 设置监视点 (Watch),继续执行 (c) 并观察变化
- 5. 条件断点
- 5.1 新增断点时直接添加条件
- 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 stripped2. 安装 CGDB
cgdb在上方显示源代码,下方保留 GDB 命令行,支持语法高亮。
Ubuntu:
sudo apt-get install -y cgdbCentOS:
sudo yum install -y cgdb基本操作:
启动:
gdb ./mycmd或cgdb ./mycmd退出:
quit或Ctrl + DCGDB 分屏技巧:按
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 [断点编号] [条件]