在初始化篇,大概讲述了线程初始化过程,这里继续讲述线程的第一次启动运行是怎么进行的。在上一篇中,最后说道线程调用了NtResumeThread函数之后,线程就可以开始调度了。当系统进行线程调度的时候,使用如下内核函数进行线程切换,这里要说的就是此函数。
BOOLEAN
FASTCALL
KiSwapContext(
IN PKTHREAD CurrentThread,
IN PKTHREAD NewThread
);
函数是汇编实现的,定义在ctxswitch.s文件中,代码如下:
.globl @KiSwapContext@8 .func @KiSwapContext@8, @KiSwapContext@8 @KiSwapContext@8: /* Save 4 registers */ sub esp, 4 * 4 /* Save all the non-volatile ones */ mov [esp+12], ebx mov [esp+8], esi mov [esp+4], edi mov [esp+0], ebp /* Get the current KPCR */ mov ebx, fs:[KPCR_SELF] /* Get the Current Thread */ mov edi, ecx /* Get the New Thread */ mov esi, edx /* Get the wait IRQL */ movzx ecx, byte ptr [edi+KTHREAD_WAIT_IRQL] /* Do the swap with the registers correctly setup */ call @KiSwapContextInternal@0 /* Return the registers */ mov ebp, [esp+0] mov edi, [esp+4] mov esi, [esp+8] mov ebx, [esp+12] /* Clean stack */ add esp, 4 * 4 ret .endfunc
函数使用16个字节的堆栈空间来保存相关变量,然后调用KiSwapContextInternal函数.此函数代码如下:
.globl @KiSwapContextInternal@0 .func @KiSwapContextInternal@0, @KiSwapContextInternal@0 @KiSwapContextInternal@0: /* Save the IRQL */ push ecx #ifdef CONFIG_SMP GetSwapLock: /* Acquire the swap lock */ cmp [esi+KTHREAD_SWAP_BUSY], 0 jz NotBusy pause jmp GetSwapLock #endif /* Increase context switches (use ES for lazy load) */ inc dword ptr es:[ebx+KPCR_CONTEXT_SWITCHES] /* Save the Exception list */ push [ebx+KPCR_EXCEPTION_LIST] /* Check for WMI */ cmp dword ptr [ebx+KPCR_PERF_GLOBAL_GROUP_MASK], 0 jnz WmiTrace AfterTrace: #ifdef CONFIG_SMP #ifdef DBG /* Assert that we're on the right CPU */ mov cl, [esi+KTHREAD_NEXT_PROCESSOR] cmp cl, [ebx+KPCR_PROCESSOR_NUMBER] jnz WrongCpu #endif #endif /* Get CR0 and save it */ mov ebp, cr0 mov edx, ebp #ifdef CONFIG_SMP /* Check NPX State */ cmp byte ptr [edi+KTHREAD_NPX_STATE], NPX_STATE_LOADED jz NpxLoaded #endif SetStack: /* Set new stack */ mov [edi+KTHREAD_KERNEL_STACK], esp /* Checking NPX, disable interrupts now */ mov eax, [esi+KTHREAD_INITIAL_STACK] cli /* Get the NPX State */ movzx ecx, byte ptr [esi+KTHREAD_NPX_STATE] /* Clear the other bits, merge in CR0, merge in FPU CR0 bits and compare */ and edx, ~(CR0_MP + CR0_EM + CR0_TS) or ecx, edx or ecx, [eax - (NPX_FRAME_LENGTH - FN_CR0_NPX_STATE)] cmp ebp, ecx jnz NewCr0 StackOk: /* Enable interrupts and set the current stack */ sti mov esp, [esi+KTHREAD_KERNEL_STACK] /* Check if address space switch is needed */ mov ebp, [esi+KTHREAD_APCSTATE_PROCESS] mov eax, [edi+KTHREAD_APCSTATE_PROCESS] cmp ebp, eax jz SameProcess #ifdef CONFIG_SMP /* Get the active processors and XOR with the process' */ mov ecx, [ebx+KPCR_SET_MEMBER_COPY] lock xor [ebp+KPROCESS_ACTIVE_PROCESSORS], ecx lock xor [eax+KPROCESS_ACTIVE_PROCESSORS], ecx /* Assert change went ok */ #ifdef DBG test [ebp+KPROCESS_ACTIVE_PROCESSORS], ecx jz WrongActiveCpu test [eax+KPROCESS_ACTIVE_PROCESSORS], ecx jz WrongActiveCpu #endif #endif /* Check if we need an LDT */ mov ecx, [ebp+KPROCESS_LDT_DESCRIPTOR0] or ecx, [eax+KPROCESS_LDT_DESCRIPTOR0] jnz LdtReload UpdateCr3: /* Switch address space */ mov eax, [ebp+KPROCESS_DIRECTORY_TABLE_BASE] mov cr3, eax SameProcess: #ifdef CONFIG_SMP /* Release swap lock */ and byte ptr [edi+KTHREAD_SWAP_BUSY], 0 #endif /* Clear gs */ xor eax, eax mov gs, ax /* Set the TEB */ mov eax, [esi+KTHREAD_TEB] mov [ebx+KPCR_TEB], eax mov ecx, [ebx+KPCR_GDT] mov [ecx+0x3A], ax shr eax, 16 mov [ecx+0x3C], al mov [ecx+0x3F], ah /* Get stack pointer */ mov eax, [esi+KTHREAD_INITIAL_STACK] /* Make space for the NPX Frame */ sub eax, NPX_FRAME_LENGTH /* Check if this isn't V86 Mode, so we can bias the Esp0 */ test dword ptr [eax - KTRAP_FRAME_SIZE + KTRAP_FRAME_EFLAGS], EFLAGS_V86_MASK jnz NoAdjust /* Bias esp */ sub eax, KTRAP_FRAME_V86_GS - KTRAP_FRAME_SS NoAdjust: /* Set new ESP0 */ mov ecx, [ebx+KPCR_TSS] mov [ecx+KTSS_ESP0], eax /* Set current IOPM offset in the TSS */ mov ax, [ebp+KPROCESS_IOPM_OFFSET] mov [ecx+KTSS_IOMAPBASE], ax /* Increase context switches */ inc dword ptr [esi+KTHREAD_CONTEXT_SWITCHES] /* Restore exception list */ pop [ebx+KPCR_EXCEPTION_LIST] /* Restore IRQL */ pop ecx /* DPC shouldn't be active */ cmp byte ptr [ebx+KPCR_PRCB_DPC_ROUTINE_ACTIVE], 0 jnz BugCheckDpc /* Check if kernel APCs are pending */ cmp byte ptr [esi+KTHREAD_PENDING_KERNEL_APC], 0 jnz CheckApc /* No APCs, return */ xor eax, eax ret CheckApc: /* Check if they're disabled */ cmp word ptr [esi+KTHREAD_SPECIAL_APC_DISABLE], 0 jnz ApcReturn test cl, cl jz ApcReturn /* Request APC Delivery */ mov cl, APC_LEVEL call @HalRequestSoftwareInterrupt@4 or eax, esp ApcReturn: /* Return with APC pending */ setz al ret LdtReload: /* Check if it's empty */ mov eax, [ebp+KPROCESS_LDT_DESCRIPTOR0] test eax, eax jz LoadLdt /* Write the LDT Selector */ mov ecx, [ebx+KPCR_GDT] mov [ecx+KGDT_LDT], eax mov eax, [ebp+KPROCESS_LDT_DESCRIPTOR1] mov [ecx+KGDT_LDT+4], eax /* Write the INT21 handler */ mov ecx, [ebx+KPCR_IDT] mov eax, [ebp+KPROCESS_INT21_DESCRIPTOR0] mov [ecx+0x108], eax mov eax, [ebp+KPROCESS_INT21_DESCRIPTOR1] mov [ecx+0x10C], eax /* Save LDT Selector */ mov eax, KGDT_LDT LoadLdt: lldt ax jmp UpdateCr3 NewCr0: #ifdef DBG /* Assert NPX State */ test byte ptr [esi+KTHREAD_NPX_STATE], ~(NPX_STATE_NOT_LOADED) jnz InvalidNpx test dword ptr [eax - (NPX_FRAME_LENGTH - FN_CR0_NPX_STATE)], ~(CR0_PE + CR0_MP + CR0_EM + CR0_TS) jnz InvalidNpx #endif /* Update CR0 */ mov cr0, ecx jmp StackOk WmiTrace: /* No WMI support yet */ int 3 /* Jump back */ jmp AfterTrace BugCheckDpc: /* Bugcheck the machine, printing out the threads being switched */ mov eax, [edi+KTHREAD_INITIAL_STACK] push 0 push eax push esi push edi push ATTEMPTED_SWITCH_FROM_DPC call _KeBugCheckEx@20 #ifdef DBG InvalidNpx: int 3 WrongActiveCpu: int 3 WrongCpu: int 3 #endif .endfunc
函数以SameProcess标签为分界点,之前的是旧线程的运行代码,之后为新线程的运行代码。新线程代码中与堆栈操作和函数调用的相关代码如下:两个POP指令和一个RET指令.
/* Restore exception list */ pop [ebx+KPCR_EXCEPTION_LIST] /* Restore IRQL */ pop ecx /* DPC shouldn't be active */ cmp byte ptr [ebx+KPCR_PRCB_DPC_ROUTINE_ACTIVE], 0 jnz BugCheckDpc /* Check if kernel APCs are pending */ cmp byte ptr [esi+KTHREAD_PENDING_KERNEL_APC], 0 jnz CheckApc /* No APCs, return */ xor eax, eax ret
对于已经运行过的线程来说,POP指令和只是为了平衡堆栈,KiSwapContextInternal函数开始的时候PUSH了两个变量:
/* Save the IRQL */ push ecx #ifdef CONFIG_SMP GetSwapLock: /* Acquire the swap lock */ cmp [esi+KTHREAD_SWAP_BUSY], 0 jz NotBusy pause jmp GetSwapLock #endif /* Increase context switches (use ES for lazy load) */ inc dword ptr es:[ebx+KPCR_CONTEXT_SWITCHES] /* Save the Exception list */ push [ebx+KPCR_EXCEPTION_LIST]
对于第一次运行的线程来说,堆栈是在初始化过程创建的Ke386InitThreadWithContext函数中创建的,初始化篇有过说明,代码再次列举如下:
VOID NTAPI Ke386InitThreadWithContext(IN PKTHREAD Thread, IN PKSYSTEM_ROUTINE SystemRoutine, IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext, IN PCONTEXT ContextPointer) { PFX_SAVE_AREA FxSaveArea; PFXSAVE_FORMAT FxSaveFormat; PKSTART_FRAME StartFrame; PKSWITCHFRAME CtxSwitchFrame; PKTRAP_FRAME TrapFrame; CONTEXT LocalContext; PCONTEXT Context = NULL; ULONG ContextFlags; /* Check if this is a With-Context Thread */ if (ContextPointer) { /* Set up the Initial Frame */ PKUINIT_FRAME InitFrame; InitFrame = (PKUINIT_FRAME)((ULONG_PTR)Thread->InitialStack - sizeof(KUINIT_FRAME)); /* Copy over the context we got */ RtlCopyMemory(&LocalContext, ContextPointer, sizeof(CONTEXT)); Context = &LocalContext; ContextFlags = CONTEXT_CONTROL; /* Zero out the trap frame and save area */ RtlZeroMemory(&InitFrame->TrapFrame, KTRAP_FRAME_LENGTH + sizeof(FX_SAVE_AREA)); /* Setup the Fx Area */ FxSaveArea = &InitFrame->FxSaveArea; /* Check if we support FXsr */ if (KeI386FxsrPresent) { /* Get the FX Save Format Area */ FxSaveFormat = (PFXSAVE_FORMAT)Context->ExtendedRegisters; /* Set an initial state */ FxSaveFormat->ControlWord = 0x27F; FxSaveFormat->StatusWord = 0; FxSaveFormat->TagWord = 0; FxSaveFormat->ErrorOffset = 0; FxSaveFormat->ErrorSelector = 0; FxSaveFormat->DataOffset = 0; FxSaveFormat->DataSelector = 0; FxSaveFormat->MXCsr = 0x1F80; } else { /* Setup the regular save area */ Context->FloatSave.ControlWord = 0x27F; Context->FloatSave.StatusWord = 0; Context->FloatSave.TagWord = -1; Context->FloatSave.ErrorOffset = 0; Context->FloatSave.ErrorSelector = 0; Context->FloatSave.DataOffset =0; Context->FloatSave.DataSelector = 0; } /* Check if the CPU has NPX */ if (KeI386NpxPresent) { /* Set an intial NPX State */ Context->FloatSave.Cr0NpxState = 0; FxSaveArea->Cr0NpxState = 0; FxSaveArea->NpxSavedCpu = 0; /* Now set the context flags depending on XMM support */ ContextFlags |= (KeI386FxsrPresent) ? CONTEXT_EXTENDED_REGISTERS : CONTEXT_FLOATING_POINT; /* Set the Thread's NPX State */ Thread->NpxState = NPX_STATE_NOT_LOADED; Thread->DispatcherHeader.NpxIrql = PASSIVE_LEVEL; } else { /* We'll use emulation */ FxSaveArea->Cr0NpxState = CR0_EM; Thread->NpxState = NPX_STATE_NOT_LOADED &~ CR0_MP; } /* Disable any debug regiseters */ Context->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS; /* Setup the Trap Frame */ TrapFrame = &InitFrame->TrapFrame; /* Set up a trap frame from the context. */ KeContextToTrapFrame(Context, NULL, TrapFrame, Context->ContextFlags | ContextFlags, UserMode); /* Set SS, DS, ES's RPL Mask properly */ TrapFrame->HardwareSegSs |= RPL_MASK; TrapFrame->SegDs |= RPL_MASK; TrapFrame->SegEs |= RPL_MASK; TrapFrame->Dr7 = 0; /* Set the debug mark */ TrapFrame->DbgArgMark = 0xBADB0D00; /* Set the previous mode as user */ TrapFrame->PreviousPreviousMode = UserMode; /* Terminate the Exception Handler List */ TrapFrame->ExceptionList = EXCEPTION_CHAIN_END; /* Setup the Stack for KiThreadStartup and Context Switching */ StartFrame = &InitFrame->StartFrame; CtxSwitchFrame = &InitFrame->CtxSwitchFrame; /* Tell the thread it will run in User Mode */ Thread->PreviousMode = UserMode; /* Tell KiThreadStartup of that too */ StartFrame->UserThread = TRUE; } else { /* Set up the Initial Frame for the system thread */ PKKINIT_FRAME InitFrame; InitFrame = (PKKINIT_FRAME)((ULONG_PTR)Thread->InitialStack - sizeof(KKINIT_FRAME)); /* Setup the Fx Area */ FxSaveArea = &InitFrame->FxSaveArea; RtlZeroMemory(FxSaveArea, sizeof(FX_SAVE_AREA)); /* Check if we have Fxsr support */ if (KeI386FxsrPresent) { /* Set the stub FX area */ FxSaveArea->U.FxArea.ControlWord = 0x27F; FxSaveArea->U.FxArea.MXCsr = 0x1F80; } else { /* Set the stub FN area */ FxSaveArea->U.FnArea.ControlWord = 0x27F; FxSaveArea->U.FnArea.TagWord = -1; } /* No NPX State */ Thread->NpxState = NPX_STATE_NOT_LOADED; /* Setup the Stack for KiThreadStartup and Context Switching */ StartFrame = &InitFrame->StartFrame; CtxSwitchFrame = &InitFrame->CtxSwitchFrame; /* Tell the thread it will run in Kernel Mode */ Thread->PreviousMode = KernelMode; /* Tell KiThreadStartup of that too */ StartFrame->UserThread = FALSE; } /* Now setup the remaining data for KiThreadStartup */ StartFrame->StartContext = StartContext; StartFrame->StartRoutine = StartRoutine; StartFrame->SystemRoutine = SystemRoutine; /* And set up the Context Switch Frame */ CtxSwitchFrame->RetAddr = KiThreadStartup; CtxSwitchFrame->ApcBypassDisable = TRUE; CtxSwitchFrame->ExceptionList = EXCEPTION_CHAIN_END;; /* Save back the new value of the kernel stack. */ Thread->KernelStack = (PVOID)CtxSwitchFrame; }
线程系统堆栈创建的代码为,也就是说线程初始化堆栈是一个KUINIT_FRAME的结构体:
PKUINIT_FRAME InitFrame; InitFrame = (PKUINIT_FRAME)((ULONG_PTR)Thread->InitialStack - sizeof(KUINIT_FRAME)); ........... CtxSwitchFrame = &InitFrame->CtxSwitchFrame; .................... /* Save back the new value of the kernel stack. */ Thread->KernelStack = (PVOID)CtxSwitchFrame;
KUINIT_FRAME结构体相关定义如下:
typedef struct _KUINIT_FRAME { KSWITCHFRAME CtxSwitchFrame; KSTART_FRAME StartFrame; KTRAP_FRAME TrapFrame; FX_SAVE_AREA FxSaveArea; } KUINIT_FRAME, *PKUINIT_FRAME;
typedef struct _KSWITCHFRAME
{
PVOID ExceptionList;
BOOLEAN ApcBypassDisable;
PVOID RetAddr;
} KSWITCHFRAME, *PKSWITCHFRAME;
typedef struct _KSTART_FRAME
{
PKSYSTEM_ROUTINE SystemRoutine;
PKSTART_ROUTINE StartRoutine;
PVOID StartContext;
BOOLEAN UserThread;
} KSTART_FRAME, *PKSTART_FRAME;
根据定义,CtxSwitchFrame就是栈顶数据,新线程执行了两次POP指令,直接运行RET指令,也就是函数返回执行的是CtxSwitchFrame结构体中RetAddr字段保存的函数,此结构体初始化为:
/* And set up the Context Switch Frame */ CtxSwitchFrame->RetAddr = KiThreadStartup; CtxSwitchFrame->ApcBypassDisable = TRUE; CtxSwitchFrame->ExceptionList = EXCEPTION_CHAIN_END;;
因此,新线程返回执行的第一个函数式KiThreadStartup.
KiThreadStartup()定义如下,函数接受156个字节的参数,也就是InitFrame中的StartFrame、TrapFrame字段。
*--*/ .func KiThreadStartup@156 .globl _KiThreadStartup@156 _KiThreadStartup@156: /* * Clear all the non-volatile registers, so the thread won't be tempted to * expect any static data (like some badly coded usermode/win9x apps do) */ xor ebx, ebx xor esi, esi xor edi, edi xor ebp, ebp /* It's now safe to go to APC */ mov ecx, APC_LEVEL call @KfLowerIrql@4 /* * Call the System Routine which is right on our stack now. * After we pop the pointer, the Start Routine/Context will be on the * stack, as parameters to the System Routine */ pop eax call eax /* The thread returned... was it a user-thread? */ pop ecx or ecx, ecx jz BadThread /* Yes it was, set our trapframe for the System Call Exit Dispatcher */ mov ebp, esp /* Exit back to user-mode */ jmp _KiServiceExit2 BadThread: /* A system thread returned...this is very bad! */ int 3 .endfunc
KiThreadStartup()通过下面的指令调用PspUserThreadStartup()函数
pop eax call eax
PspUserThreadStartup()函数定义如下,其主要工作时初始化APC,这里忽略:
View Code
PspUserThreadStartup(IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext) { PETHREAD Thread; PTEB Teb; BOOLEAN DeadThread = FALSE; PAGED_CODE(); PSTRACE(PS_THREAD_DEBUG, "StartRoutine: %p StartContext: %p\n", StartRoutine, StartContext); /* Go to Passive Level */ KeLowerIrql(PASSIVE_LEVEL); Thread = PsGetCurrentThread(); /* Check if the thread is dead */ if (Thread->DeadThread) { /* Remember that we're dead */ DeadThread = TRUE; } else { /* Get the Locale ID and save Preferred Proc */ Teb = NtCurrentTeb(); Teb->CurrentLocale = MmGetSessionLocaleId(); Teb->IdealProcessor = Thread->Tcb.IdealProcessor; } /* Check if this is a dead thread, or if we're hiding */ if (!(Thread->DeadThread) && !(Thread->HideFromDebugger)) { /* We're not, so notify the debugger */ DbgkCreateThread(StartContext); } /* Make sure we're not already dead */ if (!DeadThread) { /* Check if the Prefetcher is enabled */ if (CcPfEnablePrefetcher) { /* FIXME: Prepare to prefetch this process */ } /* Raise to APC */ KfRaiseIrql(APC_LEVEL); /* Queue the User APC */ KiInitializeUserApc(NULL, (PVOID)((ULONG_PTR)Thread->Tcb.InitialStack - sizeof(KTRAP_FRAME) - SIZEOF_FX_SAVE_AREA), PspSystemDllEntryPoint, NULL, PspSystemDllBase, NULL); /* Lower it back to passive */ KeLowerIrql(PASSIVE_LEVEL); } else { /* We're dead, kill us now */ PspTerminateThreadByPointer(Thread, STATUS_THREAD_IS_TERMINATING, TRUE); } /* Do we have a cookie set yet? */ if (!SharedUserData->Cookie) { LARGE_INTEGER SystemTime; ULONG NewCookie; PKPRCB Prcb; /* Generate a new cookie */ KeQuerySystemTime(&SystemTime); Prcb = KeGetCurrentPrcb(); NewCookie = Prcb->MmPageFaultCount ^ Prcb->InterruptTime ^ SystemTime.u.LowPart ^ SystemTime.u.HighPart ^ (ULONG)&SystemTime; /* Set the new cookie*/ InterlockedCompareExchange((LONG*)&SharedUserData->Cookie, NewCookie, 0); } }
调用完之后PspUserThreadStartup(),就跳到_KiServiceExit2,根据TrapFrame的内容,返回用户空间了。
pop eax call eax /* The thread returned... was it a user-thread? */ pop ecx or ecx, ecx jz BadThread /* Yes it was, set our trapframe for the System Call Exit Dispatcher */ mov ebp, esp /* Exit back to user-mode */ jmp _KiServiceExit2
