⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄

📁 兼容内核漫谈 适合想将Windows上的程序移植到其它平台上的朋友研究查看
💻
📖 第 1 页 / 共 5 页
字号:
  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 + -