Linux基础开发工具(下):调试器gdb/cgdb的使用详解


文章目录


引言

在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)查看函数内关键变量的计算过程(用pdisplay);
    (3)用set var修改变量,验证返回值是否恢复正常。

结语

调试是程序猿的必备技能,其核心是"大胆猜想、小心验证",熟练运用gdb/cgdb后,能帮助我们缩短找BUG的时间,快速定位问题、解决问题,进一步提升开发效率。

相关推荐
liulilittle1 小时前
Linux shell 搜索指定后缀名文件,并复制到指定目录。
linux·服务器·数据库
必胜刻1 小时前
Redis哨兵模式(Linux)
linux·数据库·redis
ULTRA??1 小时前
C++数据结构的链表实现模拟
c++·链表
双翌视觉1 小时前
服务器电源外观检测智能化机器视觉解决方案
运维·服务器·人工智能·机器学习
biter down1 小时前
C++ 组合与继承:从设计本质到实战,吃透高内聚低耦合
开发语言·c++
灰灰勇闯IT1 小时前
C语言实战:字符串元音字母提取器的实现与优化
c语言·开发语言
fantasy5_52 小时前
C++11 核心特性实战博客
java·开发语言·c++
Channing Lewis2 小时前
.ini文件格式
服务器
天若有情6732 小时前
从构造函数到Vue3响应式:C++中“常量转特殊类型”的隐藏大招
开发语言·c++