Monitor.Wait初探(2)

调试之前首先将代码编译成可执行文件,并打开运行程序,效果如下,我们看到程序在打印出2之后就Hung住了,而原因我们也很明确,就是Wait惹得。

image

现在通过Windbg attach 这个进程,

加载SOS, .load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll

使用!threads命令打印出当前托管程序所有的线程,如下:

ThreadCount: 4
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 1
Hosted Runtime: no
                                      PreEmptive   GC Alloc           Lock
       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
   0    1 1150 001636c8   200a020 Enabled  01323810:01323fe8 00159558     0 MTA
   2    2  2dc 0016fd28      b220 Enabled  00000000:00000000 00159558     0 MTA (Finalizer)
   3    3 141c 0017e720   200b020 Enabled  01324028:01325fe8 00159558     0 MTA
XXXX    4    0 0017eed8      9820 Enabled  00000000:00000000 00159558     0 Ukn

 

通过命令~3进入ID3号线程,也即是我们的ThreadProc1所在的线程;

然后使用!clrstack打印托管代码线程的call stack如下:

0:003> !clrstack
OS Thread Id: 0x141c (3)
ESP       EIP   
00e2f748 7c92e514 [GCFrame: 00e2f748]
00e2f81c 7c92e514 [HelperMethodFrame_1OBJ: 00e2f81c] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
00e2f880 792cbe48 System.Threading.Monitor.Wait(System.Object, Int32, Boolean)
00e2f890 7975a45a System.Threading.Monitor.Wait(System.Object)
00e2f894 00d201fb ConsoleApplication1.Program.ThreadProc1()
00e2f8c0 792d6e46 System.Threading.ThreadHelper.ThreadStart_Context(System.Object)
00e2f8cc 792e02cf System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
00e2f8e4 792d6dc4 System.Threading.ThreadHelper.ThreadStart()
00e2fb0c 79e71b4c [GCFrame: 00e2fb0c]

我们看到stack层层调用,Monitor.Wait最终是调用了Monitor.ObjWait方法,至此,如果是一般的程序hung之类的问题调试,到这里已经足够了,因为我们发现了导致程序线程Hung的代码行。但是对于挖掘Wait或者这个ObjWait究竟干了啥,我们还是一无所知。

我们的第一个反应可能是想看看ObjWait的代码实现,通过Relector发现在托管代码这个方法并没有函数体,不过它有个Attr修饰如下:

[MethodImpl(MethodImplOptions.InternalCall), SecurityCritical]
private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, object obj);

 

这意味这该函数的实际实现并不在托管代码中,而是在native code中,现在不要着急来看native code,我们至少知道了ObjWait至少还会继续调用更底层的代码,那麽,自然我可以打印出其对应的native code的call stack. !clrstack只能打印出托管部分。

ok,现在返回windbg,继续敲打我们的调试命令,使用kb 2000命令打印出当前线程的native部分,如下所示:

