Crash:一个死锁引发Kernel PANIC的思考

       软件开发过程中经常会遇到因驱动或内核引起的死机问题,我遇到的有hung_task和NULL point两种类型问题。通常的分析和解决这两类问题,都是通过查看kernel log的方式去解决,但是有时也会从kernel log中无法找到有用的线索,此时就可以借助crash工具分析此类型的问题了.这里借助在项目中遇到的问题记录下详细的分析过程.

背景知识:

       在借助crash工具分析内核出现dump时,通常需要掌握3点知识,arm64的通用寄存器,内核task_struct, mm_struct等结构体的关系,汇编指令。本文记录的是rw_semaphore死锁问题,涉及到的知识点为task_struct, mm_struct, rw_semaphore, rwsem_waiter之间的关系,函数调用栈的形参是如何传递的以及如何组织函数调用过程中的栈帧.

1. 主要struct之间的关系

     当创建一个进程时,会分配一个task_struct结构体并填充,同时也会将mm指针指向创建好的mm_struct结构体,通过mm_struct中的owner可以找到所关联的task. 当系统中有多个read/write semaphore在等待获取锁时,会将其加入到rwsem_waiter等待链表.

2. arm64通用寄存器

       arm64有31个64bits的通用寄存器,用x表示64为宽,w表示32位宽. 这些寄存用于传递函数的形参,保存函数的返回地址,临时变量保存等,这里做了一个总结,便于后续在分析汇编时对这些通用寄存器有较好的理解.

       x0~x7用于保存被调用函数的形参和返回结果,使用时不需要保存。x8用于存放子函数的返回地址。x9~x15临时寄存器,使用时不需要保存。x19~x28临时寄存器,使用时需要保存,如果我们在某一次函数的调用栈中使用了x19~x28中一个或多个寄存器,但是没有看到该寄存器的栈操作,可以到上一级或者下一个函数调用栈中找。X29(fp寄存器)用于连接栈帧,从而把函数的多级调用串接成一个链表,x30(lr寄存器)链接寄存器,用来存放函数的返回地址。X31(SP寄存器),堆栈指针寄存器,一般来说如果函数的调用过程为func1()-àfunc2(),在func2的压栈过程中,会将FP指针和SP指针指向栈顶,即指向同一个地址。

3. 栈帧的组织形式

       在分析汇编代码的过程中发现,在被调用的函数入栈时,会先将FP和LR寄存器压入到栈顶,然后将x29指针mov给SP,之后再将每一级栈组织成链表的形式,最终形象化为下图。

问题分析:

       用CRASH工具加载死机时抓到的dump信息,格式为: crash dump 符号表. 先查看一下系统出错时的堆栈信息,但却看不到任何明显的出错信息,问题的分析显的无从下手。回过头来想一想,在加载dmup信息时crash工具有提示”kernel panic: hung_task”, 试图猜想一下系统是否由于un-interruptable导致的系统死机(UN).

