GDB调试工具

系统化的学习 才能让你成长

碎片化的学习 才会让你焦虑

GDB是什么

archer

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 registersframe 等命令分析崩溃位置和上下文。

生成条件

  • 用户需要设置 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 main
break test.cpp:10
在函数或行号设置断点
continue (c) continue 继续执行直到下一个断点
next (n) next 单步执行,不进入函数内部
step (s) step 单步执行,会进入函数内部
finish finish 执行到当前函数返回
print (p) print x
print sizeof(arr)
打印变量或表达式的值
info locals info locals 显示当前栈帧的所有局部变量
info breakpoints(info b) info breakpoints 列出所有断点
delete (d) delete 1 删除指定编号的断点
disable / enable disable 2 禁用/启用某个断点
list (l) list
list main
显示当前位置或指定位置的源代码
backtrace (bt) backtrace 显示当前调用栈(函数调用链)
frame (f) frame 2 切换到调用栈的第 2 帧
up / down up
down 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=5end_.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 精细追踪"

  1. GDB 粗定位:在 IxScan::next 设断点,打印 iid_end_,发现页号跳跃异常
  2. fprintf 精细追踪:因为 B+ 树扫描涉及数百次调用,GDB 单步太慢。加上 fprintf(stderr, ...) 输出每次扫描的页号、槽位、next_leaf 等,一次性跑完后从日志分析整个遍历路径
  3. 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(异常捕获)等进阶用法。

posted @ 2026-05-31 20:07  韩熙隐ario  阅读(21)  评论(0)    收藏  举报