kernel空指標引起的system_server watchdog問題分析
一. 問題描述
1.1 現象
手機kernel啟動時狂點螢幕會導致手機卡死重啟.
1.3 結論
因為surfaceflinger主執行緒在等中斷處理函式執行執行結束,而中斷處理函式一直沒有執行完,所以導致surfaceflinger的主執行緒卡住.這時候system_server通過binder跨程序去呼叫 surfaceflinger中的方法時,由於surfaceflinger卡住了,所以system_server也會被卡住,最終導致了system_server watchdog.
1.4修復連結:
二. 初步分析
2.1 檢視system_server trace
一般對於watchdog問題,我們都會先看看system_server的那一個執行緒出了問題,出現問題的具體呼叫棧如下:
從這裡我們能看到system_server的binder執行緒在向surfaceflinger發起跨程序呼叫時被block了,具體的呼叫方法是createSurface,那麼接下來我們就去看看surfaceflinger的trace,看看它在幹什麼事情.
2.1 檢視surfaceflinger trace
我下了好幾份log,都沒有發現watchdog檔案中有surfaceflinger的trace,沒有能繼續往下分析,只能先去找沒有打印出surfaceflinger trace的原因.
2.2 surfaceflinger打不出trace的懷疑點
①debuggerd出問題了?
不會出問題,要是它出了問題,其他native程序也不會打印出對應的trace.
②watchdog類裡把中surfaceflinger這個要抓trace的程序給去掉了?然後查看了下c8對應的程式碼:
surfaceflinger這個程序還在預設的列表裡面,這就比較奇怪了,然後再繼續下載了幾十份Log,然而沒有一份log有surfaceflinger的trace.
繼續看下相應的Log,debuggerd確實列印了相應的log
12-29 20:42:17.814 695 831 W SurfaceFlinger: setTransactionState timed out waiting for previous animation frame 12-29 20:42:19.695 2912 3259 W PushService: 2017-12-29 20:42:19,695 - [WARN::PushService] - [Thread:99] [Slim] 08:42:19 ä¸å Sent Blob [SECMSG,5,5QbAV-13] 12-29 20:42:19.695 933 5418 I ExtendedUtils: printFileName fd(15) -> /system/media/audio/notifications/CrystalRing.ogg 12-29 20:42:19.695 2912 3259 W PushService: 2017-12-29 20:42:19,695 - [WARN::PushService] - [Thread:99] try send mi push message. packagename:com.miui.powerkeeper action:Notification 12-29 20:42:19.696 2912 3259 W PushService: 2017-12-29 20:42:19,695 - [WARN::PushService] - [Thread:99] [Slim] Wrote {cmd=SECMSG;chid=5;len=349} 12-29 20:42:22.955 695 831 W SurfaceFlinger: setTransactionState timed out waiting for previous animation frame 12-29 20:43:16.835 591 591 W : debuggerd: handling request: pid=695 uid=1000 gid=1003 tid=695 12-29 20:43:26.849 591 591 W : debuggerd: resuming target 695 12-29 20:43:26.854 591 591 W : debuggerd: handling request: pid=923 uid=1041 gid=1005 tid=923 12-29 20:43:26.857 590 590 W : debuggerd: handling request: pid=923 uid=1041 gid=1005 tid=923 12-29 20:43:26.999 591 591 W : debuggerd: handling request: pid=924 uid=1047 gid=1005 tid=924 12-29 20:43:27.000 590 590 W : debuggerd: handling request: pid=924 uid=1047 gid=1005 tid=924 12-29 20:43:27.106 591 591 W : debuggerd: handling request: pid=925 uid=1019 gid=1019 tid=925 12-29 20:43:27.107 590 590 W : debuggerd: handling request: pid=925 uid=1019 gid=1019 tid=925 12-29 20:43:27.120 591 591 W : debuggerd: handling request: pid=926 uid=0 gid=0 tid=926 12-29 20:43:57.857 5557 5557 W : debuggerd: handling request: pid=695 uid=1000 gid=1003 tid=695 12-29 20:44:07.870 5557 5557 W : debuggerd: resuming target 695 12-29 20:44:07.876 5557 5557 W : debuggerd: handling request: pid=923 uid=1041 gid=1005 tid=923 12-29 20:44:07.878 590 590 W : debuggerd: handling request: pid=923 uid=1041 gid=1005 tid=923 12-29 20:44:07.931 5557 5557 W : debuggerd: handling request: pid=924 uid=1047 gid=1005 tid=924 12-29 20:44:07.932 590 590 W : debuggerd: handling request: pid=924 uid=1047 gid=1005 tid=924 12-29 20:44:08.044 5557 5557 W : debuggerd: handling request: pid=925 uid=1019 gid=1019 tid=925 12-29 20:44:08.045 590 590 W : debuggerd: handling request: pid=925 uid=1019 gid=1019 tid=925 12-29 20:44:08.056 5557 5557 W : debuggerd: handling request: pid=926 uid=0 gid=0 tid=926 |
③surfaceflinger程序中的莫個執行緒D狀態了.
由於沒有現場,無法確認.
由於第一,第二種都被排除了,只有第三種最可疑了,如果是第三種情況的話,那就肯定是bsp同事合入的程式碼有問題導致的.
2.3 排查當時合入的程式碼
由於沒有surfaceflinger的trace,暫時無法繼續往下分析了.只能去排查當時進了哪些程式碼,並且讓測試幫忙跑穩定性測試,看看能不能復現.然後排查了一波程式碼,發現幾個改動比較大的,發了郵件讓相關的owner自己檢查一下,具體的change如下:
郵件也發完了,但是大家好像都比較自信,都相信自己的change沒有問題.
2.4 重新檢視Log,查詢規律
大家都不願意懷疑自己的change有問題,只能重新回去看看log.看下所有的Log中共同存在的特徵.然後終於找到了突破口:
<2>[ 0.883908] Kernel BUG at ffffff94ce9ca228 [verbose debug info unavailable] |
第一個kernel空指標,具體詳細log如下:
<1>[ 0.883781] Unable to handle kernel NULL pointer dereference at virtual address 00000200 |
這個log大概就是說irq/265-synapti程序在kernel裡發生了一個空指標,空指標的位置在synaptics_rmi4_add_and_update_tp_data方法中
第二個kernel空指標,具體詳細log如下:
<1>[ 0.892273] Unable to handle kernel paging request at virtual address ffffffffffffffd8 |
這個log和第一個很類似就是irq/265-synapti程序在kernel裡發生了一個空指標,空指標的位置在kthread_data方法中.
還有一個比較關鍵的一個地方是:
Fixing recursive fault but reboot is needed!
這裡說我們遇到了一個遞迴的故障,需要reboot才能解決,具體程式碼如下:
從我們的Log中也能看到這是一個連續發生的空指標問題,先發生了一次空指標,然後呼叫do_exit,在後續的流程中又發生了空指標,然後就陷入了一個迴圈掛掉的流程中,然後就呼叫到了schedule()方法,最終陷入D狀態.
三. 深入分析
初步分析後我們發現每次發生這個watchdog問題都有一個kernel 空指標問題發生,然後順著這個空指標我們能找到當時合入的一個change:XXX
空指標的地方就是這個change里加入的,然後和鍾桂林溝通了一下,有了這個復現路徑。
因為我們抓到的所有trace檔案中並沒有surafaceflinger的trace,這個很有可能是由於surafaceflinger程序中的某個執行緒D狀態了導致的.所以復現之後直接ps -t | grep " D "檢視一下存在的D狀態的執行緒,我們可以看到如下執行緒:
root 327 2 0 0 do_exit 0000000000 D irq/265-synapti |
surfaceflinger和irq/265-synapti程序都D狀態了
3.1 分析irq/265-synapti程序
抓了一個kernel的ramdump,列印了一下當前的呼叫棧:
從這個呼叫棧中我們能看出有個中斷處理函式在synaptics_rmi4_add_and_update_tp_data方法中發生了一次空指標,然後執行緒要退出,但是在執行到task_work_run方法中時又發生了空指標,然後呼叫到schedule方法.最終導致了這個執行緒D狀態.(當手指觸控式螢幕幕的時候會在硬體上產生一箇中斷,然後呼叫在probe函式中配置好的中斷處理函式)
第一個空指標的具體推到如下:
我們從呼叫棧中能看到空指標的位置是在synaptics_rmi4_add_and_update_tp_data+36,然後我們可以看一下這個方法當時的彙編資訊:
我們直接找到+36的位置:
我們從+28/+32/+36這幾行大概能看出來如下內容:+28這行先將0xffffff8a9f318000地址的內容放到x0暫存器中,+32這行將x0暫存器偏移到相對x0的#3920,+36這行又將x0再偏移到相對x0的#512位置。
我們大概能知道這裡是再取地址,然後再對比下原始碼:
結合彙編程式碼我們對再比程式碼後就能看出來,這裡是在8208行出現問題了,出現問題的原因是:f54->rmi4_data是空指標。
f54是個全域性變數對應:adrp x0, 0xffffff8a9f318000
然後我們要去取f54->rmi4_data的地址,對應:ldr x0, [x0,#3920]
接著我們去取f54->rmi4_data->es_enabled,對應:ldr x0, [x0,#512]
但是f54->rmi4_data是個空指標,所以在“ldr x0, [x0,#512]”時就會報錯了,接著這個中斷處理函式就掛了,接著就開始退出了。
第二個空指標的具體推到如下:
我們前邊說了中斷處理函數出現空指標然後開始退出,但是退出的時候又空指標了,我們直接去看的彙編資訊:
直接對比下原始碼:
這裡也是在取地址的時候空指標的,這個空指標的具體原因就不深究了,很可能就是因為上一個task執行出錯導致了,這裡出現空指標,有興趣的同學可以研究一下。
那麼程式退出為什麼會進入D狀態呢?我們取看開exit.c中的程式碼:
就是執行緒在退出的時候陷入了一個迴圈,就會呼叫到schedule,最終該執行緒會進入到D狀態
3.2 分析surfaceflinger程序
好了irq/265-synapti程序中的主執行緒D狀態的大概原因說完了,我們再看看surfaceflinger程序當時再幹嘛:
jason:/ # ps -t 692 |
我們發現surfaceflinger的主執行緒D狀態了,然後我們接著看下692這個程序的kernel的呼叫棧:
我們這裡直接找到對應的原始碼:
看到這裡可能會有人有疑問了,呼叫棧裡synchronize_irq裡不是呼叫了schedule方法嗎?這裡沒有看到這個方法呀,弄錯了?開始我也懷疑我找錯了,然後我們看下wati_event的定義,
我們能看到這裡用的都是define來定義的,所以wat_event並不是一個具體的函式方法:
在__wait_event中我們傳入了一個schedule方法,
最後會執行schedule方法。
我們再仔細看看synchronize_irq方法的註釋:
/** * synchronize_irq - wait for pending IRQ handlers (on other CPUs)
* @irq: interrupt number to wait for This function waits for any pending IRQ handlers for this interrupt
* to complete before returning. If you use this function while holding a resource the IRQ handler may need you will deadlock.
* This function may be called - with care - from IRQ context. */
大概意思是說:
synchronize_irq 方法在返回前需要等待任意一個 IRQ處理函程式數執行完成。當你持有中斷處理程式時,如果你使用這個函式,就可能造成死鎖。
那我們看看這裡再等待哪一個irq呢?我們看到disable_irq方法中來,disable_irq方法的引數就是對應的irq,那麼我們就從ramdump中來看開這個irq的值是多少,首先我們能看到如下方法:
disable_irq的第一個引數就是irq,那麼我們就去找找這個引數的值是多少:
我們先看看這個方法的彙編資訊,因為這個方法只有一個引數,他的irq就是存在w0暫存器中的,這個方法中我們並不能找到w0的地址我們繼續往上一級函式synaptics_rmi4_irq_enable看看:
我們從disable_irq和synaptics_rmi4_irq_enable彙編資訊來做如下推斷:
1.disable_irq的irq儲存再w0暫存器中,所以synaptics_rmi4_irq_enable方法在呼叫disable_irq方法時會將irq的值儲存在w0中然後呼叫disable_irq方法,我們能看到disable_irq方法的地址是0xffffff8a9c8f7990,所以在synaptics_rmi4_irq_enable的棧中我們能找到這個地址,就是圖上白色的地方,我們能看到w0是,x0偏移了#808得到的,我們再結合程式碼:
所以我們能推斷出x0存的肯定是rmi4_data的地址。因為後邊還會用到rmi4_data,而x0這個暫存器會在呼叫方法時被修改(傳參),所以rmi4_data肯定會存在其他的暫存器上儲存,我們在找找synaptics_rmi4_irq_enable棧上的資訊,看看x0會把地址儲存在哪一個暫存器上,我們能發現,他儲存在了x19上,並且這個棧中沒有再修改x19,那麼我們再看到disable_irq棧上來:
x19是sp偏移16得到的,那麼我們繼續找到sp的地址:
所以x19的地址是:0xffffffce2f173a50+16
那麼我們列印一下rmi4_data存的所有資料的值,裡邊肯定存在irq的值:
。。。
。。。
我們能找到對應的irq號,它為256.
當然了irq/265-synapti程序的呼叫棧也用類似的方法我們也能找到它的中斷號,也為:265。這裡就不反覆推到浪費大家時間了。
三. 撥開雲霧見青天
我們再來回顧一下,首先是irq/265-synapti程序執行中斷處理函式時發生了空指標異常自己連續掛了兩次,並進入D狀態卡住,然後surfaceflinger在等待它的中斷處理函式執行結束,由於執行中斷處理函式的程序已經進入D狀態,因此中斷處理函式永遠無法執行結束,最終導致surfaceflinger也卡住了,這時候system_server通過同步binder 呼叫到surfaceflinger,因為surfaceflinger無法處理,所以system_server也會卡住,最終導致了system_server watchdog.所以這個問題只需要將最開始的空指標問題修復,那麼就能修復這個問題了:XXX
四. 其他疑問
1.kernel空指標為什麼沒有重啟?
沒有重啟的原因是沒有走panic的流程,原因如下:
http://guard.pt.miui.com/opengrok2/xref/v8-n-jason-dev/kernel/msm-4.4/arch/arm64/kernel/traps.c#313
http://guard.pt.miui.com/opengrok2/xref/v8-n-jason-dev/kernel/msm-4.4/kernel/panic.c#34
觸發panic有一個標誌:panic_on_oops,它的值在c8上預設是0
311 if (in_interrupt()) 312 panic("Fatal exception in interrupt"); 313 if (panic_on_oops) 314 panic("Fatal exception"); 315 if (notify != NOTIFY_STOP) 316 do_exit(SIGSEGV); 317}
panic_on_oops標誌設定為1是在init程序中:
http://guard.pt.miui.com/opengrok2/xref/v8-n-jason-dev/system/core/rootdir/init.rc#114
發生這個空指標的時候init程序還沒有起來