目录
[一、 磨刀不误砍柴工:环境配置与模式切换](#一、 磨刀不误砍柴工:环境配置与模式切换)
[1.1 调试的前置条件:Debug vs Release](#1.1 调试的前置条件:Debug vs Release)
[1.2 开启调试大门的钥匙:-g 选项](#1.2 开启调试大门的钥匙:-g 选项)
[1.3 深度探究:如何证明文件可调试?](#1.3 深度探究:如何证明文件可调试?)
[二、 GDB 核心指令:掌控程序的"生命周期"](#二、 GDB 核心指令:掌控程序的“生命周期”)
[2.1 启动与退出](#2.1 启动与退出)
[2.2 源码查看(list)](#2.2 源码查看(list))
[2.3 断点管理(Breakpoint)](#2.3 断点管理(Breakpoint))
[2.3.1 打断点的方法](#2.3.1 打断点的方法)
[2.3.2 查看、删除与使能](#2.3.2 查看、删除与使能)
[2.4.2 栈帧回溯](#2.4.2 栈帧回溯)
[2.5 执行流控制:从逐语句到跨越式](#2.5 执行流控制:从逐语句到跨越式)
[2.6 变量监视与数据查看](#2.6 变量监视与数据查看)
[2.6.1 基础打印与批量查看](#2.6.1 基础打印与批量查看)
[2.6.2 实时监视(常显示)](#2.6.2 实时监视(常显示))
[2.7 进阶执行流控制:until 指令](#2.7 进阶执行流控制:until 指令)
[三、 GDB/CGDB 调试高阶技巧](#三、 GDB/CGDB 调试高阶技巧)
[3.1 CGDB 的安装与高效使用](#3.1 CGDB 的安装与高效使用)
[3.1.1 安装方式](#3.1.1 安装方式)
[3.1.2 窗口交互与模式切换](#3.1.2 窗口交互与模式切换)
[3.1.3 同步显示优势](#3.1.3 同步显示优势)
[3.2 变量监视:watch 指令](#3.2 变量监视:watch 指令)
[3.3 动态修改程序状态:set var](#3.3 动态修改程序状态:set var)
[3.4 条件断点(Conditional Breakpoints)](#3.4 条件断点(Conditional Breakpoints))
[3.4.1 方式一:新建断点时直接附加条件](#3.4.1 方式一:新建断点时直接附加条件)
[3.4.2 方式二:为已有断点追加/修改条件](#3.4.2 方式二:为已有断点追加/修改条件)
[3.5 指令快捷操作补充](#3.5 指令快捷操作补充)
[3.6完整速查:GDB 常用调试命令全汇总](#3.6完整速查:GDB 常用调试命令全汇总)
一、 磨刀不误砍柴工:环境配置与模式切换
1.1 调试的前置条件:Debug vs Release
在 Linux 下,默认编译生成的二进制文件通常是 Release 模式 。这种模式下,编译器会对代码进行优化并剔除符号表,导致 GDB 无法关联到源代码。
- Release: 追求运行效率,不可调试。
- Debug: 包含调试信息,体积稍大,允许逐行跟踪。
1.2 开启调试大门的钥匙:-g 选项
使用 gcc 或 g++ 时,必须显式加上 -g 参数以包含调试信息。
- 如果你在命令行手动编译:
直接在编译指令中加入该选项即可。
bash
gcc -g main.c -o processbar
- 如果你在 Makefile 中自动化编译:
通常将 -g 定义在编译参数变量 CFLAGS 中,这样在执行 make 时,所有生成的目标文件都会自动携带调试符号。
bash
# Makefile 示例片段
CC = gcc
CFLAGS = -g -Wall # 在这里统一添加 -g
processbar: main.o
$(CC) $(CFLAGS) -o $@ $^

1.3 深度探究:如何证明文件可调试?
除了观察文件体积增大外,最专业的方法是使用 readelf 工具查看 ELF 文件格式中的调试段。
bash
# 若输出包含 .debug_info 等字段,说明具备调试条件
readelf -S processbar | grep -i debug

二、 GDB 核心指令:掌控程序的"生命周期"
2.1 启动与退出
- 进入: gdb [bin_file](bin_file 为编译好的可执行程序)。
- 离开: 输入 quit 或使用 Ctrl + d。
2.2 源码查看(list)
在调试前,我们需要先看到代码。使用 list 指令可以查看源文件内容。
- l (list): 默认列出当前执行行附近的源码。
- l + 行号: 从指定的行号开始向上/向下打印代码。
- l + 文件名:行号: 在多文件工程中,查看指定文件的特定行。
- l + 函数名: 直接跳转到指定函数的起始位置。
技巧: 配合回车键使用,GDB 会自动翻页展示接下来的代码全貌。

2.3 断点管理(Breakpoint)
断点是调试的灵魂,它让程序在指定的时刻"定格"。
2.3.1 打断点的方法
- b 行号: 在当前文件的指定行设置断点(如:b 10)。
- b 函数名: 在函数入口处设置断点(如:b main 或 b Sum)。
- b 文件名:行号: 在多文件调试中,给指定文件打断点(如:b mycode.c:20)。
避坑指南: 注意不要写成 b main:20 这种错误格式。GDB 会将其识别为在 main 函数处打断点,并忽略后面的行号。




2.3.2 查看、删除与使能
- info b: 列出所有断点及其详细信息。
- Num (编号): 标识断点的 ID。
- Enb (Enable): 断点的"使能"状态(y 为启用,n 为禁用)。
- d 编号: 删除指定编号的断点(如:d 2)。
- disable 编号: 禁用断点。断点位置保留,但程序运行到此处不会停止。
- enable 编号: 重新启用断点。(断点禁用,与不禁用的意义就是断点位置的意义)
核心逻辑: 在 GDB 中,只有打断点时需要行号 ;一旦断点生成,后续所有的操作(删除、禁用、使能)都必须使用断点编号。


注意: > 1. 断点编号在单次调试会话中是依次递增 的。如果你删除了 2 号断点再新建,新断点编号将是 4(假设之前已有 3)。
- 如果想让断点编号从 1 重新开始,需要退出重进 GDB。
2.4.2 栈帧回溯
- bt (backtrace): 查看当前调用堆栈的栈帧。这能让你清晰地看到当前函数是被哪个函数调用的,以及传递的参数值。这在处理递归程序或多层嵌套调用时极其有用。

2.5 执行流控制:从逐语句到跨越式
- r (run): 运行程序。停在第一个触发的断点处。
- n (next): 逐过程执行(F10)。
- s (step): 逐语句执行(F11)。
- c (continue): 继续运行。跳过当前停顿,跑向下一个断点。
- finish: 运行直至当前函数返回。
- 应用场景: 当你不小心 s 进了一个无关紧要的库函数或工具函数时,使用 finish 可以直接执行完该函数并跳回调用处。(finish也可以触发断点)
- 深层逻辑: 如图所示,int n = Sum(start, end) 包含了"调用函数"和"赋值给临时变量"两个动作。finish 结束后会停在当前行,等待完成最后的赋值赋值动作。

2.6 变量监视与数据查看
在调试过程中,实时掌握变量的变化是定位 Bug 的关键。
2.6.1 基础打印与批量查看
- p (print) 表达式/变量: 临时打印变量值或计算表达式结果,例如:p n 或 p 1+1+2。


- info locals: 一键查看当前函数栈帧内所有被定义的局部变量及其当前值,无需手动逐个打印。

2.6.2 实时监视(常显示)
- display 变量名: 开启常显示模式。设置后,每执行一步(n 或 s),GDB 都会在屏幕上固定打印该数据及其地址的实时信息,相当于"监视"功能。
- undisplay 编号: 关闭常显示。注意此处必须指定 display 指令生成的对应标号而非变量名。


2.7 进阶执行流控制:until 指令
当面对较长循环且不便设置断点时,until 是极佳的选择。
- until 行号: 直接跑完当前位置到目标行之间的所有代码(包括循环体),并跳转到指定的第 xx 行停止。它能实现"局部跨越式"跳转,极大提高调试效率。

三、 GDB/CGDB 调试高阶技巧
3.1 CGDB 的安装与高效使用
CGDB 是 GDB 的一个轻量级前端界面,它最大的优点是提供了一个代码实时显示窗口。它通过语法高亮和分屏设计,让我们能够像在 IDE 中一样直观地看到当前程序的运行位置。
3.1.1 安装方式
- Ubuntu: sudo apt-get install -y cgdb
- CentOS: sudo yum install -y cgdb
3.1.2 窗口交互与模式切换
CGDB 将终端分为上下两部分:上方为代码窗口 ,下方为 GDB 指令窗口。
Esc (进入代码模式): 焦点切换到上方代码窗口。
- 此时可使用 Vim 快捷键 (如 j、k、u、d)进行翻页和浏览。
- 支持 / 进行关键字搜索代码。
- 支持空格键快捷打断点/取消断点。
- i (进入指令模式): 焦点切换到下方指令窗口,进行标准的 GDB 命令输入。
3.1.3 同步显示优势
- 高亮跟踪: 调试过程中,代码窗口会实时用箭头和高亮色显示当前执行到的行。
- 告别 list: 极大解决了原生 GDB 需要频繁使用 list 指令来确认上下文的痛点,调试效率成倍提升。
- 多文件支持: 在跨文件调试时,CGDB 会根据执行流自动切换上方窗口显示的源文件,保持视图与逻辑一致。
3.2 变量监视:watch 指令
如果你有⼀些变量不应该修改,但是你怀疑它修改导致了问题,你可以watch它,如果变化了,就会通知你.
- 用法: watch [变量名/表达式]。
- 原理: 当被监视的变量值发生变化时,程序会自动暂停执行,并打印出"旧值"与"新值"。
- 区别: * display 是每走一步打印一次值。
- watch 是只有值变了才停下来提醒你。

3.3 动态修改程序状态:set var
在调试时,如果你想跳过某个错误分支,或者测试特定数值下的程序反应,无需修改代码重新编译。
- 用法: set var [变量名]=[值]。
- 应用场景: 当程序运行到 if(n == 0) 处,你可以通过 set var n=1 强制程序进入 else 分支,从而验证不同逻辑路径的正确性。

3.4 条件断点(Conditional Breakpoints)
在处理大型循环或频繁调用的函数时,普通断点会让程序因停顿过于频繁而失去调试意义。条件断点允许我们设定一个过滤规则,仅当满足特定逻辑时,程序才会触发中断。
3.4.1 方式一:新建断点时直接附加条件
这是最常用的方式,在打断点的同时通过 if 关键字声明触发条件。
- 设置方法: b [行号/函数名] if [条件表达式]
- 示例: b 15 if i == 50
- 效果: 当程序执行到第 15 行时,GDB 会自动计算变量 i 的值。只有当 i 等于 50 时,程序才会停下来;否则直接跳过,极大地提高了在成千上万次循环中定位特定错误的能力。

3.4.2 方式二:为已有断点追加/修改条件
如果你已经设置了一个普通断点,但发现它停顿太频繁,可以使用 condition 指令将其转化为条件断点,或修改其现有条件。
- 设置方法: condition [断点编号] [条件表达式]
- 操作步骤:
- 通过 info b 查看目标断点的编号(假设为 2 号)。
- 输入 condition 2 i == 100。
- 效果: 2 号断点从此生效的前提变为了 i == 100。
- 取消条件: 如果只想恢复为普通断点,输入 condition [断点编号](不加任何条件)即可清除该断点的限制规则。

3.5 指令快捷操作补充
- Tab 自动补全: 在 GDB/CGDB 中输入指令前缀(如 dis)后按 Tab 键,可以快速列出所有匹配的指令(如 disable, display),有效防止拼写错误。
- 回车记忆功能: 直接按下回车键会重复执行上一条指令,在进行连续的逐过程(n)或源码翻页(l)时可以大幅减少输入工作量。
3.6完整速查:GDB 常用调试命令全汇总
| 命令 | 作用 | 样例 |
|---|---|---|
list/l |
显示源代码,从上次位置开始,每次列出 10 行 | list/l 10 |
list/l 函数名 |
列出指定函数的源代码 | list/l main |
list/l 文件名:行号 |
列出指定文件的源代码 | list/l mycmd.c:1 |
r/run |
从程序开始连续执行 | run |
n/next |
单步执行,不进入函数内部,逐过程 F10 | next |
s/step |
单步执行,进入函数内部,逐语句 F11 | step |
break/b [文件名:]行号 |
在指定行号设置断点 | break 10``break test.c:10 |
break/b 函数名 |
在函数开头设置断点 | break main |
info break/b |
查看当前所有断点的信息 | info break |
finish |
执行到当前函数返回,然后停止 | finish |
print/p 表达式 |
打印表达式的值 | print start+end |
p 变量 |
打印指定变量的值 | p x |
set var 变量=值 |
修改变量的值 | set var i=10 |
continue/c |
从当前位置开始连续执行程序 | continue |
delete/d breakpoints |
删除所有断点 | delete breakpoints |
delete/d breakpoints n |
删除序号为 n 的断点 | delete breakpoints 1 |
disable breakpoints |
禁用所有断点 | disable breakpoints |
enable breakpoints |
启用所有断点 | enable breakpoints |
info/i breakpoints |
查看当前设置的断点列表 | info breakpoints |
display 变量名 |
跟踪显示指定变量的值(每次停止时) | display x |
undisplay 编号 |
取消对指定编号的变量的跟踪显示 | undisplay 1 |
until X行号 |
执行到指定行号 | until 20 |
backtrace/bt |
查看当前执行栈的各级函数调用及参数 | backtrace |
info/i locals |
查看当前栈帧的局部变量值 | info locals |
quit |
退出 GDB 调试器 | quit |
