☰
Linux程序崩溃无coredump?六大原因与排查指南
2026/10/19 4:27:14 网站建设 项目流程

1. 程序崩溃了却找不到 coredump,问题到底卡在哪

线上服务半夜挂了,进程没了,日志里只有一行冷冰冰的Segmentation fault,你满心期待地去翻 coredump 文件,结果目录里空空如也。这种场景我遇到过不止一次,每次都要花时间从头排查一遍,后来干脆把整套排查思路整理成了自己的检查清单。

coredump 这个东西,说白了就是进程在收到某些信号(最典型的是 SIGSEGV、SIGABRT、SIGFPE、SIGILL 等)异常终止时,操作系统把进程当时的内存镜像、寄存器状态、调用栈等信息写成一个文件,供事后用调试器还原现场。它对于定位段错误、内存越界、空指针解引用这类问题几乎是不可替代的。但现实是,很多时候程序确实崩了,coredump 却死活不出现。原因可能出在内核参数、进程资源限制、信号处理、文件系统权限、容器环境、甚至程序自己把信号接管了等各个环节。

这篇文章面向的是经常和 Linux 服务打交道的后端开发、运维、SRE,以及正在学习系统编程的同学。我会把“coredump 为什么没生成”这个问题拆成几个层面,从内核到进程到文件系统逐层排查,给出可以直接抄作业的命令和配置,也会分享一些踩过的坑。读完你至少能做到:拿到一台机器,十分钟内定位 coredump 缺失的原因,并且知道怎么把它稳定地打开。

2. 先搞清楚 coredump 的生成链路

2.1 从信号到文件:内核做了什么

要排查问题,先得知道正常流程长什么样。当一个进程触发异常信号,内核在终止它之前会做一系列判断,只有全部通过才会真正写文件。这条链路大致是这样的:

  1. 进程收到致命信号,内核进入信号处理流程。
  2. 内核检查该信号的默认动作是否是“终止并产生 core”。
  3. 检查进程的RLIMIT_CORE资源限制,如果为 0 则直接跳过。
  4. 检查core_pattern配置,决定文件写到哪里、叫什么名字。
  5. 检查目标路径的权限、磁盘空间、文件系统类型。
  6. 检查进程是否设置了PR_SET_DUMPABLE,以及是否有setuid等特殊属性。
  7. 全部通过后,内核把内存镜像写入目标文件。

这条链路上任何一环断了,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.%p

core_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 五分钟快速定位清单

拿到一台机器,按这个顺序过一遍,基本能定位到原因:

  1. ulimit -c看资源限制,是 0 就打开。
  2. cat /proc/sys/kernel/core_pattern看写到哪里,是不是管道。
  3. 如果是管道,查对应服务(coredumpctl list或 apport 的目录)。
  4. 确认进程 cwd 和目标目录可写、磁盘有空间。
  5. cat /proc/<PID>/coredump_filter看过滤配置。
  6. 检查程序是否自己接管了信号。
  7. 容器场景额外检查运行时 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/coredump

1777是带 sticky 位的权限,任何用户都能写,但只能删自己的文件,适合多用户共享的 core 目录。

关于coredump_filter,默认的0x33对大多数场景够用。如果程序用了大量共享内存或文件映射,需要 dump 更多内容,可以调大这个值,但 core 文件也会相应变大。这个取舍要看具体排查需求。

5. 常见问题速查与避坑经验

5.1 典型问题对照表

现象可能原因排查命令解决方式
完全没有 core 文件ulimit -c 为 0ulimit -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 程序无 coresuid_dumpable 为 0cat /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 配置搞对才是长久之道。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询