线上服务跑得好好的,突然报警:进程异常退出,产生 core 文件。 日志里只有一句冷冰冰的 Segmentation fault (core dumped),重启能暂时缓解,但没人敢保证下次不会再炸。
这种场景,大多数 C/C++ 工程师都不陌生。真正拉开差距的,不是“会不会用 GDB”,而是能不能在最短时间内,从 core 文件里还原出问题现场。
这篇文章不讲花活,只讲一次真实可落地的 GDB 崩溃分析流程。
一、先搞清楚:Core Dump 到底是什么
当进程因为非法内存访问、除零、assert 失败等原因异常终止时,内核会把进程当时的内存、寄存器、线程栈等信息保存成一个 core 文件。
它不是日志,而是一个“时间被冻结的进程快照”。
前提条件很重要: 如果线上没生成 core,后面的一切都是空谈。
ulimit -c
- 输出
0:不会生成 core
- 输出
unlimited 或正数:允许生成
生产环境通常需要显式打开,并指定 core 路径:
ulimit -c unlimited
echo \"/data/core/core.%e.%p\" > /proc/sys/kernel/core_pattern
二、第一步:用对 GDB 的打开方式
永远不要只用 core 文件启动 GDB。
正确姿势:
gdb ./your_binary core.xxx
原因很简单: 没有可执行文件,GDB 无法还原符号信息,看到的全是地址。
如果线上是 release 版本,记住一个底线要求:
必须保留带符号的 binary 或单独的 debug 文件
否则你只能对着一堆 ?? 发呆。
三、最关键的一条命令:bt
进入 GDB 后,第一件事不是看代码,而是:
bt
或更完整一点:
bt full
一份“健康”的 backtrace,至少应该包含:
例如:
#0 std::vector<int>::operator[] (this=0x0, __n=3)
#1 Foo::process() at foo.cpp:42
#2 worker_thread() at worker.cpp:88
光这一眼,已经能判断出八成问题:空指针调用成员函数。
四、栈不是一条线,而是一棵树
很多人只看 #0,这是典型的新手误区。
真正有价值的,往往在 #1 ~ #3。
建议的阅读顺序:
#0:确认“死因”
#1:找到“谁把你送走的”
#2/#3:定位业务入口
例如一个经典的 use-after-free:
#0 strlen () from libc.so.6
#1 std::string::length()
#2 Logger::log(std::string const&)
#3 RequestHandler::handle()
这时候不要被 strlen 迷惑,它只是最后一刀。 真正的问题是:string 内部指针已经悬空。
五、多线程程序,别忘了看线程
线上服务几乎都是多线程的。
info threads
切换线程:
thread 3
bt
很多 crash 表面发生在“某个线程”,但真正的根因可能是:
- 另一个线程提前释放了内存
- 没加锁的共享对象
- 生命周期管理错误
如果 core 里只有一个线程在跑,而其他线程都卡在锁上,这本身就是重要线索。
六、结合源码,不要迷信“这一行崩了”
GDB 指到的源码行,只说明在那一刻访问了非法内存,不等于那一行写错了。
一个典型例子:
void Foo::process()
{
if (ptr_) {
ptr_->do_something();
}
}
崩在 ptr_->do_something(),但真正的问题可能是:
ptr_ 在别的线程被释放
ptr_ 指向的对象已析构
- 对象内部状态被破坏
GDB 告诉你“哪里死的”,不是“为什么会死”。
七、善用寄存器和局部变量
当 backtrace 不够直观时,可以继续深挖:
frame 1
info locals
info args
很多线上问题,最终都是靠一个“异常值”揪出来的:
- vector size 为负
- index 明显越界
- 指针值看起来像野地址(0xdeadbeef、0xcccccccc)
这些都是真实踩过的坑。
八、如果 backtrace 已经烂掉了怎么办
你可能会遇到:
这通常意味着更严重的内存破坏。
这类问题,core 只能提供方向,真正的解法往往是:
- 结合 ASan / UBSan 复现
- 加强运行时检查
- 缩小问题范围后重新抓 core
别指望一次 GDB 就“通灵成功”。
九、一个工程师该形成的习惯
真正成熟的工程实践里,GDB 分析不是“救火技能”,而是日常能力:
- 编译选项里默认保留符号
- 崩溃第一时间拉 core,而不是盲目重启
- 用堆栈说话,而不是猜
当你能冷静地从 core 文件里,把事故过程一步步还原出来,你对代码的掌控力,会明显上一个台阶。
线上崩溃不可怕,可怕的是看不懂它留下的线索。 GDB 和 core dump,本质上是在帮你复盘一场已经发生的事故。
看得懂的人,问题就已经解决了一半。
如果你在调试 C++ 程序或分析复杂系统问题上有什么心得,欢迎来 云栈社区 交流分享。