首页
社区
课程
招聘
WinDbg的 !CS -l 和 !locks 使用练习
2024-2-19 21:15 1682

WinDbg的 !CS -l 和 !locks 使用练习

2024-2-19 21:15
1682

最近尝试分析一个程序死锁的问题,发现无论是使用!cs -l 还是 !locks都是没有任何有效的输出,于是怀疑是不是自己的WinDbg版本或环境配置上出了问题,导致了这两个命令失效,所以有了本篇简单的用例来测试WinDbg命令,顺便针对这两个分析线程死锁的高频命令做一个详细的笔记。如果有对WinDbg调试道友,可以加个好友一起讨论讨论。


测试部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CRITICAL_SECTION cs1, cs2;
 
void DeadlockThread1(int threadId)
{
    EnterCriticalSection(&cs1);
    std::cout << "Thread " << threadId << " entered cs1\n";
    Sleep(100); // 让线程暂停一段时间,以便另一个线程有机会执行
    EnterCriticalSection(&cs2);
    std::cout << "Thread " << threadId << " entered cs2\n";
    LeaveCriticalSection(&cs2);
    LeaveCriticalSection(&cs1);
}
 
void DeadlockThread2(int threadId)
{
    EnterCriticalSection(&cs2);
    std::cout << "Thread " << threadId << " entered cs2\n";
    Sleep(100); // 让线程暂停一段时间,以便另一个线程有机会执行
    EnterCriticalSection(&cs1);
    std::cout << "Thread " << threadId << " entered cs1\n";
    LeaveCriticalSection(&cs1);
    LeaveCriticalSection(&cs2);
}

因为两个线程函数同时拉起,导致了临界区死锁问题。
图片描述
使用WinDbg启动程序,并使用命令 !cs -l 进行查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0:006> !cs -l
-----------------------------------------
DebugInfo          = 0x773e4a40
Critical section   = 0x00075058 (CriticalSectionDead!cs1+0x0)
LOCKED
LockCount          = 0x1
WaiterWoken        = No
OwningThread       = 0x0000367c
RecursionCount     = 0x1
LockSemaphore      = 0xFFFFFFFF
SpinCount          = 0x020007d0
-----------------------------------------
DebugInfo          = 0x773e4a60
Critical section   = 0x00075070 (CriticalSectionDead!cs2+0x0)
LOCKED
LockCount          = 0x1
WaiterWoken        = No
OwningThread       = 0x0000338c
RecursionCount     = 0x1
LockSemaphore      = 0xFFFFFFFF
SpinCount          = 0x020007d0

分析命令输出各字段的含义:

  • !cs -l 是一个 WinDbg 的扩展命令,用于列出所有当前存在的临界区以及它们的状态。
  • Critical Section 这行显示了一个临界区的地址,以及它属于哪个模块和哪个临界区变量。在这里,临界区属于 CriticalSectionDead 模块,并且它的名字是 cs1。
  • LockCount 表示这个临界区被锁定的次数。
  • SpinCount 表示在尝试获取临界区锁定时,线程会进行自旋(忙等待)的次数。如果自旋后仍然无法获取锁,线程就会进入睡眠状态。
  • OwningThread 显示当前拥有这个临界区的线程的线程 ID。
  • RecursionCount 表示拥有这个临界区的线程已经重新进入这个临界区的次数。在某些情况下,一个线程可能会多次进入同一个临界区,这个计数就是用来跟踪这种情况的。

另外,分析死锁还有 !locks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0:006> !locks
 
CritSec CriticalSectionDead!cs1+0 at 00075058
WaiterWoken        No
LockCount          1
RecursionCount     1
OwningThread       367c
EntryCount         0
ContentionCount    1
*** Locked
 
CritSec CriticalSectionDead!cs2+0 at 00075070
WaiterWoken        No
LockCount          1
RecursionCount     1
OwningThread       338c
EntryCount         0
ContentionCount    1
*** Locked
 
Scanned 5 critical sections

分析命令输出各字段的含义:

  • !locks 显示调试目标进程中所有被锁定的临界区(Critical Sections)的信息。
  • EntryCount 表示尝试进入临界区的次数。0 通常表示没有线程尝试进入这个临界区,但这可能是一个不准确的值,因为它取决于具体的调试器扩展实现。
  • ContentionCount 表示线程在尝试获取这个临界区时发生争用的次数。1 表示发生了一次争用。

上面的两个命令基本分析出了死锁的状态,接下来使用 ~*kvn 来查看所有线程的堆栈情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
0:006> ~*kvn
 
   0  Id: 3a10.1b00 Suspend: 1 Teb: 00d26000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 00f5fd30 76dbada9     000000e8 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])
01 00f5fda4 76dbad02     000000e8 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x99 (FPO: [SEH])
02 00f5fdb8 0007150e     000000e8 ffffffff 0111ef58 KERNELBASE!WaitForSingleObject+0x12 (FPO: [Non-Fpo])
03 00f5fdd0 00071700     00000001 01116cd0 0111ef58 CriticalSectionDead!main+0x4e (FPO: [0,0,4]) (CONV: cdecl) [E:\debug-demo-set\DeadLocks\CriticalSectionDead\main.cpp @ 37]
04 (Inline) --------     -------- -------- -------- CriticalSectionDead!invoke_main+0x1c (Inline Function @ 00071700) (CONV: cdecl) [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
05 00f5fe18 766bfcc9     00d23000 766bfcb0 00f5fe84 CriticalSectionDead!__scrt_common_main_seh+0xfa (FPO: [Non-Fpo]) (CONV: cdecl) [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
06 00f5fe28 77327c5e     00d23000 3d12d987 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
07 00f5fe84 77327c2e     ffffffff 77348c18 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
08 00f5fe94 00000000     00071788 00d23000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
 
   1  Id: 3a10.ef4 Suspend: 1 Teb: 00d29000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 0130f778 772f5bd0     000000c4 01115678 00000010 ntdll!NtWaitForWorkViaWorkerFactory+0xc (FPO: [5,0,0])
01 0130f938 766bfcc9     01114f60 766bfcb0 0130f9a4 ntdll!TppWorkerThread+0x2a0 (FPO: [Non-Fpo])
02 0130f948 77327c5e     01114f60 3cd7dea7 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
03 0130f9a4 77327c2e     ffffffff 77348c18 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
04 0130f9b4 00000000     772f5930 01114f60 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
 
   2  Id: 3a10.3524 Suspend: 1 Teb: 00d2c000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 0140fd74 772f5bd0     000000c4 01115af0 00000010 ntdll!NtWaitForWorkViaWorkerFactory+0xc (FPO: [5,0,0])
01 0140ff34 766bfcc9     01114f60 766bfcb0 0140ffa0 ntdll!TppWorkerThread+0x2a0 (FPO: [Non-Fpo])
02 0140ff44 77327c5e     01114f60 3ca7d8a3 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
03 0140ffa0 77327c2e     ffffffff 77348c18 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
04 0140ffb0 00000000     772f5930 01114f60 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
 
   3  Id: 3a10.38ec Suspend: 1 Teb: 00d2f000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 0154f6b0 772f5bd0     000000c4 01116918 00000010 ntdll!NtWaitForWorkViaWorkerFactory+0xc (FPO: [5,0,0])
01 0154f870 766bfcc9     01114f60 766bfcb0 0154f8dc ntdll!TppWorkerThread+0x2a0 (FPO: [Non-Fpo])
02 0154f880 77327c5e     01114f60 3cb3dfdf 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
03 0154f8dc 77327c2e     ffffffff 77348c18 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
04 0154f8ec 00000000     772f5930 01114f60 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
 
   4  Id: 3a10.367c Suspend: 1 Teb: 00d32000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 0168fae0 7731f999     00075074 00000000 00075070 ntdll!NtWaitForAlertByThreadId+0xc (FPO: [2,0,0])
01 0168fb10 7731f6ed     00000000 00000000 00075070 ntdll!RtlpWaitOnAddressWithTimeout+0x64 (FPO: [Non-Fpo])
02 0168fbb0 7730011a     000713a0 00000001 00000001 ntdll!RtlpWaitOnCriticalSection+0x18d (FPO: [Non-Fpo])
03 0168fbe8 772fff69     00000000 0168fc04 000713ef ntdll!RtlpEnterCriticalSectionContended+0x1aa (FPO: [Non-Fpo])
04 0168fbf4 000713ef     00075070 000713a0 0168fc14 ntdll!RtlEnterCriticalSection+0x49 (FPO: [1,0,0])
05 0168fc04 766bfcc9     00000001 766bfcb0 0168fc70 CriticalSectionDead!DeadlockThread1+0x4f (FPO: [Non-Fpo]) (CONV: cdecl) [E:\debug-demo-set\DeadLocks\CriticalSectionDead\main.cpp @ 12]
06 0168fc14 77327c5e     00000001 3c8fdb73 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
07 0168fc70 77327c2e     ffffffff 77348c18 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
08 0168fc80 00000000     000713a0 00000001 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
 
   5  Id: 3a10.338c Suspend: 1 Teb: 00d35000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 017cf7c8 7731f999     0007505c 00000000 00075058 ntdll!NtWaitForAlertByThreadId+0xc (FPO: [2,0,0])
01 017cf7f8 7731f6ed     00000000 00000000 00075058 ntdll!RtlpWaitOnAddressWithTimeout+0x64 (FPO: [Non-Fpo])
02 017cf898 7730011a     00071430 00000002 00000002 ntdll!RtlpWaitOnCriticalSection+0x18d (FPO: [Non-Fpo])
03 017cf8d0 772fff69     017cf8e8 0007147f 00075058 ntdll!RtlpEnterCriticalSectionContended+0x1aa (FPO: [Non-Fpo])
04 017cf8d8 0007147f     00075058 00071430 017cf8f8 ntdll!RtlEnterCriticalSection+0x49 (FPO: [1,0,0])
05 017cf8e8 766bfcc9     00000002 766bfcb0 017cf954 CriticalSectionDead!DeadlockThread2+0x4f (FPO: [Non-Fpo]) (CONV: cdecl) [E:\debug-demo-set\DeadLocks\CriticalSectionDead\main.cpp @ 23]
06 017cf8f8 77327c5e     00000002 3c9bde57 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
07 017cf954 77327c2e     ffffffff 77348c18 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
08 017cf964 00000000     00071430 00000002 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
 
#  6  Id: 3a10.35dc Suspend: 1 Teb: 00d38000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 0190f948 7736dba9     3c77de7b 7736db70 7736db70 ntdll!DbgBreakPoint (FPO: [0,0,0])
01 0190f978 766bfcc9     00000000 766bfcb0 0190f9e4 ntdll!DbgUiRemoteBreakin+0x39 (FPO: [Non-Fpo])
02 0190f988 77327c5e     00000000 3c77dee7 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
03 0190f9e4 77327c2e     ffffffff 77348c18 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
04 0190f9f4 00000000     7736db70 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

根据!cs -l可知OwningThread = 0x0000367c,线程367c拥有临界区Critical section = 0x000750585058,结合上面线程的栈帧发现,当前线程试图进入临界区5070.见4号线程栈帧04第一个参数.同理,有5号线程拥有临界区Critical section = 0x00075070 (CriticalSectionDead!cs2+0x0),但是试图进入4号线程的临界区。综上所述,两个线程各自的拥有的临界区未退出,想要访问对方的临界区而形成交叉,导致自旋锁失败而产生死锁.

点击查看测试工程代码


开头提到遇到 !cs -l!locks 无反应的程序已上传到附件TestDeadLocks.7z,如有有大佬经过,可有偿.

关于TestDeadLocks.7z起初是想复现 [原创]调试TerminateThread导致的死锁 这位大佬的文章,遇到了现在的问题。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-2-19 22:31 被_THINCT编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回