在CentOS系统上,程序崩溃后自动生成coredump文件并用GDB进行调试分析,是运维排查线上故障的核心技能。核心操作就三步:通过ulimit解除文件大小限制、配置kernel.core_pattern指定coredump存储路径和命名规则、用gdb加载core文件定位崩溃点。下面我把每一步的具体配置、常见坑和实战调试技巧全部讲透。
一、为什么程序崩溃了却没有coredump文件
很多运维遇到程序crash后找不到core文件,第一反应是"没生成"。实际上在CentOS 7/8上,默认的ulimit -c值是0,意味着系统直接禁止生成coredump。另外即使允许了,如果没有配置kernel.core_pattern,生成的文件会丢在/var/crash或者直接被丢弃。所以你要做的第一件事就是同时修改这两层限制。
二、配置ulimit解除core文件大小限制
先看当前限制:
ulimit -c
如果输出是0,说明被禁用了。临时修改可以执行:
ulimit -c unlimited
但这只对当前shell会话有效,重启就没了。要永久生效,编辑/etc/security/limits.conf,在末尾添加:
* soft core unlimited * hard core unlimited
这里的星号代表所有用户。如果只针对某个服务账号(比如运行nginx的nginx用户),就把星号换成具体用户名。改完之后需要重新登录或者重启服务才能生效。
三、配置kernel.core_pattern指定存储路径和命名
CentOS 7默认的core_pattern是"|/usr/libexec/abrt-hook-ccpp %s %c %p %u %g %t e",这意味着coredump会被abrt自动捕获处理,你在磁盘上看不到原始core文件。如果你想自己用GDB分析,需要改成直接写文件的模式。
编辑/etc/sysctl.conf,添加或修改:
kernel.core_pattern = /var/core/core-%e-%p-%t
参数含义:%e是程序名、%p是PID、%t是时间戳。这样生成的文件类似/var/core/core-nginx-12345-1700000000。然后执行sysctl -p使其立即生效。同时确保目录存在且有写权限:
mkdir -p /var/core chmod 777 /var/core
这里有个实战经验:如果你的程序是以root运行的,core文件也会以root权限生成,普通用户gdb加载时会报permission denied。所以建议把目录权限设为777,或者用chown改成对应服务用户。
四、CentOS 8及以上版本的额外注意事项
CentOS 8/Stream和Rocky Linux 8引入了systemd-coredump机制,它会接管coredump的处理。即使你配了kernel.core_pattern,systemd也可能拦截。需要禁用它:
systemctl disable --now systemd-coredump.socket
或者在/etc/systemd/coredump.conf中设置Storage=none和ProcessSizeMax=0。这一步很多人忽略,导致配了半天不生效。
五、用GDB加载coredump进行调试分析
拿到core文件后,基本命令是:
gdb /path/to/executable /var/core/core-program-12345-timestamp
注意:gdb后面跟的第一个参数是崩溃时运行的可执行文件(必须带调试符号,也就是编译时加了-g选项的版本),第二个参数才是core文件。如果你用的是strip过的发布版本,没有调试符号,gdb只能看到内存地址,看不到函数名和源码行号。
进入gdb后,最常用的几条命令:
bt # 查看完整的调用栈,这是第一步必须做的 bt full # 显示每一帧的局部变量值 info registers # 查看崩溃时的寄存器状态 x/20x $sp # 查看栈内存内容 frame N # 切换到第N帧 info locals # 查看当前帧局部变量 list # 显示崩溃位置附近的源码(需要-g编译)
六、实战案例:定位段错误(SIGSEGV)
假设一个C程序访问了空指针导致崩溃。用bt看到的调用栈类似:
#0 0x00007f3a2b1c4567 in process_request (req=0x0) at server.c:128 #1 0x00007f3a2b1c4890 in handle_connection (fd=5) at connection.c:45 #2 0x00007f3a2b1c4a12 in main_loop () at main.c:200
从#0帧可以直接看到:req是NULL,在server.c第128行解引用了它。这就是崩溃根因。如果你的程序是多线程的,还需要用info threads查看所有线程状态,用thread N切换到崩溃线程。
七、没有调试符号时的替代调试方案
线上环境的程序通常是release版本,没有-g符号。这时候可以用addr2line把地址转成源码位置:
addr2line -e /path/to/program -f 0x00007f3a2b1c4567
输出会告诉你这个地址对应哪个文件的哪一行。但前提是你保留了带符号的可执行文件副本。另外也可以用objdump -d反汇编,或者用eu-addr2line(elfutils包提供的工具)。
八、生产环境的最佳实践建议
第一,不要在生产环境把ulimit设成unlimited,coredump文件可能有几个GB,会撑爆磁盘。建议设一个合理值比如512MB:ulimit -c 524288。
第二,用脚本定期清理旧的core文件,比如写个cron每7天删除/var/core下超过30天的文件。
第三,如果程序频繁崩溃,可以配置kernel.panic_on_oops=1让内核在出错时触发kdump,获取更完整的内存快照。这对排查内核态问题特别有用。
第四,对于Java程序(JVM),coredump的生成方式不同,需要在启动参数中加-XX:+HeapDumpOnOutOfMemoryError和-XX:+CrashOnOutOfMemoryError,生成的是hprof文件而不是传统core,用jstack或MAT工具分析。
九、常见问题排查清单
如果配了之后还是不生成core,按这个顺序排查:
(1)检查ulimit -c是否非0;
(2)检查/proc/sys/kernel/core_pattern是否被正确写入;
(3)检查目录是否存在且可写;
(4)确认程序崩溃信号是否被捕获(有些程序自己注册了信号处理函数吞掉了SIGSEGV);
(5)检查磁盘空间是否足够;
(6)CentOS 8确认systemd-coredump已禁用。
总结一下,coredump配置和GDB调试是CentOS运维的基本功。核心就是解除限制、指定路径、保留符号、加载分析四个环节。掌握这些,线上程序崩溃你就能在分钟级别定位到具体代码行,而不是靠猜。