尝试输入下图的命令ps |grep UN,发现出现4个UN进程, 下一步就需要分析每一个卡死的具体原因了.

     首先查看pid号为716的进程的堆栈信息,其调用过程为proc_pid_cmdline_readàaccess_remote_vmà__access_remote_vmàdown_readàrwsem_down_read_failed,然后就出现schedule了一直等到获取读写信号量rwsem锁了,由此可以断定此进程出现了死锁。分析源代码可知down_read的形参是一个rwsem型的指针,所以需要得到的是此函数的形参值,即分析上一级函数(__access_remote_vm)的反汇编。

       通过反汇编可知down_read的形参(rw_semaphore型变量指针)是由通用寄存器x0传递,从图中标红的地方可知在当前调用栈中是保存了此形参的地址了的。

     图中最后一行我们可以看到down_read被调用,由此为线索往上看,由于x0中保存down_read的参数,x0来源于x22(mov x0, x22), 而x22来自于sp +104(str x22, [xp,#104]), 读此地址中的值即可得到rw_semaphore型变量的指针地址了,

address = sp + 104 = ffff00000b273c00 +104 = ffff00000b273c68

接下来可以使用struct 命令查看rw_semaphore结构体中的关键变量的值:

       上图中的next指针保存的是rw_semaphore锁的等待链表头list_head,指向rwsem_waiter链表的头,即目前系统中需要获取该锁的进程都会挂入到该链表,所以想要了解rw_semaphore锁的使用和等待状态就需要查看rwsem_waiter了。使用list命令可得目前阻塞在这把锁上的task有两个,从此也可以证明确实存在锁的竞争问题。

接下来需要推导出进程所拥有的mm_struct的地址了,可以带入如下公式;

Address of mm_struct = address of rw_semaphore – offset ofre_semaphore in mm_struct

由代码定义知rw_semaphore在mm_struct中的便偏移为0x60

得mm_struct的地址为”

Address = ffff80005f733120 – 0x60 = ffff80005f7330c0

        从背景知识介绍中知,mm_struct中的owner成员值是指向拥有者task_struct的地址的,所以通过该地址可以追溯到task_struct中的成员值及堵塞该进程的名称和pid。

        由此可见我们在分析ps进程(pid为716)时,最终得到的task_stuct中的pid为715,comm为test,得出结论,ps进程在等待pid为715的进程。接下来分析test进程。

        其次查看pid号为715的进程的堆栈信息,其调用过程为el0_svc_commonà__arm64_sys_brkàdown_write_killable,然后就出现schedule了一直等到获取读写信号量rwsem锁了,由此可以断定此进程也出现了死锁. 同时发现调用栈中出现了sys_brk系统调用,所以test进程在是调用malloc时出现了死锁,所以需要反汇编sys_brk系统调用来一探究竟.

        从__arm64_sys_brk的反汇编中找到down_write_killable函数的调用点,然后向前推导出其形参值(rw_semaphore类型的指针)。我们知道mmap_sem在mm_struct中的偏移为0x60,down_write_killable中的形参存放在寄存器x0中,x0来源于x24, x24 = x25+ 0x60,所以x25中存放的是mm_struct结构体地址,x25 = x21 + 10800, x21来源于sp_el0,从上述地址的推导可知形参x0并没有存在于当前的栈中,所以从这个栈中无法得到x0的值。背景知识中曾经提到过,x19~x28寄存器用于存放临时变量,使用时必须保存,由此我们猜测是否可以从下一级或者上一级的调用栈中找到x24寄存器的值?

        这里我们从rwsem_down_write_failed_killable反汇编尝试取寻找x24的身影, 惊奇的一幕发生了,果然将x24寄存器入栈了(stp入栈指令, ldp出栈指令)

        可以得出x24 = sp + 48 +8 = ffff00000b26bd20 +48 +8 = ffff00000b26bd58,这个地址中存放的是rw_semaphore类型mmap_sem结构体指针的地址。通过执行rd(读取某个地址中的值,一般用于指针操作)命令可得

和上述test进程分析方法类似,知道了mmap_sem(rw_semapore类型)指针的地址,通过其在mm_struct中的偏移offset便可得到

mm_struct的地址= ffff80005f733120 - 0x60 = ffff80005f7330c0.

通过mm_struct中的owner成员地址,便可以得到tastk_struct的地址了。

        由此也可以得到mmap_sem锁是由test进程pid为715的进程持有了,正好和ps进程的分析保持一致。那如何知道ps和test进程持有的是读者锁或者写着锁, 有哪些进程在等待这个锁, 有几个锁挂在rwsem_waiter等待链表里?

        前面我们分析rw_semaphore时提到过,wait_list里有个next和prev,next是指向rwsem_waiter的链表头的,知道这个地址便可以查看所有rwsem类型的详细信息了.

使用list -s查看,得到test进程睡眠等到一个写着类型的锁,ps进程被test进程阻塞,睡眠等待一个写着类型的锁。

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值