看了下dump,很有趣
从CRT源代码中的threadex.c中可以找到#02栈帧中的_callthreadstartex函数的源代码:
static void _callthreadstartex(void) { _ptiddata ptd; /* pointer to thread's _tiddata struct */
/* must always exist at this point */ ptd = _getptd();
/* * Guard call to user code with a _try - _except statement to * implement runtime errors and signal support */ __try { _endthreadex ( ( (unsigned (__CLR_OR_STD_CALL *)(void *))(((_ptiddata)ptd)->_initaddr) ) ( ((_ptiddata)ptd)->_initarg ) ) ; } __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) { /* * Should never reach here */ _exit( GetExceptionCode() );
} /* end of _try - _except */
} 还有#01号的_endthreadex:
void __cdecl _endthreadex ( unsigned retcode ) { _ptiddata ptd; /* pointer to thread's _tiddata struct */
ptd = _getptd_noexit();
if (ptd) { /* * Free up the _tiddata structure & its subordinate buffers * _freeptd() will also clear the value for this thread * of the FLS variable __flsindex. */ _freeptd(ptd); }
/* * Terminate the thread */ ExitThread(retcode);
} 其中的ExitThread就是#00的RtlExitThread。这意味着,这个线程的用户函数已经返回了,开始执行endthreadex,调用系统的线程退出函数了。
断点异常就是RtlExitThread触发的。虽然没有这个函数的源代码,但是看下汇编也不难理解它的逻辑,正常情况下这个函数应该调用系统服务ZwTerminateThread,但是如果当前线程是进程中的最后一个线程,那么RtlExitThread需要调用RtlExitUserProcess,也就是退出进程。
如何知道当前线程是否是最后一个线程呢?那就是RtlExitThread开头所做的调用NtQueryInformationThread 来查询
77022281 56 push esi 77022282 6a04 push 4 77022284 8d45fc lea eax,[ebp-4] 77022287 50 push eax 77022288 6a0c push 0Ch 7702228a 6afe push 0FFFFFFFEh 7702228c 8975fc mov dword ptr [ebp-4],esi 7702228f e814d9fcff call ntdll!NtQueryInformationThread (76fefba8)
其中的0C就是枚举 THREADINFOCLASS,代表AmILastThread
根据异常现场,显然是执行了RtlExitUserProcess,如果执行成功的话,就不会返回了,现在看来是执行失败了,返回了一个错误值0xc0000005,还在EAX中。而且CPU继续执行,遇到了下面的INT 3。猜想在源代码中,INT 3上面或许有句注释:never should reach here :-)
如此看来当前线程根本不是最后一个线程,但是却当作了最后一个线程,谬矣!
|