3. GDB 进阶:条件断点、观察点、回溯跟踪、Core Dump 分析
调试这件事,说白了就是跟代码的「灵魂」对话。基础断点只能让你停在某个位置,但真正的问题往往藏在特定条件下。这一章,我们来聊聊 GDB 那些真正能救命的高级功能。
3.1 条件断点:只在需要的时候停下来
普通断点就像在门口装了个铃铛,谁路过都响。条件断点呢?它只在你指定的「嫌疑人」经过时才触发。
我早期做电机控制项目时,有个 bug 只在电流超过 3A 时出现。普通断点根本没法用——每次循环都停,手都按麻了。条件断点一次搞定。
3.1.1 基本用法
(gdb) break motor_control.c:128 if current > 3.0
(gdb) break 128 if current > 3.0 && speed < 100
(gdb) break func_name if flag == 1
条件表达式支持 C 语言的所有比较和逻辑运算。你甚至可以调用函数:
(gdb) break packet_handler if check_crc(packet) == 0
3.1.2 条件断点的坑
我曾经犯过一个错:在条件里写了个 if ptr != NULL,结果程序跑飞了。为什么?因为 ptr 是个全局指针,被中断服务程序改了。条件断点每次命中都会重新求值,如果表达式本身有副作用,后果很严重。
break x if count++ > 10 这种写法,调试完记得删掉。
3.2 观察点:盯住变量的每一次变化
断点停的是「位置」,观察点盯的是「数据」。当某个变量被意外修改时,观察点能直接告诉你:谁干的?
3.2.1 硬件观察点 vs 软件观察点
| 类型 | 命令 | 原理 | 适用场景 |
|---|---|---|---|
| 硬件观察点 | watch var |
利用 CPU 调试寄存器 | 变量被写、读、执行 |
| 软件观察点 | awatch var |
GDB 单步模拟 | 硬件寄存器不够时 |
| 读写观察点 | rwatch var |
读操作也触发 | 排查数据竞争 |
硬件观察点数量有限(通常 4-8 个),但效率极高。软件观察点没有数量限制,但会让程序慢得像蜗牛。
3.2.2 实战案例
我记得有个项目,串口接收缓冲区的数据总被莫名其妙地覆盖。用 watch rx_buffer[0] 一盯,发现是 DMA 传输完成中断里有个指针偏移算错了。观察点直接定位到那行代码,省了我两天时间。
(gdb) watch rx_buffer[0]
Hardware watchpoint 1: rx_buffer[0]
(gdb) continue
Continuing.
Hardware watchpoint 1: rx_buffer[0]
Old value = 0xAA
New value = 0x55
dma_handler.c:47 rx_buffer[offset] = dma_data;
watch var if var > 100 可以避免大量无意义的触发。
3.3 回溯跟踪:事故现场的「黑匣子」
程序崩溃时,回溯跟踪就是你的黑匣子。它能告诉你:程序是怎么走到这一步的?
3.3.1 backtrace 命令
(gdb) backtrace
#0 fault_handler () at exception.c:15
#1 0x08001234 in task_scheduler () at scheduler.c:88
#2 0x08004567 in main_loop () at main.c:42
#3 0x08000100 in Reset_Handler () at startup.s:78
每一帧都对应一个函数调用。从 #0 到 #3,就是程序从启动到崩溃的完整路径。
3.3.2 帧信息查看
(gdb) frame 1 # 切换到第 1 帧
(gdb) info args # 查看函数参数
(gdb) info locals # 查看局部变量
(gdb) list # 查看当前帧的源代码
我曾经遇到一个栈溢出问题,回溯跟踪显示递归深度达到了 2000 多层。一看代码,有个递归函数忘了写终止条件。这种问题,没有回溯跟踪根本无从下手。
backtrace full 强制显示更多信息。
3.4 Core Dump:让程序「死前」留下遗言
Core Dump 是程序崩溃时内存的快照。有了它,你可以在事后慢慢分析,不用守着现场。
3.4.1 开启 Core Dump
# 设置 Core Dump 文件大小(不限)
$ ulimit -c unlimited
# 查看当前设置
$ ulimit -c
unlimited
嵌入式 Linux 系统里,还要确保 Core Dump 的路径可写:
$ echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern
3.4.2 分析 Core Dump
$ arm-none-eabi-gdb my_program.elf core.12345
(gdb) backtrace
(gdb) info registers
(gdb) x/20x $sp # 查看栈内容
Core Dump 里包含了程序崩溃瞬间的所有信息:寄存器值、栈内容、堆数据。你甚至可以查看全局变量的值。
3.4.3 嵌入式环境下的特殊处理
裸机或 RTOS 环境下,没有操作系统帮你生成 Core Dump。我的做法是:在 HardFault_Handler 里手动保存现场。
void HardFault_Handler(void) {
// 保存寄存器到预定义缓冲区
save_context_to_ram();
// 触发软件复位
NVIC_SystemReset();
}
复位后,通过调试器读取 RAM 中的保存数据,就能还原崩溃现场。这招我在好几个项目里用过,屡试不爽。
3.5 知识体系总览
下面这张图,把本章的核心知识点串起来了:
3.6 实战技巧总结
最后,分享几个我多年积累的实用技巧:
- 条件断点优先用简单比较:复杂表达式会拖慢执行,实时系统里尤其明显
- 观察点用完就删:
info watchpoints查看,delete num删除,别留着占硬件资源 - 回溯跟踪配合日志:在关键函数入口加
printf,崩溃时回溯 + 日志双管齐下 - Core Dump 要保留原始 ELF:strip 过的文件没法做符号解析,等于白费