• 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函数的描述如下:

  • 相关阅读:
    5-1 Leetcode中和链表相关的问题
    4-7 带有尾指针的链表:使用链表实现队列
    4.6 使用链表实现栈
    4.5 链表元素的删除
    4.4 链表的遍历、查询和修改
    4.3 为链表设置虚拟头结点dummyhead
    4.2在链表中添加元素
    4.1链表
    mybatis 力量操作参数为List的非空校验
    linux 运行和停止jar的shell 脚本
  • 原文地址:https://www.cnblogs.com/dancewithautomation/p/2415517.html
Copyright © 2020-2023  润新知