使用drgn调试Linux内核:blk-rq-qos崩溃案例分析

使用drgn调试Linux内核:blk-rq-qos崩溃案例分析

【免费下载链接】drgn Programmable debugger 【免费下载链接】drgn 项目地址: https://gitcode.com/gh_mirrors/dr/drgn

引言

在Linux内核开发和生产环境中,遇到内核崩溃是常见但棘手的问题。本文将通过一个真实的Linux内核崩溃案例,展示如何使用drgn工具进行深入分析。这个案例涉及Linux 6.11版本中块层(block layer)的一个bug,导致内核在生产环境中崩溃。

准备工作

在开始分析前,我们需要准备以下环境:

  1. 安装drgn工具(遵循官方安装指南)
  2. 获取崩溃转储文件和调试符号
  3. 准备好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提供的调用栈比内核日志更详细,包含:

  1. 文件名、行号和列号
  2. 内联函数调用信息

关键调用栈帧分析:

  1. 原子操作层(帧0-2):

    • 涉及底层的原子操作实现
    • 可以暂时跳过这些底层细节
  2. 自旋锁实现(帧3):

    • queued_spin_lock()函数
    • 打印锁指针:trace[3]["lock"]显示地址0x6fc
  3. 锁封装层(帧4-7):

    • 这些帧只是将锁指针传递下去
    • 最终都指向同一个地址0x6fc
  4. 唤醒任务(帧8):

    • try_to_wake_up()函数
    • 发现任务结构体指针为NULL:trace[8]["p"]显示0x0
    • 0x6fc实际上是task_struct中pi_lock的偏移量

问题根源追踪

继续向上追踪调用栈:

  1. rq_qos_wake_function(帧9):
    • 这是块层请求QoS的实现
    • 查看data结构体:trace[9]["data"]
    • 发现data->task指针异常(非NULL但无效)

关键发现:

  • 任务指针虽然不为NULL,但指向无效的内存区域
  • 任务名称(comm字段)为空,不是有效的task_struct

技术要点总结

  1. 偏移量计算

    • 使用offsetof()计算结构体成员偏移
    • 使用member_at_offset()反向查找偏移量对应的成员
  2. 调用栈分析技巧

    • 关注非内联函数调用
    • 忽略以"?"开头的调用栈行(可能是错误的回溯)
  3. 内存分析

    • 识别无效指针(非NULL但指向无效内存)
    • 检查结构体关键字段验证内存有效性

结论

通过drgn工具的深入分析,我们发现这个崩溃的根本原因是:

块层请求QoS(rq_qos)子系统在尝试唤醒一个任务时,使用了无效的任务结构体指针。虽然指针本身不是NULL,但它指向的内存区域不包含有效的task_struct结构,导致在获取任务pi_lock时发生空指针解引用。

这个案例展示了drgn在内核调试中的强大能力,特别是:

  • 精确的调用栈分析(包括内联函数)
  • 内存和结构体检查
  • 指针和偏移量计算

对于内核开发者来说,掌握这些调试技巧可以大大缩短问题诊断时间,提高开发效率。

【免费下载链接】drgn Programmable debugger 【免费下载链接】drgn 项目地址: https://gitcode.com/gh_mirrors/dr/drgn

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值