NtContinue (IN PCONTEXT Context, IN BOOLEAN TestAlert)
{
PKTHREAD Thread =KeGetCurrentThread();
PKTRAP_FRAME TrapFrame = Thread->TrapFrame;
PKTRAP_FRAME PrevTrapFrame = (PKTRAP_FRAME)TrapFrame->Edx;
PFX_SAVE_AREA FxSaveArea;
KIRQL oldIrql;
DPRINT("NtContinue: Context: Eip=0x%x, Esp=0x%x\n", Context->Eip, Context->Esp );
PULONG Frame = 0;
__asm__("mov %%ebp, %%ebx" : "=b" (Frame) : );
. . . . . .
/*
* Copy the supplied context over the register information that was saved
* on entry to kernel mode, it will then be restored on exit
* FIXME: Validate the context
*/
KeContextToTrapFrame ( Context, TrapFrame );//把当前TrapFrame恢复为上CONTEXT的内容,实际是恢复到KiServiceExit时的状态
/* Put the floating point context into the thread's FX_SAVE_AREA
* and make sure it is reloaded when needed.
*/
FxSaveArea = (PFX_SAVE_AREA)((ULONG_PTR)Thread->InitialStack –
sizeof(FX_SAVE_AREA));
if (KiContextToFxSaveArea(FxSaveArea, Context))
{
Thread->NpxState = NPX_STATE_VALID;
KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
if (KeGetCurrentPrcb()->NpxThread == Thread)
{
KeGetCurrentPrcb()->NpxThread = NULL;
Ke386SetCr0(Ke386GetCr0() | X86_CR0_TS);
}
else
{
ASSERT((Ke386GetCr0() & X86_CR0_TS) == X86_CR0_TS);
}
KeLowerIrql(oldIrql);
}
/* Restore the user context */
Thread->TrapFrame = PrevTrapFrame;//上一次TrapFrame
__asm__("mov %%ebx, %%esp;\n" "jmp _KiServiceExit": : "b" (TrapFrame));
return STATUS_SUCCESS; /* this doesn't actually happen */
}
注意从KiUserApcDispatcher()到NtContinue()并不是普通的函数调用,而是系统调用,这中间经历了空间的切换,也从用户空间堆栈切换到了系统空间堆栈。CPU进入系统调用空间后,在_KiSystemServicex下面的代码中把指向中断现场的框架指针保存在当前线程的KTHREAD数据结构的TrapFrame字段中。这样,很容易就可以找到系统空间堆栈上的调用框架。当然,现在的框架是因为系统调用而产生的框架;而要想回到当初、即在执行用户空间APC函数之前的断点,就得先恢复当初的框架。那么当初的框架在哪里呢?它保存在用户空间的堆栈上,就是前面KiInitializeUserApc()保存的CONTEXT数据结构中。所以,这里通过KeContextToTrapFrame()把当初保存的信息拷贝回来,从而恢复了当初的框架。
_KiSystemServicex下面的代码中把指向中断现场的框架指针保存在当前线程的KTHREAD数据结构的TrapFrame字段中.保存当前在TrapFrame的DX中,将新的TrapFrame对应
KTHREAD的TrapFrame,在KiServiceExit中将使用TrapFrame的值来返回,在INT2E方式下将使用IRET,在SYSENTER下,将利用TrapFrame中的值来恢复ESP-ECX,EDX-EIP,来返回上次位置
NtContinue的系统级调用将产生一个新的TrapFrame,他利用该TRAPFRAME,将开始备份的CONTEXT,恢复到他的TrapFrame中,同时将上一次的TrapFrame恢复
1. 从系统调用、中断、或异常返回途径_KiServiceExit,如果APC队列中有等待执行的APC请求,就调用KiDeliverApc()。
2. KiDeliverApc(),从用户APC队列中摘下一个APC请求。
3. 在KiInitializeUserApc()中保存当前框架,并伪造新的框架。
4. 回到用户空间。
5. 在KiUserApcDispatcher()中调用目标APC函数。
6. 通过系统调用NtContinue()进入系统空间。
7. 在NtContinue()中恢复当初保存的框架。
8. 从NtContinue()返回、途径_KiServiceExit时,如果APC队列中还有等待执行的APC请求,就调用KiDeliverApc()。于是转回上面的第二步。
这个过程一直要循环到APC队列中不再有需要执行的请求。注意这里每一次循环中保存和恢复的都是同一个框架,就是原始的、开始处理APC队列之前的那个框架,代表着原始的用户空间程序断点。一旦APC队列中不再有等待执行的APC请求,在_KiServiceExit下面就不再调用KiDeliverApc(),于是就直接返回用户空间,这次是返回到原始的程序断点了。所以,系统调用neContinue()的作用不仅仅是切换回到被中断了的上下文,还包括执行用户APC队列中的下一个APC请求。
对于KiUserApcDispatcher()而言,它对NtContinue()的调用是不返回的。因为在NtContinue()中CPU不是“返回”到对于KiUserApcDispatcher()的另一次调用、从而对另一个APC函数的调用;就是返回到原始的用户空间程序断点,这个断点既可能是因为中断或异常而形成的,也可能是因为系统调用而形成的。