Windows线程研究——基础知识
基础结构
与进程相似的在内核层具有_KTHREAD
与_ETHREAD
两个结构体
KTHREAD
1 | 0: kd> dt _KTHREAD |
以下是一些关键结构
1 | +0x028 InitialStack |
分别表示了栈底(高地址)、栈顶(低地址)、保存栈地址的变量。
- 有关线程等待的:
+0x074 Alertable : Pos 4, 1 Bit: 说明线程是否可以被唤醒。
+0x072 Alerted : [2] UChar :指示r0、r1下的唤醒状态,[0]是r0下的,[1]是r3下的。 值为1就是一种就绪等待状态,可随时唤醒,0则是一种死等待。
+0x0c8 WaitStatus : Int8B: 记录了等待的结果状态。
+0x074 WaitNext : Pos 2, 1 Bit:1表示这个线程马上要调用一个内核等待函数。
+0x186 WaitIrql : UChar:当WaitNext为1时,该位置记录了原先的IRQL值。
+0x187 WaitMode : Char:记录了处理器的模式,内核模式或用户模式。
+0x1b4 WaitTime : Uint4B: 记录了一个线程进入等待时刻的时间点(时钟滴答值的低32位)。
+0x0d0 WaitBlockList : Ptr64 _KWAIT_BLOCK: 该成员指向了一个以
KWAIT_BLOCK
为元素的链表,其中KWAIT_BLOCK
对象指明了哪个线程在等待哪个分发器对象。+0x0d8 WaitListEntry 与 SwapListEntry 分别是一个双向链表和单向链表节点,它们是一个联合体。当线程等待执行的时候
WaitListEntry
作为线程节点加到链表中。当线程的内核栈需要被换入时,SwapListEntry
将被插入到KiStackInSwapListHead单链表中。另外的如果是处于延迟等待状态下将会插入某个处理器(KPRCB)的DeferredReadyListHead单链表中。+0x140 WaitBlock : [4] _KWAIT_BLOCK: 该成员包含了4个
KWAIT_BLOCK
类型的数组,前3个是用于分发器对象的等待,第4个是用于定时器对象的等待。如果等待的数量大于4则需要额外的分配了。这个成员是一种优化的手段。
1 | nt!_KWAIT_BLOCK |
KWAIT_BLOCK结构代表了一个线程正在等待一个分发器对象,或者说一个分发器对象正在被一个线程等待,它会被同时加入到两个双链表结构中。
+0x283 WaitReason : UChar:记录了进程等待的原因(具体解释参考《软件调试(第二版)》P42页)。
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
40typedef enum _KWAIT_REASON {
Executive,
FreePage,
PageIn,
PoolAllocation,
DelayExecution,
Suspended,
UserRequest,
WrExecutive,
WrFreePage,
WrPageIn,
WrPoolAllocation,
WrDelayExecution,
WrSuspended,
WrUserRequest,
WrEventPair,
WrQueue,
WrLpcReceive,
WrLpcReply,
WrVirtualMemory,
WrPageOut,
WrRendezvous,
Spare2,
Spare3,
Spare4,
Spare5,
Spare6,
WrKernel,
WrResource,
WrPushLock,
WrMutex,
WrQuantumEnd,
WrDispatchInt,
WrPreempted,
WrYieldExecution,
WrFastMutex,
WrGuardedMutex,
WrRundown,
MaximumWaitReason
} KWAIT_REASON;有关优先级的:
+0x0c3 Priority : Char 线程动态优先级:
KPCR
中线程就绪队列中的位置(大小为32)初始优先级为8,越大优先级越高(0-15为普通线程的优先级,16-31是实时线程的优先级,这两个区域在优先级调整时不会跨越)。+0x233 BasePriority : Char 线程的静态优先级:从所属进程的
BasePriority
继承而来。+0x285 Saturation : Char:表明了线程的基本优先级相当于与基本优先级的调整量是否超过了整个区间的一半,值为0、1、-1。
+0x234 PriorityDecrement : Char:记录了一个线程在优先级动态调整过程中的递减值(例如
KiComputeNewPriority
函数中会使用)。+0x235 Preempted : UChar:说明这个线程是否被高优先级的线程抢占,只有当一个线程正在运行或者正在等待运行而被高优先级线程抢占的时候,会被设为1。
有关APC的:
+0x074 ApcQueueable : Pos 14, 1 Bit:表示了是否可以插入APC。
+0x1e4 KernelApcDisable 与 +0x1e6 SpecialApcDisable合成了一个CombinedApcDisable域,
KernelApcDisable
与SpecialApcDisable
都是16位的整数值,分别控制 内核APC和特殊的内核APC。其值为0则可以插入APC,负数则无法插入APC。一个线程在执行的时候用多种原因要禁止APC,所以这些因素可以累加起来,消除的时候按累加的值消去。+0x24a ApcStateIndex : UChar:指示使用
ApcState
还是SavedApcState
的成员+0x258 SavedApcState : _KAPC_STATE: 在该线程被attach到了另一个进程中后,未交付的APC将会挂在这个底下,detach之后又会被转移到
ApcState
底下。+0x098 ApcState : _KAPC_STATE :其中
_KAPC_STATE
结构值得关注
1 | nt!_KAPC_STATE |
其中+0x020 Process : Ptr64 _KPROCESS
表示了当前线程所属的进程的_KPROCESS
。
一些其他的结构
- +0x074 SystemThread : Pos 10, 1 Bit :值为1则表示是系统线程,可以使用API
PsIsSystemThread
来判断。 - +0x078 EnableStackSwap : Pos 6, 1 Bit :说明本线程的内核栈是否允许被换出。
- +0x078 KernelStackResident : Pos 17, 1 Bit :值为1则可以拓展内核栈,反之则不行。(内核栈大小默认为12K)
- +0x090 TrapFrame : Ptr64 _KTRAP_FRAME:当该线程离开运行状态时,状态会被保存至此。
- +0x0e8 Queue : Ptr64 _DISPATCHER_HEADER:该成员是一个队列分发器对象,如果不为NULL,则表示当前线程正在处理此队列对象中的项。
- +0x208 QueueListEntry : _LIST_ENTRY:记录了线程在处理一个队列项时加入到队列对象的线程链表中的节点地址。
- +0x100 Timer : _KTIMER:附在一个线程上的定时器,当线程执行的时候需要定时器的时候会用此对象。
- **+0x1c8 Win32Thread :**:指向Windows子系统的管理区域。
- +0x250 NpxState : Uint8B :反应了浮点处理器的状态。
- +0x154 ContextSwitches : Uint4B : 当前线程切换次数。
- +0x218 NextProcessor : Uint4B : 下一次运行时跑在哪个核心上。
- +0x184 State : UChar : 当前线程运行状态。
- +0x220 Process : Ptr64 _KPROCESS : 创建该线程的进程
_KPROCESS
。 - +0x2f8 ThreadListEntry : _LIST_ENTRY :创建该线程的进程的线程列表,其
_KPROCESS
结构中的ThreadListHead
指向此结构。
进程对象提供了线程的基本执行环境,线程对象提供了为参与线程调度而必须的各种信息。
ETHREAD
1 | 0: kd> dt _ETHREAD |
关键结构
- +0x450 StartAddress : Ptr64 Void :真正的线程起始地址,但是通常是
kernel32!BaseProcessStart
或kernel32!BaseThreadStart
。 - +0x4d0 Win32StartAddress : Ptr64 Void:Windows子系统接收到的线程启动地址,即
CreateThread
接收到的线程启动地址。 - +0x478 Cid: _CLIENT_ID : 其中保存的是创建该线程的进程id与线程id结构如下:
1
2
3nt!_CLIENT_ID
+0x000 UniqueProcess : Ptr64 Void
+0x008 UniqueThread : Ptr64 Void - +0x4b0 IrpList : _LIST_ENTRY:是一个双链表头,其中包含了当前线程所有正在处理但是尚未完成的IO请求(IRP对象)。
- +0x4e8 ThreadListEntry : _LIST_ENTRY :与上文相仿,也是线程列表其
_EPROCESS
结构中的ThreadListHead
指向此结构。 - +0x510 CrossThreadFlags:其中是一些针对跨线程访问的标志位。
- Terminated——Pos 0:线程已终止操作。
- HideFromDebugger——Pos 2:该线程对于调试器不可见。
- HardErrorsAreDisabled——Pos 4:该线程对于硬件错误无效。
- BreakOnTermination——Pos 5:调试器在线程终止时停下来。
- +0x514 SameThreadPassiveFlags:在
PASSIVE_LEVEL
才能访问的标志位,且只能被线程自身访问。 - +0x518 SameThreadApcFlags:在
APC_LEVEL
及以上被该线程访问自身的标志位。
KiFindReadyThread逆向
该函数用于寻找一个就绪态的线程。
在x64下其结构较为复杂,但是大致与x86下一样。
1 | _LIST_ENTRY **__fastcall KiFindReadyThread(_KPRCB *a1, _KPRCB *a2, __int64 a3, unsigned int readyBitmap) |
使用文本描述流程则是:
- 在就绪位图中找到优先级最大的就绪线程
- 判断其是否可以运行在当前CPU上
- 从就绪队列中抹除该线程
- 把该线程的下一次运行CPU设置好
- 返回就绪线程
- 标题: Windows线程研究——基础知识
- 作者: moshui
- 创建于 : 2025-01-28 13:40:32
- 更新于 : 2025-02-17 15:04:00
- 链接: https://www.moshui.eu.org/2025/01/28/windows-thread-1/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。