跑得好好的程序突然崩溃,终端显示
Segmentation fault (core dumped),没有日志,没有报错堆栈,怎么排查?
目录
- [一、什么是 Core Dump?](#一、什么是 Core Dump?)
- [二、什么时候会触发 Core Dump?](#二、什么时候会触发 Core Dump?)
- [三、从 0 到 1 开启 Core Dump](#三、从 0 到 1 开启 Core Dump)
-
- 第一步:检查当前是否开启
- 第二步:临时开启(仅当前终端会话有效)
- 第三步:永久开启(所有用户生效)
- [第四步:配置 core 文件的存储路径和命名规则](#第四步:配置 core 文件的存储路径和命名规则)
- 四、调试前的必备准备:编译时加调试符号
- [五、实战:用 GDB 分析 Core Dump](#五、实战:用 GDB 分析 Core Dump)
- [六、现代 Linux 的 Core Dump 管理:systemd-coredump](#六、现代 Linux 的 Core Dump 管理:systemd-coredump)
- 七、生产环境避坑指南
-
- [1. 安全问题](#1. 安全问题)
- [2. 磁盘空间](#2. 磁盘空间)
- [3. 找不到 core 文件?排查步骤](#3. 找不到 core 文件?排查步骤)
- [4. GDB 加载 core 文件显示 `??` 没有行号?](#4. GDB 加载 core 文件显示
??没有行号?)
一、什么是 Core Dump?
字面含义
- Core:早期计算机的内存叫"磁芯内存"(Magnetic Core Memory),这个称呼沿用至今,代指程序的内存状态。
- Dumped:转储、倾倒。
Core Dump 就是当程序发生严重错误崩溃时,操作系统在进程退出前,将其当时的内存状态、寄存器值、函数调用栈、局部变量等所有运行信息,完整保存到一个文件(通常叫 core)中的过程。
核心价值
- 事后调试:无需复现崩溃场景,直接分析 core 文件就能找到崩溃点
- 保留现场:即使程序已经退出,也能查看崩溃瞬间的所有变量值、函数调用链
- 排查偶发 Bug:对于随机崩溃的问题,只要拿到 core 文件就能稳定分析,不用再"等它再崩一次"
二、什么时候会触发 Core Dump?
当程序收到以下未捕获的信号时,系统会自动触发 core dump:
| 信号 | 含义 | 常见触发场景 |
|---|---|---|
| SIGSEGV | 段错误 | 空指针解引用、数组越界、访问已释放内存 |
| SIGABRT | 主动中止 | 调用 abort()、assert 断言失败 |
| SIGBUS | 总线错误 | 内存对齐错误、硬件故障 |
| SIGFPE | 算术异常 | 整数除以 0、浮点运算溢出 |
| SIGILL | 非法指令 | 执行了 CPU 不支持的指令、二进制文件损坏 |
三、从 0 到 1 开启 Core Dump
第一步:检查当前是否开启
bash
ulimit -c
- 输出
0:表示关闭,不会生成 core 文件 - 输出
unlimited或具体数字:表示已开启
第二步:临时开启(仅当前终端会话有效)
bash
ulimit -c unlimited
第三步:永久开启(所有用户生效)
编辑 /etc/security/limits.conf,添加以下两行:
* soft core unlimited
* hard core unlimited
如果崩溃的程序是 systemd 管理的服务,需要在对应的 service 文件中添加:
[Service]
LimitCORE=infinity
第四步:配置 core 文件的存储路径和命名规则
默认的 core 文件可能在当前目录生成,也可能被 systemd 接管。如果想自定义存储位置和文件名,修改内核参数 core_pattern:
临时生效:
bash
sudo sysctl -w kernel.core_pattern=/var/crash/core-%e-%p-%t
永久生效 :
在 /etc/sysctl.conf 中添加:
kernel.core_pattern=/var/crash/core-%e-%p-%t
常用占位符说明:
| 占位符 | 含义 |
|---|---|
| %e | 可执行文件名 |
| %p | 进程 PID |
| %t | 崩溃时的时间戳(秒) |
| %u | 进程用户 ID |
| %s | 触发崩溃的信号编号 |
| %h | 主机名 |
注意:提前创建存储目录并设置写权限,否则 core 文件无法生成:
bash
sudo mkdir -p /var/crash
sudo chmod 777 /var/crash
四、调试前的必备准备:编译时加调试符号
这一步非常重要! 如果编译时没有添加调试信息,gdb 只能看到内存地址,无法显示对应的源码行号和变量名,core 文件基本等于废了。
编译时必须添加 -g 参数,建议同时用 -O0 关闭优化,避免优化后代码行号和实际执行位置对不上:
bash
gcc -g -O0 -o my_app my_app.c
如果是 CMake 项目,设置构建类型为 Debug 即可:
cmake
set(CMAKE_BUILD_TYPE Debug)
五、实战:用 GDB 分析 Core Dump
1. 准备示例代码
我们写一个会触发段错误的程序:
c
// crash_demo.c
#include <stdio.h>
int main() {
int *ptr = NULL;
printf("即将访问空指针...\n");
*ptr = 42; // 空指针解引用,触发 SIGSEGV
return 0;
}
2. 编译并运行
bash
gcc -g -O0 -o crash_demo crash_demo.c
./crash_demo
输出:
即将访问空指针...
段错误 (核心已转储)
3. 找到 core 文件
根据刚才配置的 core_pattern,core 文件应该在 /var/crash/ 目录下,文件名类似 core-crash_demo-12345-1629876543。
4. 用 GDB 加载分析
bash
gdb ./crash_demo /var/crash/core-crash_demo-12345-1629876543
进入 gdb 交互界面后,常用命令如下:
查看崩溃调用栈
gdb
(gdb) bt
#0 0x0000555555555149 in main () at crash_demo.c:6
6 *ptr = 42; // 空指针解引用,触发 SIGSEGV
栈顶的 #0 就是崩溃发生的具体位置,直接定位到第 6 行代码。
查看变量值
gdb
(gdb) print ptr
$1 = (int *) 0x0
可以看到 ptr 的值是 0x0,确认是空指针解引用导致的问题。
其他常用命令
gdb
frame 0 # 切换到第 0 层栈帧(崩溃点)
info locals # 查看当前函数所有局部变量
list # 查看崩溃点附近的源码
info registers # 查看 CPU 寄存器状态
如果是多线程程序,还可以用:
gdb
info threads # 查看所有线程状态
thread 2 # 切换到第 2 个线程
thread apply all bt # 打印所有线程的调用栈
六、现代 Linux 的 Core Dump 管理:systemd-coredump
现在很多主流发行版(Ubuntu 20.04+、CentOS 8+、Debian 11+)默认使用 systemd-coredump 接管 core 文件,不会在当前目录生成 core 文件,而是统一存储到 /var/lib/systemd/coredump/ 下,用 coredumpctl 工具管理:
bash
coredumpctl list # 查看所有 core dump 记录
coredumpctl info # 查看最近一次崩溃的详情
coredumpctl debug # 直接用 gdb 调试最近的崩溃
coredumpctl debug <PID> # 调试指定 PID 的 core 文件
七、生产环境避坑指南
1. 安全问题
core 文件里可能包含密码、API 密钥、用户数据等敏感信息,绝对不能传到公开的地方,分析完成后及时删除,建议设置文件权限为 600:
bash
chmod 600 /var/crash/core-*
2. 磁盘空间
core 文件的大小和程序占用的内存差不多,可能达到几 GB 甚至几十 GB,容易把磁盘撑爆。可以设置大小限制:
bash
ulimit -c 102400 # 限制 core 文件最大为 100MB
或者定期清理旧的 core 文件:
bash
find /var/crash -name "core.*" -mtime +7 -delete
3. 找不到 core 文件?排查步骤
- 检查
ulimit -c是否开启 - 检查
core_pattern配置的路径是否有写权限 - 检查磁盘空间是否满了
- 如果是 setuid 程序,需要设置
fs.suid_dumpable=2
4. GDB 加载 core 文件显示 ?? 没有行号?
- 检查编译时是否加了
-g参数 - 检查加载的可执行文件和崩溃时的是不是同一个版本,不能重新编译后再分析旧的 core 文件,否则符号表对不上