Waits until one or all of the specified objects are in the signaled state, an I/O completion routine or asynchronous procedure call (APC) is queued to the thread, or the time-out interval elapses.
Syntax
DWORD WINAPI WaitForMultipleObjectsEx( __in DWORD nCount, __in const HANDLE *lpHandles, __in BOOL bWaitAll, __in DWORD dwMilliseconds, __in BOOL bAlertable );
现在我们终于知道是WaitForMultipleObjectsEx的调用才导致了线程处于等待状态,因为Wait函数等待的锁对象一直没有得到signaled,原因是啥,因为没有Pulse嘛,关于Pulse如何工作的,这一点以后有时间还会讨论。但是WaitForMultipleObjectsEx等就等了,不是还有一个参数dwMilliseconds是控制超时的嘛,如果超时了就应该可以退出等待状态对不,那究竟本文示例中的等待会因为超时自动结束嘛?
其实Reflector一下看看Monitor.Wait发现它调用了其重载:
[SecuritySafeCritical] public static bool Wait(object obj) { return Wait(obj, -1, false); }
而这个重载的第二个参数就是一个时间变量,传来-1应该就是无限等待的意思?我只是大胆猜测咯。至于最终调用WaitForMultipleObjectsEx的参dwMilliseconds数是多少,最好还是科学态度严谨一些。所以,
我们还需要调试一番,先来看看dwMilliseconds的用法:
dwMilliseconds[in]
The time-out interval, in milliseconds. If a nonzero value is specified, the function waits until the specified objects are signaled, an I/O completion routine or APC is queued, or the interval elapses. If dwMilliseconds is zero, the function does not enter a wait state if the criteria is not met; it always returns immediately. If dwMilliseconds is INFINITE, the function will return only when the specified objects are signaled or an I/O completion routine or APC is queued.
顺便再来看看另外一个参数bAlertable以及其他几个参数的用法(当然,这里略过不分析,我只需要确定dwMilliseconds的值是不是INFINITE就ok了,INFINITE=FFFFFFFF=-1):
bAlertable [in]If this parameter is TRUEand the thread is in the waiting state, the function returns when the system queues an I/O completion routine or APC, and the thread runs the routine or function. Otherwise, the function does not return and the completion routine or APC function is not executed.
A completion routine is queued when the ReadFileEx or WriteFileEx function in which it was specified has completed. The wait function returns and the completion routine is called only if bAlertable is TRUE and the calling thread is the thread that initiated the read or write operation. An APC is queued when you call QueueUserAPC.
nCount [in]The number of object handles to wait for in the array pointed to by lpHandles. The maximum number of object handles is MAXIMUM_WAIT_OBJECTS. This parameter cannot be zero.
lpHandles[in]An array of object handles. For a list of the object types whose handles can be specified, see the following Remarks section. The array can contain handles of objects of different types. It may not contain multiple copies of the same handle.
If one of these handles is closed while the wait is still pending, the function's behavior is undefined.
The handles must have the SYNCHRONIZE access right. For more information, see Standard Access Rights.
bWaitAll[in]
If this parameter is TRUE, the function returns when the state of all objects in the lpHandles array is set to signaled. If FALSE, the function returns when the state of any one of the objects is set to signaled. In the latter case, the return value indicates the object whose state caused the function to return.
现在让我们再回到windbg,通过Debug菜单的重启命令重启被调试的程序,然后在调用WaitForMultipleObjectsEx的地方加一个断点,命令:
bp KERNEL32!WaitForMultipleObjectsEx
g命令 继续执行到断点停下,
自动获得输出如下:
0:000> bp KERNEL32!WaitForMultipleObjectsEx
0:000> g
ModLoad: 77da0000 77e49000 C:\WINDOWS\system32\ADVAPI32.dll
ModLoad: 77e50000 77ee3000 C:\WINDOWS\system32\RPCRT4.dll
ModLoad: 77fc0000 77fd1000 C:\WINDOWS\system32\Secur32.dll
ModLoad: 603b0000 60416000 c:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll
ModLoad: 77f40000 77fb6000 C:\WINDOWS\system32\SHLWAPI.dll
ModLoad: 77ef0000 77f39000 C:\WINDOWS\system32\GDI32.dll
ModLoad: 77d10000 77da0000 C:\WINDOWS\system32\USER32.dll
ModLoad: 77be0000 77c38000 C:\WINDOWS\system32\msvcrt.dll
ModLoad: 76300000 7631d000 C:\WINDOWS\system32\IMM32.DLL
ModLoad: 62c20000 62c29000 C:\WINDOWS\system32\LPK.DLL
ModLoad: 73fa0000 7400b000 C:\WINDOWS\system32\USP10.dll
ModLoad: 79e70000 7a419000 c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
ModLoad: 78130000 781cb000 C:\WINDOWS\WinSxS\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.4053_x-ww_e6967989\MSVCR80.dll
Breakpoint 0 hit
eax=00b1ff1c ebx=00000000 ecx=00000003 edx=0000000d esi=00162138 edi=00000200
eip=7c8095d8 esp=00b1fedc ebp=00b1fef4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
KERNEL32!WaitForMultipleObjectsEx:
7c8095d8 6a64 push 64h
在kb一下获取当前的call stack:
0:001> kb
ChildEBP RetAddr Args to Child
00b1fed8 7c80a115 00000003 00b1ff1c 00000000 KERNEL32!WaitForMultipleObjectsEx
00b1fef4 79f339e1 00000003 00b1ff1c 00000000 KERNEL32!WaitForMultipleObjects+0x18
00b1ff54 79f3393e 34fd7cea 00000000 79f327d4 mscorwks!DebuggerRCThread::MainLoop+0xe9
00b1ff84 79f33865 34fd7cda 79f1c171 79f327d4 mscorwks!DebuggerRCThread::ThreadProc+0xe5
00b1ffb4 7c80b729 00000000 79f1c171 79f327d4 mscorwks!DebuggerRCThread::ThreadProcStatic+0x9c
00b1ffec 00000000 79f3381f 00000000 00000000 KERNEL32!BaseThreadStart+0x37
我们看到stack最后停在KERNEL32!WaitForMultipleObjectsEx,说明我家的断点生效了,这不是废话嘛。注意看Args to Child三列列出来的是函数的前三个参数,对照WaitForMultipleObjectsEx的原型,第一个参数nCount=3,第二个放的是对象句柄数组指针地址,第三个值0意味bWaitAll为False。那么问题又来了,我有五个参数啊,后面两个参数如何获取?别急慢慢来,我们看到kb的输出第一列ChildEBP,这个用途很大,Args to Child显示的三列就分别是EBP+4,EBP+C,EBP+10的值,而同理类推递增4,则第4,5个参数的地址是EBP+14,EBP+18.
好了,由此我们再敲打命令dd 00b1fed8+14,输出如下:
0:001> dd 00b1fed8+14
00b1feec ffffffff 00000000 00b1ff54 79f339e1
00b1fefc 00000003 00b1ff1c 00000000 ffffffff
00b1ff0c 34fd7c3a 00162138 80000200 00000000
00b1ff1c 00000780 00000778 00000784 34fd7c22
00b1ff2c 00162138 00162070 00162070 00000000
00b1ff3c 00000001 00000003 ffffffff 00b1ff78
00b1ff4c 7a3885ac 00000002 00b1ff84 79f3393e
00b1ff5c 34fd7cea 00000000 79f327d4 00000000
红色字体即是第四个参数的物理地址,而蓝色部分是其对应的值,ffffffff ,终于,在这里验证了自己的猜想。
不过好奇心驱使使得我想知道最终WaitForMultipleObjectsEx等待的对象句柄究竟是个什麽玩意,该函数等待的是对象句柄的数组,所以其参数值对应数组第一个对象句柄的地址00b1ff1c ,使用dd 00b1ff1c L1获取数组的第一个对象句柄,输出如下:
0:001> dd 00b1ff1c L1
00b1ff1c 00000780
使用命令: !handle 00000780 f获得句柄的具体信息:
0:001> !handle 00000780 f
Handle 780
Type Event
Attributes 0
GrantedAccess 0x1f0003:
Delete,ReadControl,WriteDac,WriteOwner,Synch
QueryState,ModifyState
HandleCount 2
PointerCount 3
Name <none>
Object Specific Information
Event Type Auto Reset
Event is Waiting
啊哈,原来是一个Event类型的句柄,且Event Type Auto Reset,且Event is Waiting,我很快想到了Windows线程同步的事件,也想到.Net中的AutoResetEvent和ManualResetEvent,但是这已经偏离了本文Monitor.Wait的调查路线,故就那样吧。