
文章目录
- 引言
- 1.程序的发布模式
- 2.gdb核心命令详解(纯命令行)
- 3.cgdb可视化调试
- 4.常见调试场景与解决方案
-
- [4.1.场景一:程序崩溃(Segmentation fault)](#4.1.场景一:程序崩溃(Segmentation fault))
- 4.2.场景二:循环逻辑错误(如死循环等)
- 4.3.场景三:函数返回值异常
- 结语
引言
在Linux系统下编写代码时,你是否遇到过:程序运行结果和预期完全不符,却找不到错在哪一行;循环看似没问题,却陷入死循环;函数返回值异常,却不知道哪里出了问题?
而gdb/cgdb作为专业调试工具,能帮助我们穿透代码表面,直击问题核心,告别"找BUG两小时,改BUG五分钟"的困境。
1.程序的发布模式
程序的发布方式有两种:
- Release模式:默认编译模式,编译器会优化代码(如删除冗余变量、合并循环等),不包含调试信息,体积小、运行快,但无法调试;
- Debug模式 :编译时添加
-g参数,会保留代码的行号、变量名等调试信息,体积稍大,但支持调试工具介入。
Release就像在饭店吃厨子做的饭,看不到食材的原本样貌;而Debug模式相当于自己做饭自己吃,能清楚知道每一步的用料和做法,方便"排查问题"。
编译生成Debug模式程序的具体命令:
bash
# 以test.c文件为例 生成Debug模式程序(添加-g参数)
gcc -g test.c -o test
# 验证是否包含调试信息(输出含"with debug_info")
file test
如果忘记加-g参数,则无法进行调试。
2.gdb核心命令详解(纯命令行)
gdb是GNU调试器,是调试的"基础工具"。
gdb的安装命令:
(1)Debian/Ubuntu:
bash
# 更新软件源(可选,确保最新版本)
sudo apt update
# 安装 GDB
sudo apt install gdb -y
# 验证安装
gdb --version
(2)CentOS:
bash
sudo yum install gdb -y
gdb --version
下面我们结合mycmd.c(计算1-100的和)代码,来深入理解gdb中的核心命令:
c
// mycmd.c
#include <stdio.h>
int Sum(int s, int e) {
int result = 0;
int i = s;
for(; 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;
}
先编译生成Debug程序:
bash
# 生成Debug模式程序(添加-g参数)
gcc -g mycmd.c -o mycmd
# 验证是否包含调试信息(输出含"with debug_info")
file mycmd
2.1.调试的启动与退出
bash
# 启动gdb,加载可执行程序
gdb mycmd
# 退出gdb
quit 或 Ctrl+d
2.2.查看源代码
bash
# 查看当前位置附近10行代码(默认)
l 或 list
# 查看指定函数的代码(如Sum函数)
l Sum
# 查看指定行附近的代码(如第10行)
l 10
# 查看指定文件的代码(多文件项目)
l mycmd.c:15
2.3.设置断点
设置断点是调试的关键步骤,能让程序暂停在指定位置,便于观察变量、逻辑是否正常。
bash
# 在指定行设置断点(如第10行)
b 10 或 break 10
# 在函数开头设置断点(如Sum函数入口)
b Sum
# 在指定文件的指定行设置断点(多文件)
b mycmd.c:20
# 查看所有断点信息(编号、位置、状态)
info b 或 info break
2.4.运行程序
bash
# 从程序开头运行,直到遇到断点或程序结束(无参数)
r 或 run
# 运行程序并传入命令行参数(如./mycmd 10 20)
r 10 20
2.5.单步执行
bash
# 单步执行,不进入函数内部(逐过程,类似F10)
n 或 next
# 单步执行,进入函数内部(逐语句,类似F11)
s 或 step
# 执行到当前函数结束,返回上一层
finish
2.6.查看与修改变量
bash
# 打印变量的值(如查看result变量)
p result 或 print result
# 打印表达式的值(如查看start+end的和)
p start+end
# 跟踪变量,每次暂停自动显示(不用反复输入p)
display result
# 取消跟踪变量(根据display的编号,如取消编号1的跟踪)
undisplay 1
# 修改变量的值(如将i改为50,验证逻辑)
set var i=50
2.7.继续执行与断点管理
bash
# 从当前位置继续执行,直到下一个断点
c 或 continue
# 删除所有断点
delete breakpoints
# 删除指定编号的断点(如删除编号1的断点)
delete breakpoints 1
# 禁用所有断点(暂时不用,不删除)
disable breakpoints
# 启用所有断点
enable breakpoints
2.8.高级技巧
2.8.1.监视变量
bash
# 监视变量result,当它的值变化时,程序暂停并提示
watch result
执行效果:
bash
Hardware watchpoint 2: result
Old value = 0
New value = 1
Sum (s=1, e=100) at mycmd.c:7
7 for(int i = s; i <= e; i++)
2.8.2.条件断点
bash
# 仅当i==30时暂停,在第9行设置断点
b 9 if i == 30
# 给已有断点添加条件(如给编号2的断点添加条件i==50)
condition 2 i==50
2.8.3.查看调用栈
bash
# 查看当前函数的调用栈(谁调用了当前函数,参数是什么)
bt 或 backtrace
执行效果:
#0 Sum (s=1, e=100) at mycmd.c:7
#1 0x00005555555551d2 in main () at mycmd.c:20
3.cgdb可视化调试
gdb纯命令行调试需要频繁输入l查看代码,步骤繁琐,不够直观。而cgdb是gdb的升级版,增加了可视化效果,能同时显示代码和命令,操作更高效。
3.1.cgdb安装
bash
# CentOS安装
sudo yum install -y cgdb
# Ubuntu安装
sudo apt install -y cgdb
# 验证安装
cgdb --version
3.2.cgdb核心优势
启动cgdb后,界面自动分为两部分:
- 上半区:源代码窗口(语法高亮、显示行号、当前行高亮);
- 下半区:命令窗口(与gdb命令完全兼容)。
相当于同时打开了"代码文件"和"调试终端",不用来回切换,直观且高效。
3.3.cgdb基础操作
cgdb支持vim键位,操作非常灵活:
| 操作 | 功能 | 适用场景 |
|---|---|---|
按ESC |
切换焦点到源代码窗口 | 查看代码、移动光标 |
按i |
切换焦点到命令窗口 | 输入gdb命令 |
h/j/k/l |
源代码窗口光标移动(左/下/上/右) | 快速定位代码行 |
Ctrl + L |
刷新界面(解决乱码/错位) | 界面异常时修复 |
按q |
退出cgdb | 调试结束 |
/关键字 |
源代码窗口搜索关键字 | 查找变量、函数名 |
3.4.cgdb实战
我们还是以mycmd.c为例,并在其中故意加入几个错误:
c
// 有BUG的mycmd.c:flag初始值为0,导致返回结果为0
#include <stdio.h>
int flag = 0; // 故意错误,正确应为1
int Sum(int s, int e) {
int result = 0;
int i = s;
for(; i <= e; i++) {
result += i;
}
return result * flag; // BUG:结果乘以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;
}
调试步骤:
(1)编译生成 Debug 程序:gcc -g mycmd.c -o mycmd;
(2)启动 cgdb:cgdb mycmd(上半区显示代码,下半区显示命令提示符);
(3)设置断点:在Sum函数的return行(第 11 行)按b设置断点(行号左侧显示B+);
(4)运行程序:r(程序执行到断点处暂停,上半区高亮第 12 行);
(5)查看变量:p result(输出$1 = 5050,说明循环计算正确);
(6)查看 flag 变量:p flag(输出$2 = 0,发现 BUG 原因);
(7)修改变量验证:set var flag=1(将 flag 改为 1);
(8)继续执行:n(执行 return 语句);
(9)查看最终结果:n(回到 main 函数,输出result is: [1-100]=5050,BUG 修复)。
3.5.函数跳转
如果项目有多个文件,或函数定义在其他地方,cgdb支持快速跳转:
- 生成索引文件
ctags -R *.c *.h(在代码目录执行); - 在源代码窗口,将光标移到函数名(如
Sum)上,按Ctrl + ],直接跳转到函数定义处; - 按
Ctrl + T返回上一个位置。
4.常见调试场景与解决方案
4.1.场景一:程序崩溃(Segmentation fault)
- 存在问题:访问了非法内存(如空指针、数组越界等)。
- 解决方案:
(1)cgdb 可执行文件名启动调试;
(2)输入r运行程序,程序崩溃时会显示崩溃位置;
(3)用bt查看调用栈,定位崩溃的函数和行号;
(4)检查该位置的指针、数组是否正常。
4.2.场景二:循环逻辑错误(如死循环等)
- 解决方案:
(1)在循环内设置断点(如b 7 if i>100,当i>100时暂停);
(2)用display i跟踪循环变量,观察变化是否符合预期;
(3)用set var i = 100修改循环变量,验证是否能退出循环。
4.3.场景三:函数返回值异常
- 解决方案:
(1)在函数返回行设置断点;
(2)查看函数内关键变量的计算过程(用p或display);
(3)用set var修改变量,验证返回值是否恢复正常。
结语
调试是程序猿的必备技能,其核心是"大胆猜想、小心验证",熟练运用gdb/cgdb后,能帮助我们缩短找BUG的时间,快速定位问题、解决问题,进一步提升开发效率。