0:003> kb 2000
ChildEBP RetAddr  Args to Child             
00e2f490 7c92df4a 7c809590 00000001 00e2f4bc ntdll!KiFastSystemCallRet
00e2f494 7c809590 00000001 00e2f4bc 00000001 ntdll!ZwWaitForMultipleObjects+0xc
00e2f530 79fccf9a 00000001 0017e848 00000000 KERNEL32!WaitForMultipleObjectsEx+0x12c
00e2f598 79fccbc7 00000001 0017e848 00000000 mscorwks!WaitForMultipleObjectsEx_SO_TOLERANT+0x6f
00e2f5b8 79fcccd0 00000001 0017e848 00000000 mscorwks!Thread::DoAppropriateAptStateWait+0x3c
00e2f63c 79fccd65 00000001 0017e848 00000000 mscorwks!Thread::DoAppropriateWaitWorker+0x13c
00e2f68c 79fccee9 00000001 0017e848 00000000 mscorwks!Thread::DoAppropriateWait+0x40
00e2f6e8 79e7549a ffffffff 00000001 00e2f794 mscorwks!CLREvent::WaitEx+0xf7
00e2f6fc 79f8659e ffffffff 00000001 00e2f794 mscorwks!CLREvent::Wait+0x17
00e2f710 79f865ba 0017e848 ffffffff 00e2f794 mscorwks!Thread::Wait+0x1b
00e2f724 79f86431 ffffffff 00e2f794 306d0144 mscorwks!Thread::Block+0x18
00e2f7b0 79f86487 ffffffff 00000000 0017e720 mscorwks!SyncBlock::Wait+0x12e
00e2f7c4 79f86553 ffffffff 00000000 306d0e80 mscorwks!ObjHeader::Wait+0x2a
*** WARNING: Unable to verify checksum for C:\WINDOWS\assembly\NativeImages_v2.0.50727_32\mscorlib\ca87ba84221991839abbe7d4bc9c6721\mscorlib.ni.dll
00e2f874 792cbe48 013235b4 00000000 00e2f8b8 mscorwks!ObjectNative::WaitTimeout+0xe6
00e2f884 7975a45a 00000000 00d201fb 013235b4 mscorlib_ni+0x20be48
00e2f8b8 792d6e46 013236d8 00e2f8d8 792e02cf mscorlib_ni+0x69a45a
00e2f8c4 792e02cf 00e2f91c 013236d8 01323618 mscorlib_ni+0x216e46
00e2f8d8 792d6dc4 01323618 00000000 0017e720 mscorlib_ni+0x2202cf
00e2f8f0 79e71b4c 7c925d48 00e2f9ca 00e2f980 mscorlib_ni+0x216dc4
00e2f900 79e8968e 00e2f9d0 00000000 00e2f9a0 mscorwks!CallDescrWorker+0x33
00e2f980 79e96d11 00e2f9d0 00000000 00e2f9a0 mscorwks!CallDescrWorkerWithHandler+0xa3
00e2fab8 79e96d44 7924290c 00e2fc14 00e2fb4c mscorwks!MethodDesc::CallDescr+0x19c
00e2fad4 79e96d62 7924290c 00e2fc14 00e2fb4c mscorwks!MethodDesc::CallTargetWorker+0x1f
00e2faec 79f88387 00e2fb4c 306d0a20 0017e720 mscorwks!MethodDescCallSite::CallWithValueTypes+0x1a
00e2fcd4 79e9caff 00e2fe50 00000000 00000000 mscorwks!ThreadNative::KickOffThread_Worker+0x192
00e2fce8 79e9ca9b 00e2fdc4 00e2fd70 79fbb3cb mscorwks!Thread::DoADCallBack+0x32a
00e2fd7c 79e9c9c1 00e2fdc4 306d0b4c 00000000 mscorwks!Thread::ShouldChangeAbortToUnload+0xe3
00e2fdb8 79e9cb4d 00e2fdc4 00000001 00000000 mscorwks!Thread::ShouldChangeAbortToUnload+0x30a
00e2fde0 79f88158 00000001 79f8826d 00e2fe50 mscorwks!Thread::ShouldChangeAbortToUnload+0x33e
00e2fdf8 79f88232 00000001 79f8826d 00e2fe50 mscorwks!ManagedThreadBase::KickOff+0x13
00e2fe94 79f0e255 00185830 00000000 00000000 mscorwks!ThreadNative::KickOffThread+0x269
00e2ffb4 7c80b729 00185810 00000000 00000000 mscorwks!Thread::intermediateThreadProc+0x49
00e2ffec 00000000 79f0e20f 00185810 00000000 KERNEL32!BaseThreadStart+0x37

 

good!现在情况更加明晰了,原来ObjWait方法最后调用到了Kernel32的WaitForMultipleObjectsEx,熟悉Windows编程的同学应该知道这是一个Windows的线程同步的函数,它会使前线程进入等待状态,直到指定的内核对象状态变为signaled或等待超时才会被重新调度。

MSDN对该API函数的描述如下:

posted @ 2012-03-24 15:10  Dance With Automation  Views(863)  Comments(0Edit  收藏  举报