使用 WinDbg 查找谁持有本机进程的 SRW 锁

Posted

技术标签:

【中文标题】使用 WinDbg 查找谁持有本机进程的 SRW 锁【英文标题】:Find who holds a SRW Lock for a native process with WinDbg 【发布时间】:2020-03-31 09:55:15 【问题描述】:

我有一个由 c++ 编写的程序,但我无法找到哪个线程获得了Slim Reader/Writer (SRW) Locks。我用谷歌搜索并找到了Determining which method is holding a ReaderWriterLockSlim WriteLock,但它是关于一个由 C# 编写的程序。此外,某些命令(例如.rwlock)不可用。

0:796> !handle 0 ff Mutant
Handle c
  Type          Mutant
  Attributes    0
  GrantedAccess 0x1f0001:
         Delete,ReadControl,WriteDac,WriteOwner,Synch
         QueryState
  HandleCount   4
  PointerCount  103240
  Name          \BaseNamedObjects\DBWinMutex
  Object Specific Information
    Mutex is Free
Handle 474
  Type          Mutant
  Attributes    0
  GrantedAccess 0x1f0001:
         Delete,ReadControl,WriteDac,WriteOwner,Synch
         QueryState
  HandleCount   2
  PointerCount  65536
  Name          \BaseNamedObjects\SM0:928:304:WilStaging_02
  Object Specific Information
    Mutex is Free
