3. Kdump与Crash工具
当 Linux 内核发生致命错误(如 NULL 指针解引用、内核态死锁、非法指令)时,系统通常会在几毫秒内触发 panic 或 oops。如果没有有效的捕获机制,现场信息会随系统重启而丢失。Kdump 是 Linux 内核官方推荐的崩溃转储机制,它利用 kexec 在崩溃瞬间快速启动一个“捕获内核”,将崩溃时的内存镜像(vmcore)保存到磁盘或网络。配合 Crash 工具,开发者可以像调试用户态程序一样,对 vmcore 进行结构化分析,定位崩溃根源。
3.1 Kdump 工作原理与架构
Kdump 基于 kexec 机制实现。kexec 允许系统在不经过 BIOS/UEFI 的情况下直接加载并跳转到另一个内核。当主内核崩溃时,kdump 服务触发 kexec,快速启动一个预加载的捕获内核(capture kernel),该内核以极小的资源占用将主内核的内存镜像写入指定位置。
3.2 Kdump 配置步骤
以下配置基于 RHEL/CentOS 7/8 及主流发行版,Ubuntu/Debian 的包名和服务名略有差异,但原理一致。
| 步骤 | 操作 | 关键命令 / 配置 |
|---|---|---|
| 1 | 安装 kexec-tools 与 kdump | yum install kexec-tools 或 apt install kdump-tools |
| 2 | 配置内核启动参数 | 在 GRUB_CMDLINE_LINUX 中添加 crashkernel=256M |
| 3 | 更新 GRUB 并重启 | grub2-mkconfig -o /boot/grub2/grub.cfg && reboot |
| 4 | 验证预留内存 | cat /proc/iomem | grep "Crash kernel" |
| 5 | 配置 vmcore 保存路径 | 编辑 /etc/kdump.conf,设置 path /var/crash |
| 6 | 启动并启用 kdump 服务 | systemctl enable kdump && systemctl start kdump |
| 7 | 测试触发崩溃 | echo c > /proc/sysrq-trigger(系统会立即 panic) |
/var/crash/ 目录下后再投入生产。
3.2.1 验证 kdump 配置是否生效
重启后执行以下命令确认 kdump 处于 active 状态:
# 检查 kdump 服务状态
systemctl status kdump
# 确认 crashkernel 预留内存
dmesg | grep -i crash
# 输出示例:
# [ 0.000000] Reserving 256MB of memory at 256MB for crashkernel (System RAM: 2048MB)
# 检查 /proc/iomem 中的 Crash kernel 区域
cat /proc/iomem | grep "Crash kernel"
# 输出示例:
# 100000000-1fffffff : Crash kernel
3.3 Crash 工具基础使用
Crash 工具是分析 vmcore 的核心利器。它提供了一个类似 gdb 的交互式环境,支持查看堆栈、内存、变量、数据结构,并内置了大量针对内核对象的分析命令。
3.3.1 启动 Crash
# 基本语法:crash [vmcore文件] [vmlinux调试符号文件]
crash /var/crash/2024-01-15-10:30/vmcore /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
kernel-debuginfo-$(uname -r).rpm。没有符号文件,crash 将无法解析函数名和变量。
3.3.2 常用 Crash 命令速查表
| 命令 | 功能 | 典型输出 |
|---|---|---|
bt |
打印当前或指定任务的堆栈回溯 | 崩溃时的函数调用链 |
log |
查看内核日志缓冲区(dmesg) | panic 消息、oops 信息 |
ps |
列出所有进程/线程 | 进程 PID、状态、命令名 |
files |
查看进程打开的文件 | 文件描述符、inode、路径 |
vm |
查看虚拟内存区域 | VMA 起始地址、权限、映射文件 |
rd |
读取指定内存地址的内容 | 原始字节或反汇编 |
dis |
反汇编指定地址或函数 | 汇编指令序列 |
struct |
打印内核结构体定义及成员 | 结构体字段偏移与类型 |
sym |
将地址解析为符号名 | 函数名 + 偏移 |
3.4 提取关键信息:实战分析流程
当拿到一个 vmcore 后,建议按以下顺序提取信息,快速定位崩溃根因。
3.4.1 实战案例:PCI 驱动导致的内核崩溃
假设某 PCIe 网卡驱动在中断处理函数中解引用了一个已释放的指针,导致系统 panic。以下是使用 crash 工具的分析过程。
步骤 1:查看 panic 消息
crash> log
[ 123.456789] Kernel panic - not syncing: Fatal exception in interrupt
[ 123.456790] CPU: 2 PID: 0 Comm: swapper/2 Tainted: P O
[ 123.456791] Hardware name: Supermicro X11DPi-NT
[ 123.456792] RIP: 0010:my_pci_driver_irq_handler+0x3f/0x80 [my_driver]
[ 123.456793] RSP: 0018:ffffc9000120be88 EFLAGS: 00010086
[ 123.456794] RAX: 0000000000000000 RBX: ffff888123456789 RCX: 0000000000000001
[ 123.456795] RDX: 0000000000000000 RSI: ffff888987654321 RDI: 0000000000000000
[ 123.456796] RBP: ffffc9000120bea0 R08: 0000000000000000 R09: 0000000000000000
[ 123.456797] R10: ffff888abcdef012 R11: 0000000000000000 R12: ffff888123456789
[ 123.456798] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
[ 123.456799] CR2: 0000000000000010
步骤 2:获取崩溃堆栈
crash> bt
PID: 0 TASK: ffff888111111111 CPU: 2 COMMAND: "swapper/2"
#0 [ffffc9000120be88] my_pci_driver_irq_handler+0x3f/0x80 [my_driver]
#1 [ffffc9000120bea0] __handle_irq_event_percpu+0x6b/0x1a0
#2 [ffffc9000120bec0] handle_irq_event_percpu+0x1f/0x50
#3 [ffffc9000120bee0] handle_irq_event+0x3f/0x70
#4 [ffffc9000120bf00] handle_fasteoi_irq+0x9b/0x160
#5 [ffffc9000120bf20] __common_interrupt+0x3a/0x80
#6 [ffffc9000120bf40] common_interrupt+0x7a/0x90
#7 [ffffc9000120bf60] asm_common_interrupt+0x22/0x40
步骤 3:分析崩溃指令
crash> dis my_pci_driver_irq_handler+0x3f
0xffffffffc000103f <my_pci_driver_irq_handler+0x3f>: mov (%rax),%rdx
# 崩溃发生在解引用 RAX 寄存器指向的内存,而 RAX = 0x0,即 NULL 指针解引用
步骤 4:检查驱动数据结构
# 查看中断处理函数中使用的 pci_dev 结构体
crash> struct pci_dev ffff888123456789
struct pci_dev {
dev = {
init_name = 0xffffc9000120bf00 "my_pci_device"
},
vendor = 0x8086,
device = 0x10fb,
...
irq = 0x2f,
...
}
# 进一步检查驱动私有数据
crash> struct my_driver_priv *priv = (struct my_driver_priv *)0xffff888987654321
crash> struct my_driver_priv ffff888987654321
struct my_driver_priv {
tx_ring = 0x0, # 关键:tx_ring 指针为 NULL,导致中断处理中解引用失败
rx_ring = 0xffff888abcdef012,
...
}
3.5 高级技巧:使用 Crash 脚本自动化分析
对于重复性分析任务,Crash 支持通过 crash -i script.crash 执行预定义的命令序列。以下是一个用于 PCI 驱动崩溃的自动化分析脚本示例:
# pci_crash_analysis.crash
# 自动提取 PCI 驱动崩溃的关键信息
log | grep -E "Kernel panic|Oops|RIP"
bt -a
foreach bt
ps -t
sym RIP
struct pci_dev *pdev_list
rd -s ffff888123456789 0x100
quit
执行方式:
crash /var/crash/vmcore /usr/lib/debug/vmlinux -i pci_crash_analysis.crash
3.6 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| vmcore 文件过大(> 系统内存) | 未启用压缩或过滤 | 在 /etc/kdump.conf 中设置 core_collector makedumpfile -c -d 31,启用压缩并过滤掉无用页 |
| crash 启动报 "not a supported file format" | vmlinux 与 vmcore 版本不匹配 | 确保使用与崩溃内核完全一致的 debuginfo 包,检查 uname -r |
| bt 命令输出不完整 | 堆栈被破坏或捕获内核内存不足 | 尝试 bt -a 查看所有 CPU 堆栈,或增加 crashkernel 预留内存 |
| 无法解析驱动符号 | 驱动模块未加载到捕获内核的符号表 | 使用 mod -S /path/to/driver.ko 手动加载驱动模块的调试符号 |
3.7 本章小结
Kdump 与 Crash 工具的组合是 Linux 内核崩溃定位的黄金标准。通过本章的学习,你应该能够:
- 理解 kdump 基于 kexec 的崩溃转储架构
- 独立完成 kdump 的安装、配置与验证
- 使用 crash 工具打开 vmcore 并执行基本的分析命令
- 按照标准流程提取 panic 消息、堆栈、寄存器和驱动数据结构
- 结合 PCI 驱动场景,定位常见的空指针、内存越界和资源未初始化问题