GDB调试工具
系统化的学习 才能让你成长
碎片化的学习 才会让你焦虑
GDB是什么
GDB调试器允许你再程序运行时查看里面发生了上面。
可以做以下四件事情
- 开始并设置参数
- 打断点,在断点停下
- 当停下时,可以加检查程序发生了什么
GDB支持很多语言,不只是支持C和C++
GDB下载
GDB可以在主流操作系统中运行,比如Linux、Windows和MAC
下面主要介绍Linux
首先检查是否已经安装gdb
gdb --version
安装命令
yum install gdb
快速上手
以下面这个简单的程序为例
//test.cpp
#include <iostream>
using namespace std;
int main() {
int i = 0;
for (i = 0; i < 5; ++i) {
cout << i << endl;
}
return 0;
}
编译时准备
如果想调试,我们需要在编译的时候启用调试功能,比如g++开启调试
g++ -g test.cpp -o test
运行程序
然后用 GDB(GNU Debugger)调试器启动并调试当前目录下名为 test 的可执行文件。
也就是进入gdb了
gdb ./test
运行程序
run #可以缩写r
退出gdb模式
quit
简单调试
打断点
#break [行数] #缩写b
break 9
#break [函数名]
break main
查看程序行数
list
查看断点
info b
单步执行,不进入函数内部
next 下一步
查看变量值
print命令
p arr[0] #查看变量值
p &arr[1] #查看变量地址
常用功能
shell
可以通过加shell在gdb中运行终端命令
shell ls
日志功能
开启后可以将这个过程中的页面输出日志输出到文件中
set logging enabled on
观察点
可以跟踪观察某个变量的变化
watchpoint [地址]
同时可以用info观察我们已有的观察点
info watchpoint
core文件调试
先提一句core文件是什么
core 文件(核心转储文件,core dump)是当程序异常终止(如段错误、非法指令等)时,操作系统将进程在崩溃瞬间的内存映像、寄存器状态、堆栈指针等信息保存下来的一个二进制文件。
核心作用
- 事后调试:无需重新运行程序,即可通过
gdb <可执行文件> <core文件>进入调试,查看崩溃时的调用栈、变量值、内存状态等。 - 定位崩溃原因:通过
bt(backtrace)、info registers、frame等命令分析崩溃位置和上下文。
生成条件
- 用户需要设置 core 文件大小限制(否则不生成):
ulimit -c unlimited(临时生效,可通过ulimit -c查看当前值)。 - 程序需要有写 core 文件的权限(通常是当前工作目录,或由
/proc/sys/kernel/core_pattern指定的路径)。 - 部分系统会禁止 setuid 程序生成 core 文件。
内容包含
- 进程的虚拟内存映射(代码段、数据段、堆、栈)
- CPU 寄存器值(PC、SP、FP 等)
- 信号及崩溃时的运行环境信息
使用实例
$ gdb ./my_program core.12345
(gdb) bt full # 查看完整调用栈及局部变量
(gdb) frame 2 # 切换到第2帧
(gdb) info locals # 查看局部变量
(gdb) print my_var # 打印变量值
调试运行中的程序
gdb -p [进程id]
然后就可以进入调试模式了。可以一步一步的调试
常见命令速查
以下是常见 GDB 命令的速查表格:
| 命令(缩写) | 样例 | 含义 |
|---|---|---|
run (r) |
run arg1 arg2 |
启动程序(可带参数) |
break (b) |
break mainbreak test.cpp:10 |
在函数或行号设置断点 |
continue (c) |
continue |
继续执行直到下一个断点 |
next (n) |
next |
单步执行,不进入函数内部 |
step (s) |
step |
单步执行,会进入函数内部 |
finish |
finish |
执行到当前函数返回 |
print (p) |
print xprint sizeof(arr) |
打印变量或表达式的值 |
info locals |
info locals |
显示当前栈帧的所有局部变量 |
info breakpoints(info b) |
info breakpoints |
列出所有断点 |
delete (d) |
delete 1 |
删除指定编号的断点 |
disable / enable |
disable 2 |
禁用/启用某个断点 |
list (l) |
listlist main |
显示当前位置或指定位置的源代码 |
backtrace (bt) |
backtrace |
显示当前调用栈(函数调用链) |
frame (f) |
frame 2 |
切换到调用栈的第 2 帧 |
up / down |
updown 1 |
在调用栈中上/下移动 |
watch |
watch x |
设置观察点,变量 x 被修改时暂停 |
display |
display x |
每次停下来时自动打印 x 的值 |
set var |
set var x=10 |
在运行时修改变量的值 |
quit (q) |
quit |
退出 GDB |
help (h) |
help break |
查看某个命令的帮助信息 |
提示:大多数命令有缩写(括号中的字母),熟练后可以大幅提高调试效率。
工程文件调试
下面是调试一个数据库代码的过程总结
在这次调试中,先用 GDB 定位问题方向,然后配合 fprintf 日志【自定义打印信息】做精细追踪。下面逐一列出用到的命令和思路。
一、启动与附加
1. gdb -p <PID> — 附加到运行中的进程
gdb -p $(pgrep rmdb)
# 或
gdb -p 12345
对于 rmdb 这种 C/S 架构的服务端程序,没办法直接 gdb ./rmdb 启动(它一启动就阻塞等待连接)。所以让服务端先跑起来,再用 -p 热附加。
2. gdb --args — 带参数启动(调试模式可行时)
gdb --args ./rmdb test_db
如果程序支持前台运行且不需要事先准备数据库,这种方式最直接。
二、断点设置(核心技能)
3. break 文件名:行号 — 在指定行设断点
(gdb) break ix_scan.cpp:24
(gdb) break ix_scan.h:34
4. break 函数名 — 在函数入口设断点
(gdb) break IxScan::next
(gdb) break IxScan::is_end
(gdb) break IxIndexHandle::lower_bound
(gdb) break IxIndexHandle::upper_bound
对于多文件项目,gdb 会自动从符号表中查找,无需指定文件路径。
5. break 文件名:函数名 — 精准指定(避免同名函数冲突)
(gdb) break ix_scan.cpp:IxScan::next
6. info breakpoints / info b — 查看所有断点
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000401a2b in IxScan::next() at ix_scan.cpp:24
2 breakpoint keep y 0x0000000000401a8c in IxScan::is_end() at ix_scan.h:34
7. delete <编号> / disable <编号> — 删除/禁用断点
(gdb) delete 1
(gdb) disable 2
(gdb) enable 2
三、条件断点(排查特定输入的关键手段)
8. break ... if 条件 — 条件断点
(gdb) break IxScan::next if iid_.page_no == 5
这个非常实用:IxScan::next 被调用几百次,但只有扫描到页 5 时才停下来。能过滤掉无关循环,精准捕捉 bug 触发时刻。
9. condition <编号> 条件 — 给已有断点加条件
(gdb) condition 3 iid_.page_no > end_.page_no
四、执行控制
10. continue / c — 继续运行到下一个断点
11. next / n — 单步执行,不进入函数
12. step / s — 单步执行,进入函数体
(gdb) s # 进入 is_end() 看 iid_less 的详细逻辑
13. finish — 执行完当前函数并返回
(gdb) finish # 从 is_end() 返回,看返回值
14. until 行号 — 执行到指定行
(gdb) until 40 # 跑完循环体,到关键判断前停下
五、变量与内存检查
15. print / p — 打印变量值
(gdb) p iid_
(gdb) p iid_.page_no
(gdb) p iid_.slot_no
(gdb) p end_
(gdb) p end_.page_no
(gdb) p node->get_next_leaf()
(gdb) p ih_->file_hdr_->last_leaf_
这次调试的关键输出就是这些:发现 iid_.page_no=5 而 end_.page_no=3。
16. print 表达式 — 计算表达式
(gdb) p iid_.page_no == end_.page_no
(gdb) p !iid_less(iid_, end_)
直接验证 iid_less 在错误情况下返回什么。
17. display — 每次停下自动打印
(gdb) display iid_.page_no
(gdb) display iid_.slot_no
(gdb) display end_.page_no
设好后每次 n/s/c 停下都会自动输出这些值,不用反复 p。
18. info display / undisplay <编号> — 管理自动显示
19. x / examine — 查看内存原始内容
(gdb) x/4d &iid_ # 以十进制打印 iid_ 结构体的 4 个 int
(gdb) x/s key # 打印 key 指向的字符串
六、调用栈分析
20. backtrace / bt — 查看调用栈
(gdb) bt
#0 IxScan::next() at ix_scan.cpp:24
#1 IndexScanExecutor::next() at executor_index_scan.cpp:56
#2 ...
知道谁调用了 IxScan::next,方便往上追踪问题来源。
21. frame <编号> — 切换到指定栈帧
(gdb) frame 1
(gdb) p key_range # 查看上层函数的局部变量
22. up / down — 在栈帧间移动
七、实战中更常用的:fprintf + GDB 配合
这次调试的真实流程是 "先 GDB 定位 → 再 fprintf 精细追踪":
- GDB 粗定位:在
IxScan::next设断点,打印iid_、end_,发现页号跳跃异常 - fprintf 精细追踪:因为 B+ 树扫描涉及数百次调用,GDB 单步太慢。加上
fprintf(stderr, ...)输出每次扫描的页号、槽位、next_leaf 等,一次性跑完后从日志分析整个遍历路径 - GDB 验证修复:改完代码后,设断点在
is_end()验证新逻辑是否正确
多文件大项目的核心思路:先用 bt + p 缩小范围到具体函数,再用 fprintf / 日志覆盖高频调用,最后用 GDB 验证修复。
八、一个典型的调试会话模板
# 1. 附加进程
$ gdb -p $(pgrep rmdb)
# 2. 加载符号(多文件项目可能需要)
(gdb) set directories /rmdb/db2026/src
(gdb) dir /rmdb/db2026/src/index
(gdb) dir /rmdb/db2026/src/execution
# 3. 设断点
(gdb) break IxScan::next
(gdb) break executor_index_scan.h:next
# 4. 设自动显示变量
(gdb) display iid_.page_no
(gdb) display end_.page_no
# 5. 继续运行,触发测试
(gdb) continue
# 6. 断点命中后,单步或继续
(gdb) p iid_.page_no # 看关键状态
(gdb) c # 继续到下一轮
# ... 反复 c + p 观察趋势 ...
# 7. 发现问题后,设条件断点精准捕获
(gdb) break IxScan::is_end
(gdb) condition 3 iid_.page_no == 5
# 8. 分离
(gdb) detach
(gdb) quit
这几条命令覆盖了多文件 C++ 项目 GDB 调试 80% 的场景,剩下的往往是 watch(内存监控)、catch throw(异常捕获)等进阶用法。

浙公网安备 33010602011771号