📄
字号:
DbgkCreateThread((PVOID)ThreadContext->Eip);
/* First, force the thread to be non-alertable for user-mode alerts. */
Thread->Tcb.Alertable = FALSE;
/*
* If the thread is to be created suspended then queue an APC to
* do the suspend before we run any userspace code.
*/
if (CreateSuspended)
{
PsSuspendThread(Thread, NULL);
}
/* Queue an APC to the thread that will execute the ntdll startup routine. */
LdrInitApc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
KeInitializeApc(LdrInitApc, &Thread->Tcb, OriginalApcEnvironment,
LdrInitApcKernelRoutine, LdrInitApcRundownRoutine,
LdrpGetSystemDllEntryPoint(),UserMode, NULL);
KeInsertQueueApc(LdrInitApc, NULL, NULL, IO_NO_INCREMENT);
/*
* The thread is non-alertable, so the APC we added did not set UserApcPending to TRUE.
* We must do this manually. Do NOT attempt to set the Thread to Alertable before the call,
* doing so is a blatant and erronous hack.
*/
Thread->Tcb.ApcState.UserApcPending = TRUE;
Thread->Tcb.Alerted[KernelMode] = TRUE;
oldIrql = KeAcquireDispatcherDatabaseLock ();
PsUnblockThread(Thread, NULL, 0);
KeReleaseDispatcherDatabaseLock(oldIrql);
Status = ObInsertObject((PVOID)Thread, NULL, DesiredAccess, 0, NULL, &hThread);
. . . . . .
return Status;
}[/code]
这里我们只看其中的KiArchInitThreadWithContext(),其余的都留给读者了。里面除DbgkCreateThread()是向内核Debug端口发送调试信息外,别的函数读者都已经耳熟能详了。不过有个事情倒是要提一下,线程的系统空间堆栈是在由PsInitializeThread()调用的KeInitializeThread()内部为其分配存储空间的。分配了空间以后,Thread->KernelStack指向其系统空间堆栈。
KiArchInitThreadWithContext()其实是个宏定义,因具体CPU类型的不同而定义成不同的函数。对于386结构的CPU定义成Ke386InitThreadWithContext():
[code]#define KiArchInitThreadWithContext Ke386InitThreadWithContext
[CreateProcessW() > KlCreateFirstThread() > RtlRosCreateUserThreadVa()
> RtlRosCreateUserThread() > NtCreateThread() > Ke386InitThreadWithContext()]
NTSTATUS
Ke386InitThreadWithContext(PKTHREAD Thread, PCONTEXT Context)
{
PULONG KernelStack;
ULONG InitSize;
PKTRAP_FRAME TrapFrame;
PFX_SAVE_AREA FxSaveArea;
/* Setup a stack frame for exit from the task switching routine */
InitSize = 6 * sizeof(DWORD) + sizeof(DWORD) + 6 * sizeof(DWORD) +
+ sizeof(KTRAP_FRAME) + sizeof (FX_SAVE_AREA);
KernelStack = (PULONG)((char*)Thread->KernelStack - InitSize);
/* Set up the initial frame for the return from the dispatcher. */
KernelStack[0] = (ULONG)Thread->InitialStack - sizeof(FX_SAVE_AREA); /* TSS->Esp0 */
KernelStack[1] = 0; /* EDI */
KernelStack[2] = 0; /* ESI */
KernelStack[3] = 0; /* EBX */
KernelStack[4] = 0; /* EBP */
KernelStack[5] = (ULONG)&PsBeginThreadWithContextInternal ; /* EIP */
/* Save the context flags. */
KernelStack[6] = Context->ContextFlags;
/* Set up the initial values of the debugging registers. */
KernelStack[7] = Context->Dr0;
KernelStack[8] = Context->Dr1;
KernelStack[9] = Context->Dr2;
KernelStack[10] = Context->Dr3;
KernelStack[11] = Context->Dr6;
KernelStack[12] = Context->Dr7;
/* Set up a trap frame from the context. */
TrapFrame = (PKTRAP_FRAME)(&KernelStack[13]);
TrapFrame->DebugEbp = (PVOID)Context->Ebp;
TrapFrame->DebugEip = (PVOID)Context->Eip;
TrapFrame->DebugArgMark = 0;
TrapFrame->DebugPointer = 0;
TrapFrame->TempCs = 0;
TrapFrame->TempEip = 0;
TrapFrame->Gs = (USHORT)Context->SegGs;
TrapFrame->Es = (USHORT)Context->SegEs;
TrapFrame->Ds = (USHORT)Context->SegDs;
TrapFrame->Edx = Context->Edx;
TrapFrame->Ecx = Context->Ecx;
TrapFrame->Eax = Context->Eax;
TrapFrame->PreviousMode = UserMode;
TrapFrame->ExceptionList = (PVOID)0xFFFFFFFF;
TrapFrame->Fs = TEB_SELECTOR;
TrapFrame->Edi = Context->Edi;
TrapFrame->Esi = Context->Esi;
TrapFrame->Ebx = Context->Ebx;
TrapFrame->Ebp = Context->Ebp;
TrapFrame->ErrorCode = 0;
TrapFrame->Cs = Context->SegCs;
TrapFrame->Eip = Context->Eip;
TrapFrame->Eflags = Context->EFlags | X86_EFLAGS_IF;
TrapFrame->Eflags &= ~(X86_EFLAGS_VM | X86_EFLAGS_NT | X86_EFLAGS_IOPL);
TrapFrame->Esp = Context->Esp;
TrapFrame->Ss = (USHORT)Context->SegSs;
/* FIXME: Should check for a v86 mode context here. */
/* Set up the initial floating point state. */
/* FIXME: Do we have to zero the FxSaveArea or is it already? */
FxSaveArea = (PFX_SAVE_AREA)
( (ULONG_PTR)KernelStack + InitSize - sizeof(FX_SAVE_AREA));
if (KiContextToFxSaveArea(FxSaveArea, Context))
{
Thread->NpxState = NPX_STATE_VALID;
}
else
{
Thread->NpxState = NPX_STATE_INVALID;
}
/* Save back the new value of the kernel stack. */
Thread->KernelStack = (PVOID)KernelStack;
return(STATUS_SUCCESS);
}[/code]
这里,FX_SAVE_AREA数据结构用于与浮点处理器有关的信息。KTRAP_FRAME数据结构就是在发生异常、中断时所保留的“现场”,这个现场当然是在线程的系统空间堆栈上。而前面所准备的CONTEXT数据结构,则现在就要将其内容转移到KTRAP_FRAME数据结构中。此外,还要安排好新建线程在系统空间的那部分上下文,这也在系统堆栈上。
前面曾提到,Thread->KernelStack指向线程的系统空间堆栈。由于此刻的系统空间堆栈是空的,所以Thread->KernelStack指向其区间的顶点(地址最高),逻辑上则是堆栈的底部。需要伪造的原始堆栈(内容)的大小为InitSize,里面包括一个FX_SAVE_AREA数据结构、一个KTRAP_FRAME数据结构,以及一个数组KernelStack[]。如果从堆栈的地址顶点往下数,则首先是FX_SAVE_AREA数据结构,然后是KTRAP_FRAME数据结构,地址最低的是KernelStack[]。注意堆栈是由上向下伸展的,所以最先恢复/使用的是KernelStack[]。
所以,KernelStack[]中是系统空间的那部分上下文,这是线程在受调度运行时最先恢用到的。其中的Eip指向内核函数PsBeginThreadWithContextInternal (),所以这是新建线程最早的入口。从代码中可以看出,这部分上下文的大小是从KernelStack[0]到KernelStack[12]共13个32位整数。
在这上面(按地址高低)是KTRAP_FRAME数据结构TrapFrame。虽然也是在系统空间堆栈上,这实际上却是用户空间上下文的一部分,这是在新建线程“返回”用户空间时才予以恢复的,其大部分内容已经在前面的CONTEXT数据结构中准备好,有些则是固定的。
再往上的FX_SAVE_AREA数据结构,我们就不关心了。
所以,前面所说尚未见到的第一个起始地址就是PsBeginThreadWithContextInternal ()。
[注:不同版本的ReactOS在这一部分代码上有明显不同,例如0.2.9版中的第一个起始地址是KiThreadStartup(),而且Ke386InitThreadWithContext()的代码也有较大不同,但是基本的原理和过程还是一样的]。
当前线程在NtCreateThread()中执行完PsUnblockThread(),把新建线程的数据结构插入调度队列之后,新建线程就可以受调度运行了。当新建线程受调度运行时,首先进入的就是PsBeginThreadWithContextInternal ()。
[code]__declspec(naked)
VOID PsBeginThreadWithContextInternal (VOID)
{
/* This isn't really a function, we are called as the return address of a context switch */
/* Do the necessary prolog before the context switch */
__asm
{
call PiBeforeBeginThread
/* Load the context flags. */
pop ebx
/* Load the debugging registers */
test ebx, (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386)
jz L1
pop eax __asm mov dr0, eax
pop eax _asm mov dr1, eax
pop eax __asm mov dr2, eax
pop eax __asm mov dr3, eax
pop eax __asm mov dr6, eax
pop eax __asm mov dr7, eax
jmp L3
L1:
add esp, 24
L3:
/* Load the floating point registers */
mov eax, HardwareMathSupport
test eax,eax
jz L2
test ebx, (CONTEXT_FLOATING_POINT & ~CONTEXT_i386)
jz L2
frstor [esp]
L2:
add esp, 112
/* Load the rest of the thread's user mode context. */
mov eax, 0
jmp KeReturnFromSystemCallWithHook
}
}[/code]
这里的PiBeforeBeginThread()把CPU的运行级别降低到PASSIVE_LEVEL。我们在这里所关心的是最后的jmp指令,这是跳转到了KeReturnFromSystemCallWithHook():
[code]__declspec(naked)
void KeReturnFromSystemCallWithHook()
{
__asm
{
/* Call the post system call hook and deliver any pending APCs */
push esp
push eax
call KiAfterSystemCallHook
add esp, 8
// TMN: Added, to be able to separate this into different functions
jmp KeReturnFromSystemCall
}
}[/code]
我们在这里关心的是最后向KeReturnFromSystemCall的跳转,那已经是在从中断/异常/系统调用返回的那段汇编代码中了。正是在那里,将会检查是否有APC函数需要执行,并且在“正常”返回用户空间之前偏离航向,先进入用户空间执行APC函数LdrInitializeThunk()的。执行完LdrInitializeThunk()以后,CPU又回到内核,并继续其“正常”返回用户空间的航程。
如前所述,当新建线程受调度运行而“返回”到用户空间时,首先进入的是BaseProcessStart(),而且用户空间堆栈的安排使得逻辑上的起始地址、即目标映像的程序入口地址成为BaseProcessStart()的第一个参数,这就是下面的参数lpStartAddress:
[code]VOID STDCALL
BaseProcessStart(LPTHREAD_START_ROUTINE lpStartAddress, DWORD lpParameter)
{
UINT uExitCode = 0;
DPRINT("BaseProcessStart(..) - setting up exception frame.\n");
__try1(_except_handler)
{
uExitCode = (lpStartAddress)((PVOID)lpParameter);
} __except1
ExitProcess(uExitCode);
}[/code]
可见,之所以需要BaseProcessStart(),是要把整个目标映像的执行置于Windows的“结构化异常处理”即SHE的控制之下,不过那已经不在本文的范围之内。
最后,注意这里的ExitProcess(uExitCode),这意味着BaseProcessStart()是不返回的。下面是ExitProcess的代码,留给读者自己阅读:
[code][BaseProcessStart() > ExitProcess()]
VOID STDCALL ExitProcess(UINT uExitCode)
{
. . . . . .
/* unload all dll's */
LdrShutdownProcess ();
/* notify csrss of process termination */
CsrRequest.Type = CSRSS_TERMINATE_PROCESS;
Status = CsrClientCallServer(&CsrRequest, &CsrReply, sizeof(CSRSS_API_REQUEST),
sizeof(CSRSS_API_REPLY));
. . . . . .
NtTerminateProcess (NtCurrentProcess (),uExitCode);
/* should never get here */
ASSERT(0);
while(1);
}[/code]
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -