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 调试符号将二进制数据解析为可读的内核结构体。其核心流程如下:
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.gitcd 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 命令组合实战示例
以下是一个完整的文件系统调试流程,展示如何组合使用上述命令:
- 定位 D 状态进程:
ps -a | grep UN找到处于 uninterruptible sleep 的进程 PID。 - 查看调用栈:
bt <PID>确认进程是否阻塞在文件系统函数(如__lock_page、inode_wait)。 - 检查打开文件:
files -p <PID>查看进程正在操作的文件路径和 inode。 - 确认挂载状态:
mount -s检查相关文件系统的超级块是否正常。
实战口诀:先看 D 状态,再查调用栈;文件描述符,挂载不能忘。
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 <地址> 手动验证关键结构体完整性。