Windbg跟蹤臨界區的BUG

  最近跟蹤了一個程序的界面卡死問題,該卡死偶爾出現,在抓到一次dump後用windbg載入分析,打印出函數調用堆棧後,一眼可以看出是臨界區死鎖了。
代碼:
0:000:x86> kb
ChildEBP RetAddr  Args to Child              
0032dd0c 779ed993 00000710 00000000 00000000 ntdll_779b0000!NtWaitForSingleObject+0x15
0032dd70 779ed877 00000000 00000000 024023f0 ntdll_779b0000!RtlpWaitOnCriticalSection+0x13e
0032dd98 58a2fac3 02404c50 856fd57e 024023f0 ntdll_779b0000!RtlEnterCriticalSection+0x150
0032dffc 58a0d4d7 856fea8a 00000000 001c41a0 SogouSoftware_589d0000!CDownloadListUI::UpdateDownloadListUI+0x43
  輸出該臨界區的信息:
代碼:
0:000:x86> !cs 02404c50
-----------------------------------------
Critical section   = 0x0000000002404c50 (+0x2404C50)
DebugInfo          = 0x0000000000611e08
LOCKED
LockCount          = 0xFFFFFFFF
WaiterWoken        = Yes
OwningThread       = 0x0000000000000710
RecursionCount     = 0x1A38
LockSemaphore      = 0x2433B08
SpinCount          = 0x0000000000000000
  Windbg指示是0x710號線程佔有該臨界區在,於是看看0x710號線程是那一個,結果發現報錯,一般這種情況是線程已經退出或者被終止掉了。
代碼:
0:000:x86> ~~[710]
                 ^ Illegal thread error in '~~[710]'
  該臨界區死鎖的位置部分代碼如下:
代碼:
void CDownloadListUI::UpdateDownloadListUI()
{
  m_vctLock.Lock();
  vector<int> vecDeleteItems(GetCount());  // record index to be delete
  std::iota(vecDeleteItems.begin(), vecDeleteItems.end(), 0);
  ......
  m_vctLock.UnLock();
}
  m_vctLock是ATL一個對臨界區簡單封裝的類,讓同事仔細對m_vctLock所有加鎖的位置進行檢查,發現除了主線程外只有另外一個工作線程會使用,用windbg查了下改工作線程並未退出,線程ID也不爲 0x710,難道又被Windbg給忽悠了?打印下該臨界區的數據結構看看:
代碼:
0:000:x86> dt _RTL_CRITICAL_SECTION 02404c50
DuiLib!_RTL_CRITICAL_SECTION
   +0x000 DebugInfo        : 0x00611e08 _RTL_CRITICAL_SECTION_DEBUG
   +0x004 LockCount        : 0n-6
   +0x008 RecursionCount   : 0n1
   +0x00c OwningThread     : 0x00001a38 Void
   +0x010 LockSemaphore    : 0x00000710 Void
   +0x014 SpinCount        : 0
  發現這裏顯示的擁有者線程號和上面不一致,試試看是那個線程:
代碼:
0:000:x86> ~~[1a38]
   6  Id: 2058.1a38 Suspend: 0 Teb: 7ef94000 Unfrozen
      Start: SogouSoftware_589d0000!_threadstartex (58a5192d) 
      Priority: 0  Priority class: 32  Affinity: f
0:000:x86> ~6s
ntdll_779b0000!ZwWaitForMultipleObjects+0x15:
779d019d 83c404          add     esp,4
0:006:x86> kb
ChildEBP RetAddr  Args to Child              
0370fa5c 768615f7 00000002 0370faac 00000001 ntdll_779b0000!ZwWaitForMultipleObjects+0x15
0370faf8 773519f8 0370faac 0370fb20 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100
0370fb40 773541d8 00000002 7efde000 00000000 kernel32!WaitForMultipleObjectsExImplementation+0xe0
0370fb5c 589f6ba0 00000002 0370fb84 00000000 kernel32!WaitForMultipleObjects+0x18
0370fbd4 58a51907 58aab894 862df68e 00000000 SogouSoftware_589d0000!CThreadQueue<TagDownloadTask>::ThreadProc+0x100 
0370fc0c 58a51991 00000000 0370fc24 7735336a SogouSoftware_589d0000!_callthreadstartex+0x1b [f:\dd\vctools\crt_bld\self_x86\crt\src\threadex.c @ 314]
0370fc18 7735336a 023f5170 0370fc64 779e9882 SogouSoftware_589d0000!_threadstartex+0x64 [f:\dd\vctools\crt_bld\self_x86\crt\src\threadex.c @ 292]
0370fc24 779e9882 023f5170 771cc6bb 00000000 kernel32!BaseThreadInitThunk+0xe
0370fc64 779e9855 58a5192d 023f5170 00000000 ntdll_779b0000!__RtlUserThreadStart+0x70
0370fc7c 00000000 58a5192d 023f5170 00000000 ntdll_779b0000!_RtlUserThreadStart+0x1b
  6號線程正處於等待任務中,對照代碼,6號線程有一個觀察者的回調函數會調用到CDownloadListUI類中的m_vctLock鎖,但是該回調函數已經執行完畢了。那麼只有一種可能,就是鎖泄露了,即Lock後沒有解鎖。再去看該回調函數,果然發現有一個很少出現的分支下沒有調用Unlock釋放臨界區而直接返回了,這就造成了經典的鎖泄露而引起死鎖的bug。
  但 !cs 命令在此種情況下卻給出了錯誤的信息,很容易造成誤導而懷疑是一個擁有該鎖的線程退出了而引起的。這應該算是Windbg的一個bug吧。
  進一步測試該情況,發現是當64位系統下的32位進程產生的dump會有此問題,32位系統下產生的dump使用 !cs 命令執行正常。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章