1. 程序崩溃了却找不到 coredump,问题到底卡在哪
线上服务半夜挂了,进程没了,日志里只有一行冷冰冰的Segmentation fault,你满心期待地去翻 coredump 文件,结果目录里空空如也。这种场景我遇到过不止一次,每次都要花时间从头排查一遍,后来干脆把整套排查思路整理成了自己的检查清单。
coredump 这个东西,说白了就是进程在收到某些信号(最典型的是 SIGSEGV、SIGABRT、SIGFPE、SIGILL 等)异常终止时,操作系统把进程当时的内存镜像、寄存器状态、调用栈等信息写成一个文件,供事后用调试器还原现场。它对于定位段错误、内存越界、空指针解引用这类问题几乎是不可替代的。但现实是,很多时候程序确实崩了,coredump 却死活不出现。原因可能出在内核参数、进程资源限制、信号处理、文件系统权限、容器环境、甚至程序自己把信号接管了等各个环节。
这篇文章面向的是经常和 Linux 服务打交道的后端开发、运维、SRE,以及正在学习系统编程的同学。我会把“coredump 为什么没生成”这个问题拆成几个层面,从内核到进程到文件系统逐层排查,给出可以直接抄作业的命令和配置,也会分享一些踩过的坑。读完你至少能做到:拿到一台机器,十分钟内定位 coredump 缺失的原因,并且知道怎么把它稳定地打开。
2. 先搞清楚 coredump 的生成链路
2.1 从信号到文件:内核做了什么
要排查问题,先得知道正常流程长什么样。当一个进程触发异常信号,内核在终止它之前会做一系列判断,只有全部通过才会真正写文件。这条链路大致是这样的:
- 进程收到致命信号,内核进入信号处理流程。
- 内核检查该信号的默认动作是否是“终止并产生 core”。
- 检查进程的
RLIMIT_CORE资源限制,如果为 0 则直接跳过。 - 检查
core_pattern配置,决定文件写到哪里、叫什么名字。 - 检查目标路径的权限、磁盘空间、文件系统类型。
- 检查进程是否设置了
PR_SET_DUMPABLE,以及是否有setuid等特殊属性。 - 全部通过后,内核把内存镜像写入目标文件。
这条链路上任何一环断了,coredump 都不会产生,而且大多数情况下内核不会给你任何显式报错,只是默默跳过。这就是为什么这个问题让人头疼——它不报错,你只能靠经验逐项排查。
2.2 为什么默认经常是关闭的
很多发行版默认把 core 文件大小限制设为 0,也就是ulimit -c显示为 0。原因很实际:coredump 文件可能非常大,一个占用几个 G 内存的进程,core 文件也能到几个 G,如果每个崩溃都写一份,磁盘很快就被撑爆了。所以在服务器场景下,默认关闭是一种保护。
另外,core_pattern的默认值在不同发行版、不同版本之间差异很大。有的写当前目录的core,有的走systemd-coredump交给系统服务管理,有的管道给apport。这些差异导致你在 A 机器上能拿到 core,换到 B 机器就找不到,非常容易让人误判。
提示:排查任何 coredump 问题,第一步永远是确认“当前这台机器、这个进程”的完整配置,而不是套用别的机器的经验。
3. 逐层排查:coredump 缺失的六大原因
3.1 资源限制 RLIMIT_CORE 为 0
这是最常见的原因,没有之一。RLIMIT_CORE决定了进程能产生的 core 文件最大字节数,单位是块(通常 1 块 = 512 字节,但ulimit -c显示的是 KB)。如果它是 0,内核直接跳过写文件。
查看方式:
ulimit -c # 输出 0 表示关闭,unlimited 表示不限制临时打开(只对当前 shell 及其子进程有效):
ulimit -c unlimited永久打开需要改/etc/security/limits.conf:
# 在文件末尾添加 * soft core unlimited * hard core unlimited改完之后要注意,limits.conf只对通过 PAM 登录的会话生效,对 systemd 管理的服务不一定生效。systemd 服务需要在 unit 文件里单独设置:
[Service] LimitCORE=infinity改完执行systemctl daemon-reload再重启服务。我见过不少人改了limits.conf却发现服务还是不出 core,就是漏了这一步。
3.2 core_pattern 配置指向了意想不到的地方
core_pattern决定 core 文件写到哪里。查看:
cat /proc/sys/kernel/core_pattern几种典型情况:
| core_pattern 值 | 含义 | 文件位置 |
|---|---|---|
core | 写到进程当前工作目录 | 进程 cwd 下的 core |
/var/crash/core.%p | 写到指定目录,带 PID | /var/crash/ |
|/usr/lib/systemd/systemd-coredump %P %u %g | 管道给 systemd | 由 systemd 管理 |
|/usr/share/apport/apport %p | 管道给 apport | 由 apport 管理 |
如果看到以|开头的值,说明 core 被管道给了某个用户态程序处理,文件不会出现在你预期的目录。这时候要去查那个程序把文件存哪了。比如 systemd-coredump 会把 core 存在/var/lib/systemd/coredump/,用coredumpctl list查看,coredumpctl dump <PID>导出。
临时修改:
echo '/tmp/core.%e.%p' > /proc/sys/kernel/core_pattern永久修改写/etc/sysctl.conf:
kernel.core_pattern = /tmp/core.%e.%pcore_pattern里支持的占位符很实用,常用的有%e(可执行文件名)、%p(PID)、%t(时间戳)、%u(UID)、%g(GID)、%h(主机名)。用%e.%p.%t组合可以避免同名覆盖。
注意:如果
core_pattern里包含目录路径,那个目录必须存在且进程有写权限,否则 core 写不进去。内核不会帮你创建目录。
3.3 进程工作目录不可写或磁盘空间不足
当core_pattern是相对路径(比如默认的core)时,文件会写到进程的当前工作目录。如果进程的 cwd 是一个只读目录,或者进程用户没有写权限,core 就写不出来。
排查方法:先确认进程的 cwd。
ls -l /proc/<PID>/cwd注意进程崩溃后/proc/<PID>就没了,所以这个信息要么在崩溃前抓,要么从服务的启动脚本里推断。systemd 服务可以用WorkingDirectory=指定,查 unit 文件即可。
磁盘空间也是常见坑。core 文件可能很大,df -h看一下目标分区剩余空间。如果空间不够,内核写一半失败,文件可能不完整甚至不生成。
3.4 进程被设置了不可 dump 属性
Linux 有个dumpable标志,控制进程是否允许产生 core。以下情况会把进程设为不可 dump:
- 进程执行了
setuid或setgid程序,且没有显式恢复 dumpable。 - 进程自己调用了
prctl(PR_SET_DUMPABLE, 0)。 - 进程的
/proc/<PID>/coredump_filter被改成了不包含任何内存类型。
查看:
cat /proc/<PID>/coredump_filter默认值通常是0x33,表示 dump 匿名私有映射、匿名共享映射、ELF 头等。如果被改成 0,就什么都不会写。
另外,/proc/sys/fs/suid_dumpable这个内核参数控制 setuid 程序的 core 行为:
- 0:不产生 core(默认)
- 1:产生 core,但文件归 root 所有
- 2:产生 core,文件归进程用户所有
如果程序涉及权限切换,这个参数要留意。
3.5 信号被程序自己接管了
有些程序会自己注册 SIGSEGV、SIGABRT 的处理函数,做自定义的崩溃处理,比如打印日志后调用exit()。这种情况下,信号被用户态处理函数消费掉了,内核不会走默认的“终止并 core”流程,自然没有 coredump。
判断方法:看程序源码里有没有signal(SIGSEGV, ...)或sigaction注册。如果是第三方库或框架,查文档确认。这种情况要么改代码让处理函数重新抛出信号(signal(sig, SIG_DFL); raise(sig);),要么接受没有 core 的事实,靠日志排查。
还有一种隐蔽情况:程序用了abort(),但在此之前把 SIGABRT 屏蔽了或者接管了,导致 abort 也没能触发 core。
3.6 容器环境下的额外限制
容器里排查 coredump 更麻烦,因为涉及宿主机内核和容器运行时的双重配置。
- 容器内的
ulimit -c可能被运行时限制,需要在启动容器时加--ulimit core=-1。 - 容器内的
core_pattern读的是宿主机的值(因为它是内核参数,namespace 隔离不一定覆盖),但写文件时受容器挂载的目录限制。 - 如果
core_pattern是管道形式,管道程序在宿主机上运行,容器里可能根本看不到文件。 - 容器以只读根文件系统启动时,core 写不进去。
容器场景我一般建议把core_pattern设成宿主机上的一个可写目录,并且用%e.%p命名,然后在容器里挂载对应目录,或者干脆在宿主机上收集。
4. 一套可复现的排查流程
4.1 五分钟快速定位清单
拿到一台机器,按这个顺序过一遍,基本能定位到原因:
ulimit -c看资源限制,是 0 就打开。cat /proc/sys/kernel/core_pattern看写到哪里,是不是管道。- 如果是管道,查对应服务(
coredumpctl list或 apport 的目录)。 - 确认进程 cwd 和目标目录可写、磁盘有空间。
cat /proc/<PID>/coredump_filter看过滤配置。- 检查程序是否自己接管了信号。
- 容器场景额外检查运行时 ulimit 和挂载。
4.2 用一个小程序验证配置是否生效
排查完配置,最好用一个最小程序验证一下,避免改了半天其实没生效。写一个必然崩溃的小程序:
// crash.c #include <stdlib.h> int main(void) { int *p = NULL; *p = 42; // 空指针解引用,触发 SIGSEGV return 0; }编译并运行:
gcc -g -o crash crash.c ulimit -c unlimited ./crash然后看当前目录有没有 core 文件。如果有,说明配置链路是通的。用 gdb 验证 core 可用:
gdb ./crash core (gdb) bt能打出调用栈就说明 core 完整可用。这个验证步骤非常重要,我每次调整完 core 配置都会跑一遍,确认无误再上生产。
4.3 参数选择背后的考量
core_pattern到底怎么设,其实有讲究。直接写core最简单,但多个进程崩溃会互相覆盖,而且散落在各个 cwd 里不好收集。我一般推荐这种:
kernel.core_pattern = /var/coredump/core.%e.%p.%t理由:/var/coredump是独立目录,方便统一管理和清理;%e带可执行文件名,一眼能看出是哪个程序;%p带 PID,避免同名覆盖;%t带时间戳,方便按时间排序。目录要提前创建并给足权限:
mkdir -p /var/coredump chmod 1777 /var/coredump1777是带 sticky 位的权限,任何用户都能写,但只能删自己的文件,适合多用户共享的 core 目录。
关于coredump_filter,默认的0x33对大多数场景够用。如果程序用了大量共享内存或文件映射,需要 dump 更多内容,可以调大这个值,但 core 文件也会相应变大。这个取舍要看具体排查需求。
5. 常见问题速查与避坑经验
5.1 典型问题对照表
| 现象 | 可能原因 | 排查命令 | 解决方式 |
|---|---|---|---|
| 完全没有 core 文件 | ulimit -c 为 0 | ulimit -c | 设为 unlimited |
| core 出现在奇怪目录 | core_pattern 是相对路径 | cat /proc/sys/kernel/core_pattern | 改成绝对路径 |
| 找不到 core 但配置正常 | 被管道程序接管 | 看 core_pattern 是否以 | 开头 | 查 coredumpctl 或 apport |
| core 文件大小为 0 | 磁盘满或权限不足 | df -h、ls -l | 清理空间、改权限 |
| 只有部分内存 | coredump_filter 限制 | cat /proc/<PID>/coredump_filter | 调整过滤值 |
| setuid 程序无 core | suid_dumpable 为 0 | cat /proc/sys/fs/suid_dumpable | 按需设为 1 或 2 |
| 容器内无 core | 运行时 ulimit 限制 | 查容器启动参数 | 加 --ulimit core=-1 |
5.2 几个容易忽略的坑
第一个坑是limits.conf对 systemd 服务无效。很多人改了/etc/security/limits.conf就以为万事大吉,结果 systemd 服务还是不出 core。systemd 有自己的资源限制体系,必须在 unit 文件里用LimitCORE=单独设置。这个坑我踩过,排查了半天才发现是 systemd 在“作祟”。
第二个坑是 core_pattern 的管道程序路径写错。如果core_pattern是|/some/program,而这个程序不存在或没有执行权限,内核会静默失败,core 既不写文件也不报错。改管道配置前一定要确认程序路径正确。
第三个坑是相对路径的 cwd 问题。服务由 systemd 启动时,默认 cwd 是/,而/通常不可写,所以core_pattern=core会失败。要么改成绝对路径,要么在 unit 里设WorkingDirectory=。
第四个坑是 core 文件被同名覆盖。默认的core命名,同一个目录下多次崩溃只留最后一份。用%e.%p.%t命名可以彻底避免。
第五个坑是调试信息缺失。core 文件有了,但 gdb 打不出有意义的栈,因为二进制没带-g编译,或者符号被 strip 了。生产环境建议保留一份带符号的二进制,core 用对应版本调试。
5.3 生产环境的取舍建议
生产环境开 coredump 要权衡磁盘和排查需求。我的做法是:核心服务开启,但用core_pattern写到独立分区,配合定时清理脚本,只保留最近 N 天的 core。非核心服务可以不开,靠日志和监控定位。
清理脚本可以很简单:
find /var/coredump -name 'core.*' -mtime +7 -delete配合 cron 每天跑一次。这样既保证了排查能力,又不会把磁盘撑爆。
另外,core 文件可能包含敏感数据(内存里的密钥、用户信息等),权限要控制好,别用1777这种谁都能读的权限。生产环境建议用0700加专用用户,或者干脆用管道程序做脱敏处理。
6. 我在实际排查中的几点体会
coredump 这个问题,表面看是“文件没生成”,本质是“内核在某个环节拒绝了写文件”。排查的关键不是记住所有配置项,而是理解那条从信号到文件的链路,然后顺着链路逐环确认。我现在的习惯是,遇到 core 缺失,先跑一遍第 4 节那个最小验证程序,确认基础配置通不通,再去看具体进程的特殊性。这样能快速区分是“全局配置问题”还是“这个进程自己的问题”。
还有一点,别迷信默认值。不同发行版、不同版本、不同容器运行时,默认配置差异很大,A 机器能用的经验搬到 B 机器经常失效。每次换环境,花两分钟确认一下ulimit -c和core_pattern,能省下后面几小时的排查时间。这个习惯养成之后,我处理崩溃问题的效率至少提升了一倍。
最后分享一个小技巧:如果实在搞不定 core 文件,又急需定位崩溃点,可以用gdb直接 attach 到进程,或者用catchsegv、libSegFault这类工具在崩溃时打印栈。虽然不如 core 完整,但应急够用。不过这些都是权宜之计,把 coredump 配置搞对才是长久之道。