Breakpad 与 Kernel Coredump 用法
线上环境排查崩溃,核心问题是:崩溃现场能不能保住? 本文介绍两种主流方案,Google Breakpad 和 Kernel Coredump,分别说明它们的工作原理、局限性,以及如何选型。
Breakpad:用户态信号处理方案
Breakpad 的工作方式是:在程序启动时注册信号处理函数,当进程收到致命信号(SIGSEGV、SIGABRT 等)时,在进程终止之前,将调用栈、寄存器状态、关键内存区域写入一个 minidump 文件。
执行流程如下:
程序启动 → 注册信号处理函数
↓
程序崩溃 → 内核发送信号(如 SIGSEGV)
↓
Breakpad 信号处理函数接管
├─ 进程暂停但尚未终止,内存状态仍可访问
├─ 遍历调用栈、收集寄存器状态
└─ 写入 .dmp 文件(通常只有几 MB)
↓
恢复默认信号处理,重新 raise 信号 → 进程退出
最小接入示例:
#include "client/linux/handler/exception_handler.h"
bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
void* context, bool succeeded) {
// 注意:这里只能用 async-signal-safe 的函数
const char msg[] = "Crash dump saved\n";
write(STDERR_FILENO, msg, sizeof(msg) - 1);
return succeeded;
}
int main() {
google_breakpad::MinidumpDescriptor descriptor("/var/crash");
google_breakpad::ExceptionHandler eh(descriptor, NULL,
DumpCallback, NULL,
true, -1);
run_application();
return 0;
}Breakpad 的优势在于:dump 文件小(几 MB vs. 动辄 GB 的 core 文件)、跨平台、支持过滤敏感数据、便于离线分析。但它的本质局限是运行在崩溃进程自身的地址空间内,依赖进程状态的完整性。
Breakpad 的失效场景
Breakpad 运行在用户态,依赖进程自身的执行环境。当进程状态已经严重损坏时,Breakpad 无法正常工作。具体有这些场景:
回调中二次崩溃
如果 DumpCallback 内部触发了新的段错误,内核不允许嵌套处理同一信号,进程立即终止,dump 文件可能不完整。因此回调中禁止使用
malloc、printf、std::string等非信号安全函数,只能使用write、open、close、_exit等 async-signal-safe 函数。**SIGKILL 和 OOM Killer*
kill -9和 OOM Killer 发送的是 SIGKILL,该信号不经过用户态信号处理函数,Breakpad 没有执行机会。内存严重损坏
double free 破坏堆元数据、buffer overflow 覆盖栈帧链表等情况下,Breakpad 遍历调用栈时读取到的数据本身就是错误的,生成的 dump 不完整或具有误导性。
多线程竞态
多个线程同时触发致命信号,竞争写 minidump,可能产出损坏的文件。
此外还有一些环境问题:dump 目录无写权限、磁盘空间不足、容器沙箱限制了系统调用等。总之,Breakpad 要求进程的执行环境仍然基本可用。
Kernel Coredump:内核态转储方案
Kernel Coredump 由内核直接完成:内核捕获到致命信号后,从内核态读取进程的整个虚拟地址空间,写成 ELF 格式的 core 文件。进程自身的代码不参与这个过程。
这种设计上的差异决定了它在极端场景下的可靠性:
栈溢出。 内核使用独立的内核栈执行 dump 操作,不依赖用户栈。
内存损坏。
内核逐页遍历进程的虚拟内存区域(VMA),不依赖堆的元数据结构。遇到无法读取的页面,copy_from_user
返回错误后跳过,继续处理剩余区域。
二次崩溃。 不存在此问题。内核代码运行在独立的地址空间,与用户进程完全隔离。
权限。 内核以特权模式运行,不受 ulimit 和文件系统权限的约束。
// 内核 do_coredump 的核心逻辑(极度简化)
do_coredump() {
struct mm_struct *mm = current->mm;
// 遍历所有虚拟内存区域,逐页写入
for (vma = mm->mmap; vma; vma = vma->vm_next) {
dump_range(vma->vm_start, vma->vm_end);
}
}Kernel Coredump 的代价:
| 问题 | 说明 |
|---|---|
| 文件巨大 | 进程使用 10GB 内存,core 文件就是 10GB |
| 生成慢 | 写入大型 core 文件可能耗时数十秒 |
| 隐私风险 | 完整内存转储,strings core \| grep password
可提取敏感数据 |
| 分析门槛高 | 需要原始可执行文件 + 调试符号,且必须在同架构环境下用 gdb 分析 |
需要注意的是,SIGKILL 信号 Kernel Coredump 同样无法处理。SIGKILL 的语义是”立即终止,不可捕获、不可忽略”,内核不会为其生成 core 文件。
对比
| 维度 | Breakpad | Kernel Coredump |
|---|---|---|
| 执行环境 | 用户态,进程内 | 内核态,进程外 |
| 栈溢出 | 通常失败(除非配置了备用栈) | 正常工作 |
| 内存损坏 | 可能失败或产出错误数据 | 不受影响 |
| 二次崩溃 | 致命 | 不存在此问题 |
| SIGKILL | 无法捕获 | 同样无法处理 |
| 文件大小 | 几 MB | 可达 GB 级别 |
| 生成速度 | 快 | 慢 |
| 跨平台 | 支持 | 平台相关 |
| 隐私控制 | 可过滤敏感数据 | 全量转储 |
| 离线分析 | 有专用工具链(minidump_stackwalk) | 需要匹配的调试环境 |
退出码与信号的对应关系
进程被信号终止时,退出码 = 128 + 信号编号。通过退出码可以直接判断终止原因:
| 退出码 | 信号 | 含义 | Breakpad 能捕获? |
|---|---|---|---|
| 132 | SIGILL | 非法指令 | 能 |
| 133 | SIGTRAP | 断点陷阱 | 能 |
| 134 | SIGABRT | 主动 abort | 能 |
| 135 | SIGBUS | 总线错误 | 能 |
| 136 | SIGFPE | 浮点异常 | 能 |
| 137 | SIGKILL | 强制终止 | 不能 |
| 139 | SIGSEGV | 段错误 | 能 |
Breakpad 在生成 minidump 后,会恢复默认信号处理并重新
raise(sig),使进程以原始信号退出。因此外部观察到的退出码与未安装
Breakpad 时一致,监控脚本无需特殊适配。
监控脚本示例:
#!/bin/bash
./my_program
EXIT_CODE=$?
case $EXIT_CODE in
0) echo "正常退出" ;;
134) echo "SIGABRT - 检查 assert 或主动 abort" ;;
137) echo "SIGKILL - 被强杀,无 minidump" ;;
139) echo "SIGSEGV - 段错误,查看 minidump" ;;
*)
if [ $EXIT_CODE -ge 128 ]; then
echo "被信号 $((EXIT_CODE - 128)) 终止"
else
echo "异常退出,退出码:$EXIT_CODE"
fi ;;
esac生产环境选型建议
大多数场景下 Breakpad 是首选。 文件体积小、网络传输快、隐私可控,适合线上大规模部署。
推荐的做法是两者同时启用:Breakpad 作为主力生成轻量的 minidump 用于快速定位问题,Kernel Coredump 作为兜底方案,覆盖 Breakpad 失效的场景。
int main() {
// 启用 kernel coredump 作为兜底
struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit(RLIMIT_CORE, &rl);
// 初始化 Breakpad
setup_alternate_stack(); // 配置备用信号栈
google_breakpad::MinidumpDescriptor descriptor("/var/crash");
google_breakpad::ExceptionHandler eh(descriptor, NULL,
DumpCallback, NULL,
true, -1);
return run_application();
}配套的运维配置:
# dump 目录权限
mkdir -p /var/crash && chown myapp:myapp /var/crash
# kernel coredump 路径配置
echo "/var/crash/core.%e.%p.%t" | sudo tee /proc/sys/kernel/core_pattern
# 自动清理(保留 7 天)
find /var/crash -name "*.dmp" -mtime +7 -delete
# 构建时生成符号文件,供离线分析
dump_syms ./myapp > myapp.sym最后,定期验证 dump 功能是否正常。 可以通过
eh.WriteMinidump() 主动触发一次
dump,确认目录权限、磁盘空间、符号文件等整条链路都是可用的。