【Linux系统编程】(十)从入门到精通!Linux 调试器 gdb/cgdb 超全使用指南,程序员必备调试神器

目录

前言

[一、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

有两种常用退出方式:

  • 输入quitq命令:(gdb) quit
  • 使用快捷键Ctrl + D

二、gdb 核心命令:从基础到实战

2.1 查看源代码(list/l)

调试时需要对照源代码查看执行位置,list命令(简写l)可以显示程序源代码。

常用用法

  1. 显示当前位置附近的代码(默认每次 10 行):(gdb) l(gdb) list
  2. 显示指定行号附近的代码:(gdb) l 行号(示例:l 10 显示第 10 行附近代码)
  3. 显示指定函数的代码:(gdb) l 函数名(示例:l main 显示 main 函数代码)
  4. 显示指定文件的指定行代码:(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 设置断点

常用断点设置方式

  1. 按行号设置断点:(gdb) b 行号(示例:b 20 在第 20 行设置断点)
  2. 按函数名设置断点:(gdb) b 函数名(示例:b Sum 在 Sum 函数开头设置断点)
  3. 按文件 + 行号设置断点(多文件项目):(gdb) b 文件名:行号(示例:b mycmd.c:10

2.2.2 查看断点信息

使用info breakinfo 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 删除断点

常用删除方式

  1. 删除所有断点:(gdb) delete breakpoints(gdb) d
  2. 删除指定编号的断点:(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 中输入runr命令启动程序,程序会执行到第一个断点处暂停:

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 单步执行:逐过程与逐语句

单步执行是调试的核心操作,分为两种模式:

  1. 逐过程(不进入函数):nextn(类似 VSCode 的 F10)

    执行当前行代码,如果是函数调用,不进入函数内部,直接执行完函数并返回结果

  2. 逐语句(进入函数):steps(类似 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)

程序暂停在断点或单步执行后,使用continuec命令让程序继续执行到下一个断点或程序结束:

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)

使用printp命令查看变量、表达式的值,支持直接计算表达式。

常用用法

  1. 查看变量值:(gdb) p 变量名(示例:p result 查看 result 变量值);
  2. 查看表达式值:(gdb) p 表达式(示例:p start+end 计算并显示 start+end 的值);
  3. 连续查看变量(自动递增序号):多次使用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命令可以设置变量跟踪,每次程序暂停时自动显示变量值,适合需要持续观察的变量。

常用用法

  1. 跟踪变量:(gdb) display 变量名(示例:display i 跟踪循环变量 i);
  2. 查看跟踪列表:(gdb) info display
  3. 取消跟踪:(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)

当程序崩溃时(如段错误),最关键的是定位崩溃发生的函数调用链。backtracebt命令可以显示当前的函数调用栈,包括每个函数的调用位置和参数。

实战场景:假设程序崩溃,使用 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 localsi 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 两种设置方式

  1. 新增断点 时直接添加条件:(gdb) b 行号/函数名 if 条件

    示例:b 9 if i == 30(在第 9 行设置断点,仅当 i=30 时暂停)。

  2. 已有断点 添加条件:(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 三种监视模式

  1. watch 变量名:监视变量值的读写变化(读写触发);
  2. rwatch变量名:仅监视变量被读时触发;
  3. 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 一致,quitCtrl + 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 开发者!

相关推荐
嘉禾望岗50329 分钟前
lvs+keepalived轮询访问doris集群
linux·服务器·lvs
本妖精不是妖精29 分钟前
在 CentOS 7 上部署 Node.js 18 + Claude Code
linux·python·centos·node.js·claudecode
李少兄30 分钟前
在 Linux 中精准查找名为 `xxx` 的文件或目录路径
android·linux·adb
2501_9160088932 分钟前
App 上架服务行业的实际工作流程与工具选择 从人工代办到跨平台自动化的转变
android·运维·ios·小程序·uni-app·自动化·iphone
不会kao代码的小王32 分钟前
突破局域网!OpenObserve,数据观测随时随地
linux·windows·后端
老条码新物联数字派33 分钟前
【学习Linux】 乌班图(UBuntu)和Linux
linux·运维·ubuntu
Lynnxiaowen34 分钟前
今天继续学习Kubernetes内容namespace资源对象和pod简介
linux·运维·学习·容器·kubernetes
倔强的石头10635 分钟前
openEuler 云原生容器基础搭建与Podman应用部署实操
运维·云原生·podman·openeuler
我在人间贩卖青春38 分钟前
输入输出相关命令
linux·输入输出