Windbg調試關鍵區(CriticalSection)死鎖


一. 准備工作

這里一個有關鍵區鎖死問題的程序,運行之后依次點擊“CS鎖死”按鈕、右上角退出按鈕,程序就會卡死。(圖1)
圖1

對於眼下的這個問題,界面完全失去響應,這說明負責消息處理的UI線程阻塞了。對於幾乎所有的windows GUI程序,編號為0的初始線程就是UI線程,windows發現該界面一段時間沒有消息響應之后就會在標題后面加上“(未響應)”。

二. 開始調試

啟動Windbg,附加到執行進程(F6),這時如果在windbg輸出的上面看到如下內容(圖2),說明第一步的環境變量設置生效了。
圖2

~*knv3 查看各個線程的調用堆棧(圖3),數字3表示顯示的堆棧深度,省略即顯示完整堆棧。
圖3

#0號線的棧幀0表示線程程阻塞在NtWaitForSingleObject函數,MSDN得知該函數原型為:

NTSTATUS WINAPI NtWaitForSingleObject(
 _In_ HANDLE         Handle, 
  _In_ BOOLEAN        Alertable,
  _In_ PLARGE_INTEGER Timeout
);

第一個參數Handle為其等待的句柄,第三個參數TimeOut為超時時間。
同樣從棧幀0得知NtWaitForSingleObject正在等待句柄000000c4,超時時間為0(即沒信號就一直等待)。

!handle 000000c4 f 命令查看該句柄的信息(圖4):
圖4

現在我們知道c4句柄就是線程20d0的句柄,主線程在退出的時候等待該線程退出,而該線程一直沒有退出,所以主線程卡死了。

根據圖3得知20d0線程就是#1線程,~1kvn 查看該線程完整堆棧(圖5):
圖5

棧幀00 NtWaitForSingleObject 表示線程在等待000000c0句柄。
!handle 000000c0 f 得知c0句柄為事件句柄:

0:002> !handle c0 f
Handle c0
  Type         	Event
  Attributes   	0
  GrantedAccess	0x100003:
         Synch
         QueryState,ModifyState
  HandleCount  	2
  PointerCount 	4
  Name         	<none>
  Object Specific Information
    Event Type Auto Reset
    Event is Waiting

!locks 查看進程中哪些鎖處於鎖定狀態(圖6):
圖6

從第一行結果可以得知是gcsName臨界區(需要有pdb才會顯示具體變量名)處於鎖定狀態。

其實,我們從棧幀02 RtlEnterCriticalSection 也可以很快的知道該線程一直在等待進入關鍵區。

經過分析,知道程序如法退出的原因了:線程#1中的關鍵區gcsName處於鎖定狀態(也就是一直等待進入關鍵區),導致線程#1阻塞無法執行。又因主線程在退出的時候執行了WaitForSingleObject等待#1線程,從而導致主線程卡死。

關鍵區機制主要是通過下面這樣的RTL_CRITICAL_SECTION結構來實現的,可以通過dt 命令查看該結構定義:

0:002> dt RTL_CRITICAL_SECTION
Test1!RTL_CRITICAL_SECTION
   +0x000 DebugInfo        : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
   +0x004 LockCount        : Int4B
   +0x008 RecursionCount   : Int4B
   +0x00c OwningThread     : Ptr32 Void
   +0x010 LockSemaphore    : Ptr32 Void
   +0x014 SpinCount        : Uint4B

其中,LockCount字段用來標識關鍵區的鎖狀態,RecursionCount字段用來記錄遞歸次數,用來支持同一個線程多次進入關鍵區,OwningThread字段用來記錄進入(擁有)關鍵區的線程ID,LockSemaphore用來記錄這個關鍵區對應的事件對象,當有線程需要等待這個關鍵區時,便是通過等待這個事件來做到的,這個事件對象是按需創建的,如果LockSemaphore為NULL表示這個關鍵區從來沒有線程在此等待過。

通過圖6中的OwningThread=738得知,關鍵區被線程ID為738的線程所擁有,即Enter之后一直沒有Leave。

知道了是哪個線程獲取了關鍵區但沒有釋放,就可以很容易的在代碼中定位問題了。

!locks 沒有顯示LockSemaphore字段,我們可以通過!cs -l 命令獲取更為全面的關鍵區信息:
圖7

從上圖可以看到LockSemaphore=0xC0,正好是#1線程NtWaitForSingleObject的事件對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM