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

本文通过一个简单的Windows环境下多线程程序实例,使用Windbg调试工具详细分析了临界区(CriticalSection)引发的死锁现象,并介绍了如何利用Windbg的!cs命令深入查看临界区的状态及锁定情况。

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

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



 


 

<think>我们正在讨论WinDbg中禁用符号加载优化选项(SYMOPT_100)的问题。SYMOPT_100是WinDbg的一个符号选项,它控制符号加载的行为。在WinDbg中,符号选项可以通过`.symopt`命令来查看和修改。SYMOPT_100的具体含义是:忽略符号服务器上的索引,而直接使用符号文件。禁用这个选项(即关闭SYMOPT_100)意味着在从符号服务器加载符号时,会使用索引来加快符号的查找速度。 用户可能遇到的问题是:在调试过程中,符号加载速度慢,或者加载了不正确的符号。通过禁用SYMOPT_100(即关闭该选项),可以尝试使用符号服务器上的索引来加速符号加载,并确保加载正确的符号。 在WinDbg中,我们可以通过以下步骤来禁用SYMOPT_100(注意:禁用SYMOPT_100实际上是关闭这个选项,即从当前符号选项设置中移除SYMOPT_100): 1. 首先,查看当前的符号选项设置:`.symopt` 2. 然后,计算新的符号选项值:当前值 与上 SYMOPT_100的反码(即从当前选项中移除SYMOPT_100) - SYMOPT_100的值为0x100(即256),所以反码为0xFFFFFEFF(在32位下)或0xFFFFFFFFFFFFFEFF(在64位下)。但在WinDbg中,我们通常使用按位操作来移除某个标志。 3. 使用命令:`.symopt- 0x100` 来移除SYMOPT_100标志。 另一种方法是直接设置符号选项值: `.symopt <new_value>` 其中`<new_value>`是新的符号选项的整数值。我们可以先获取当前值,然后减去0x100(如果当前包含SYMOPT_100的话)。 但是,更直接的方法是使用`.symopt-`命令来移除某个选项。 具体步骤: 1. 打开WinDbg。 2. 在命令窗口中,输入以下命令来查看当前符号选项: ``` .symopt ``` 记下当前的值,并注意是否包含0x100(即SYMOPT_IGNORE_NT_SYMPATH)。 注意:实际上,SYMOPT_100在文档中称为SYMOPT_IGNORE_NT_SYMPATH,但根据Windows调试工具文档,符号选项的常量名称可能有所不同。在最新的文档中,0x100对应的选项是忽略_NT_SYMBOL_PATH环境变量。然而,在较早的文档中,0x100可能对应其他含义。因此,我们需要确认用户使用的WinDbg版本。 根据最新的Windows调试工具文档(WinDbg 10),符号选项常量如下: - 0x100: SYMOPT_IGNORE_NT_SYMPATH - 忽略_NT_SYMBOL_PATH环境变量。 - 0x200: SYMOPT_DEBUG_TYPE_PTR - 用于调试类型指针。 - 等等。 但是,在用户的问题中,SYMOPT_100通常指的是0x100这个选项。不过,在WinDbg中,符号选项的默认设置通常不包括0x100。用户可能希望禁用这个选项,即关闭忽略_NT_SYMBOL_PATH环境变量的功能,从而让调试器使用环境变量中设置的符号路径。 因此,如果用户想要禁用SYMOPT_100(即不再忽略_NT_SYMBOL_PATH),那么应该移除0x100这个标志。 操作命令: ``` .symopt- 0x100 ``` 然后,再次运行`.symopt`来确认0x100已经被移除。 另外,如果用户想要完全重置符号选项,可以使用: ``` .symopt+ 0x80000000 // 恢复默认值(默认值包含一些基本选项) ``` 但注意,默认值中并不包含0x100,所以如果用户只是希望移除0x100,那么直接移除即可。 如果用户遇到符号加载问题,除了调整符号选项,还应该检查符号路径(`.sympath`)设置是否正确,以及符号服务器是否可用。 总结: - 禁用SYMOPT_100(即关闭0x100选项)的命令是:`.symopt- 0x100` - 启用SYMOPT_100(即打开0x100选项)的命令是:`.symopt+ 0x100` 用户的需求是禁用(disable)SYMOPT_100,所以我们使用移除选项的命令。 注意:在WinDbg中,符号选项的修改只影响当前调试会话。 如果用户需要持久化设置,可以考虑在启动WinDbg时通过命令行参数设置,或者使用脚本初始化。 参考文档:[Symbol Options](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/symbol-options) 由于我们无法直接链接外部文档,这里仅提供参考。 相关问题: 1. 如何查看WinDbg中的当前符号选项? 2. 如何设置符号路径(_NT_SYMBOL_PATH)? 3. WinDbg中常见的符号加载问题有哪些? 4. 如何重置WinDbg的符号选项到默认值? 以上问题可以帮助用户进一步了解符号配置和故障排除。</think>### WinDbg 禁用 SYMOPT_100 的步骤 在 WinDbg 中,`SYMOPT_100`(十六进制值 `0x100`)是符号选项(Symbol Options)的一部分,它控制符号加载的行为。禁用此选项通常用于解决符号加载问题或优化调试过程。以下是具体操作步骤: #### 1. **查看当前符号选项** 在 WinDbg 命令窗口中输入: ```windbg .symopt ``` 输出示例: ``` Symbol options: 0x30237 SYMOPT_CASE_INSENSITIVE - on SYMOPT_UNDNAME - on SYMOPT_DEFERRED_LOADS - on SYMOPT_LOAD_LINES - on SYMOPT_OMAP_FIND_NEAREST - on SYMOPT_DEBUG - on ``` 记录十六进制值(如 `0x30237`)[^1]。 #### 2. **禁用 SYMOPT_100** - **方法 1:直接移除标志位** 使用命令: ```windbg .symopt- 0x100 ``` 这会从当前符号选项中减去 `0x100`。 - **方法 2:手动计算新值** 若当前值为 `0x30237`,禁用 `0x100` 后的新值为: $$ \text{新值} = \text{原值} \ \& \ \sim\text{0x100} = \text{0x30237} \ \& \ \text{0xFFFFFEFF} = \text{0x30137} $$ 然后显式设置: ```windbg .symopt 0x30137 ``` #### 3. **验证是否禁用** 再次运行: ```windbg .symopt ``` 检查输出中是否包含 `SYMOPT_IGNORE_NT_SYMPATH`(即 `0x100` 的别名),确保该选项已消失[^2]。 --- ### 关键说明 - **作用**:`SYMOPT_100`(`SYMOPT_IGNORE_NT_SYMPATH`)会忽略 `_NT_SYMBOL_PATH` 环境变量。禁用后,WinDbg 将重新遵守该变量指定的符号路径。 - **应用场景**: - 解决符号加载失败问题(如 `PDB not found`)。 - 强制使用本地符号缓存而非远程服务器。 - 调试时需动态切换符号路径。 - **持久性**:此修改仅对当前调试会话有效。重启 WinDbg 后需重新配置。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值