635 lines
24 KiB
C
635 lines
24 KiB
C
// Thread manager. This file owns thread allocation, kernel/user startup contexts, and lifecycle transitions.
|
|
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <intrin.h>
|
|
#include <kernel/ke_thread.h>
|
|
#include <kernel/ke_sched.h>
|
|
#include <kernel/ke_cpu.h>
|
|
#include <kernel/ke_irql.h>
|
|
#include <kernel/ke_event.h>
|
|
#include <kernel/ke_mutex.h>
|
|
#include <kernel/ke_semaphore.h>
|
|
#include <kernel/ke_apc.h>
|
|
#include <kernel/ke_debug.h>
|
|
#include <kernel/ke_userenv.h>
|
|
#include <kernel/mm_virt.h>
|
|
#include <kernel/ob_object.h>
|
|
|
|
#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;
|
|
}
|