使用drgn调试Linux内核:blk-rq-qos崩溃案例分析
【免费下载链接】drgn Programmable debugger 项目地址: https://gitcode.com/gh_mirrors/dr/drgn
引言
在Linux内核开发和生产环境中,遇到内核崩溃是常见但棘手的问题。本文将通过一个真实的Linux内核崩溃案例,展示如何使用drgn工具进行深入分析。这个案例涉及Linux 6.11版本中块层(block layer)的一个bug,导致内核在生产环境中崩溃。
准备工作
在开始分析前,我们需要准备以下环境:
- 安装drgn工具(遵循官方安装指南)
- 获取崩溃转储文件和调试符号
- 准备好Linux 6.11版本的源代码
初步分析:内核日志
内核日志是分析崩溃的第一手资料。使用drgn查看内核日志缓冲区:
print_dmesg()
关键信息包括:
BUG: kernel NULL pointer dereference, address: 00000000000006fc:内核尝试解引用空指针RIP: 0010:_raw_spin_lock_irqsave+0x36/0x70:崩溃发生在_raw_spin_lock_irqsave函数中- 完整的调用栈显示了从磁盘I/O完成中断到尝试获取自旋锁的过程
深入分析:调用栈
使用drgn获取崩溃线程的调用栈:
trace = prog.crashed_thread().stack_trace()
drgn提供的调用栈比内核日志更详细,包含:
- 文件名、行号和列号
- 内联函数调用信息
关键调用栈帧分析:
-
原子操作层(帧0-2):
- 涉及底层的原子操作实现
- 可以暂时跳过这些底层细节
-
自旋锁实现(帧3):
queued_spin_lock()函数- 打印锁指针:
trace[3]["lock"]显示地址0x6fc
-
锁封装层(帧4-7):
- 这些帧只是将锁指针传递下去
- 最终都指向同一个地址0x6fc
-
唤醒任务(帧8):
try_to_wake_up()函数- 发现任务结构体指针为NULL:
trace[8]["p"]显示0x0 - 0x6fc实际上是task_struct中pi_lock的偏移量
问题根源追踪
继续向上追踪调用栈:
- rq_qos_wake_function(帧9):
- 这是块层请求QoS的实现
- 查看data结构体:
trace[9]["data"] - 发现data->task指针异常(非NULL但无效)
关键发现:
- 任务指针虽然不为NULL,但指向无效的内存区域
- 任务名称(comm字段)为空,不是有效的task_struct
技术要点总结
-
偏移量计算:
- 使用
offsetof()计算结构体成员偏移 - 使用
member_at_offset()反向查找偏移量对应的成员
- 使用
-
调用栈分析技巧:
- 关注非内联函数调用
- 忽略以"?"开头的调用栈行(可能是错误的回溯)
-
内存分析:
- 识别无效指针(非NULL但指向无效内存)
- 检查结构体关键字段验证内存有效性
结论
通过drgn工具的深入分析,我们发现这个崩溃的根本原因是:
块层请求QoS(rq_qos)子系统在尝试唤醒一个任务时,使用了无效的任务结构体指针。虽然指针本身不是NULL,但它指向的内存区域不包含有效的task_struct结构,导致在获取任务pi_lock时发生空指针解引用。
这个案例展示了drgn在内核调试中的强大能力,特别是:
- 精确的调用栈分析(包括内联函数)
- 内存和结构体检查
- 指针和偏移量计算
对于内核开发者来说,掌握这些调试技巧可以大大缩短问题诊断时间,提高开发效率。
【免费下载链接】drgn Programmable debugger 项目地址: https://gitcode.com/gh_mirrors/dr/drgn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



