// Thread manager. This file owns thread allocation, kernel/user startup contexts, and lifecycle transitions. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FPU_FCW_DEFAULT 0x037Fu #define FPU_MXCSR_DEFAULT 0x1F80u // These offsets are consumed directly by ke_switch.asm. Keep the asserts // here so THREAD layout edits fail at build time instead of corrupting // context switches at runtime. #define STATIC_ASSERT_OFFSET(type, field, expected) \ _Static_assert(offsetof(type, field) == (expected), \ #type "." #field " offset must match assembly") STATIC_ASSERT_OFFSET(THREAD, SavedContext, 88); STATIC_ASSERT_OFFSET(THREAD, TssRsp0, 104); STATIC_ASSERT_OFFSET(THREAD, Teb, 112); STATIC_ASSERT_OFFSET(THREAD, FpuContext, 168); static uint32_t gNextThreadId = 1; extern void KeThreadEntry(void); // Implements the thread Destroy operation. static void KiThreadDestroy(OBJECT* obj) { THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header); // A wait that uses auxiliary stack wait blocks must be removed before the // kernel stack is released. A terminated thread may never return to the // wait syscall to run normal cleanup. KeRundownThreadWait(thread, STATUS_ALERTED); KiRundownApcState(thread); if (thread->ExitEvent) { ObDereferenceObject(&thread->ExitEvent->Header); thread->ExitEvent = (EVENT*)0; } PROCESS* ownerForVadList = thread->Owner; if (ownerForVadList) { if (thread->Teb && thread->TebSize) { MmFreeUserMemoryForProcess(ownerForVadList, thread->Teb); thread->Teb = (void*)0; thread->TebSize = 0; } if (thread->UserStackBase) { MmFreeUserMemoryForProcess(ownerForVadList, thread->UserStackBase); thread->UserStackBase = (void *)0; } } if (thread->Owner) { PROCESS* owner = thread->Owner; uint32_t remaining = 0; uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); ListRemove(&thread->ProcessLink); if (owner->ThreadCount) owner->ThreadCount--; remaining = owner->ThreadCount; KeReleaseSpinlock(&owner->HandleLock, flags); if (remaining == 0) { // The last thread leaving a process is process exit even if user // code called ExitThread instead of ExitProcess. KeExitProcess / // KeTerminateProcess set Terminating earlier; this covers the last // thread voluntarily exiting without an explicit process-exit call. _InterlockedExchange(&owner->Terminating, 1); KeCompleteProcessExitIfLastThread(owner); } ObDereferenceObject(&owner->Header); thread->Owner = (PROCESS *)0; } if (thread->KernelStackBase) { MmFreePool(thread->KernelStackBase); thread->KernelStackBase = (void *)0; } } __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, THREAD_PRIORITY priority, THREAD** out) { if (!owner || !out) return STATUS_INVALID_PARAMETER; if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER; THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); if (!thread) return STATUS_NO_MEMORY; thread->Header.Destroy = KiThreadDestroy; thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1); thread->State = ThreadReady; thread->Priority = priority; thread->QuantaConsumed = 0; thread->QuantumTicks = 0; thread->QuantumRemaining = 0; thread->CpuBurstTicks = 0; thread->LastRunTicks = 0; thread->RunningCpu = KE_INVALID_CPU; thread->Queued = 0; thread->IsIdleThread = 0; thread->SchedulerFlags = 0; thread->LastCpu = KE_INVALID_CPU; thread->AffinityMask = KeGetDefaultThreadAffinityMask(); LIST_INIT(&thread->ProcessLink); LIST_INIT(&thread->SchedulerLink); thread->WaitObject = (OBJECT *)0; thread->WaitStatus = STATUS_SUCCESS; thread->WaitListHead = (LIST_ENTRY*)0; thread->AlertableWait = 0; thread->SleepUntilTick = 0; thread->TimeoutStatus = STATUS_SUCCESS; thread->SwitchLockCookie = 0x202ULL; KiInitializeApcState(thread); thread->TrapFrame = (KTRAP_FRAME*)0; void* kstack = MmAllocatePoolTag(KSTACK_SIZE, MM_POOL_TAG('k', 's', 't', 'k')); if (!kstack) { ObDereferenceObject(&thread->Header); return STATUS_NO_MEMORY; } memset(kstack, 0, KSTACK_SIZE); thread->KernelStackBase = kstack; thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE; thread->TssRsp0 = thread->KernelStackTop; KSTATUS s; void* ustack = (void*)0; s = MmVadCreateCommitted(owner, USTACK_SIZE, PAGE_SIZE, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, MM_VAD_ZERO | MM_VAD_GUARD_LOW | MM_VAD_GUARD_HIGH, MmVadStack, &ustack); if (!KSUCCESS(s) || !ustack) { MmFreePool(kstack); thread->KernelStackBase = (void*)0; ObDereferenceObject(&thread->Header); return STATUS_NO_MEMORY; } thread->UserStackBase = ustack; // Ring-3 starts at the image entry via IRET, not CALL. The MS x64 ABI // nevertheless lets callees immediately use the caller-provided 32-byte // home/shadow area above RSP, and MSVC commonly emits a first instruction // like "mov [rsp+8], rcx" in unoptimized test code. Bias the synthetic // entry stack as if a CALL with a normal home area had happened: // // RSP % 16 == 8 at function entry // [RSP + 0] fake return-address slot // [RSP + 8..39] home/shadow space // // Without the 32-byte home area, the first spill lands in the high stack // guard page and every freshly-created MSVC thread faults immediately. uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8 - 32; SYS_TEB* teb = (SYS_TEB*)MmAllocateUserMemoryForProcess(owner, sizeof(SYS_TEB)); if (!teb) { MmFreeUserMemoryForProcess(owner, ustack); thread->UserStackBase = (void*)0; MmFreePool(kstack); thread->KernelStackBase = (void*)0; ObDereferenceObject(&thread->Header); return STATUS_NO_MEMORY; } memset(teb, 0, sizeof(SYS_TEB)); teb->Peb = (SYS_PEB*)owner->UserPebBase; teb->StackBase = (void*)userStackTop; teb->StackLimit = ustack; teb->Self = teb; teb->ThreadId = thread->ThreadId; teb->Magic = SYS_TEB_MAGIC; thread->Teb = teb; thread->TebSize = sizeof(SYS_TEB); thread->UserEntry = entry; thread->UserArg = arg; uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop; *(--ksp) = userStackTop; *(--ksp) = 0x23ULL; *(--ksp) = userStackTop; *(--ksp) = 0x202ULL; *(--ksp) = 0x1BULL; *(--ksp) = entry; *(--ksp) = arg; ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); KCONTEXT* ctx = (KCONTEXT *)ksp; memset(ctx, 0, sizeof(KCONTEXT)); ctx->Rip = (uint64_t)KeThreadEntry; thread->SavedContext = ctx; ObReferenceObject(&owner->Header); thread->Owner = owner; // Allocate the exit event before the thread becomes visible. Manual-reset // so multiple joiners (if any) all wake up when the thread exits. EVENT* exitEvent; s = KeEventCreate(EventManualReset, 0, &exitEvent); if (!KSUCCESS(s)) { if (thread->Teb && thread->TebSize) MmFreeUserMemoryForProcess(owner, thread->Teb); thread->Teb = (void*)0; thread->TebSize = 0; MmFreeUserMemoryForProcess(owner, ustack); thread->UserStackBase = (void*)0; MmFreePool(kstack); thread->KernelStackBase = (void*)0; ObDereferenceObject(&owner->Header); thread->Owner = (PROCESS*)0; ObDereferenceObject(&thread->Header); return s; } thread->ExitEvent = exitEvent; uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); ListInsertTail(&owner->ThreadList, &thread->ProcessLink); owner->ThreadCount++; KeReleaseSpinlock(&owner->HandleLock, flags); KeInitThreadFpuContext(thread); // Hand the creator a short-lived reference before making the thread // runnable. On SMP a new user thread can run and exit before // KeCreateThread() returns; without this reference, callers that merely // read ThreadId for logging or syscall output can race the scheduler's // dead-thread dereference and touch freed pool. ObReferenceObject(&thread->Header); *out = thread; KeReadyThread(thread); return STATUS_SUCCESS; } // Implements the exit Thread operation. __declspec(dllexport) void KeExitThread(THREAD* thread, int code) { (void)code; if (!thread) return; KiRundownApcState(thread); // Wake any pthread_join waiters before the thread is marked dead and // potentially freed. Manual-reset means all concurrent waiters unblock. if (thread->ExitEvent) KeSignalEvent(thread->ExitEvent); thread->State = ThreadDead; KeYield(); } // KeCreateKernelThread // Kernel threads never go to ring 3: they have no user stack, no TEB, and // their entry stub (KeKernelThreadEntry) calls the function directly then // calls KeExitThread instead of IRETQ-ing. // Stack layout built here (from KernelStackTop downward): // [ksp - 8 ] arg <-- popped by KeKernelThreadEntry as r12 // [ksp - 16] fn <-- popped by KeKernelThreadEntry as rbx // [ksp - 16 - sizeof(KCONTEXT)] KCONTEXT { .Rip = KeKernelThreadEntry }. extern void KeKernelThreadEntry(void); static KSTATUS KiCreateKernelThreadWithAffinity(uintptr_t fn, uintptr_t arg, THREAD_PRIORITY priority, uint64_t affinityMask, THREAD** out) { // Kernel worker threads are owned by the system process. The scheduler's // hand-built CPU idle thread stays attached to PID 0 (idle), while driver, // async I/O, and init worker threads are grouped under PID 1 (system). PROCESS* owner = KeGetSystemProcess(); if (!owner) { THREAD* currentThread = KeGetCurrentThread(); owner = currentThread ? currentThread->Owner : (PROCESS*)0; } if (!owner) return STATUS_INVALID_PARAMETER; if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER; THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); if (!thread) return STATUS_NO_MEMORY; thread->Header.Destroy = KiThreadDestroy; thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1); thread->State = ThreadReady; thread->Priority = priority; thread->QuantaConsumed = 0; thread->QuantumTicks = 0; thread->QuantumRemaining = 0; thread->CpuBurstTicks = 0; thread->LastRunTicks = 0; thread->RunningCpu = KE_INVALID_CPU; thread->Queued = 0; thread->IsIdleThread = 0; thread->SchedulerFlags = 0; thread->LastCpu = KE_INVALID_CPU; thread->AffinityMask = affinityMask ? affinityMask : KeGetDefaultThreadAffinityMask(); LIST_INIT(&thread->ProcessLink); LIST_INIT(&thread->SchedulerLink); thread->WaitObject = (OBJECT*)0; thread->WaitStatus = STATUS_SUCCESS; thread->WaitListHead = (LIST_ENTRY*)0; thread->AlertableWait = 0; thread->SleepUntilTick = 0; thread->TimeoutStatus = STATUS_SUCCESS; thread->SwitchLockCookie = 0x202ULL; KiInitializeApcState(thread); thread->TrapFrame = (KTRAP_FRAME*)0; thread->UserStackBase = (void*)0; // no user stack thread->Teb = (void*)0; // no TEB; GS_BASE will be 0 in kernel ctx thread->TebSize = 0; void* kstack = MmAllocatePoolTag(KSTACK_SIZE, MM_POOL_TAG('k', 's', 't', 'k')); if (!kstack) { ObDereferenceObject(&thread->Header); return STATUS_NO_MEMORY; } memset(kstack, 0, KSTACK_SIZE); thread->KernelStackBase = kstack; thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE; thread->TssRsp0 = thread->KernelStackTop; // Build the initial stack: arg, fn, then KCONTEXT. uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop; *(--ksp) = arg; // popped second (-> r12) *(--ksp) = fn; // popped first (-> rbx) ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); KCONTEXT* ctx = (KCONTEXT*)ksp; memset(ctx, 0, sizeof(KCONTEXT)); ctx->Rip = (uint64_t)KeKernelThreadEntry; thread->SavedContext = ctx; // Register with the owning process. ObReferenceObject(&owner->Header); thread->Owner = owner; uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); ListInsertTail(&owner->ThreadList, &thread->ProcessLink); owner->ThreadCount++; KeReleaseSpinlock(&owner->HandleLock, flags); // Hand the creator a short-lived reference before making the kernel // thread runnable. Kernel workers can run, signal their completion event, // exit, and be reaped on another CPU before KeCreateKernelThread()'s // caller drops its returned pointer. The live scheduler reference remains // separate and is released by KeSchedulerPostSwitch() after the dead // thread has switched away. if (out) { ObReferenceObject(&thread->Header); *out = thread; } KeInitThreadFpuContext(thread); KeReadyThread(thread); return STATUS_SUCCESS; } __declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, THREAD_PRIORITY priority, THREAD** out) { return KiCreateKernelThreadWithAffinity(fn, arg, priority, KeGetDefaultThreadAffinityMask(), out); } __declspec(dllexport) KSTATUS KeCreateKernelThreadWithAffinity(uintptr_t fn, uintptr_t arg, THREAD_PRIORITY priority, uint64_t affinityMask, THREAD** out) { return KiCreateKernelThreadWithAffinity(fn, arg, priority, affinityMask, out); } // Prepares block Thread. void KePrepareBlockThread(THREAD* thread, OBJECT* waitObject, uint8_t alertable) { // Called with the synchronization object's spinlock held, so the caller is // normally at DISPATCH_LEVEL here. This function only marks intent to // block; the actual scheduling point is KeCommitBlockThread at PASSIVE. KeAssertIrqlAtOrBelow(DISPATCH_LEVEL); if (!thread) return; KiSchedulerMarkThreadWaiting(thread); thread->WaitObject = waitObject; thread->WaitStatus = STATUS_PENDING; thread->WaitListHead = (LIST_ENTRY*)0; thread->AlertableWait = alertable ? 1 : 0; thread->WaitRundownRoutine = (KE_WAIT_RUNDOWN_ROUTINE)0; thread->WaitRundownContext = (void*)0; } // Updates thread Wait List. void KeSetThreadWaitList(THREAD* thread, LIST_ENTRY* waitListHead) { KeAssertIrqlAtOrBelow(DISPATCH_LEVEL); if (!thread) return; thread->WaitListHead = waitListHead; } // Registers cleanup for waits with auxiliary wait blocks. void KeSetThreadWaitRundown(THREAD* thread, KE_WAIT_RUNDOWN_ROUTINE routine, void* context) { KeAssertIrqlAtOrBelow(DISPATCH_LEVEL); if (!thread) return; thread->WaitRundownRoutine = routine; thread->WaitRundownContext = context; } // Clears auxiliary wait cleanup after the owning wait has removed its blocks. void KeClearThreadWaitRundown(THREAD* thread) { KeAssertIrqlAtOrBelow(DISPATCH_LEVEL); if (!thread) return; thread->WaitRundownRoutine = (KE_WAIT_RUNDOWN_ROUTINE)0; thread->WaitRundownContext = (void*)0; } // Runs auxiliary wait cleanup for abort and thread teardown paths. void KeRundownThreadWait(THREAD* thread, KSTATUS status) { if (!thread || !thread->WaitRundownRoutine) return; KE_WAIT_RUNDOWN_ROUTINE routine = thread->WaitRundownRoutine; void* context = thread->WaitRundownContext; thread->WaitRundownRoutine = (KE_WAIT_RUNDOWN_ROUTINE)0; thread->WaitRundownContext = (void*)0; routine(thread, context, status); } // Implements the finish Wait operation. static void KiFinishWait(THREAD* thread, KSTATUS status) { thread->WaitObject = (OBJECT*)0; thread->WaitStatus = status; thread->WaitListHead = (LIST_ENTRY*)0; thread->AlertableWait = 0; thread->SleepUntilTick = 0; thread->TimeoutStatus = STATUS_SUCCESS; } // Implements the commit Block Thread operation. KSTATUS KeCommitBlockThread(THREAD* thread, uint8_t alertable) { KeAssertIrql(PASSIVE_LEVEL); if (!thread) return STATUS_INVALID_PARAMETER; if (alertable) thread->AlertableWait = 1; // A signal/APC may have arrived after the waiter was placed on the // object's wait list but before this thread reached the scheduling point. // In that case KeUnblockThreadWithStatus completed the wait in-place and // there is nothing left to block on. if (thread->State != ThreadWaiting) { KSTATUS status = thread->WaitStatus; thread->AlertableWait = 0; thread->SleepUntilTick = 0; thread->TimeoutStatus = STATUS_SUCCESS; return status; } // If a user APC was already pending before the thread fully blocked, // consume the alertable wait immediately instead of going to sleep. if (alertable && thread->UserApcPending && thread->UserApcDispatcher) { if (thread->WaitListHead) ListRemove(&thread->SchedulerLink); KiFinishWait(thread, STATUS_USER_APC); thread->State = ThreadRunning; return STATUS_USER_APC; } KiSchedulerNoteVoluntaryBlock(thread); KeYield(); // Kernel APCs are delivered before an alertable wait returns // STATUS_KERNEL_APC to its caller. KiDeliverKernelApcs(); thread->AlertableWait = 0; thread->SleepUntilTick = 0; thread->TimeoutStatus = STATUS_SUCCESS; return thread->WaitStatus; } // Implements the block Thread operation. KSTATUS KeBlockThread(THREAD* thread, OBJECT* waitObject, uint8_t alertable) { KePrepareBlockThread(thread, waitObject, alertable); return KeCommitBlockThread(thread, alertable); } // Implements the unblock Thread With Status operation. void KeUnblockThreadWithStatus(THREAD* thread, KSTATUS status) { if (!thread) return; KiSchedulerWakeThread(thread, status); } // Returns whether the scheduler link still belongs to an object wait list. static int KiThreadWaitLinkIsLinked(const THREAD* thread) { if (!thread) return 0; return thread->SchedulerLink.Fwd != &thread->SchedulerLink || thread->SchedulerLink.Bwd != &thread->SchedulerLink; } // Removes a thread from one wait list while that wait object's lock is held. static int KiRemoveThreadFromObjectWaitListLocked(THREAD* thread, LIST_ENTRY* waitListHead) { if (!thread || !waitListHead) return 0; if (thread->State != ThreadWaiting || thread->WaitListHead != waitListHead) return 0; // The signaling side pops the wait-list entry before it wakes the thread. // In that handoff window the scheduler link is already self-linked; abort // must not remove it again or turn a successful mutex/semaphore handoff // into an aborted wait for a thread that may already own the object. if (!KiThreadWaitLinkIsLinked(thread)) return 0; ListRemove(&thread->SchedulerLink); thread->WaitListHead = (LIST_ENTRY*)0; thread->SleepUntilTick = 0; thread->TimeoutStatus = STATUS_SUCCESS; return 1; } // Removes a thread from an object-owned wait queue using that object's lock. static int KiAbortObjectWait(THREAD* thread) { if (!thread || !thread->WaitObject || !thread->WaitListHead) return 0; OBJECT* object = thread->WaitObject; switch (object->Type) { case ObjectTypeEvent: { EVENT* event = CONTAINING_RECORD(object, EVENT, Header); uintptr_t flags = KeAcquireSpinlock(&event->Lock); int removed = KiRemoveThreadFromObjectWaitListLocked(thread, &event->WaitList); KeReleaseSpinlock(&event->Lock, flags); return removed; } case ObjectTypeMutex: { MUTEX* mutex = CONTAINING_RECORD(object, MUTEX, Header); uintptr_t flags = KeAcquireSpinlock(&mutex->Lock); int removed = KiRemoveThreadFromObjectWaitListLocked(thread, &mutex->WaitList); KeReleaseSpinlock(&mutex->Lock, flags); return removed; } case ObjectTypeSemaphore: { SEMAPHORE* semaphore = CONTAINING_RECORD(object, SEMAPHORE, Header); uintptr_t flags = KeAcquireSpinlock(&semaphore->Lock); int removed = KiRemoveThreadFromObjectWaitListLocked(thread, &semaphore->WaitList); KeReleaseSpinlock(&semaphore->Lock, flags); return removed; } default: return 0; } } // Aborts a thread's current wait regardless of whether the wait was marked // alertable. Process teardown uses this for crash/kill paths: a background // worker blocked forever on a semaphore must not keep the process exit event // unsignaled after the foreground thread has faulted. Object wait-list removal // must use the object's own lock; otherwise signal/abort races can leave stale // scheduler links in an event, mutex, or semaphore wait queue. void KeAbortThreadWait(THREAD* thread, KSTATUS status) { if (!thread) return; if (thread->State != ThreadWaiting) return; if (thread->WaitRundownRoutine) { KeRundownThreadWait(thread, status); return; } if (KiAbortObjectWait(thread)) { KeUnblockThreadWithStatus(thread, status); return; } // Sleep/timeouts and already-dequeued handoffs are owned by the scheduler. // This path removes from the scheduler sleep list when present and otherwise // completes the wait in place. KeUnblockThreadFromWaitListWithStatus(thread, status); } // Implements the unblock Thread operation. void KeUnblockThread(THREAD* thread) { KeUnblockThreadWithStatus(thread, STATUS_SUCCESS); } // Implements the alert Thread With Status operation. static KSTATUS KiAlertThreadWithStatus(THREAD* thread, KSTATUS status) { if (!thread) return STATUS_INVALID_PARAMETER; if (thread->State != ThreadWaiting || !thread->AlertableWait) return STATUS_PENDING; if (thread->WaitListHead) ListRemove(&thread->SchedulerLink); KeUnblockThreadWithStatus(thread, status); return STATUS_SUCCESS; } // Implements the alert Thread operation. KSTATUS KeAlertThread(THREAD* thread) { return KiAlertThreadWithStatus(thread, STATUS_ALERTED); } // Implements the wake Thread For Kernel Apc operation. void KiWakeThreadForKernelApc(THREAD* thread) { (void)KiAlertThreadWithStatus(thread, STATUS_KERNEL_APC); } // Implements the wake Thread For User Apc operation. void KiWakeThreadForUserApc(THREAD* thread) { (void)KiAlertThreadWithStatus(thread, STATUS_USER_APC); } void KeInitThreadFpuContext(THREAD* thread) { uintptr_t raw = (uintptr_t)thread->FpuStorage; uintptr_t aligned = (raw + 15) & ~(uintptr_t)15; thread->FpuContext = (void*)aligned; memset(thread->FpuContext, 0, 512); *(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F; *(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80; }