https://www.vergiliusproject.com/
KPCR 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 struct _KPCR { union { struct _NT_TIB NtTib ; struct { struct _EXCEPTION_REGISTRATION_RECORD * Used_ExceptionList ; VOID* Used_StackBase; VOID* Spare2; VOID* TssCopy; ULONG ContextSwitches; ULONG SetMemberCopy; VOID* Used_Self; }; }; struct _KPCR * SelfPcr ; struct _KPRCB * Prcb ; UCHAR Irql; ULONG IRR; ULONG IrrActive; ULONG IDR; VOID* KdVersionBlock; struct _KIDTENTRY * IDT ; struct _KGDTENTRY * GDT ; struct _KTSS * TSS ; USHORT MajorVersion; USHORT MinorVersion; ULONG SetMember; ULONG StallScaleFactor; UCHAR SpareUnused; UCHAR Number; UCHAR Spare0; UCHAR SecondLevelCacheAssociativity; ULONG VdmAlert; ULONG KernelReserved[14 ]; ULONG SecondLevelCacheSize; ULONG HalReserved[16 ]; ULONG InterruptMode; UCHAR Spare1; ULONG KernelReserved2[17 ]; struct _KPRCB PrcbData ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 struct _KPRCB { USHORT MinorVersion; USHORT MajorVersion; struct _KTHREAD * CurrentThread ; struct _KTHREAD * NextThread ; struct _KTHREAD * IdleThread ; UCHAR LegacyNumber; UCHAR NestingLevel; USHORT BuildType; CHAR CpuType; CHAR CpuID; union { USHORT CpuStep; struct { UCHAR CpuStepping; UCHAR CpuModel; }; }; struct _KPROCESSOR_STATE ProcessorState ; ULONG KernelReserved[16 ]; ULONG HalReserved[16 ]; ULONG CFlushSize; UCHAR CoresPerPhysicalProcessor; UCHAR LogicalProcessorsPerCore; UCHAR PrcbPad0[2 ]; ULONG MHz; UCHAR CpuVendor; UCHAR GroupIndex; USHORT Group; ULONG GroupSetMember; ULONG Number; UCHAR PrcbPad1[72 ]; struct _KSPIN_LOCK_QUEUE LockQueue [17]; struct _KTHREAD * NpxThread ; ULONG InterruptCount; ULONG KernelTime; ULONG UserTime; ULONG DpcTime; ULONG DpcTimeCount; ULONG InterruptTime; ULONG AdjustDpcThreshold; ULONG PageColor; UCHAR DebuggerSavedIRQL; UCHAR NodeColor; UCHAR PrcbPad20[2 ]; ULONG NodeShiftedColor; struct _KNODE * ParentNode ; ULONG SecondaryColorMask; ULONG DpcTimeLimit; ULONG PrcbPad21[2 ]; ULONG CcFastReadNoWait; ULONG CcFastReadWait; ULONG CcFastReadNotPossible; ULONG CcCopyReadNoWait; ULONG CcCopyReadWait; ULONG CcCopyReadNoWaitMiss; volatile LONG MmSpinLockOrdering; volatile LONG IoReadOperationCount; volatile LONG IoWriteOperationCount; volatile LONG IoOtherOperationCount; union _LARGE_INTEGER IoReadTransferCount ; union _LARGE_INTEGER IoWriteTransferCount ; union _LARGE_INTEGER IoOtherTransferCount ; ULONG CcFastMdlReadNoWait; ULONG CcFastMdlReadWait; ULONG CcFastMdlReadNotPossible; ULONG CcMapDataNoWait; ULONG CcMapDataWait; ULONG CcPinMappedDataCount; ULONG CcPinReadNoWait; ULONG CcPinReadWait; ULONG CcMdlReadNoWait; ULONG CcMdlReadWait; ULONG CcLazyWriteHotSpots; ULONG CcLazyWriteIos; ULONG CcLazyWritePages; ULONG CcDataFlushes; ULONG CcDataPages; ULONG CcLostDelayedWrites; ULONG CcFastReadResourceMiss; ULONG CcCopyReadWaitMiss; ULONG CcFastMdlReadResourceMiss; ULONG CcMapDataNoWaitMiss; ULONG CcMapDataWaitMiss; ULONG CcPinReadNoWaitMiss; ULONG CcPinReadWaitMiss; ULONG CcMdlReadNoWaitMiss; ULONG CcMdlReadWaitMiss; ULONG CcReadAheadIos; ULONG KeAlignmentFixupCount; ULONG KeExceptionDispatchCount; ULONG KeSystemCalls; ULONG AvailableTime; ULONG PrcbPad22[2 ]; struct _PP_LOOKASIDE_LIST PPLookasideList [16]; struct _GENERAL_LOOKASIDE_POOL PPNPagedLookasideList [32]; struct _GENERAL_LOOKASIDE_POOL PPPagedLookasideList [32]; volatile ULONG PacketBarrier; volatile LONG ReverseStall; VOID* IpiFrame; UCHAR PrcbPad3[52 ]; VOID* volatile CurrentPacket[3 ]; volatile ULONG TargetSet; VOID (* volatile WorkerRoutine)(VOID*, VOID*, VOID*, VOID*); volatile ULONG IpiFrozen; UCHAR PrcbPad4[40 ]; volatile ULONG RequestSummary; struct _KPRCB * volatile SignalDone ; UCHAR PrcbPad50[56 ]; struct _KDPC_DATA DpcData [2]; VOID* DpcStack; LONG MaximumDpcQueueDepth; ULONG DpcRequestRate; ULONG MinimumDpcRate; ULONG DpcLastCount; ULONG PrcbLock; struct _KGATE DpcGate ; UCHAR ThreadDpcEnable; volatile UCHAR QuantumEnd; volatile UCHAR DpcRoutineActive; volatile UCHAR IdleSchedule; union { volatile LONG DpcRequestSummary; SHORT DpcRequestSlot[2 ]; struct { SHORT NormalDpcState; union { volatile USHORT DpcThreadActive : 1 ; SHORT ThreadDpcState; }; }; }; volatile ULONG TimerHand; ULONG LastTick; LONG MasterOffset; ULONG PrcbPad41[2 ]; ULONG PeriodicCount; ULONG PeriodicBias; ULONGLONG TickOffset; struct _KTIMER_TABLE TimerTable ; struct _KDPC CallDpc ; LONG ClockKeepAlive; UCHAR ClockCheckSlot; UCHAR ClockPollCycle; UCHAR PrcbPad6[2 ]; LONG DpcWatchdogPeriod; LONG DpcWatchdogCount; LONG ThreadWatchdogPeriod; LONG ThreadWatchdogCount; volatile LONG KeSpinLockOrdering; ULONG PrcbPad70[1 ]; struct _LIST_ENTRY WaitListHead ; ULONG WaitLock; ULONG ReadySummary; ULONG QueueIndex; struct _SINGLE_LIST_ENTRY DeferredReadyListHead ; ULONGLONG StartCycles; volatile ULONGLONG CycleTime; volatile ULONG HighCycleTime; ULONG PrcbPad71; ULONGLONG PrcbPad72[2 ]; struct _LIST_ENTRY DispatcherReadyListHead [32]; VOID* ChainedInterruptList; LONG LookasideIrpFloat; volatile LONG MmPageFaultCount; volatile LONG MmCopyOnWriteCount; volatile LONG MmTransitionCount; volatile LONG MmCacheTransitionCount; volatile LONG MmDemandZeroCount; volatile LONG MmPageReadCount; volatile LONG MmPageReadIoCount; volatile LONG MmCacheReadCount; volatile LONG MmCacheIoCount; volatile LONG MmDirtyPagesWriteCount; volatile LONG MmDirtyWriteIoCount; volatile LONG MmMappedPagesWriteCount; volatile LONG MmMappedWriteIoCount; volatile ULONG CachedCommit; volatile ULONG CachedResidentAvailable; VOID* HyperPte; UCHAR PrcbPad8[4 ]; UCHAR VendorString[13 ]; UCHAR InitialApicId; UCHAR LogicalProcessorsPerPhysicalProcessor; UCHAR PrcbPad9[5 ]; ULONG FeatureBits; union _LARGE_INTEGER UpdateSignature ; volatile ULONGLONG IsrTime; ULONGLONG RuntimeAccumulation; struct _PROCESSOR_POWER_STATE PowerState ; struct _KDPC DpcWatchdogDpc ; struct _KTIMER DpcWatchdogTimer ; VOID* WheaInfo; VOID* EtwSupport; union _SLIST_HEADER InterruptObjectPool ; union _SLIST_HEADER HypercallPageList ; VOID* HypercallPageVirtual; VOID* VirtualApicAssist; ULONGLONG* StatisticsPage; VOID* RateControl; struct _CACHE_DESCRIPTOR Cache [5]; ULONG CacheCount; ULONG CacheProcessorMask[5 ]; struct _KAFFINITY_EX PackageProcessorSet ; ULONG PrcbPad91[1 ]; ULONG CoreProcessorSet; struct _KDPC TimerExpirationDpc ; ULONG SpinLockAcquireCount; ULONG SpinLockContentionCount; ULONG SpinLockSpinCount; ULONG IpiSendRequestBroadcastCount; ULONG IpiSendRequestRoutineCount; ULONG IpiSendSoftwareInterruptCount; ULONG ExInitializeResourceCount; ULONG ExReInitializeResourceCount; ULONG ExDeleteResourceCount; ULONG ExecutiveResourceAcquiresCount; ULONG ExecutiveResourceContentionsCount; ULONG ExecutiveResourceReleaseExclusiveCount; ULONG ExecutiveResourceReleaseSharedCount; ULONG ExecutiveResourceConvertsCount; ULONG ExAcqResExclusiveAttempts; ULONG ExAcqResExclusiveAcquiresExclusive; ULONG ExAcqResExclusiveAcquiresExclusiveRecursive; ULONG ExAcqResExclusiveWaits; ULONG ExAcqResExclusiveNotAcquires; ULONG ExAcqResSharedAttempts; ULONG ExAcqResSharedAcquiresExclusive; ULONG ExAcqResSharedAcquiresShared; ULONG ExAcqResSharedAcquiresSharedRecursive; ULONG ExAcqResSharedWaits; ULONG ExAcqResSharedNotAcquires; ULONG ExAcqResSharedStarveExclusiveAttempts; ULONG ExAcqResSharedStarveExclusiveAcquiresExclusive; ULONG ExAcqResSharedStarveExclusiveAcquiresShared; ULONG ExAcqResSharedStarveExclusiveAcquiresSharedRecursive; ULONG ExAcqResSharedStarveExclusiveWaits; ULONG ExAcqResSharedStarveExclusiveNotAcquires; ULONG ExAcqResSharedWaitForExclusiveAttempts; ULONG ExAcqResSharedWaitForExclusiveAcquiresExclusive; ULONG ExAcqResSharedWaitForExclusiveAcquiresShared; ULONG ExAcqResSharedWaitForExclusiveAcquiresSharedRecursive; ULONG ExAcqResSharedWaitForExclusiveWaits; ULONG ExAcqResSharedWaitForExclusiveNotAcquires; ULONG ExSetResOwnerPointerExclusive; ULONG ExSetResOwnerPointerSharedNew; ULONG ExSetResOwnerPointerSharedOld; ULONG ExTryToAcqExclusiveAttempts; ULONG ExTryToAcqExclusiveAcquires; ULONG ExBoostExclusiveOwner; ULONG ExBoostSharedOwners; ULONG ExEtwSynchTrackingNotificationsCount; ULONG ExEtwSynchTrackingNotificationsAccountedCount; struct _CONTEXT * Context ; ULONG ContextFlags; struct _XSAVE_AREA * ExtendedState ; };
进程相关 进程结构 Windows 进程的完整描述确实涉及 KPROCESS
、EPROCESS
、PEB
三个结构,它们分别代表:
KPROCESS
:进程的内核调度上下文(最底层、最精简)
EPROCESS
:内核中代表进程的主对象,包含完整管理信息(含 KPROCESS
)
PEB
:用户态结构,向 ntdll 和应用程序提供运行环境(仅用户可见)
由于 KPROCESS
是 EPROCESS
的第一个成员 Pcb
,因此 KPROCESS
和 EPROCESS
的地址是相同的。
WDK 中导入的一个全局变量 PsInitialSystemProcess
指向系统启动后创建的第一个进程(PID 4),即 System
进程。
1 extern NTKERNELAPI PEPROCESS PsInitialSystemProcess;
我们可以通过 PsGetCurrentProcess
可以获取到当前线程的 KPROCESS
地址,该函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 PsGetCurrentProcess proc near mov eax , fs :[KPCR. PrcbData. CurrentThread] mov eax , [eax +_KTHREAD. ApcState. Process] retn PsGetCurrentProcess endp
通过 PsGetProcessPeb
可以获取到用户态的 PEB
的地址。
1 2 3 4 PPEB PsGetProcessPeb (__in PEPROCESS Process) { return Process->Peb; }
KPROCESS KPROCESS
调度器层面的“进程调度结构体”,轻量,仅描述 CPU 调度相关属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 struct _KPROCESS { struct _DISPATCHER_HEADER Header ; struct _LIST_ENTRY ProfileListHead ; ULONG DirectoryTableBase; struct _KGDTENTRY LdtDescriptor ; struct _KIDTENTRY Int21Descriptor ; struct _LIST_ENTRY ThreadListHead ; ULONG ProcessLock; struct _KAFFINITY_EX Affinity ; struct _LIST_ENTRY ReadyListHead ; struct _SINGLE_LIST_ENTRY SwapListEntry ; volatile struct _KAFFINITY_EX ActiveProcessors ; union { struct { volatile LONG AutoAlignment:1 ; volatile LONG DisableBoost:1 ; volatile LONG DisableQuantum:1 ; volatile ULONG ActiveGroupsMask:1 ; volatile LONG ReservedFlags:28 ; }; volatile LONG ProcessFlags; }; CHAR BasePriority; CHAR QuantumReset; UCHAR Visited; UCHAR Unused3; ULONG ThreadSeed[1 ]; USHORT IdealNode[1 ]; USHORT IdealGlobalNode; union _KEXECUTE_OPTIONS Flags ; UCHAR Unused1; USHORT IopmOffset; ULONG Unused4; union _KSTACK_COUNT StackCount ; struct _LIST_ENTRY ProcessListEntry ; volatile ULONGLONG CycleTime; ULONG KernelTime; ULONG UserTime; VOID* VdmTrapcHandler; };
EPROCESS EPROCESS
是 Windows 内核中最核心的进程对象结构,管理进程生命周期、对象句柄、内存空间等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 struct _EPROCESS { struct _KPROCESS Pcb ; struct _EX_PUSH_LOCK ProcessLock ; union _LARGE_INTEGER CreateTime ; union _LARGE_INTEGER ExitTime ; struct _EX_RUNDOWN_REF RundownProtect ; VOID* UniqueProcessId; struct _LIST_ENTRY ActiveProcessLinks ; ULONG ProcessQuotaUsage[2 ]; ULONG ProcessQuotaPeak[2 ]; volatile ULONG CommitCharge; struct _EPROCESS_QUOTA_BLOCK * QuotaBlock ; struct _PS_CPU_QUOTA_BLOCK * CpuQuotaBlock ; ULONG PeakVirtualSize; ULONG VirtualSize; struct _LIST_ENTRY SessionProcessLinks ; VOID* DebugPort; union { VOID* ExceptionPortData; ULONG ExceptionPortValue; ULONG ExceptionPortState:3 ; }; struct _HANDLE_TABLE * ObjectTable ; struct _EX_FAST_REF Token ; ULONG WorkingSetPage; struct _EX_PUSH_LOCK AddressCreationLock ; struct _ETHREAD * RotateInProgress ; struct _ETHREAD * ForkInProgress ; ULONG HardwareTrigger; struct _MM_AVL_TABLE * PhysicalVadRoot ; VOID* CloneRoot; volatile ULONG NumberOfPrivatePages; volatile ULONG NumberOfLockedPages; VOID* Win32Process; struct _EJOB * volatile Job ; VOID* SectionObject; VOID* SectionBaseAddress; ULONG Cookie; ULONG Spare8; struct _PAGEFAULT_HISTORY * WorkingSetWatch ; VOID* Win32WindowStation; VOID* InheritedFromUniqueProcessId; VOID* LdtInformation; VOID* VdmObjects; ULONG ConsoleHostProcess; VOID* DeviceMap; VOID* EtwDataSource; VOID* FreeTebHint; union { struct _HARDWARE_PTE PageDirectoryPte ; ULONGLONG Filler; }; VOID* Session; UCHAR ImageFileName[15 ]; UCHAR PriorityClass; struct _LIST_ENTRY JobLinks ; VOID* LockedPagesList; struct _LIST_ENTRY ThreadListHead ; VOID* SecurityPort; VOID* PaeTop; volatile ULONG ActiveThreads; ULONG ImagePathHash; ULONG DefaultHardErrorProcessing; LONG LastThreadExitStatus; struct _PEB * Peb ; struct _EX_FAST_REF PrefetchTrace ; union _LARGE_INTEGER ReadOperationCount ; union _LARGE_INTEGER WriteOperationCount ; union _LARGE_INTEGER OtherOperationCount ; union _LARGE_INTEGER ReadTransferCount ; union _LARGE_INTEGER WriteTransferCount ; union _LARGE_INTEGER OtherTransferCount ; ULONG CommitChargeLimit; volatile ULONG CommitChargePeak; VOID* AweInfo; struct _SE_AUDIT_PROCESS_CREATION_INFO SeAuditProcessCreationInfo ; struct _MMSUPPORT Vm ; struct _LIST_ENTRY MmProcessLinks ; VOID* HighestUserAddress; ULONG ModifiedPageCount; union { ULONG Flags2; struct { ULONG JobNotReallyActive:1 ; ULONG AccountingFolded:1 ; ULONG NewProcessReported:1 ; ULONG ExitProcessReported:1 ; ULONG ReportCommitChanges:1 ; ULONG LastReportMemory:1 ; ULONG ReportPhysicalPageChanges:1 ; ULONG HandleTableRundown:1 ; ULONG NeedsHandleRundown:1 ; ULONG RefTraceEnabled:1 ; ULONG NumaAware:1 ; ULONG ProtectedProcess:1 ; ULONG DefaultPagePriority:3 ; ULONG PrimaryTokenFrozen:1 ; ULONG ProcessVerifierTarget:1 ; ULONG StackRandomizationDisabled:1 ; ULONG AffinityPermanent:1 ; ULONG AffinityUpdateEnable:1 ; ULONG PropagateNode:1 ; ULONG ExplicitAffinity:1 ; }; }; union { ULONG Flags; struct { ULONG CreateReported:1 ; ULONG NoDebugInherit:1 ; ULONG ProcessExiting:1 ; ULONG ProcessDelete:1 ; ULONG Wow64SplitPages:1 ; ULONG VmDeleted:1 ; ULONG OutswapEnabled:1 ; ULONG Outswapped:1 ; ULONG ForkFailed:1 ; ULONG Wow64VaSpace4Gb:1 ; ULONG AddressSpaceInitialized:2 ; ULONG SetTimerResolution:1 ; ULONG BreakOnTermination:1 ; ULONG DeprioritizeViews:1 ; ULONG WriteWatch:1 ; ULONG ProcessInSession:1 ; ULONG OverrideAddressSpace:1 ; ULONG HasAddressSpace:1 ; ULONG LaunchPrefetched:1 ; ULONG InjectInpageErrors:1 ; ULONG VmTopDown:1 ; ULONG ImageNotifyDone:1 ; ULONG PdeUpdateNeeded:1 ; ULONG VdmAllowed:1 ; ULONG CrossSessionCreate:1 ; ULONG ProcessInserted:1 ; ULONG DefaultIoPriority:3 ; ULONG ProcessSelfDelete:1 ; ULONG SetTimerResolutionLink:1 ; }; }; LONG ExitStatus; struct _MM_AVL_TABLE VadRoot ; struct _ALPC_PROCESS_CONTEXT AlpcContext ; struct _LIST_ENTRY TimerResolutionLink ; ULONG RequestedTimerResolution; ULONG ActiveThreadsHighWatermark; ULONG SmallestTimerResolution; struct _PO_DIAG_STACK_RECORD * TimerResolutionStackRecord ; };
PEB PEB
仅存在于用户模式,描述当前进程的用户态环境,如模块、堆、TLS、参数等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 struct _PEB { UCHAR InheritedAddressSpace; UCHAR ReadImageFileExecOptions; UCHAR BeingDebugged; union { UCHAR BitField; struct { UCHAR ImageUsesLargePages:1 ; UCHAR IsProtectedProcess:1 ; UCHAR IsLegacyProcess:1 ; UCHAR IsImageDynamicallyRelocated:1 ; UCHAR SkipPatchingUser32Forwarders:1 ; UCHAR SpareBits:3 ; }; }; VOID* Mutant; VOID* ImageBaseAddress; struct _PEB_LDR_DATA * Ldr ; struct _RTL_USER_PROCESS_PARAMETERS * ProcessParameters ; VOID* SubSystemData; VOID* ProcessHeap; struct _RTL_CRITICAL_SECTION * FastPebLock ; VOID* AtlThunkSListPtr; VOID* IFEOKey; union { ULONG CrossProcessFlags; struct { ULONG ProcessInJob:1 ; ULONG ProcessInitializing:1 ; ULONG ProcessUsingVEH:1 ; ULONG ProcessUsingVCH:1 ; ULONG ProcessUsingFTH:1 ; ULONG ReservedBits0:27 ; }; }; union { VOID* KernelCallbackTable; VOID* UserSharedInfoPtr; }; ULONG SystemReserved[1 ]; ULONG AtlThunkSListPtr32; VOID* ApiSetMap; ULONG TlsExpansionCounter; VOID* TlsBitmap; ULONG TlsBitmapBits[2 ]; VOID* ReadOnlySharedMemoryBase; VOID* HotpatchInformation; VOID** ReadOnlyStaticServerData; VOID* AnsiCodePageData; VOID* OemCodePageData; VOID* UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; union _LARGE_INTEGER CriticalSectionTimeout ; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; VOID** ProcessHeaps; VOID* GdiSharedHandleTable; VOID* ProcessStarterHelper; ULONG GdiDCAttributeList; struct _RTL_CRITICAL_SECTION * LoaderLock ; ULONG OSMajorVersion; ULONG OSMinorVersion; USHORT OSBuildNumber; USHORT OSCSDVersion; ULONG OSPlatformId; ULONG ImageSubsystem; ULONG ImageSubsystemMajorVersion; ULONG ImageSubsystemMinorVersion; ULONG ActiveProcessAffinityMask; ULONG GdiHandleBuffer[34 ]; VOID (*PostProcessInitRoutine)(); VOID* TlsExpansionBitmap; ULONG TlsExpansionBitmapBits[32 ]; ULONG SessionId; union _ULARGE_INTEGER AppCompatFlags ; union _ULARGE_INTEGER AppCompatFlagsUser ; VOID* pShimData; VOID* AppCompatInfo; struct _UNICODE_STRING CSDVersion ; struct _ACTIVATION_CONTEXT_DATA * ActivationContextData ; struct _ASSEMBLY_STORAGE_MAP * ProcessAssemblyStorageMap ; struct _ACTIVATION_CONTEXT_DATA * SystemDefaultActivationContextData ; struct _ASSEMBLY_STORAGE_MAP * SystemAssemblyStorageMap ; ULONG MinimumStackCommit; struct _FLS_CALLBACK_INFO * FlsCallback ; struct _LIST_ENTRY FlsListHead ; VOID* FlsBitmap; ULONG FlsBitmapBits[4 ]; ULONG FlsHighIndex; VOID* WerRegistrationData; VOID* WerShipAssertPtr; VOID* pContextData; VOID* pImageHeaderHash; union { ULONG TracingFlags; struct { ULONG HeapTracingEnabled:1 ; ULONG CritSecTracingEnabled:1 ; ULONG SpareTracingBits:30 ; }; }; };
进程防护技巧 进程查找 这里主要是根据进程名查找进程对象 EPROCESS
,这是通常进行进程保护的第一步。
遍历进程链表 EPROCESS
的 ActiveProcessLinks
字段表示当前活动的进程的 EPROCESS
构成的双向链表,我们可以遍历这个双向链表来查找指定进程名对应的 EPROCESS
。
然而 EPROCESS
的 ActiveProcessLinks
字段在不同版本的操作系统中的偏移不同,一种思路是通过 PsGetProcessId
函数来定位 UniqueProcessId
字段。
对于 32 位我们只需要搜索 8B 80
然后取后面的 4 字节即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 _PsGetProcessId@4 proc near mov edi , edi push ebp mov ebp , esp mov eax , [ebp +Process] mov eax , [eax +_EPROCESS. UniqueProcessId] pop ebp retn 4 _PsGetProcessId@4 endp
对于 64 位我们只需要取 PsGetProcessId
后面 3 字节偏移位置的 4 字节即可。
1 2 3 4 5 6 7 8 PsGetProcessId proc near mov rax , [rcx +_EPROCESS. UniqueProcessId] retn PsGetProcessId endp
由于 UniqueProcessId
与 ActiveProcessLinks
相邻,因此可以定位到 ActiveProcessLinks
字段的偏移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 ULONG GetUniqueProcessIdOffset () { #ifdef _WIN64 ULONG offset = *(ULONG*)((PUCHAR)PsGetProcessId + 3 ); return offset; #else PUCHAR base = (PUCHAR)PsGetProcessId; for (int i = 0 ; i < 0x40 ; i++) { PUCHAR p = base + i; if (p[0 ] == 0x8B && p[1 ] == 0x80 ) { ULONG pidOffset = *(ULONG*)(p + 2 ); if (pidOffset >= 0x80 && pidOffset < 0x1000 ) { return pidOffset; } } } return (ULONG)-1 ; #endif } ULONG DetectActiveProcessLinksOffset () { ULONG UniqueProcessIdOffset = GetUniqueProcessIdOffset(); if (UniqueProcessIdOffset == (ULONG)-1 ) { return (ULONG)-1 ; } return UniqueProcessIdOffset + sizeof (void *); }
之后就是链表遍历的过程。
SeLocateProcessImageName
通过 EPROCESS
的 SeAuditProcessCreationInfo
字段可以获取完整映像路径,要想与查找的进程名进行需要提取映像路径中的文件名。
提取文件名需要从后向前查找第一个 L'\\'
字符然后取后面的字符串。
wcsrchr
需要以 L'\0'
结尾的字符串,但 UNICODE_STRING.Buffer
只是个 定长缓存,不一定以 L'\0'
结尾 ,因此需要手动向后遍历查找 L'\\'
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <ntddk.h> #include <ntstrsafe.h> PEPROCESS FindProcessByNameW (PCWSTR targetName) { ULONG activeLinksOffset = DetectActiveProcessLinksOffset(); if (activeLinksOffset == (ULONG)-1 ) { DbgPrint("[-] Failed to detect ActiveProcessLinks offset.\n" ); return NULL ; } PEPROCESS systemProcess = PsInitialSystemProcess; PLIST_ENTRY head = (PLIST_ENTRY)((PUCHAR)systemProcess + activeLinksOffset); PLIST_ENTRY entry = head; UNICODE_STRING target; RtlInitUnicodeString(&target, targetName); do { PEPROCESS process = (PEPROCESS)((PUCHAR)entry - activeLinksOffset); UNICODE_STRING *imagePath = NULL ; NTSTATUS status = SeLocateProcessImageName(process, &imagePath); if (NT_SUCCESS(status) && imagePath && imagePath->Buffer) { USHORT length = imagePath->Length / sizeof (WCHAR); USHORT offset = 0 ; for (USHORT i = length; i > 0 ; i--) { if (imagePath->Buffer[i - 1 ] == L'\\' ) { offset = i; break ; } } UNICODE_STRING fileName = { .Buffer = &imagePath->Buffer[offset], .Length = (length - offset) * sizeof (WCHAR), .MaximumLength = (length - offset) * sizeof (WCHAR) }; if (RtlEqualUnicodeString(&fileName, &target, TRUE)) { ExFreePool(imagePath); return process; } ExFreePool(imagePath); } entry = entry->Flink; } while (entry != head && entry != NULL ); return NULL ; }
枚举进程 Id 在某些场景下(如内核 Rootkit),ActiveProcessLinks
链表可能被恶意修改(如断链)以隐藏进程。这种情况下,仅靠 ActiveProcessLinks
遍历将无法发现目标进程。
Windows 为所有活动进程维护了系统对象句柄表。PsLookupProcessByProcessId
会通过这些内核内部机制而不是链表查找 EPROCESS
,因此仍能定位被“断链”隐藏的进程。
进程 ID 通常是 4 的倍数,递增步长为 4。这是因为进程 ID 是句柄,类型为 EXHANDLE
,最低 2 位是保留位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <ntifs.h> #include <ntstrsafe.h> static BOOLEAN ExtractFileNameFromPath (PUNICODE_STRING fullPath, UNICODE_STRING* fileName) { if (!fullPath || !fullPath->Buffer || fullPath->Length == 0 ) return FALSE; PWCH start = fullPath->Buffer; USHORT length = fullPath->Length / sizeof (WCHAR); USHORT i; for (i = length; i > 0 ; i--) { if (fullPath->Buffer[i - 1 ] == L'\\' ) { break ; } } fileName->Buffer = &fullPath->Buffer[i]; fileName->Length = (length - i) * sizeof (WCHAR); fileName->MaximumLength = fileName->Length; return TRUE; } PEPROCESS FindProcessByNameW (PCWSTR targetName) { if (!targetName) return NULL ; UNICODE_STRING targetStr; RtlInitUnicodeString(&targetStr, targetName); for (ULONG_PTR pid = 4 ; pid < 0x100000 ; pid += 4 ) { PEPROCESS tempProcess = NULL ; if (!NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)pid, &tempProcess))) continue ; if (PsGetProcessExitStatus(tempProcess) != STATUS_PENDING) { ObDereferenceObject(tempProcess); continue ; } PUNICODE_STRING imagePath = NULL ; if (!NT_SUCCESS(SeLocateProcessImageName(tempProcess, &imagePath)) || !imagePath) { ObDereferenceObject(tempProcess); continue ; } UNICODE_STRING exeName; if (ExtractFileNameFromPath(imagePath, &exeName)) { if (RtlCompareUnicodeString(&exeName, &targetStr, TRUE) == 0 ) { ExFreePool(imagePath); return tempProcess; } } ExFreePool(imagePath); ObDereferenceObject(tempProcess); } return NULL ; }
Windows 内核中的 PEPROCESS
是一种引用计数对象。当调用 PsLookupProcessByProcessId
时系统会对该进程对象 引用计数 +1 。这个引用是你“拥有”的,意味着你必须在用完后 调用 ObDereferenceObject
来 减少引用计数 ,否则会导致内核对象泄漏(内核内存无法释放,进程无法完全终止)。
1 2 3 4 5 6 7 8 PEPROCESS proc = FindProcessByNameW(L"notepad.exe" ); if (proc) { DbgPrint("找到进程: %p\n" , proc); ObDereferenceObject(proc); } else { DbgPrint("未找到目标进程\n" ); }
进程隐藏 进程断链 Windows 操作系统通过双向链表来管理所有运行中的进程,进程之间通过 ActiveProcessLinks
字段相互连接。通过从该链表中移除目标进程,可以实现进程隐藏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <ntddk.h> #include <ntstrsafe.h> VOID HideProcessByNameW (PCWSTR targetName) { PEPROCESS targetProcess = FindProcessByNameW(targetName); if (!targetProcess) { DbgPrint("[-] Process not found.\n" ); return ; } ULONG activeLinksOffset = DetectActiveProcessLinksOffset(); if (activeLinksOffset == (ULONG)-1 ) { DbgPrint("[-] Failed to detect ActiveProcessLinks offset.\n" ); return ; } PLIST_ENTRY activeLinks = (PLIST_ENTRY)((PUCHAR)targetProcess + activeLinksOffset); RemoveEntryList(activeLinks); InitializeListHead(activeLinks); DbgPrint("[+] Successfully hid process %ws\n" , targetName); }
修改进程 ID 在某些情况下,修改进程的 PID
(进程 ID)可能是实现进程隐藏的另一个重要步骤。通过修改 EPROCESS
结构中的 UniqueProcessId
字段,可以伪造进程的 ID,使得它在系统中的任务管理器中无法正常找到被隐藏进程。
由于部分操作系统的进程管理器的实现问题,PID
重复会导致其显示出错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 VOID ChangeProcessIdByNameW (PCWSTR targetName, ULONG newPid) { PEPROCESS targetProcess = FindProcessByNameW(targetName); if (!targetProcess) { DbgPrint("[-] Process not found.\n" ); return ; } ULONG pidOffset = GetUniqueProcessIdOffset(); if (pidOffset == (ULONG)-1 ) { DbgPrint("[-] Failed to detect UniqueProcessId offset.\n" ); return ; } ULONG* pidAddr = (ULONG*)((PUCHAR)targetProcess + pidOffset); *pidAddr = newPid; DbgPrint("[+] Successfully changed process %ws PID to %lu\n" , targetName, newPid); }
进程保护 BreakOnTermination (Flags bit13) BreakOnTermination
是一种机制,在进程即将被终止时触发断点。通常用于调试进程的终止过程,帮助开发人员在进程即将结束时进行故障排除或保存重要信息。
如果该标志位置为 1,则当其他进程终止被保护的进程时,系统会触发蓝屏。这种保护机制用于防止重要进程的意外终止,通过检测进程结束时是否存在其他进程的干扰,如果发现不当操作,系统会抛出异常,进而引发蓝屏,以确保系统的完整性和安全性。
ProcessInserted (Flags bit26) 如果 ProcessInserted
标志位设置为 0,则当其他进程尝试打开受保护进程时,它们可以成功获取该进程的句柄,但无法将该句柄插入到自身进程的句柄表中。最终的结果是,该进程无法被正常操作,即使可以获得句柄,仍然无法进行操作(如调试、修改等)。
此标志位用于限制对进程句柄的访问,确保在某些情况下,其他进程无法通过获得句柄对受保护进程进行修改或干预。常见的应用场景包括防止恶意软件或不当操作干扰受保护进程的运行。
ProcessInserted
设置为 0,进程自身也将无法创建线程、访问或操作其他句柄,甚至可能无法执行对自身的管理操作。具体来说,创建新线程、分配内存、访问文件句柄等操作将受到影响,导致进程无法正常执行这些任务。
ProtectedProcess (Flags2 bit11) ProtectedProcess
是一种通过在操作系统中标记进程来防止其被结束或修改的技术。它可以确保某些关键进程(如操作系统服务或反病毒进程)不被恶意软件或未经授权的操作干扰。
ProtectedProcess
标志与 CreateProcess
函数中的 dwCreationFlags
参数中的 CREATE_PROTECTED_PROCESS
标志位相关。使用 CREATE_PROTECTED_PROCESS
启动的进程会被标记为受保护进程。然而,要使进程成为受保护进程,二进制文件必须具有由 Microsoft 提供的特殊签名。当前,只有 Microsoft 提供的进程和二进制文件才能享受此保护,非 Microsoft 的二进制文件无法直接成为受保护进程。
此标志机制通过严格限制对受保护进程的访问,防止它们被外部操作(如终止、调试或挂起)影响,通常用于保护系统的关键进程或高安全性进程,以保障系统的稳定性和安全性。
线程相关 线程结构 KTHREAD 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 struct _KTHREAD { struct _DISPATCHER_HEADER Header ; volatile ULONGLONG CycleTime; volatile ULONG HighCycleTime; ULONGLONG QuantumTarget; VOID* InitialStack; VOID* volatile StackLimit; VOID* KernelStack; ULONG ThreadLock; union _KWAIT_STATUS_REGISTER WaitRegister ; volatile UCHAR Running; UCHAR Alerted[2 ]; union { struct { ULONG KernelStackResident:1 ; ULONG ReadyTransition:1 ; ULONG ProcessReadyQueue:1 ; ULONG WaitNext:1 ; ULONG SystemAffinityActive:1 ; ULONG Alertable:1 ; ULONG GdiFlushActive:1 ; ULONG UserStackWalkActive:1 ; ULONG ApcInterruptRequest:1 ; ULONG ForceDeferSchedule:1 ; ULONG QuantumEndMigrate:1 ; ULONG UmsDirectedSwitchEnable:1 ; ULONG TimerActive:1 ; ULONG SystemThread:1 ; ULONG Reserved:18 ; }; LONG MiscFlags; }; union { struct _KAPC_STATE ApcState ; struct { UCHAR ApcStateFill[23 ]; CHAR Priority; }; }; volatile ULONG NextProcessor; volatile ULONG DeferredProcessor; ULONG ApcQueueLock; ULONG ContextSwitches; volatile UCHAR State; CHAR NpxState; UCHAR WaitIrql; CHAR WaitMode; volatile LONG WaitStatus; struct _KWAIT_BLOCK * WaitBlockList ; union { struct _LIST_ENTRY WaitListEntry ; struct _SINGLE_LIST_ENTRY SwapListEntry ; }; struct _KQUEUE * volatile Queue ; ULONG WaitTime; union { struct { SHORT KernelApcDisable; SHORT SpecialApcDisable; }; ULONG CombinedApcDisable; }; VOID* Teb; struct _KTIMER Timer ; union { struct { ULONG AutoAlignment:1 ; ULONG DisableBoost:1 ; ULONG EtwStackTraceApc1Inserted:1 ; ULONG EtwStackTraceApc2Inserted:1 ; ULONG CalloutActive:1 ; ULONG ApcQueueable:1 ; ULONG EnableStackSwap:1 ; ULONG GuiThread:1 ; ULONG UmsPerformingSyscall:1 ; ULONG VdmSafe:1 ; ULONG UmsDispatched:1 ; ULONG ReservedFlags:21 ; }; volatile LONG ThreadFlags; }; VOID* ServiceTable; struct _KWAIT_BLOCK WaitBlock [4]; struct _LIST_ENTRY QueueListEntry ; struct _KTRAP_FRAME * TrapFrame ; VOID* FirstArgument; union { VOID* CallbackStack; ULONG CallbackDepth; }; UCHAR ApcStateIndex; CHAR BasePriority; union { CHAR PriorityDecrement; struct { UCHAR ForegroundBoost : 4 ; UCHAR UnusualBoost : 4 ; }; }; UCHAR Preempted; UCHAR AdjustReason; CHAR AdjustIncrement; CHAR PreviousMode; CHAR Saturation; ULONG SystemCallNumber; ULONG FreezeCount; volatile struct _GROUP_AFFINITY UserAffinity ; struct _KPROCESS * Process ; volatile struct _GROUP_AFFINITY Affinity ; ULONG IdealProcessor; ULONG UserIdealProcessor; struct _KAPC_STATE * ApcStatePointer [2]; union { struct _KAPC_STATE SavedApcState ; struct { UCHAR SavedApcStateFill[23 ]; UCHAR WaitReason; }; }; CHAR SuspendCount; CHAR Spare1; UCHAR OtherPlatformFill; VOID* volatile Win32Thread; VOID* StackBase; union { struct _KAPC SuspendApc ; struct { UCHAR SuspendApcFill0[1 ]; UCHAR ResourceIndex; }; struct { UCHAR SuspendApcFill1[3 ]; UCHAR QuantumReset; }; struct { UCHAR SuspendApcFill2[4 ]; ULONG KernelTime; }; struct { UCHAR SuspendApcFill3[36 ]; struct _KPRCB * volatile WaitPrcb ; }; struct { UCHAR SuspendApcFill4[40 ]; VOID* LegoData; }; struct { UCHAR SuspendApcFill5[47 ]; UCHAR LargeStack; }; }; ULONG UserTime; union { struct _KSEMAPHORE SuspendSemaphore ; UCHAR SuspendSemaphorefill[20 ]; }; ULONG SListFaultCount; struct _LIST_ENTRY ThreadListEntry ; struct _LIST_ENTRY MutantListHead ; VOID* SListFaultAddress; struct _KTHREAD_COUNTERS * ThreadCounters ; struct _XSTATE_SAVE * XStateSave ; };
ETHREAD 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 struct _ETHREAD { struct _KTHREAD Tcb ; union _LARGE_INTEGER CreateTime ; union { union _LARGE_INTEGER ExitTime ; struct _LIST_ENTRY KeyedWaitChain ; }; LONG ExitStatus; union { struct _LIST_ENTRY PostBlockList ; struct { VOID* ForwardLinkShadow; VOID* StartAddress; }; }; union { struct _TERMINATION_PORT * TerminationPort ; struct _ETHREAD * ReaperLink ; VOID* KeyedWaitValue; }; ULONG ActiveTimerListLock; struct _LIST_ENTRY ActiveTimerListHead ; struct _CLIENT_ID Cid ; union { struct _KSEMAPHORE KeyedWaitSemaphore ; struct _KSEMAPHORE AlpcWaitSemaphore ; }; union _PS_CLIENT_SECURITY_CONTEXT ClientSecurity ; struct _LIST_ENTRY IrpList ; ULONG TopLevelIrp; struct _DEVICE_OBJECT * DeviceToVerify ; union _PSP_CPU_QUOTA_APC * CpuQuotaApc ; VOID* Win32StartAddress; VOID* LegacyPowerObject; struct _LIST_ENTRY ThreadListEntry ; struct _EX_RUNDOWN_REF RundownProtect ; struct _EX_PUSH_LOCK ThreadLock ; ULONG ReadClusterSize; volatile LONG MmLockOrdering; union { ULONG CrossThreadFlags; struct { ULONG Terminated : 1 ; ULONG ThreadInserted : 1 ; ULONG HideFromDebugger : 1 ; ULONG ActiveImpersonationInfo : 1 ; ULONG Reserved : 1 ; ULONG HardErrorsAreDisabled : 1 ; ULONG BreakOnTermination : 1 ; ULONG SkipCreationMsg : 1 ; ULONG SkipTerminationMsg : 1 ; ULONG CopyTokenOnOpen : 1 ; ULONG ThreadIoPriority : 3 ; ULONG ThreadPagePriority : 3 ; ULONG RundownFail : 1 ; ULONG NeedsWorkingSetAging : 1 ; }; }; union { ULONG SameThreadPassiveFlags; struct { ULONG ActiveExWorker : 1 ; ULONG ExWorkerCanWaitUser : 1 ; ULONG MemoryMaker : 1 ; ULONG ClonedThread : 1 ; ULONG KeyedEventInUse : 1 ; ULONG RateApcState : 2 ; ULONG SelfTerminate : 1 ; }; }; union { ULONG SameThreadApcFlags; struct { UCHAR Spare : 1 ; volatile UCHAR StartAddressInvalid : 1 ; UCHAR EtwPageFaultCalloutActive : 1 ; UCHAR OwnsProcessWorkingSetExclusive : 1 ; UCHAR OwnsProcessWorkingSetShared : 1 ; UCHAR OwnsSystemCacheWorkingSetExclusive : 1 ; UCHAR OwnsSystemCacheWorkingSetShared : 1 ; UCHAR OwnsSessionWorkingSetExclusive : 1 ; UCHAR OwnsSessionWorkingSetShared : 1 ; UCHAR OwnsProcessAddressSpaceExclusive : 1 ; UCHAR OwnsProcessAddressSpaceShared : 1 ; UCHAR SuppressSymbolLoad : 1 ; UCHAR Prefetching : 1 ; UCHAR OwnsDynamicMemoryShared : 1 ; UCHAR OwnsChangeControlAreaExclusive : 1 ; UCHAR OwnsChangeControlAreaShared : 1 ; UCHAR OwnsPagedPoolWorkingSetExclusive : 1 ; UCHAR OwnsPagedPoolWorkingSetShared : 1 ; UCHAR OwnsSystemPtesWorkingSetExclusive : 1 ; UCHAR OwnsSystemPtesWorkingSetShared : 1 ; UCHAR TrimTrigger : 2 ; UCHAR Spare1 : 2 ; UCHAR PriorityRegionActive; }; }; UCHAR CacheManagerActive; UCHAR DisablePageFaultClustering; UCHAR ActiveFaultCount; UCHAR LockOrderState; ULONG AlpcMessageId; union { VOID* AlpcMessage; ULONG AlpcReceiveAttributeSet; }; struct _LIST_ENTRY AlpcWaitListEntry ; ULONG CacheManagerCount; ULONG IoBoostCount; ULONG IrpListLock; VOID* ReservedForSynchTracking; struct _SINGLE_LIST_ENTRY CmCallbackListHead ; };
TEB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 struct _TEB { struct _NT_TIB NtTib ; VOID* EnvironmentPointer; struct _CLIENT_ID ClientId ; VOID* ActiveRpcHandle; VOID* ThreadLocalStoragePointer; struct _PEB * ProcessEnvironmentBlock ; ULONG LastErrorValue; ULONG CountOfOwnedCriticalSections; VOID* CsrClientThread; VOID* Win32ThreadInfo; ULONG User32Reserved[26 ]; ULONG UserReserved[5 ]; VOID* WOW32Reserved; ULONG CurrentLocale; ULONG FpSoftwareStatusRegister; VOID* SystemReserved1[54 ]; LONG ExceptionCode; struct _ACTIVATION_CONTEXT_STACK * ActivationContextStackPointer ; UCHAR SpareBytes[36 ]; ULONG TxFsContext; struct _GDI_TEB_BATCH GdiTebBatch ; struct _CLIENT_ID RealClientId ; VOID* GdiCachedProcessHandle; ULONG GdiClientPID; ULONG GdiClientTID; VOID* GdiThreadLocalInfo; ULONG Win32ClientInfo[62 ]; VOID* glDispatchTable[233 ]; ULONG glReserved1[29 ]; VOID* glReserved2; VOID* glSectionInfo; VOID* glSection; VOID* glTable; VOID* glCurrentRC; VOID* glContext; ULONG LastStatusValue; struct _UNICODE_STRING StaticUnicodeString ; WCHAR StaticUnicodeBuffer[261 ]; VOID* DeallocationStack; VOID* TlsSlots[64 ]; struct _LIST_ENTRY TlsLinks ; VOID* Vdm; VOID* ReservedForNtRpc; VOID* DbgSsReserved[2 ]; ULONG HardErrorMode; VOID* Instrumentation[9 ]; struct _GUID ActivityId ; VOID* SubProcessTag; VOID* EtwLocalData; VOID* EtwTraceData; VOID* WinSockData; ULONG GdiBatchCount; union { struct _PROCESSOR_NUMBER CurrentIdealProcessor ; ULONG IdealProcessorValue; struct { UCHAR ReservedPad0; UCHAR ReservedPad1; UCHAR ReservedPad2; UCHAR IdealProcessor; }; }; ULONG GuaranteedStackBytes; VOID* ReservedForPerf; VOID* ReservedForOle; ULONG WaitingOnLoaderLock; VOID* SavedPriorityState; ULONG SoftPatchPtr1; VOID* ThreadPoolData; VOID** TlsExpansionSlots; ULONG MuiGeneration; ULONG IsImpersonating; VOID* NlsCache; VOID* pShimData; ULONG HeapVirtualAffinity; VOID* CurrentTransactionHandle; struct _TEB_ACTIVE_FRAME * ActiveFrame ; VOID* FlsData; VOID* PreferredLanguages; VOID* UserPrefLanguages; VOID* MergedPrefLanguages; ULONG MuiImpersonation; union { volatile USHORT CrossTebFlags; USHORT SpareCrossTebBits:16 ; }; union { USHORT SameTebFlags; struct { USHORT SafeThunkCall:1 ; USHORT InDebugPrint:1 ; USHORT HasFiberData:1 ; USHORT SkipThreadAttach:1 ; USHORT WerInShipAssertCode:1 ; USHORT RanProcessInit:1 ; USHORT ClonedThread:1 ; USHORT SuppressDebugMsg:1 ; USHORT DisableUserStackWalk:1 ; USHORT RtlExceptionAttached:1 ; USHORT InitialThread:1 ; USHORT SpareSameTebBits:5 ; }; }; VOID* TxnScopeEnterCallback; VOID* TxnScopeExitCallback; VOID* TxnScopeContext; ULONG LockCount; ULONG SpareUlong0; VOID* ResourceRetValue; };
线程切换 线程切换(Thread Context Switch) 是指操作系统将 CPU 执行权从一个线程转移到另一个线程的过程。这个过程中,系统需要保存当前线程的上下文 (CPU 寄存器、程序计数器、栈指针等),并恢复另一个线程的上下文 ,使其可以从上次停止的位置继续执行。
在 Windows 内核中,线程对象由结构体 _KTHREAD
表示,其 State
字段(类型为 KTHREAD_STATE
)用于标识线程当前所处的调度阶段。该状态直接影响调度器是否会选择该线程参与运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef enum _KTHREAD_STATE { Initialized, Ready, Running, Standby, Terminated, Waiting, Transition, DeferredReady, GateWait } KTHREAD_STATE;
Windows 的线程切换可以分为两种主要类型:
抢占式线程切换(Preemptive Context Switch) :由系统调度器(Scheduler)主动发起,强制当前线程让出 CPU。这个过程非线程自身控制,可能在任意用户态或内核态发生。常见的触发过程如下:
当前线程的时间片耗尽(Quantum 用尽);
有更高优先级的线程进入 Ready 状态;
电源管理事件(如睡眠/唤醒);
核心调度器策略判断需换出当前线程。
协作式线程切换(Voluntary / Cooperative Context Switch) :线程自身调用系统服务(如阻塞调用)主动放弃 CPU 使用权。这种情况是线程主动调用内核 API 完成,对于 UI 线程、I/O 密集型线程非常常见。常见的触发方式包括:
调用休眠或等待类 API,例如 Sleep()
、WaitForSingleObject()
、NtDelayExecution()
;
调用 KiSwapThread()
主动让出 CPU;
执行同步或阻塞型 I/O 操作(如 ReadFile()
,在数据尚未就绪时会导致线程阻塞)。
线程切换过程 不论是哪种线程切换方式,最终线程切换都是调用 KiSwapContext
函数完成。这个函数是 Windows 内核调度器中的线程上下文切换包装函数,具体作用是保存调用者环境,准备参数(OldThread / NewThread / WaitIrql / KPCR),调用核心的 _SwapContext
函数进行线程切换,然后恢复环境返回 。
由于这个函数内部发生了线程切换,因此从这个函数返回时的堆栈不一定是这个函数调用时的堆栈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @KiSwapContext@8 proc near sub esp , 10h mov [esp +10h -4 ], ebx mov [esp +10h -8 ], esi mov [esp +10h -0Ch ], edi mov [esp +10h -10h ], ebp mov ebx , large fs :_KPCR. SelfPcr mov edi , ecx mov esi , edx movzx ecx , [edi +_KTHREAD. WaitIrql] call _SwapContext@0 mov ebp , [esp +10h -10h ] mov edi , [esp +10h -0Ch ] mov esi , [esp +10h -8 ] mov ebx , [esp +10h -4 ] add esp , 10h retn @KiSwapContext@8 endp
_SwapContext
是线程切换的核心函数,我们主要关注其中的:
切换内核栈:ESP <=> KTHREAD.KernelStack
切换 CR3
:CR3 <=> KTHREAD.DirectoryTableBase
除此之外,由于 CPU 核心不是线程独占的,因此线程切换的时候还需要更新与 CPU 核心绑定的结构中跟新线程相关的信息:
更新 TSS
中的 ESP0
指向新内核栈的 TRAP_FRAME
尾部。
更新 KPCR
和 GDT 表中对应的 FS
段指向新线程的 TEB
。
更新 KPCR
中的异常链表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 _SwapContext@0 proc near CheckRunningFlag: cmp [esi +_ETHREAD. Tcb. Running], 0 jz SetRunning pause jmp CheckRunningFlag SetRunning: mov [esi +_ETHREAD. Tcb. Running], 1 push ecx cli dec [ebx +_KPCR. PrcbData. NestingLevel] rdtsc sub eax , [ebx +_KPCR. PrcbData. StartCycles] sbb edx , [ebx +_KPCR. PrcbData. StartCycles+4 ] add [ebx +_KPCR. PrcbData. CycleTime], eax adc [ebx +_KPCR. PrcbData. CycleTime+4 ], edx add [ebx +_KPCR. PrcbData. StartCycles], eax adc [ebx +_KPCR. PrcbData. StartCycles+4 ], edx test [esi +_KTHREAD. Header. ThreadControlFlags], 5 jnz HandleProfiling ContinueContextSwitch: sti inc [ebx +_KPCR. ContextSwitches] push [ebx +_KPCR. NtTib. ExceptionList] mov ebp , cr0 movsx eax , [edi +_KTHREAD. NpxState] test al , al jz SkipFpuSave mov ecx , [edi +_KTHREAD. InitialStack] test ebp , 0Eh jz SkipCr0Restore and ebp , 0FFFFFFF1h mov cr0 , ebp SkipCr0Restore: lea ecx , [ecx -210h ] cdq fxsave [ecx ] and [edi +_KTHREAD. NpxState], 0F8h mov [ebx +_KPCR. PrcbData. NpxThread], 0 SkipFpuSave: mov [edi +_KTHREAD. KernelStack], esp mov eax , [esi +_KTHREAD. InitialStack] mov esp , [esi +_KTHREAD. KernelStack] mov ebp , [esi +_KTHREAD. ApcState. Process] mov eax , [edi +_KTHREAD. ApcState. Process] cmp ebp , eax jz SkipCr3Switch mov ecx , [ebx +_KPCR. SetMemberCopy] lock xor [ebp +_KPROCESS. ActiveProcessors. Bitmap], ecx lock xor [eax +_KPROCESS. ActiveProcessors. Bitmap], ecx mov ecx , [ebp +_KPROCESS. LdtDescriptor. LimitLow] or ecx , [eax +_KPROCESS. LdtDescriptor. LimitLow] jnz SwitchToLdt mov eax , [ebp +_EPROCESS. Pcb. DirectoryTableBase] mov cr3 , eax SkipCr3Switch: mov ecx , [esi +_ETHREAD. Tcb. InitialStack] lea eax , [ecx -210h ] test byte ptr [eax -1Ah ], 2 jnz SetTss sub eax , 10h SetTss: mov edx , [ebx +_KPCR. TssCopy] mov [edx +_KTSS. Esp0], eax mov ax , [ebp +_KPROCESS. IopmOffset] mov [edx +_KTSS. IoMapBase], ax test ds :EtwLogFlags, 4 jnz LogEtwContextSwitch mov [edi +_ETHREAD. Tcb. Running], 0 xor eax , eax mov gs , eax movsx eax , [esi +_ETHREAD. Tcb. NpxState] mov ebp , cr0 and eax , 0FFFFFFF8h jz SkipXrstor test ebp , 0Eh jz RestoreFpu and ebp , 0FFFFFFF1h mov cr0 , ebp RestoreFpu: cdq xrstor byte ptr [ecx -210h ] SkipXrstor: mov edx , [ecx -14h ] test [esi +_ETHREAD. Tcb. NpxState], 7 jnz CheckFpuFlags or edx , ds :_NpxStateNotLoaded CheckFpuFlags: mov eax , ebp and eax , 0FFFFFFF1h or edx , eax cmp edx , ebp jnz ReloadCr0 mov ebp , [esi +_ETHREAD. Tcb. ApcState. Process] mov eax , [esi +_ETHREAD. Tcb. Teb] mov [ebx +_KPCR. Used_Self], eax mov ecx , [ebx +_KPCR. GDT] mov [ecx +3Ah ], ax shr eax , 10h mov [ecx +3Ch ], al mov [ecx +3Fh ], ah inc [esi +_ETHREAD. Tcb. ContextSwitches] pop [ebx +_KPCR. NtTib. ExceptionList] pop ecx cmp [ebx +_KPCR. PrcbData. DpcRoutineActive], 0 jnz FatalBugcheckDpc cmp [esi +_ETHREAD. Tcb. ApcState. KernelApcPending], 0 jnz DeliverKernelApc xor eax , eax retn
协作式线程切换 协作式线程切换即线程主动切换让出 CPU,主要通过 KiSwapThread
函数实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 LONG_PTR FASTCALL KiSwapThread ( IN PKTHREAD OldThread, IN PKPRCB CurrentPrcb ) { PKTHREAD NewThread; PKTHREAD IdleThread; KIRQL NewIrql; ULONGLONG Timestamp, Delta; BOOLEAN Success; LONG WaitStatus; if (Prcb->DeferredReadyListHead.Next != NULL ) { KiProcessThreadWaitList(Prcb, TRUE, FALSE, FALSE); } _disable(); PKTHREAD CurrentThread = Prcb->CurrentThread; Prcb->NestingLevel = 1 ; Timestamp = __rdtsc(); Delta = Timestamp - Prcb->StartCycles; CurrentThread->CycleTime.QuadPart += Delta; CurrentThread->HighCycleTime = CurrentThread->CycleTime.HighPart; if (CurrentThread->Header.CycleProfiling) { PsChargeProcessCpuCycles(Prcb, Delta, (ULONG)(Delta >> 32 )); } if (CurrentThread->Header.CounterProfiling) { KiEndCounterAccumulation(CurrentThread); } Prcb->StartCycles = Timestamp; _enable(); KiAcquirePrcbLock(Prcb); NewThread = KiSearchForNewThread(Prcb, FALSE); if (!NewThread) { NewThread = Prcb->NextThread; if (NewThread != NULL ) { Prcb->NextThread = NULL ; } else { NewThread = Prcb->IdleThread; } Prcb->CurrentThread = NewThread; NewThread->State = Running; } if (NewThread != Prcb->IdleThread && NewThread != OldKThread && NewThread->Running) { NewThread->State = Standby; Prcb->NextThread = NewThread; NewThread = Prcb->IdleThread; NewThread->State = Running; Prcb->CurrentThread = NewThread; } KiReleasePrcbLock(Prcb); NewIrql = OldKThread->WaitIrql; if (OldKThread == NewThread) { Success = FALSE; if (!NewThread->ApcState.KernelApcPending || NewThread->SpecialApcDisable != 0 || NewIrql != 0 ) { Success = FALSE; } else { Success = TRUE; } _disable(); PKTHREAD NewCurrent = Prcb->CurrentThread; ULONGLONG TscNow = __rdtsc(); ULONGLONG CpuCycle = TscNow - Prcb->StartCycles + Prcb->CycleTime; Prcb->CycleTime = CpuCycle; Prcb->HighCycleTime = (ULONG)(CpuCycle >> 32 ); Prcb->StartCycles = TscNow; if (NewCurrent->Header.CounterProfiling) { KiBeginCounterAccumulation(NewCurrent, FALSE); } Prcb->NestingLevel = 0 ; _enable(); if (EtwLogFlags & EVENT_TRACE_FLAG_DISPATCHER) { EtwTraceContextSwap(OldKThread, OldKThread); } } else { Success = KiSwapContext(OldKThread, NewThread); } WaitStatus = OldKThread->WaitStatus; if (OldKThread->MiscFlagsStruct.TimerActive && !KiCancelTimer(OldKThread->Timer, TRUE)) { OldKThread->WaitBlock[3 ].BlockState = BlockStateInactive; OldKThread->Timer.Header.WaitListHead.Flink = &OldKThread->WaitBlock[3 ].WaitListEntry; OldKThread->Timer.Header.WaitListHead.Blink = &OldKThread->WaitBlock[3 ].WaitListEntry; } if (Success) { KfLowerIrql(APC_LEVEL); KiDeliverApc(FALSE, NULL , NULL ); } KfLowerIrql(NewIrql); return WaitStatus; }
KiSwapThread
首先通过 KiSearchForNewThread
找到要切换到的线程,然后调用 KiSwapContext
完成线程切换。
KiSearchForNewThread
函数会选择一个最合适的线程来运行在当前处理器上 ,涉及 Windows 内核调度器的任务调度算法,在不同版本的具体实现不同。但是总的来说逻辑都是优先使用本地资源,必要时进行跨核抢占 。
下面这个版本代码的主要逻辑为:
[Step 1]:使用预选线程(Prcb->NextThread
)
若调度器之前已选中线程(例如抢占迁移时设置),直接切换该线程为当前线程;
设置线程状态为 Running
,并清除 NextThread
字段;
不进行任何就绪队列遍历,快速完成调度。
[Step 2]:本地就绪队列选择线程
调用 KiSelectReadyThread
从当前 PRCB 的本地 ReadyList
中选择优先级最高的线程;
若调度为 idle 类型(IsIdleSchedule == TRUE
):
清除本核心的 NotUsedCoreProcessor
位;
清除本组的 UsedCoreProcessor
中的 CoreProcessorSet
,表明该核心已被重新使用。
[Step 3]:本地无线程且非 idle 调度
若 IsIdleSchedule == FALSE
且未找到线程,说明当前核心资源空闲;
将当前核心在 NotUsedCoreProcessor
中置位,表示本核心目前未参与运行;
若该 PRCB 所属的所有核心都 idle(即 CoreProcessorSet
的全部位都在 NotUsedCoreProcessor
中):
将 CoreProcessorSet 添加到 UsedCoreProcessor
,允许其他节点迁移线程到该组。
[Step 4]:本地调度失败,释放 PRCB 锁
因后续需要访问其他处理器或 Node,为避免死锁,在继续之前先释放当前 PRCB
的锁。
[Step 5]:尝试公平调度(PsCpuFairShareEnabled
)
若启用了公平调度策略,则尝试从 idle-only 队列中拉取待执行线程;
若成功(通过 PsReleaseThreadFromIdleOnlyQueue
),则调度完成,返回该线程;
否则进入跨 NUMA 节点线程窃取阶段。
[Step 6]:NUMA 感知线程窃取
遍历所有与当前 PRCB 属于同一个 Group 的 NUMA 节点,查找活跃核心并尝试从其就绪队列中“抢线程”:
对每个目标节点:
构造跨核心调度掩码 ScanSet
(仅包含活跃核心);
遍历活跃核心,获取其对应的 TargetPrcb
;
加锁目标 PRCB
(按地址顺序双锁当前和目标 PRCB
);
若目标 PRCB
有 ReadySummary
,可尝试使用 KiFindReadyThread
抢占线程;
若抢占成功,将该线程设置为 Running
,并更新本地 PRCB
;
同时清除本核心的空闲标志。
若目标 PRCB
没有可抢线程,则解锁并尝试下一个 NUMA 节点或处理器。
如果在尝试过程中发现本地 Prcb->NextThread
被设置(例如远程迁移),优先使用它,并终止搜索。
[Step 7]:所有路径失败
若所有调度路径(本地、本组、NUMA、IdleOnly)均无可调度线程:
返回 NULL
,调度器进入 Idle 状态,当前核心维持空闲;
由 IdleThread
维持处理器活跃,等待下一次中断或调度请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 PKTHREAD KiSearchForNewThread ( IN PKPRCB Prcb, IN BOOLEAN IsIdleSchedule ) { PKTHREAD Thread = Prcb->NextThread; if (Thread) { Prcb->NextThread = NULL ; Prcb->CurrentThread = Thread; Thread->State = Running; return Thread; } Thread = KiSelectReadyThread(0 , Prcb); if (Thread) { Prcb->CurrentThread = Thread; Thread->State = Running; if (IsIdleSchedule) { _interlockedbittestandreset(&NotUsedCoreProcessor[Prcb->Group], (UCHAR)Prcb->GroupIndex); _InterlockedAnd(&UsedCoreProcessor[Prcb->Group], ~Prcb->CoreProcessorSet); } return Thread; } if (!IsIdleSchedule) { _interlockedbittestandset(&NotUsedCoreProcessor[Prcb->Group], (UCHAR)Prcb->GroupIndex); if ((Prcb->CoreProcessorSet & NotUsedCoreProcessor[Prcb->Group]) == Prcb->CoreProcessorSet) { _InterlockedOr(&UsedCoreProcessor[Prcb->Group], Prcb->CoreProcessorSet); } } KiReleasePrcbLock(Prcb); if (!PsCpuFairShareEnabled || !PsReleaseThreadFromIdleOnlyQueue(Prcb->Number, Prcb->Number)) { if ((Prcb->GroupSetMember & KiAllowedProcessorSetInGroup[Prcb->Group]) != 0 ) { PKNODE ParentNode = Prcb->ParentNode; USHORT NodeNumber = ParentNode->NodeNumber; ULONG GroupSetMember = Prcb->GroupSetMember; USHORT NodeIndex = 0 ; while (NodeIndex < KeNumberNodes) { PKNODE Node; if (NodeIndex == 0 ) { Node = ParentNode; } else { USHORT NextNodeId = MiNodeGraph[NodeNumber * KeNumberNodes + NodeIndex]; Node = KeNodeBlock[NextNodeId]; if (Node->Affinity.Group != Prcb->Group) { ++NodeIndex; continue ; } } ULONG Mask = GroupSetMember ^ Node->Affinity.Mask; ULONG ScanSet = ~NotUsedCoreProcessor[Node->Affinity.Group] & Mask; while (ScanSet != 0 ) { ULONG Bit; _BitScanReverse(&Bit, ScanSet); ScanSet ^= KiMask32Array[Bit]; ULONG ProcIndex = KiProcessorNumberToIndexMappingTable[(Node->Affinity.Group << 6 ) + Bit]; PKPRCB TargetPrcb = KiProcessorBlock[ProcIndex]; if (TargetPrcb->ReadySummary == 0 ) continue ; PKPRCB FirstLock = (Prcb < TargetPrcb) ? Prcb : TargetPrcb; PKPRCB SecondLock = (Prcb < TargetPrcb) ? TargetPrcb : Prcb; KiAcquirePrcbLock(FirstLock); if (FirstLock != SecondLock) KiAcquirePrcbLock(SecondLock); Thread = Prcb->NextThread; if (Thread) { Prcb->NextThread = NULL ; if (Thread != Prcb->IdleThread) { Thread->State = Running; Prcb->CurrentThread = Thread; KiReleasePrcbLock(TargetPrcb); return Thread; } Prcb->IdleSchedule = 0 ; KiReleasePrcbLock(Prcb); Thread = NULL ; } else { Thread = KiFindReadyThread(TargetPrcb->ReadySummary, Prcb, TargetPrcb); if (Thread) { Thread->State = Running; Prcb->CurrentThread = Thread; _interlockedbittestandreset(&NotUsedCoreProcessor[Prcb->Group], (UCHAR)Prcb->GroupIndex); _InterlockedAnd(&UsedCoreProcessor[Prcb->Group], ~Prcb->CoreProcessorSet); KiReleasePrcbLock(TargetPrcb); return Thread; } } KiReleasePrcbLock(Prcb); KiReleasePrcbLock(TargetPrcb); if (PsCpuFairShareEnabled && PsReleaseThreadFromIdleOnlyQueue(Prcb->Number, TargetPrcb->Number)) { return NULL ; } } ++NodeIndex; } } } return NULL ; }
KiSelectReadyThread
是从当前 PRCB 的本地 ReadyList
中选择优先级最高的线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 PKTHREAD KiSelectReadyThread ( IN ULONG LowPriority, IN PKPRCB Prcb ) { ULONG PrioritySet; ULONG HighPriority; PLIST_ENTRY ListEntry; PKTHREAD Thread = NULL ; PrioritySet = Prcb->ReadySummary >> LowPriority; if (PrioritySet != 0 ) { _BitScanReverse(&HighPriority, PrioritySet); HighPriority += LowPriority; ListEntry = Prcb->DispatcherReadyListHead[HighPriority].Flink; Thread = CONTAINING_RECORD(ListEntry, KTHREAD, WaitListEntry); if (RemoveEntryList(&Thread->WaitListEntry)) { Prcb->ReadySummary ^= KiMask32Array[HighPriority]; } } return Thread; }
KiFindReadyThread
函数主要是在与当前 PRCB 属于同一个 Group 的 NUMA 节点 的就绪队列选择线程,在获取时会按照优先级从高到低依次从调度队列中获取。由于在获取时还要线程是否允许运行在当前处理器上,因此需要遍历链表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 PKTHREAD FASTCALL KiFindReadyThread ( IN ULONG PrioritySet, IN PKPRCB CurrentPrcb, IN PKPRCB TargetPrcb ) { ULONG HighPriority; ULONG Index; PLIST_ENTRY ListHead; PLIST_ENTRY Flink; PKTHREAD Thread; while (TRUE) { if (!_BitScanReverse(&HighPriority, PrioritySet)) { break ; } PrioritySet ^= KiMask32Array[HighPriority]; Index = HighPriority; ListHead = &TargetPrcb->DispatcherReadyListHead[HighPriority]; Flink = ListHead->Flink; while (Flink != ListHead) { Thread = CONTAINING_RECORD(Flink, KTHREAD, WaitListEntry); if ((Thread->Affinity.Group == CurrentPrcb->Group) && ((Thread->Affinity.Mask & CurrentPrcb->GroupSetMember) != 0 )) { if (RemoveEntryList(Flink)) { TargetPrcb->ReadySummary ^= KiMask32Array[Index]; } Thread->NextProcessor = (UCHAR)CurrentPrcb->Number; return Thread; } Flink = Flink->Flink; } if (PrioritySet == 0 ) { break ; } } return NULL ; }
抢占式线程切换 以 Windows 在时间片到期后调度线程为例,调用栈如下:
1 2 3 4 5 6 7 8 9 10 nt!KiQuantumEnd nt!KiDispatchInterrupt hal!HalpDispatchSoftwareInterrupt hal!HalpCheckForSoftwareInterrupt hal!KfLowerIrql hal!HalRequestSoftwareInterrupt KeUpdateRunTime KeUpdateSystemTime nt!KeUpdateSystemTimeAssist hal!HalpHpetClockInterrupt
其中 KiQuantumEnd
会在时间片用尽后进行线程切换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 VOID KiQuantumEnd () { PKPRCB Prcb = KeGetPcr()->Prcb; PETHREAD CurrentThread = (PETHREAD)Prcb->CurrentThread; PKTHREAD Thread = &CurrentThread->Tcb; PKPROCESS Process = Thread->ApcState.Process; while (TRUE) { if (Thread->CycleTime.HighPart == Thread->HighCycleTime) break ; _mm_pause(); } KiAcquireThreadLock(Thread); KiAcquirePrcbLock(Prcb); if (Thread->CycleTime.QuadPart >= Thread->QuantumTarget) { UCHAR Quantum = 0 ; if (Process->DisableQuantum && Thread->Priority >= LOW_REALTIME_PRIORITY) { Quantum = MAXCHAR; } else { Quantum = Thread->QuantumReset; if (Thread->Priority < LOW_REALTIME_PRIORITY) { UCHAR Decrement = Thread->UnusualBoost + Thread->ForegroundBoost; KPRIORITY NewPriority = Thread->Priority - Decrement; if (NewPriority < Thread->BasePriority) NewPriority = Thread->BasePriority; Thread->Priority = NewPriority; Thread->PriorityDecrement = 0 ; } if (!Prcb->NextThread) { KPRIORITY Priority = Thread->Priority; ULONG Summary = Prcb->ReadySummary >> Priority; PKTHREAD Next = NULL ; if (Summary) { ULONG Offset; _BitScanReverse(&Offset, Summary); ULONG Index = Priority + Offset; PLIST_ENTRY Entry = Prcb->DispatcherReadyListHead[Index].Flink; Next = CONTAINING_RECORD(Entry, KTHREAD, WaitListEntry); if (RemoveEntryList(Entry)) Prcb->ReadySummary ^= KiMask32Array[Index]; } Prcb->NextThread = Next; } else { Thread->Preempted = FALSE; } } Thread->QuantumTarget = Thread->CycleTime.QuadPart + (Quantum * KiCyclesPerClockQuantum); if (Prcb->GroupSetMember != Prcb->CoreProcessorSet && Thread->QuantumEndMigrate) { Thread->QuantumEndMigrate = FALSE; if (!Prcb->NextThread && (Prcb->CoreProcessorSet & (Prcb->GroupSetMember | NotUsedCoreProcessor[Prcb->Group])) != Prcb->CoreProcessorSet && (Thread->Affinity.Mask & KiProcessorBlock[Thread->IdealProcessor]->ParentNode->Affinity.Mask & UsedCoreProcessor[Thread->Affinity.Group])) { KiSelectNextThread(Prcb); Thread->ForceDeferSchedule = TRUE; Thread->QuantumEndMigrate = TRUE; } } } KiReleaseThreadLock(Thread); PKTHREAD NextThread = Prcb->NextThread; if (NextThread) { Prcb->NextThread = NULL ; _disable(); PKTHREAD OldThread = Prcb->CurrentThread; Prcb->NestingLevel = 1 ; ULONGLONG Now = __rdtsc(); ULONGLONG Delta = Now - Prcb->StartCycles; OldThread->CycleTime.QuadPart += Delta; OldThread->HighCycleTime = OldThread->CycleTime.HighPart; Prcb->StartCycles = Now; if (OldThread->Header.CycleProfiling) PsChargeProcessCpuCycles(Prcb, Delta, Delta >> 32 ); if (OldThread->Header.CounterProfiling) KiEndCounterAccumulation(OldThread); _enable(); Prcb->CurrentThread = NextThread; NextThread->State = Running; Thread->WaitReason = WrQuantumEnd; KiQueueReadyThread(Thread, Prcb); Thread->WaitIrql = APC_LEVEL; KiSwapContext(Thread, NextThread); } KiReleasePrcbLock(Prcb); }
其中选择下一个可用现成的 KiSelectNextThread
函数本质上还是调用了 KiSelectReadyThread
函数,与主动切换类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 PKTHREAD KiSelectNextThread ( IN PKPRCB Prcb ) { PKTHREAD Thread; Thread = KiSelectReadyThread(0 , Prcb); if (!Thread) { Thread = Prcb->IdleThread; Prcb->IdleSchedule = 1 ; _interlockedbittestandset( &NotUsedCoreProcessor[Prcb->Group], (UCHAR)Prcb->GroupIndex ); if ((Prcb->CoreProcessorSet & NotUsedCoreProcessor[Prcb->Group]) == Prcb->CoreProcessorSet) { _InterlockedOr( &UsedCoreProcessor[Prcb->Group], Prcb->CoreProcessorSet ); } } Thread->State = Standby; Prcb->NextThread = Thread; return Thread; }