2、GDB核心概念:断点、观察点、捕获点、调试符号、核心转储文件
好,咱们正式开始啃GDB的核心概念。说实话,很多新手一上来就敲break main,然后觉得GDB不过如此。但真正到了系统编程层面,你会发现这几个概念才是调试的根基。我当年在调试一个多线程死锁问题时,就是靠观察点和捕获点组合才定位到问题,单靠断点根本搞不定。
2.1 断点(Breakpoint)——最基础的武器
断点,说白了就是让程序在指定位置停下来。你想想看,程序跑得飞快,你肉眼根本跟不上,只有让它停下,你才能看清现场。
设置断点的方式有很多,我列几个最常用的:
(gdb) break main # 在main函数入口
(gdb) break file.c:42 # 在file.c的第42行
(gdb) break func_name # 在某个函数入口
(gdb) break *0x400520 # 在某个内存地址(高级用法)
break 文件名:行号这种形式。因为同一个函数名可能出现在多个文件中,用行号更精确。
断点其实分好几种类型:
- 普通断点:遇到就停,最简单
- 条件断点:满足条件才停,比如
break foo if x > 10 - 临时断点:停一次就自动删除,用
tbreak命令 - 硬件断点:依赖CPU硬件支持,数量有限但效率高
2.2 观察点(Watchpoint)——盯住变量的变化
观察点和断点最大的区别是什么?断点盯的是代码位置,观察点盯的是数据变化。你想想看,一个全局变量被莫名其妙改了,你难道要满世界下断点?用观察点就对了。
(gdb) watch my_var # 监控my_var被写入
(gdb) rwatch my_var # 监控my_var被读取
(gdb) awatch my_var # 监控my_var被读写
我记得有一次,一个结构体里的flag字段总在某个时刻变成0xDEAD,但代码里明明没有赋值操作。我下了个watch flag,结果发现是内存越界写导致的——隔壁数组越界,恰好踩到了这个字段。
2.3 捕获点(Catchpoint)——捕捉事件
捕获点是个好东西,但很多人不知道。它不是停在你指定的代码行,而是停在某种事件发生时。比如:
(gdb) catch syscall write # 捕获write系统调用
(gdb) catch throw # 捕获C++异常抛出
(gdb) catch fork # 捕获fork调用
(gdb) catch signal SIGSEGV # 捕获段错误信号
嗯,这里要注意:捕获点在调试动态链接库、信号处理、系统调用时特别有用。我曾在调试一个网络服务时,用catch syscall accept来监控所有新连接,比在accept函数里下断点更底层、更可靠。
2.4 调试符号(Debug Symbols)——没有它,寸步难行
调试符号是什么?说白了就是编译器在可执行文件里额外塞进去的「地图」。没有这张地图,你看到的全是地址和汇编,根本不知道变量名叫什么、函数参数是什么。
编译时加-g选项就能生成调试符号:
gcc -g -o my_prog my_prog.c
调试符号包含的信息:
| 信息类型 | 说明 | 没有它你会怎样 |
|---|---|---|
| 行号映射 | 地址对应到源代码行号 | 只能看汇编,不知道对应哪行代码 |
| 变量信息 | 变量名、类型、作用域 | 无法用print my_var,只能看内存地址 |
| 函数信息 | 函数名、参数、返回值类型 | 无法用backtrace看到函数名 |
| 类型信息 | 结构体、联合体、枚举定义 | 无法打印结构体成员 |
-g,结果可执行文件大了3倍。但反过来,线上环境如果遇到coredump,没有调试符号你根本分析不了。所以我的做法是:发布时用-g -O2编译,然后用strip命令去掉符号,同时保留一份带符号的副本。
2.5 核心转储文件(Core Dump)——事故现场还原
核心转储文件,就是程序崩溃时内存的「快照」。你想想看,程序挂了,现场没了,你怎么查?有了core文件,你可以像调试活着的程序一样,查看崩溃时的变量值、调用栈、寄存器状态。
首先,确保系统允许生成core文件:
ulimit -c unlimited # 取消core文件大小限制
echo "/tmp/core.%p" > /proc/sys/kernel/core_pattern # 设置core文件路径
然后用GDB分析core文件:
gdb ./my_prog /tmp/core.12345
(gdb) bt # 查看崩溃时的调用栈
(gdb) info registers # 查看寄存器状态
(gdb) print my_var # 查看变量值
(gdb) frame 3 # 切换到调用栈的第3层
我记得有一次线上服务半夜挂了,没有core文件,只能靠日志猜。后来我强制要求所有服务必须开启core dump,并且用core_pattern把文件命名带上进程名和时间戳。从那以后,线上崩溃的排查效率提升了不止一个档次。
2.6 知识体系总览
这几个概念之间的关系,我画了张图帮你理一理:
这张图把五个核心概念串起来了。你想想看,断点、观察点、捕获点决定了「什么时候停」,调试符号决定了「停下来后能看到什么」,核心转储则是「程序挂了之后还能不能复盘」。这五个东西配合起来,你才能高效地定位问题。
好了,这一章的核心概念就讲到这里。记住,工具是死的,思路是活的。下次你遇到bug,先想想:该用断点还是观察点?需不需要捕获系统调用?有没有core文件可以分析?把这些概念用熟了,调试效率至少翻一倍。