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進程阻塞,睡眠等待一個寫着類型的鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章