3. Kdump与Crash工具

当 Linux 内核发生致命错误(如 NULL 指针解引用、内核态死锁、非法指令)时,系统通常会在几毫秒内触发 panic 或 oops。如果没有有效的捕获机制,现场信息会随系统重启而丢失。Kdump 是 Linux 内核官方推荐的崩溃转储机制,它利用 kexec 在崩溃瞬间快速启动一个“捕获内核”,将崩溃时的内存镜像(vmcore)保存到磁盘或网络。配合 Crash 工具,开发者可以像调试用户态程序一样,对 vmcore 进行结构化分析,定位崩溃根源。

核心目标: 掌握 kdump 的完整配置流程,熟练使用 crash 工具从 vmcore 中提取 panic 栈、进程上下文、内存布局和寄存器状态,为 PCI 驱动调试提供关键证据。

3.1 Kdump 工作原理与架构

Kdump 基于 kexec 机制实现。kexec 允许系统在不经过 BIOS/UEFI 的情况下直接加载并跳转到另一个内核。当主内核崩溃时,kdump 服务触发 kexec,快速启动一个预加载的捕获内核(capture kernel),该内核以极小的资源占用将主内核的内存镜像写入指定位置。

Kdump 崩溃转储架构流程图 主内核(Production Kernel) 正常运行业务与驱动 预留 crashkernel 内存区域 内核崩溃(Panic / Oops) 触发 kdump 服务 调用 kexec 跳转 捕获内核(Capture Kernel) 极小内存占用 读取主内核内存镜像 保存 vmcore 写入磁盘 /var/crash/ 或通过网络转储 kexec 快速跳转 Crash 工具离线分析 vmcore
关键概念: crashkernel 参数用于预留一小段物理内存(通常 64MB~512MB),专供捕获内核使用。这段内存对主内核不可见,确保崩溃时捕获内核有足够的运行空间。

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)
生产环境注意: 测试触发崩溃会导致系统重启,请先在测试机或虚拟机中验证。确认 vmcore 成功生成在 /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
调试符号文件: vmlinux 是包含调试符号(debuginfo)的内核映像。发行版通常提供单独的 debuginfo 包,如 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 后,建议按以下顺序提取信息,快速定位崩溃根因。

Crash 工具分析 vmcore 标准流程 1. 查看 panic 消息 log | grep -A 20 "Kernel panic" 2. 获取崩溃堆栈 bt -a 或 bt -c <cpu> 3. 定位崩溃进程 ps | grep "UN" 或 bt 中的 PID 4. 检查寄存器 bt -f 或 gdb 模式 info registers 5. 分析异常地址 rd <fault_addr> 或 dis <ip> 6. 检查驱动数据 struct pci_dev <addr> 7. 交叉验证与根因确认 结合源码、dmesg、驱动状态综合判断 提示:PCI 驱动调试中,重点关注 pci_dev 结构体、MMIO 基地址、中断号及 DMA 映射状态

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,
  ...
}
根因定位: 从崩溃堆栈和数据结构分析可知,驱动在中断处理函数中访问了未初始化的 tx_ring 指针(值为 NULL)。结合代码审查,发现驱动在某个错误路径下跳过了 tx_ring 的分配,但中断使能未同步关闭,导致中断触发时访问了空指针。

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
效率提升: 将常用的分析命令组合成脚本,可以大幅缩短从拿到 vmcore 到定位根因的时间。建议团队维护一个针对不同驱动类型的 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 手动加载驱动模块的调试符号
安全提醒: vmcore 包含系统完整的内存镜像,可能包含敏感数据(如密码、密钥)。请确保 vmcore 文件的访问权限严格受限,分析完成后及时清理。

3.7 本章小结

Kdump 与 Crash 工具的组合是 Linux 内核崩溃定位的黄金标准。通过本章的学习,你应该能够:

  • 理解 kdump 基于 kexec 的崩溃转储架构
  • 独立完成 kdump 的安装、配置与验证
  • 使用 crash 工具打开 vmcore 并执行基本的分析命令
  • 按照标准流程提取 panic 消息、堆栈、寄存器和驱动数据结构
  • 结合 PCI 驱动场景,定位常见的空指针、内存越界和资源未初始化问题