服务器内核崩溃后如何离线还原现场:kdump + crash 工具 5 步完整指南
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
生产机内核一崩,现场就没了?kdump 就是为此准备的"黑匣子":它先把崩溃瞬间的内存完整保存下来,再由 crash 工具在事后把 vmcore 转储文件离线加载,逐帧复现崩溃现场,最终定位到肇事函数。本文按一次真实排障的顺序,走完从配置到分析的完整链路。
一次真实宕机:现象、目标与 kdump 的定位
凌晨两点,机器"无声"重启了
凌晨的生产服务器突然失联:业务报超时,监控里只有一个 reboot 事件。机器起来之后,内核日志里没有完整的 oops 堆栈,应用层的 core 文件也没生成——典型的内核级崩溃。你面对的是两个问题:崩溃发生在哪个函数、是什么数据触发的。
没有现场,就没有分析
如果崩溃瞬间的内存没有被保留,后面的一切推断都只是猜测。kdump 的职责就是解决这个问题:系统正常运行时,它先预留一块内存并加载一个"第二内核";一旦触发 panic,系统通过 kexec 切到这个第二内核,由它把第一个内核的内存导出成 /proc/vmcore(ELF 格式的内存快照),落盘后就能交给 crash 工具做"尸检"。所以整个工作只有两段:崩溃时保现场,崩溃后读现场。
搭好环境:最小可运行配置的 3 步
1. 装 crash 工具和带符号的内核
分析机需要两样东西:crash 工具本身,以及一个带调试符号的 vmlinux(发行版里通常是 debuginfo 包)。
# Debian / Ubuntu sudo apt-get install crash linux-image-$(uname -r)-dbgsym # RHEL / CentOS sudo yum install crash kernel-debuginfo装完你会看到crash命令可用;缺了 vmlinux,后面的符号、函数名全都对不上。
2. 用 crashkernel 参数预留内存
在启动参数里加 crashkernel。除了crashkernel=512M这种固定值写法,还支持按机器内存容量分档,推荐用这种,小机器不多占、大机器留够:
GRUB_CMDLINE_LINUX="crashkernel=1G-4G:192M,4G-:384M"意思是:内存 1G~4G 的机器预留 192M,4G 以上预留 384M。改完执行grub2-mkconfig -o /boot/grub2/grub.cfg(或 update-grub)并重启,这块内存才会真正生效。
3. 加载崩溃内核并验证
预留只是"盖好仓库",还要把崩溃内核装进预留区。用发行版自带的 kdump 服务可以免手动:sudo systemctl enable --now kdump。重启后做三项检查:
cat /proc/cmdline # 确认 crashkernel= 参数在 ls -l /sys/kernel/kexec_crash_image # 存在即表示崩溃内核已加载 grep -i crash /proc/iomem # 物理内存中出现 CRASH 保留区三条都通过,说明"仓库建好、货也入库"。想在测试机上主动演练,可以执行echo c > /proc/sysrq-trigger人为触发一次崩溃,验证 vmcore 能否正常落盘。
到这里,整条链路已经可以概括为 5 步:预留内存 → 加载崩溃内核 → 触发转储 → 加载 vmcore → 追堆栈。后文从第 4 步开始。
还原现场:加载 vmcore 到追出堆栈的 4 个标准动作
加载转储:crash + 匹配的 vmlinux
panic 发生后,系统切到崩溃内核,你把 /proc/vmcore 拷下来落盘(kdump 服务一般会代劳,默认放在 /var/crash 下的时间戳目录)。分析机上这样启动:
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/*/vmcorevmlinux 必须与产生 vmcore 的那次内核版本完全一致,否则符号全部错位。进入后你会看到crash>提示符。
第一步看 sys:确认"死因"和版本
crash> sys KERNEL: /usr/lib/debug/lib/modules/6.1.0-9/vmlinux DUMPFILE: /var/crash/10.0.0.5-.../vmcore PANIC: "BUG: unable to handle page fault for address: 0000000000000018" PID: 4321 COMMAND: "myworker" TASK: ffffc90000123450 CPU: 3一眼拿到三个信息:崩溃时的 panic 信息、崩溃进程的 PID 和名字、内核版本。后续动作基本都围绕 PID 展开。
再看 log,锁定崩溃前最后时刻
crash> log输出是崩溃时刻的内核环形缓冲,等价于崩溃瞬间的 dmesg。不要从头看,直接翻最后几十行:Oops/BUG 文本、Call Trace、Modules linked in。它和 bt 的堆栈互相印证,能帮你判断是内核代码还是模块代码。
追堆栈:ps → bt → 异常指令
crash> ps | grep myworker # 拿到崩溃任务的地址和状态 crash> bt 4321 # 打印该任务的完整调用栈bt的输出从当前栈帧往上依次列出每层函数和寄存器,最关键的行是形如[exception RIP: xxx+0x2c]的异常指令——那就是真正踩雷的那条指令。从它出发,往下读函数上下文、往上读调用链,肇事者基本就出来了。
深入一层:反汇编、结构体与内存
crash> dis my_func # 反汇编出错的函数,找到坏指令 crash> struct task_struct 4321 crash> rd ffffc90000123450 32dis让你看到具体是 mov 还是 call 出了问题;struct查看任意内核结构体的实时内容;rd读任意地址的原始字节。配合源码一起看,基本能把"谁、在哪、访问了什么"还原完整。
对症下药:三类高频崩溃的判读方法
空指针解引用:看 Oops 错误码和寄存器
症状:log 里出现Oops: 0002 [#1] SMP。错误码低位的 2 表示写操作,再结合高低位可判断是内核态还是用户态触发。动作:bt定位异常 RIP →dis看指令操作数 → 对照寄存器值,哪个寄存器是 0 或接近 0,哪个就是空指针。结论:顺着该指针的来源变量回源码,通常能直接看到漏掉的判空。
页错误:先核对错误地址和指令是否对得上
症状:BUG: unable to handle page fault for address: 0x...。动作:把 bt 给出的异常 RIP 交给dis -l(带行号反汇编),确认指令要访问的地址和报告值一致,排除误判;再rd该地址附近区域。结论:地址合法却访问失败,多半是 use-after-free——对象已释放但指针还活着。
内存异常与内存泄漏:用 kmem 说话
症状:page state 报错、refcount 异常,或运行数天后内存只涨不跌。动作:kmem -p <地址>查看对应 page 的引用计数、映射数等状态;kmem -s列出 slab 缓存占用,隔一段时间再采一次做对比,涨得最凶的 cache 就是嫌疑对象;对可疑结构用search在内存里按值查找。结论:单次快照看状态,两次快照看趋势,泄漏方向往往就藏在这里。
顺手的两个小技巧
看到sys输出里 Tainted 带字母,说明有厂商模块或人为因素,优先怀疑对应组件。另外,频繁出现的 WARN 默认不会留现场,测试机可以加上panic_on_warn=1让 WARN 也触发转储,把现场抓回来。
下一步:进阶路径与一套可带走的清单
让 vmcore 更小、让工具更懂布局
vmcore 动辄几十 GB 时,用makedumpfile -d 31只保留内核数据页,能大幅瘦身。crash 之所以能直接读懂转储,是因为 vmcore 里嵌了一段 VMCOREINFO 元信息(页大小、结构体偏移、符号值),想了解它记录了什么,可以查树内文档 Documentation/admin-guide/kdump/vmcoreinfo.rst;GDB 配合 gdbmacros 宏做轻量分析的方法也在同目录的 kdump.rst 里。
把排障动作固化成肌肉记忆
🧩 建议的下一步很具体:挑一台测试机,完整走一遍"预留 crashkernel 内存 → 启动 kdump 服务 → sysrq 触发崩溃 → 用 crash 加载 vmcore 并执行 sys、log、ps、bt"闭环。把这个四命令序列打印出来贴在排障手册里,下次再遇到无声宕机,你只需要从第 4 步接着跑。
结合 kernel/ 源码逐函数核对,比任何教程都更接近真相。觉得这条链路有用的话,收藏本文,关注后续的内核调试系列。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考