windbg的使用四(Windbg檢查死鎖 )
先上個程式碼,自己隨手寫的:
[cpp] view plaincopyprint?- #include
- CRITICAL_SECTION cs1;
- CRITICAL_SECTION cs2;
- DWORD __stdcall thread1(LPVOID lp)
- {
- EnterCriticalSection(&cs1);
- Sleep(10);
- EnterCriticalSection(&cs2);
- return 0;
- }
- DWORD __stdcall thread2(LPVOID lp)
- {
-
EnterCriticalSection(&cs2);
- Sleep(10);
- EnterCriticalSection(&cs1);
- return 0;
- }
- int main()
- {
- InitializeCriticalSection(&cs1);
- InitializeCriticalSection(&cs2);
- CreateThread(NULL, 0, thread1, 0, 0, NULL);
- CreateThread(NULL, 0, thread2, 0, 0, NULL);
- system("pause");
-
return 0;
- }
執行,生成release版本,去掉pdb,執行,程式停住了,windbg載入到程序,
先用~*kb檢視下所有的執行緒堆疊:
0:003> ~*kb
0 Id: 1a98.24c Suspend: 1 Teb: 7ffdf000 Unfrozen ChildEBP RetAddr Args to Child 0012fddc 7c92df5a 7c8025db 00000044 00000000 ntdll!KiFastSystemCallRet 0012fde0 7c8025db 00000044 00000000 00000000 ntdll!NtWaitForSingleObject+0xc 0012fe44 7c802542 00000044 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xa8 0012fe58 7854bd40 00000044 ffffffff 00000000 kernel32!WaitForSingleObject+0x12 0012fedc 7854c702 00000000 00392b98 00392de0 MSVCR90!_dospawn+0x1d1 [f:\dd\vctools\crt_bld\self_x86\crt\src\dospawn.c @ 215] 0012ff00 7854c84b 00000000 00392b98 0012ff5c MSVCR90!comexecmd+0x60 [f:\dd\vctools\crt_bld\self_x86\crt\src\spawnve.c @ 137] 0012ff38 7854cc71 00000000 00392b98 0012ff5c MSVCR90!_spawnve+0x12a [f:\dd\vctools\crt_bld\self_x86\crt\src\spawnve.c @ 273] 0012ff70 004010a8 004020f4 00000001 00401218 MSVCR90!system+0x8e [f:\dd\vctools\crt_bld\self_x86\crt\src\system.c @ 87] WARNING: Stack unwind information not available. Following frames may be wrong. 0012ffc0 7c817077 00300031 0032002d 7ffdc000 test2+0x10a8 0012fff0 00000000 00401360 00000000 78746341 kernel32!BaseProcessStart+0x23
1 Id: 1a98.1588 Suspend: 1 Teb: 7ffde000 Unfrozen ChildEBP RetAddr Args to Child 0050ff14 7c92df5a 7c939b23 0000002c 00000000 ntdll!KiFastSystemCallRet 0050ff18 7c939b23 0000002c 00000000 00000000 ntdll!NtWaitForSingleObject+0xc 0050ffa0 7c921046 00403370 0040101d 00403370 ntdll!RtlpWaitForCriticalSection+0x132 0050ffa8 0040101d 00403370 000203a8 7c80b729 ntdll!RtlEnterCriticalSection+0x46 WARNING: Stack unwind information not available. Following frames may be wrong. 0050ffec 00000000 00401000 00000000 00000000 test2+0x101d
2 Id: 1a98.185c Suspend: 1 Teb: 7ffdd000 Unfrozen ChildEBP RetAddr Args to Child 0060ff14 7c92df5a 7c939b23 00000034 00000000 ntdll!KiFastSystemCallRet 0060ff18 7c939b23 00000034 00000000 00000000 ntdll!NtWaitForSingleObject+0xc 0060ffa0 7c921046 00403388 0040104d 00403388 ntdll!RtlpWaitForCriticalSection+0x132 0060ffa8 0040104d 00403388 000203a8 7c80b729 ntdll!RtlEnterCriticalSection+0x46 WARNING: Stack unwind information not available. Following frames may be wrong. 0060ffec 00000000 00401030 00000000 00000000 test2+0x104d
# 3 Id: 1a98.159c Suspend: 1 Teb: 7ffdb000 Unfrozen ChildEBP RetAddr Args to Child 003dffc8 7c972119 00000005 00000004 00000001 ntdll!DbgBreakPoint 003dfff4 00000000 00000000 00000000 00000000 ntdll!DbgUiRemoteBreakin+0x2d[cpp] view plaincopyprint?
- 我們注意到1號執行緒的執行緒堆疊是從ntdll!RtlEnterCriticalSection中開始的,那麼ntdll!RtlEnterCriticalSection又是什麼函式的入口呢,首先猜到的是EnterCriticalSection,這個函式是kernel32.dll中的,為了驗證猜測,我們用dump檢視到kernel32.dll的匯出函式:
果然如此,
1. !cs
!cs 擴展顯示一個或多個臨界區(critical section)或者整個臨界區樹
前面說的ntdll!RtlEnterCriticalSection的第一個引數是臨界區的地址,事實上用uf反彙編它,可以看到是ret 4,說明就只有一個引數
那麼,
- !cs Address 指定要顯示的臨界區地址。如果省略該引數,偵錯程式顯示當前程序中所有臨界區。
- 0:003> ~1kb
- ChildEBP RetAddr Args to Child
- 0050ff14 7c92df5a 7c939b23 0000002c 00000000 ntdll!KiFastSystemCallRet
- 0050ff18 7c939b23 0000002c 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
- 0050ffa0 7c921046 00403370 0040101d 00403370 ntdll!RtlpWaitForCriticalSection+0x132
- 0050ffa8 0040101d 00403370 000203a8 7c80b729 ntdll!RtlEnterCriticalSection+0x46
- WARNING: Stack unwind information not available. Following frames may be wrong.
- 0050ffec 00000000 00401000 00000000 00000000 test2+0x101d
- 0:003> !cs 00403370
- -----------------------------------------
- Critical section = 0x00403370 (test2+0x3370)
- DebugInfo = 0x7c99e9e0
- LOCKED
- LockCount = 0x1
- OwningThread = 0x0000185c
- RecursionCount = 0x1
- LockSemaphore = 0x2C
- SpinCount = 0x00000000
- 這裡LockCount為1意思為除了一個執行緒擁有它外,另外還有一個執行緒在等待它,它是由EnterCriticalSection增加,LeaveCriticalSection來減小的,比如我再加一點程式碼:
- DWORD __stdcall thread3(LPVOID lp)
- {
- EnterCriticalSection(&cs2);
- Sleep(10);
- EnterCriticalSection(&cs1);
- return 0;
- }
- int main()
- {
- InitializeCriticalSection(&cs1);
- InitializeCriticalSection(&cs2);
- CreateThread(NULL, 0, thread1, 0, 0, NULL);
- CreateThread(NULL, 0, thread2, 0, 0, NULL);
- CreateThread(NULL, 0, thread3, 0, 0, NULL);
- system("pause");
- return 0;
- }
這時執行windbg:
[cpp] view plaincopyprint?- 0:004> ~1kb
- ChildEBP RetAddr Args to Child
- 0051fe48 7c92df5a 7c939b23 00000034 00000000 ntdll!KiFastSystemCallRet
- 0051fe4c 7c939b23 00000034 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
- 0051fed4 7c921046 00417140 00411420 00417140 ntdll!RtlpWaitForCriticalSection+0x132
- *** WARNING: Unable to verify checksum for D:\Project1\test2\Debug\test2.exe
- 0051fedc 00411420 00417140 00000000 00000000 ntdll!RtlEnterCriticalSection+0x46
- 0051ffb4 7c80b729 00000000 00000000 00000000 test2!thread1+0x50 [d:\project1\test2\test2\test2.cpp @ 10]
- 0051ffec 00000000 00411122 00000000 00000000 kernel32!BaseThreadStart+0x37
- 0:004> !cs 00417140
- -----------------------------------------
- Critical section = 0x00417140 (test2!cs2+0x0)
- DebugInfo = 0x7c99ea00
- LOCKED
- LockCount = 0x2
- OwningThread = 0x00001f60
- RecursionCount = 0x1
- LockSemaphore = 0x34
- SpinCount = 0x00000000
可以發現LockCount變成了2,如果臨界區是有訊號的,則顯示NOT LOCKED(值為-1)
OwningThread表示擁有這個臨界區的執行緒ID,RecursionCount表示擁有執行緒調了幾次EnterCriticalSection,這其實也影響到了LockCount,如果擁有執行緒多呼叫一次EnterCriticalSection,那麼 LockCount也會相應加1,因為LockCount標識了任意執行緒呼叫EnterCriticalSection請求這個互斥量的次數減1,(所以0-1=-1為NOT LOCKED)當然,前面如果呼叫了LeaveCriticalSection,那麼 LockCount也會相應減1
我們繼續看原有的程式:
~~[TID]執行緒 ID 為 TID 的執行緒。(中括號是必需的,而且在第二個~和左括號間不能有空格)
[cpp] view plaincopyprint?- 0:003> ~~[0x0000185c]
- 2 Id: 1a98.185c Suspend: 1 Teb: 7ffdd000 Unfrozen
- Start: test2+0x1030 (00401030)
- Priority: 0 Priority class: 32 Affinity: f
這意思就是1號執行緒等待的臨界區擁有者是2號執行緒,那麼同樣我們對2號執行緒進行分析:
[cpp] view plaincopyprint?- 0:003> ~2kb
- ChildEBP RetAddr Args to Child
- 0060ff14 7c92df5a 7c939b23 00000034 00000000 ntdll!KiFastSystemCallRet
- 0060ff18 7c939b23 00000034 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
- 0060ffa0 7c921046 00403388 0040104d 00403388 ntdll!RtlpWaitForCriticalSection+0x132
- 0060ffa8 0040104d 00403388 000203a8 7c80b729 ntdll!RtlEnterCriticalSection+0x46
- WARNING: Stack unwind information not available. Following frames may be wrong.
- 0060ffec 00000000 00401030 00000000 00000000 test2+0x104d
- 0:003> !cs 00403388
- -----------------------------------------
- Critical section = 0x00403388 (test2+0x3388)
- DebugInfo = 0x7c99e9c0
- LOCKED
- LockCount = 0x1
- OwningThread = 0x00001588
- RecursionCount = 0x1
- LockSemaphore = 0x34
- SpinCount = 0x00000000
- 0:003> ~~[0x00001588]
- 1 Id: 1a98.1588 Suspend: 1 Teb: 7ffde000 Unfrozen
- Start: test2+0x1000 (00401000)
- Priority: 0 Priority class: 32 Affinity: f
原來2號執行緒等待的臨界區擁有者是1號執行緒,所以經典的死鎖現象出現了!!!!!!!!!!!!!!!!!!!!!!!!
下面繼續介紹下!cs的擴充套件:
- !cs -s 如果可能的話,顯示每個臨界區的初始堆疊回溯。
- !cs -l 僅顯示鎖定的臨界區。
- 0:003> !cs -l
- -----------------------------------------
- DebugInfo = 0x7c99e9c0
- Critical section = 0x00403388 (test2+0x3388)
- LOCKED
- LockCount = 0x1
- OwningThread = 0x00001588
- RecursionCount = 0x1
- LockSemaphore = 0x34
- SpinCount = 0x00000000
- -----------------------------------------
- DebugInfo = 0x7c99e9e0
- Critical section = 0x00403370 (test2+0x3370)
- LOCKED
- LockCount = 0x1
- OwningThread = 0x0000185c
- RecursionCount = 0x1
- LockSemaphore = 0x2C
- SpinCount = 0x00000000
!cs starAddress EndAddress指定要搜尋臨界區的地址範圍
- 0:003> !cs 0x00400000 0x00500000
- -----------------------------------------
- DebugInfo = 0x7c99e9c0
- Critical section = 0x00403388 (test2+0x3388)
- LOCKED
- LockCount = 0x1
- OwningThread = 0x00001588
- RecursionCount = 0x1
- LockSemaphore = 0x34
- SpinCount = 0x00000000
- -----------------------------------------
- DebugInfo = 0x7c99e9e0
- Critical section = 0x00403370 (test2+0x3370)
- LOCKED
- LockCount = 0x1
- OwningThread = 0x0000185c
- RecursionCount = 0x1
- LockSemaphore = 0x2C
- SpinCount = 0x00000000
-
!cs -o 對所有顯示出來的已鎖定的臨界區,顯示所有者的堆疊。
!cs -?顯示該命令的幫助文字。
- 0:003> !cs -?
- !cs [-s] [-l] [-o] - dump all the active critical sections in the current process.
- !cs [-s] [-o] address - dump critical section at this address.
- !cs [-s] [-l] [-o] address1 address2 - dump all the active critical sections in this range.
- !cs [-s] [-o] -d address - dump critical section corresponding to DebugInfo at this address.
- "-s" will dump the critical section initialization stack trace if it is available.
- "-l" will dump only the locked critical sections.
- "-o" will dump the critical section owner's stack.