Linux内核文件系统调试:crash与trace工具实战 - 第3节

3.1 crash工具简介

crash 是 Red Hat 开发的一款交互式内核转储分析工具,专门用于分析 Linux 内核的 vmcore(内核崩溃转储文件)或正在运行的系统内存镜像。它结合了 gdb 的调试能力与内核专用的上下文命令,是文件系统调试场景下最核心的离线分析工具。

核心定位:crash 不依赖内核源码编译,直接解析内存数据结构,通过偏移量自动匹配符号表,实现对进程、文件系统、内存管理、设备驱动等子系统的深度检查。

3.1.1 适用场景

  • 内核 panic / oops 分析:定位崩溃时的执行栈、寄存器、进程上下文。
  • 文件系统死锁或异常:检查 inode、dentry、super_block 等核心结构体状态。
  • 内存泄漏与 slab 损坏:遍历 slab 缓存,识别异常对象。
  • 进程 D 状态排查:分析处于 uninterruptible sleep 的进程及其等待队列。

3.1.2 工作原理

crash 通过 /dev/mem、/proc/kcore 或 vmcore 文件读取内核内存,利用 vmlinux 调试符号将二进制数据解析为可读的内核结构体。其核心流程如下:

crash 工具工作原理 vmcore / kcore vmlinux (含 debuginfo) System.map crash 解析引擎 符号解析 + 结构体偏移 bt / ps / files struct 打印 内存/文件系统

3.2 安装与配置

3.2.1 安装方式

发行版 安装命令 说明
RHEL / CentOS 7/8/9 yum install crash 或 dnf install crash EPEL 仓库可选,但 base 源已包含
Ubuntu / Debian apt install crash 需启用 universe 仓库
Fedora dnf install crash 默认仓库包含
源码编译 git clone https://github.com/crash-utility/crash.git
cd crash && make
需要 ncurses-devel 和 zlib-devel
提示:生产环境建议使用发行版官方包,避免符号表版本不匹配。源码编译适用于定制内核或交叉调试场景。

3.2.2 启动配置

crash 启动时需要指定 vmlinux(带调试符号的内核镜像)和 vmcore(转储文件)。典型启动命令:

# 分析 vmcore 文件
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /var/crash/vmcore

# 分析正在运行的系统(实时调试)
crash /usr/lib/debug/lib/modules/$(uname -r)/vmlinux /proc/kcore

# 使用 System.map 辅助(可选)
crash -s /boot/System.map-$(uname -r) vmlinux vmcore

3.2.3 配置文件

crash 支持 ~/.crashrc 或 /etc/crash.conf 配置文件,用于预设命令别名和自动加载扩展:

# ~/.crashrc 示例
alias psall = "ps -a -t -s"
alias fsinfo = "files -d -i"
extend /usr/lib64/crash/extensions/snap.so

3.3 基本命令使用

crash 提供超过 100 条内置命令,本节聚焦文件系统调试最常用的四个命令:bt、ps、files、mount。

3.3.1 bt — 回溯调用栈

bt 命令用于显示当前或指定进程的内核态调用栈,是定位崩溃现场的第一入口。

子选项 作用 示例
-a 显示所有 CPU 的栈回溯 bt -a
-t 显示原始栈帧(含十六进制数据) bt -t
-l 显示栈帧对应的源码行号 bt -l
-e 显示异常栈(如 panic 时的额外信息) bt -e

典型输出示例:

crash> bt
PID: 12345  TASK: ffff88003e2c0000  CPU: 2   COMMAND: "kworker/u16:4"
 #0 [ffff88003e2c3b50] __schedule at ffffffff8163d2c5
 #1 [ffff88003e2c3bc0] schedule at ffffffff8163d8b9
 #2 [ffff88003e2c3bd0] io_schedule at ffffffff8163e0a1
 #3 [ffff88003e2c3be0] __lock_page at ffffffff811a7b35
 #4 [ffff88003e2c3c20] filemap_fault at ffffffff811a8e42
 #5 [ffff88003e2c3c70] __do_fault at ffffffff8120c1f0
 #6 [ffff88003e2c3ca0] handle_pte_fault at ffffffff8120d4b1
 #7 [ffff88003e2c3cd0] handle_mm_fault at ffffffff8120d7e0
 #8 [ffff88003e2c3d00] __do_page_fault at ffffffff8104b8f1
 #9 [ffff88003e2c3d30] do_page_fault at ffffffff8104ba6e
#10 [ffff88003e2c3d60] page_fault at ffffffff8164b5e5
注意:如果栈回溯中出现 __lock_page 或 inode_wait 等函数,通常表示进程在等待文件系统 I/O 完成,是文件系统调试的关键线索。

3.3.2 ps — 进程状态查看

ps 命令显示系统中所有进程的状态、PID、CPU 和命令名,支持按条件过滤。

