最近自己写程序遇到了死锁的问题,看了网上网友的文章写的很好,转载过来,作为一个学习的方向
原文链接 https://blog.csdn.net/hgy413/article/details/7572097#comments,这位大神对windbg和汇编都有很多优秀的原创文章
先上个代码,自己随手写的:
#include <windows.h >
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:005> ~*kn
0 Id: 2d68.b3b8 Suspend: 1 Teb: 00772000 Unfrozen
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0098fb24 76bdae72 ntdll!ZwWaitForSingleObject+0xc
01 0098fb38 729dbd40 KERNELBASE!WaitForSingleObject+0x12
02 0098fbbc 729dc702 MSVCR90!wunlink+0x5bf
03 0098fbe0 729dc84b MSVCR90!spawnv+0xb7
04 0098fc18 729dcc71 MSVCR90!spawnve+0x12a
05 0098fc50 008810a8 MSVCR90!system+0x8e
06 0098fca0 76a462c4 Test+0x10a8
07 0098fcb4 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24
08 0098fcfc 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439
09 0098fd0c 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404
1 Id: 2d68.aef8 Suspend: 1 Teb: 00775000 Unfrozen
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 00cefc68 76a462c4 ntdll!NtWaitForWorkViaWorkerFactory+0xc
01 00cefc7c 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24
02 00cefcc4 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439
03 00cefcd4 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404
2 Id: 2d68.8f38 Suspend: 1 Teb: 00778000 Unfrozen
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 00e2fe94 76a462c4 ntdll!NtWaitForWorkViaWorkerFactory+0xc
01 00e2fea8 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24
02 00e2fef0 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439
03 00e2ff00 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404
3 Id: 2d68.b1b4 Suspend: 1 Teb: 0077b000 Unfrozen
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 00f6fb80 776b6f0d ntdll!NtWaitForAlertByThreadId+0xc
01 00f6fbc4 776b6dff ntdll!RtlWaitOnAddress+0x1dd
02 00f6fc00 776d0045 ntdll!RtlWaitOnAddress+0xcf
03 00f6fc20 776cff65 ntdll!RtlEnterCriticalSection+0x125
04 00f6fc2c 0088101d ntdll!RtlEnterCriticalSection+0x45
05 00f6fc4c 776f0fd9 Test+0x101d
06 00f6fc94 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439
07 00f6fca4 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404
4 Id: 2d68.20bc Suspend: 1 Teb: 0077e000 Unfrozen
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 010ffce0 776b6f0d ntdll!NtWaitForAlertByThreadId+0xc
01 010ffd24 776b6dff ntdll!RtlWaitOnAddress+0x1dd
02 010ffd60 776d0045 ntdll!RtlWaitOnAddress+0xcf
03 010ffd80 776cff65 ntdll!RtlEnterCriticalSection+0x125
04 010ffd8c 0088104d ntdll!RtlEnterCriticalSection+0x45
05 010ffdac 776f0fd9 Test+0x104d
06 010ffdf4 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439
07 010ffe04 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404
# 5 Id: 2d68.1f98 Suspend: 1 Teb: 00781000 Unfrozen
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0153fecc 76a462c4 ntdll!DbgBreakPoint
01 0153fee0 776f0fd9 KERNEL32!BaseThreadInitThunk+0x24
02 0153ff28 776f0fa4 ntdll!RtlSubscribeWnfStateChangeNotification+0x439
03 0153ff38 00000000 ntdll!RtlSubscribeWnfStateChangeNotification+0x404
我们注意到3,4号线程的线程堆栈是从ntdll!RtlEnterCriticalSection中开始的,那么ntdll!RtlEnterCriticalSection又是什么函数的入口呢,首先猜到的是EnterCriticalSection,这个函数是kernel32.dll中的,为了验证猜测,我们用dump查看到kernel32.dll的导出函数:
!cs
!cs 扩展显示一个或多个临界区(critical section)或者整个临界区树
前面说的ntdll!RtlEnterCriticalSection的第一个参数是临界区的地址,事实上用uf反汇编它,可以看到是ret 4,说明就只有一个参数
那么,
!cs Address 指定要显示的临界区地址。 如果省略该参数,调试器显示当前进程中所有临界区。!cs -s 如果可能的话,显示每个临界区的初始堆栈回溯。!cs -l 仅显示锁定的临界区。
0:005> !cs -l
-----------------------------------------
DebugInfo = 0x7779f820
Critical section = 0x0088338c (Test+0x338C)
LOCKED
LockCount = 0x1
WaiterWoken = No
OwningThread = 0x0000b1b4
RecursionCount = 0x1
LockSemaphore = 0xFFFFFFFF
SpinCount = 0x020007d0
-----------------------------------------
DebugInfo = 0x7779f840
Critical section = 0x00883374 (Test+0x3374)
LOCKED
LockCount = 0x1
WaiterWoken = No
OwningThread = 0x000020bc
RecursionCount = 0x1
LockSemaphore = 0xFFFFFFFF
SpinCount = 0x020007d0
从底层来讲,关键区机制是通过_RTL_CRITICAL_SECTION来实现的
0:005> dt _RTL_CRITICAL_SECTION
MSVCR90!_RTL_CRITICAL_SECTION
+0x000 DebugInfo : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
+0x004 LockCount : Int4B///< 标识关键区的锁状态
+0x008 RecursionCount : Int4B///< 记录递归次数,用来支持同一个线程多次进入关键区
+0x00c OwningThread : Ptr32 Void///< 记录拥有关键区的线程ID
+0x010 LockSemaphore : Ptr32 Void///< 记录这个关键区对应的事件对象,当有线程需要等待这个关键区时,便等待这个事件,这个对象是按需创建的,如果LockSemaphore为空,表示这个关键区没人用过,从没有线程在此等待过
+0x014 SpinCount : Uint4B
~~[TID]线程 ID 为 TID 的线程。(中括号是必需的,而且在第二个~和左括号间不能有空格)
0:005> ~~[0x0000b1b4]
3 Id: 2d68.b1b4 Suspend: 1 Teb: 0077b000 Unfrozen
Start: Test+0x1000 (00881000)
Priority: 0 Priority class: 32 Affinity: f
0:005> ~~[0x000020bc]
4 Id: 2d68.20bc Suspend: 1 Teb: 0077e000 Unfrozen
Start: Test+0x1030 (00881030)
Priority: 0 Priority class: 32 Affinity: f
0:005> ~3kv
# ChildEBP RetAddr Args to Child
05 00f6fc2c 0088101d 00883374 00881000 76a462c4 ntdll!RtlEnterCriticalSection+0x45 (FPO: [Non-Fpo])
0:005> ~4kv
# ChildEBP RetAddr Args to Child
05 010ffd8c 0088104d 0088338c 00881030 76a462c4 ntdll!RtlEnterCriticalSection+0x45 (FPO: [Non-Fpo])
这意思就是3号线程等待的临界区 00883374拥有者是4号线程,4号线程等待的临界区 0088338c 拥有者是3号线程
这里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:
0:005> !cs -l
-----------------------------------------
DebugInfo = 0x7779f820
Critical section = 0x00063374 (Test+0x3374)
LOCKED
LockCount = 0x2
WaiterWoken = No
OwningThread = 0x000009f0
RecursionCount = 0x1
LockSemaphore = 0xFFFFFFFF
SpinCount = 0x020007d0
-----------------------------------------
DebugInfo = 0x7779f840
Critical section = 0x0006338c (Test+0x338C)
LOCKED
LockCount = 0x1
WaiterWoken = No
OwningThread = 0x00009a58
RecursionCount = 0x1
LockSemaphore = 0xFFFFFFFF
SpinCount = 0x020007d0
可以发现LockCount变成了2,如果临界区是有信号的,则显示NOT LOCKED(值为-1)OwningThread表示拥有这个临界区的线程ID,RecursionCount表示拥有线程调了几次EnterCriticalSection,这其实也影响到了LockCount,如果拥有线程多调用一次EnterCriticalSection,那么 LockCount也会相应加1,因为LockCount标识了任意线程调用EnterCriticalSection请求这个互斥量的次数减1,(所以0-1=-1为NOT LOCKED)当然,前面如果调用了LeaveCriticalSection,那么 LockCount也会相应减1
!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.