2 handles of type Mutant
0:796> kb
RetAddr           : Args to Child                                                           : Call Site
00007ff9`b6e3d33a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!ZwWaitForAlertByThreadId+0x14
00007ff9`a85726a9 : 00000000`00000000 00000000`00000000 00000192`83338180 00000000`00000000 : ntdll!RtlAcquireSRWLockExclusive+0x13a
00007ff9`a6231724 : c000000d`00000000 00000000`00000000 00000192`83338180 00000000`00000002 : MSVCP140!mtx_do_lock+0x7d [d:\agent\_work\2\s\src\vctools\crt\crtw32\stdcpp\thr\mutex.cpp @ 106]
00007ff9`a626749e : 00000192`f6a26e38 00000193`4aaa3d80 00000052`897fea60 00000000`00000000 : AZSDK!AZConnection::Post+0x54 [g:\prod\sdk\src\connection.cpp @ 1147]
...
00007ff9`9c8ba9c1 : 00000192`c3b3d770 00000000`00000000 00000192`f5d616b0 00000000`00000000 : prod!Task::Execute+0x28 [g:\prod\src\task.cpp @ 51]
00007ff9`b6e97529 : 00000193`491b9830 00000000`7ffe0386 00000052`897ff998 00000193`491b98f8 : prod!Proxy::TaskExecuter+0x11 [g:\prod\src\proxy.cpp @ 2042]
00007ff9`b6e3bec4 : 00000000`00000000 00000192`f1dd03a0 00000000`00000000 00000000`00000000 : ntdll!TppSimplepExecuteCallback+0x99
00007ff9`b6c47e94 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x644
00007ff9`b6e87ad1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
0:796> !rwlock
No export rwlock found

C++代码sn-p:

std::mutex m_mutex;

Status AZConnection::Post(const Request* request, Result** pResult)

    std::lock_guard<std::mutex> sbguard(m_mutex);

更新:

根据rustyx's answer,我明白了。现在我不得不放弃了。

事实上,我的程序仍在运行,但已停止服务。我必须找到原因。我发现有 806 个线程,其中大多数都在焦急地等待Post。此外,如果它没有重现,我无法重新启动添加已获得锁定的日志打印。因此,我只想检查持有锁的线程在做什么。

【问题讨论】:

【参考方案1】:

Win32 本机 SRWLock 不保留该信息。在非竞争状态下,它只是一个原子标志。

因此,没有 WinDbg 命令可以做到这一点。

当发生争用时,等待队列由正在等待的线程组成。尽管如此,没有关于持有锁的线程的信息。

有关 SRWLock 实现的更多详细信息,请参阅this answer。

【讨论】:

【参考方案2】:

我可以使用MEX Debugging Extension for WinDbg 中的!foreachframe!if 来grep 调用堆栈并执行一个命令(不支持由; (Command Separator) 分隔的多个命令)来查找不等待锁但其之前的电话是Post。该扩展程序可以从here 下载。下载后可以放到C:\WinDDK\7600.16385.1\Debuggers\winext(另见Loading Debugger Extension DLLs)。

以下代码中我将MSVCP140!mtx_do_lock替换为msvcrt!_threadstartex,并将AZSDK!AZConnection::Post替换为KERNEL32!BaseThreadInitThunk作为示例:

~*e r @$t0 = -1; !foreachframe -q -f 'KERNEL32!BaseThreadInitThunk' r @$t0= @#FrameNum - 1; .if(0<=@$t0)  !if -DoesNotContainRegex 'msvcrt!_threadstartex' -then '.printf /D "Thread: <link cmd=\"~~[%x]\">0x%x</link> (<link cmd=\"!mex.t %d\">%d</link>)", $tid, $tid, $dtid, $dtid' .frame @$t0 

!foreachthread -q !foreachframe -q -f 'KERNEL32!BaseThreadInitThunk' !if -DoesNotContainRegex 'msvcrt!_threadstartex' -then '.printf /D "Thread: <link cmd=\"~~[%x]\">0x%x</link> (<link cmd=\"!mex.t %d\">%d</link>)", $tid, $tid, $dtid, $dtid' .frame @#FrameNum - 1

notepad 进程的输出示例:

Thread: 0xd14c (0)
Thread: 0x4f88 (1)
Thread: 0xd198 (7)

所有线程的调用栈

0:001> !foreachthread k
Child-SP          RetAddr           Call Site
00000062`fabaf8c8 00007ffd`54c7409d win32u!ZwUserGetMessage+0x14
00000062`fabaf8d0 00007ff7`bb4c449f USER32!GetMessageW+0x2d
00000062`fabaf930 00007ff7`bb4dae07 notepad+0x449f
00000062`fabafa30 00007ffd`570f7974 notepad+0x1ae07
00000062`fabafaf0 00007ffd`5841a261 KERNEL32!BaseThreadInitThunk+0x14
00000062`fabafb20 00000000`00000000 ntdll!RtlUserThreadStart+0x21

Changing to thread: 0xda94 (1)
Child-SP          RetAddr           Call Site
00000062`fae7fbc8 00007ffd`5847f01b ntdll!DbgBreakPoint
00000062`fae7fbd0 00007ffd`570f7974 ntdll!DbgUiRemoteBreakin+0x4b
00000062`fae7fc00 00007ffd`5841a261 KERNEL32!BaseThreadInitThunk+0x14
00000062`fae7fc30 00000000`00000000 ntdll!RtlUserThreadStart+0x21

Changing to thread: 0xdb60 (7)
Child-SP          RetAddr           Call Site
00000062`fb17f968 00007ffd`54c7004d win32u!ZwUserMsgWaitForMultipleObjectsEx+0x14
00000062`fb17f970 00007ffc`e4e7d078 USER32!MsgWaitForMultipleObjectsEx+0x9d
00000062`fb17f9b0 00007ffc`e4e7cec2 DUser!GetMessageExA+0x2f8
00000062`fb17fa50 00007ffd`54c77004 DUser!GetMessageExA+0x142
00000062`fb17fab0 00007ffd`584534a4 USER32!Ordinal2582+0x64
00000062`fb17fb50 00007ffd`54101164 ntdll!KiUserCallbackDispatcher+0x24
00000062`fb17fbc8 00007ffd`54c7409d win32u!ZwUserGetMessage+0x14
00000062`fb17fbd0 00007ffd`2e4efa3c USER32!GetMessageW+0x2d
00000062`fb17fc30 00007ffd`1d0b30f8 DUI70!StartMessagePump+0x3c
00000062`fb17fc90 00007ffd`1d0b31ce msctfuimanager!DllCanUnloadNow+0xf3e8
00000062`fb17fd50 00007ffd`570f7974 msctfuimanager!DllCanUnloadNow+0xf4be
00000062`fb17fd80 00007ffd`5841a261 KERNEL32!BaseThreadInitThunk+0x14
00000062`fb17fdb0 00000000`00000000 ntdll!RtlUserThreadStart+0x21

Changing to thread: 0xc490 (8)
Child-SP          RetAddr           Call Site
00000062`fb1ff708 00007ffd`54c7004d win32u!ZwUserMsgWaitForMultipleObjectsEx+0x14
00000062`fb1ff710 00007ffc`e4e7d1ca USER32!MsgWaitForMultipleObjectsEx+0x9d
00000062`fb1ff750 00007ffc`e4e7cde7 DUser!GetMessageExA+0x44a
00000062`fb1ff7f0 00007ffc`e4e7ca53 DUser!GetMessageExA+0x67
00000062`fb1ff840 00007ffd`5505b0ea DUser!GetGadgetFocus+0x33b3
00000062`fb1ff8d0 00007ffd`5505b1bc msvcrt!_callthreadstartex+0x1e
00000062`fb1ff900 00007ffd`570f7974 msvcrt!_threadstartex+0x7c
00000062`fb1ff930 00007ffd`5841a261 KERNEL32!BaseThreadInitThunk+0x14
00000062`fb1ff960 00000000`00000000 ntdll!RtlUserThreadStart+0x21

更新:

我发现Someone 有同样的想法(如果出现死链接,经常出现):

细长的读/写锁不记得所有者是谁,因此您必须通过其他方式找到它们

雷蒙德 | 2011 年 8 月 10 日

slim reader/writer lock 是一种非常方便的同步工具,但缺点之一是它不能 > 跟踪当前所有者是谁。 当您的线程被卡住等待获取纤细的读取器/写入器锁时,很自然地想知道哪些线程拥有您卡住的线程 > 等待的资源。

由于无法从等待线程转到拥有线程,因此您只需要通过其他方式找到拥有线程。这是在共享模式下等待锁的线程>:

ntdll!ZwWaitForKeyedEvent+0xc
ntdll!RtlAcquireSRWLockShared+0x126
dbquery!CSearchSpace::Validate+0x10b
dbquery!CSearchSpace::DecomposeSearchSpace+0x3c
dbquery!CQuery::AddConfigs+0xdc
dbquery!CQuery::ResolveProviders+0x89
dbquery!CResults::CreateProviders+0x85
dbquery!CResults::GetProviders+0x61
dbquery!CResults::CreateResults+0x11c

好的,如何找到拥有锁的线程?

首先,slim reader/writer locks只能在进程中使用,所以候选线程是进程中的线程。

其次,锁的使用模式几乎总是类似

enter lock
do something
exit lock

函数获取锁并退出是非常不寻常的 带锁的外部代码。 (它可能会退出到同一组件中的其他代码,将退出锁的义务转移到其他代码。) 因此,您要查找仍在 dbquery.dll 内的线程,甚至可能仍在 CSearch­Space 内的线程(如果锁是每个对象锁而不是全局锁)。

当然,也有可能是进入锁的代码搞砸了忘记释放它,但如果是这样的话,再多的搜索也不会找到任何东西,因为罪魁祸首早已不在。 由于debugging is an exercise in optimism,我们不妨假设 > 我们不是这种情况。如果它没有找到锁的拥有者,那么我们可能不得不重新审视这个假设。

最后,最后一个技巧是知道which threads to ignore。

现在,您还可以忽略等待锁定的线程,因为它们是受害者而不是原因。 (同样,如果我们未能找到锁的所有者,我们 > 可以重新考虑他们不是原因的假设;例如,他们可能正在尝试递归获取锁。)

碰巧, 进程中只有一个线程通过了上述所有过滤器。

dbquery!CProp::Marshall+0x3b
dbquery!CRequest::CRequest+0x24c
dbquery!CQuery::Execute+0x668
dbquery!CResults::FillParams+0x1c4
dbquery!CResults::AddProvider+0x4e
dbquery!CResults::AddConfigs+0x1c5
dbquery!CResults::CreateResults+0x145

这可能不是问题的根源,但这是一个好的开始。 (实际上,它看起来很有希望,因为问题可能是 编组器另一端的进程被卡住了。)

【讨论】:

以上是关于使用 WinDbg 查找谁持有本机进程的 SRW 锁的主要内容,如果未能解决你的问题,请参考以下文章

WINDBG,如何查看数组的内容?

windbg调试相关命令

Android 如何获取当前状态下所有持有wakelock的进程

windbg使用方法

如何使用WinDbg调试进程信息

Windbg