子选项 作用 示例
-a 显示所有进程(含内核线程) ps -a
-t 按线程组显示 ps -t
-s 显示进程的详细状态(含等待队列) ps -s
pid 显示指定 PID 的进程信息 ps 12345

典型输出示例:

crash> ps -a
   PID    PPID  CPU   TASK        ST  %MEM     VSZ    RSS  COMM
>     0      0   0  ffffffff81c8d000  RU   0.0       0      0  [swapper/0]
      1      0   0  ffff88003a0f0000  IN   0.1   123456  12345  systemd
      2      0   0  ffff88003a0f2000  IN   0.0       0      0  [kthreadd]
   1234      1   1  ffff88003e2c0000  UN   0.5   234567  23456  kworker/u16:4
   5678   1234   2  ffff88003e2c4000  IN   0.3   345678  34567  mysqld
文件系统调试要点:关注 ST 列为 UN(uninterruptible sleep,D 状态)的进程,它们通常因文件系统锁或 I/O 阻塞而无法响应信号。

3.3.3 files — 文件描述符与打开文件

files 命令显示进程打开的文件描述符、关联的 dentry 和 inode 信息,是文件系统调试的核心命令。

子选项 作用 示例
-d 显示 dentry 详细信息 files -d 12345
-i 显示 inode 详细信息 files -i 12345
-p 按 PID 显示文件描述符 files -p 12345
-c 显示文件路径缓存 files -c 12345

典型输出示例:

crash> files -p 12345
PID: 12345  TASK: ffff88003e2c0000  COMMAND: "kworker/u16:4"
ROOT: /    CWD: /var/log
 FD   FILE                 DENTRY            INODE       PATH
  0  ffff88003b0f0000  ffff88003c0f0000  ffff88003d0f0000  /dev/null
  1  ffff88003b0f1000  ffff88003c0f1000  ffff88003d0f1000  /var/log/syslog
  2  ffff88003b0f2000  ffff88003c0f2000  ffff88003d0f2000  /var/log/syslog
  3  ffff88003b0f3000  ffff88003c0f3000  ffff88003d0f3000  /data/db/mysql.ibd
调试技巧:当怀疑文件系统损坏时,使用 files -d 检查 dentry 的 d_inode 指针是否为空或指向无效地址;使用 files -i 检查 inode 的 i_state 和 i_flags 是否异常。

3.3.4 mount — 挂载信息查看

mount 命令显示内核中所有挂载点的详细信息,包括设备、挂载路径、超级块地址和挂载标志。

子选项 作用 示例
-a 显示所有挂载点(含隐藏挂载) mount -a
-f 显示文件系统类型 mount -f
-s 显示超级块详细信息 mount -s
-v 显示 vfsmount 结构体 mount -v

典型输出示例:

crash> mount
  MOUNT           SUPERBLK     TYPE   DEVNAME   DIRNAME
ffff88003a0f0000  ffff88003b0f0000  ext4   /dev/sda1  /
ffff88003a0f1000  ffff88003b0f1000  ext4   /dev/sda2  /boot
ffff88003a0f2000  ffff88003b0f2000  xfs    /dev/sdb1  /data
ffff88003a0f3000  ffff88003b0f3000  tmpfs  tmpfs      /dev/shm
注意:如果某个挂载点的 SUPERBLK 地址为 0 或指向无效内存,说明该文件系统的超级块已损坏或未正确加载,这是文件系统崩溃的典型症状。

3.4 命令组合实战示例

以下是一个完整的文件系统调试流程,展示如何组合使用上述命令:

  1. 定位 D 状态进程:ps -a | grep UN 找到处于 uninterruptible sleep 的进程 PID。
  2. 查看调用栈:bt <PID> 确认进程是否阻塞在文件系统函数(如 __lock_page、inode_wait)。
  3. 检查打开文件:files -p <PID> 查看进程正在操作的文件路径和 inode。
  4. 确认挂载状态:mount -s 检查相关文件系统的超级块是否正常。
实战口诀:先看 D 状态,再查调用栈;文件描述符,挂载不能忘。
文件系统调试命令组合流程 ps -a 找 D 状态进程 bt <PID> 查看调用栈 files -p <PID> 检查文件描述符 mount -s 检查超级块 综合分析定位根因 锁冲突 / I/O 超时 / 结构体损坏

3.5 常见问题与避坑指南

问题 原因 解决方案
crash 启动报 "no vmlinux" 未安装 kernel-debuginfo 包 dnf install kernel-debuginfo-$(uname -r)
bt 输出显示 "??" 符号表版本与内核不匹配 使用与 vmcore 同版本内核的 vmlinux
files 命令无输出 进程已退出或 task_struct 损坏 使用 ps -a 确认进程是否存在
mount 显示超级块地址为 0 文件系统未正确挂载或已卸载 检查 dmesg 日志确认挂载状态
最佳实践:每次分析前先执行 help -D 查看 crash 的调试信息,确认符号表加载成功;使用 struct task_struct <地址> 手动验证关键结构体完整性。