Windbg,如何阅读!locks输出?

8

我正在调试一个程序,我怀疑其中可能存在死锁或其他多线程相关的错误。我按照他人的建议使用WinDBG打开崩溃转储文件,并使用!locks命令获取以下输出:

CritSec MSVCR100D!lclcritsects+48 at 73541e40
WaiterWoken        No
LockCount          6
RecursionCount     1
OwningThread       164c
EntryCount         0
ContentionCount    9
*** Locked

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for qsqlited4.dll - 
CritSec qsqlited4!qt_plugin_instance+a1b21 at 70fc301c
WaiterWoken        No
LockCount          0
RecursionCount     1
OwningThread       2344
EntryCount         0
ContentionCount    0
*** Locked

CritSec +73c2380 at 073c2380
WaiterWoken        No
LockCount          0
RecursionCount     4
OwningThread       2344
EntryCount         0
ContentionCount    0
*** Locked

CritSec +73bf9e8 at 073bf9e8
WaiterWoken        No
LockCount          0
RecursionCount     1
OwningThread       2344
EntryCount         0
ContentionCount    0    
*** Locked

Scanned 817 critical sections

我对输出结果感到困惑,能否有人帮忙解释一下?

2
http://msdn.microsoft.com/en-us/library/windows/hardware/ff541979(v=vs.85).aspx 展示给您 :) - Mark Nunberg
谢谢,但是每个部分后面的 *** Locked 是什么意思?在微软的页面上没有解释。 - Nyaruko
这意味着 CRITICAL_SECTION 目前被锁定了 :). 你可以尝试转储一个你知道 没有 被锁定的 CRITICAL_SECTION。我期望输出会有所不同。 - Mark Nunberg
!cs -l将列出相同的内容,但可能会有更好的输出。 !cs -l -o是我最喜欢的,它还显示拥有线程的堆栈。 - Kjell Gunnar
4个回答

12

!锁可能会令人困惑。如果您真的想调试死锁情况,请执行~*kvn(或kb,任选其一)查找正在等待关键部分(将以WaitForSingleObject结束)并在此之前进行RtlEnterCriticalSection调用的线程。找到大多数线程正在等待的关键部分。转储关键部分。如果您在调试基于x64的转储文件并缩小到携带RtlCrticalSection的帧上,则使用.frame /c post您处于线程上下文~[threadnum]s,rbx将包含您的关键部分。

转储临界区域,找到所有者。如果所有者正在等待,请找出所有者正在等待的内容,直到我们达到链的末尾或阻塞事物的原因。如果我们没有将!cs -l -o放入上下文中,则可能会令人困惑。

希望这有所帮助。


谢谢,但您能详细解释一下如何找出线程正在等待哪个关键部分吗? - Nyaruko
我使用了~*kvn,每个线程都以类似于这样的方式开始:Id: 1920.164c Suspend: 0 Teb: 7ef28000 Unfrozen,那么suspend、teb和unfrozen是什么意思? - Nyaruko
我真的没有时间登录到网站,我想我将不得不进行更多的实验,但是以下链接显示了倒推根本原因的技术,可以用来转储CS。http://blogs.msdn.com/b/ntdebugging/archive/2012/02/28/debugging-backwards-proving-root-cause.aspx - Addy
*kvn在内核模式调试中无法使用。有没有一个好的内核模式替代方案? - Aaron Campbell

5

Teb是线程环境块的缩写,暂停和冻结当前不相关。

假设这是一个32位场景,您可以通过以下方法查看等待哪个临界区的线程:

a) Switch to the thread
b) dump stack
c) Find 1 argument to RtlEnterCriticalSection

(如果您按照Addy的建议执行64项操作)
(图片请见上)

2
这是我对如何解读信息的理解,从你样本输出中显示的第一个锁开始:
CritSec MSVCR100D!lclcritsects+48 at 73541e40 - address of the lock
WaiterWoken        No - unclear, ignore
LockCount          6 - how many threads are waiting to acquire the lock
RecursionCount     1 - how many times the owner thread has acquired the lock
OwningThread       164c - the thread ID of the owner, in hex
EntryCount         0 - obsolete?
ContentionCount    9 - the highest LockCount value seen
*** Locked - this indicates that the critical is currently held

所以,我们可以看到线程164c拥有锁,但不是递归的。有六个线程正在等待获取锁。在过去的某个时刻,有九个线程试图获取锁。
可能你想要做的是切换到拥有锁的线程,并查看它在做什么,为什么仍然持有锁等等。你可以在windbg进程和线程编号中查找该线程,或者从命令窗口中执行:
首先,列出所有线程:
~*

然后,找到感兴趣的线程,查找其线程ID并切换到该线程。例如,您可能会找到以下输出:
9  Id: 15b8.164c Suspend: 0 Teb: fffa4000 Unfrozen
   Priority: 10

9是线程号,15b8.164c是进程ID和线程ID。由于164c是您要查找的内容,这意味着感兴趣的线程是第9号,因此您需要发出以下命令:

~9s

然后您可以查看堆栈并找出发生了什么。在我的情况下,我发现我的线程正在等待事件,同时持有加载器锁,我们需要避免这样做。


0
要查找由于临界区引起的死锁,请尝试SOSEX!dlk命令。虽然该扩展似乎只适用于.NET,但!dlk命令也将识别本地关键部分中的死锁。
优点:如果它识别出死锁,则非常容易阅读。如果没有,您仍然需要应用其他技术(例如,在链中包括其他类型的同步对象)。
示例输出(不特定针对临界区):
0:010> !dlk
Deadlock detected:
CLR thread 4 holds sync block 00000000024c6970 OBJ:000000007fff0f80[System.String] STRVAL=SYNC1
             waits sync block 00000000024c6928 OBJ:000000007fff0fa8[System.String] STRVAL=SYNC2
CLR thread 5 holds sync block 00000000024c6928 OBJ:000000007fff0fa8[System.String] STRVAL=SYNC2
             waits sync block 00000000024c6970 OBJ:000000007fff0f80[System.String] STRVAL=SYNC1
CLR Thread 4 is waiting at ConsoleTestApp.ConsoleTestApp.MonitorDeadlockThreadProc()+0xa4(IL) [C:\dev\ConsoleTestApp\ConsoleTestApp.cs, line 195]
CLR Thread 5 is waiting at ConsoleTestApp.ConsoleTestApp.MonitorDeadlockThreadProc()+0xa4(IL) [C:\dev\ConsoleTestApp\ConsoleTestApp.cs, line 195]

1 deadlock detected. 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接