17.windbg-!cs、~~[TID](经典死锁)

先上个代码,自己随手写的:

#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.



 


 

  • 5
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值