线程是系统处理器调度的基本单元,而且线程调度是在内核层完成的,所以,KTHREAD 的许多域都跟Windows 的线程调度机制有关.

找到进程的线程可以使用!process


1


2


3




​kd> !process 0 2 calc.exe​


​THREAD 84b0cc20  Cid 03fc.0f90  Teb: 7ffdd000 Win32Thread: e1b235d0 WAIT: (WrUserRequest) UserMode Non-Alertable​


​8488d138  SynchronizationEvent​


也可以根据KPROCESS的ThreadListHead来反推,这里仅找第一个,因为calc就一个线程


1


2


3


4


5


6


7


8




​kd> dt 8489d208 KPROCESS -y ThreadListHead​


​nt!KPROCESS​


​+0x050 ThreadListHead : _LIST_ENTRY [ 0x84b0cdc8 - 0x84b0cdc8 ]  ​​​​// 地址为0x84b0cdc8,位为KTHREAD的ThreadListEntry处​


​kd> dt KTHREAD -y ThreadListEntry​


​nt!KTHREAD​


​+0x1a8 ThreadListEntry : _LIST_ENTRY        ​​​​//ThreadListEntry在偏移1a8处​


​kd> ? 0x84b0cdc8 -0x1a8​


​Evaluate expression: -2068788192 = 84b0cc20​​​​// 上移1a8即得KTHREAD首地址​



1




​DISPATCHER_HEADER Header;​​​​//​


说明了内核层的线程对象同样是一个分发器对象,线程可以被等待,当线程结束时,它变成有信号状态


1




​LIST_ENTRY MutantListHead; ​​​​// 链表中包含了所有属于该线程的突变体对象(mutant,对应于API 中的互斥体[mutex]对象)​


由于突变体对象是有所有权的,一旦被某个线程等到,则其所有权归该线程所有,它也被连接到MutantListHead 链表中。

对应KMUTANT中的MutantListEntry


View Code CPP

 



1


2


3




​PVOID​​ ​​InitialStack;​​​​// 记录了原始的栈位置(高地址)​


​PVOID​​ ​​StackLimit;  ​​​​// 记录了栈的低地址​


​PVOID​​ ​​KernelStack; ​​​​// 记录了真正内核调用栈的开始位置​


后面有个StackBase记录了当前栈的基位置(高地址),在线程初始化时,InitialStack 和StackBase是相等的,都指向原始的内核栈高地址。


1


2




​+0x018 InitialStack     : 0xf621d000 Void​


​+0x158 StackBase        : 0xf621d000 Void​



1




​KSPIN_LOCK ThreadLock; ​​​​// ThreadLock域是一个自旋锁,用于保护线程数据成员​



1


2


3


4


5


6


7


8


9


10


11




​union​​ ​​{​


​KAPC_STATE ApcState; ​​​​// 指定了一个线程的APC(Asynchronous Procedure Call)信息​


​struct​​ ​​{​


​UCHAR​​ ​​ApcStateFill[KAPC_STATE_ACTUAL_LENGTH];​


​BOOLEAN​​ ​​ApcQueueable;                           ​​​​// 是否可以插入APC​


​volatile​​ ​​UCHAR​​ ​​NextProcessor;                   ​​​​// 下个处理器         ​


​volatile​​ ​​UCHAR​​ ​​DeferredProcessor;               ​​​​// 默认处理器​


​UCHAR​​ ​​AdjustReason;                             ​​​​// 优先级调整原因​


​SCHAR AdjustIncrement;                          ​​​​// 调整量​


​};​


​};​



1




​KSPIN_LOCK ApcQueueLock;​​​​// ApcQueueLock 也是一个自旋锁,用于保护APC 队列的操作​



1




​+0x048 ContextSwitches  : 0x12c​​​​// ContextSwitches域记录了该线程进行了多少次环境切换​



1




​+0x04c State            : 0x5 ​​​​''​​​​// State 域反映了该线程当前的状态KTHREAD_STATE​



View Code CPP

 


示例图如下:

WD-线程KTHREAD结构(WRK)_信号量

在一个线程的生命周期中,它从完成初始化开始进入到线程调度器的视野中,之后,一直到完成所有预定的功能,最后终止并被销毁。在此过程中,为了获得系统的处理器资源,它必须要经历上图中的状态转移。


1




​+0x04d NpxState         : 0xa ​​​​''​​​​//NpxState 反映了浮点处理器的状态​



1


2


3


4


5


6




​+0x04e WaitIrql         : 0 ​​​​''​


​+0x04f WaitMode         : 1 ​​​​''​


​...​


​+0x058 Alertable        : 0 ​​​​''​


​+0x059 WaitNext         : 0 ​​​​''​


​+0x05a WaitReason       : 0xd ​​​​''​


Alertable域说明了一个线程是否可以被唤醒,当一个线程正在等待时,如果它的Alertable值为TRUE,则它可以被唤醒。WaitNext 域也是一个布尔值,TRUE 值表示这个线程马上要调用一个内核等待函数,它的用途是,在发出了一个信号(比如释放了一个信号量对象)以后,接下来该线程会马上调用等待函数,所以,它不必解除线程调度器锁。WaitIrql 域与WaitNext 一起使用,当WaitNext 为TRUE 时,WaitIrql 记录了原先的IRQL值。WaitReason 域记录了一个线程的等待理由,其值的含义见base\ntos\inc\ke.h 中的KWAIT_REASON 枚举类型。WaitReason 基本上只是记录了等待的理由,而并不参与到线程调度或决策中。WaitMode 域记录了当线程等待时的处理器模式,即内核模式或用户模式的等待。接下来是WaitStatus 域,它记录了等待的结果状态.


1


2




​+0x054 WaitBlockList    : 0x84b0ccc8 _KWAIT_BLOCK  ​​​​// 都是0x54偏移,union​


​+0x054 GateObject       : 0x84b0ccc8 _KGATE​


WaitBlockList 成员指向一个以KWAIT_BLOCK 为元素的链表,其中的KWAIT_BLOCK 对象指明了哪个线程在等待哪个分发器对象。对于一个线程而言,WaitBlockList域以及每个KWAIT_BLOCK 对象中的WaitListEntry 域构成了一个双链表,指明了该线程正在等待哪些分发器对象.

当一个线程正在等待门对象(Gate Object,也是一种分发器对象)时,GateObject 域记录了正在等待的门对象。由于等待门对象和等待其他分发器对象是不会同时发生的,所以,GateObject 和WaitBlockList 域构成了一个union,共用一个指针内存.


1


2




​+0x05b Priority         : 12 ​​​​''​​​​//该线程的动态优先级值,即在执行过程中可能由于某些原因而调整过的优先级​


​+0x121 BasePriority     : 8 ​​​​''​​ ​​//线程的静态优先级​



1


2


3




​+0x05c EnableStackSwap  : 0x1 ​​​​''​​​​// 本线程的内核栈是否允许被换出到外存中​


​+0x05d SwapBusy         : 0 ​​​​''​​  ​​// 指定了本线程当前是否正在进行上下文环境切换​


​+0x05e Alerted          : [2]  ​​​​""​​​​//数组,指该线程分别在内核模式和用户模式下是否可以被唤醒​



1


2




​+0x060 WaitListEntry    : _LIST_ENTRY [ 0x84761520 - 0x84c0a320 ] ​​​​// union​


​+0x060 SwapListEntry    : _SINGLE_LIST_ENTRY​


当一个线程正在等待被执行时,WaitListEntry 作为一个线程节点加入到某个链表中。例如,在进程被换入内存过程中,就绪状态的线程将被加入到以进程的ReadyListHead域为链表头的双链表中,链表中的节点即为线程的WaitListEntry 域。SwapListEntry 域则被用于当线程的内核栈需要被换入时,插入到以全局变量KiStackInSwapListHead 为链表头的单链表中。另外,当一个线程处于DeferredReady状态时,其SwapListEntry 将被插入到某个处理器的DeferredReadyListHead 链表中(参见KiInsertDeferredReadyList 和KiProcessDeferredReadyList内核函数).


1




​+0x068 Queue            : (null) ​​​​//队列分发器对象,如果不为NULL,则表示当前线程正在处理此队列对象中的项​



1




​+0x06c WaitTime         : 0xd9e6​​​​//记录了一个线程进入等待时刻的时间点(时钟滴答值的低32位)​



View Code CPP

 


KernelApcDisable 和SpecialApcDisable 都是16 位的整数值,0 表示不禁止APC,负数表示禁止APC,一个线程在执行过程中可以有多种因素要禁止APC,这些因素以负值来表示,并累加起来,当因素消除的时候再减去相应的负值。只有当KernelApcDisable 或SpecialApcDisable 为0 的时候,该线程才允许插入或提交APC。这两个值分别控制普通的内核APC 和特殊的内核APC.


1




​+0x074 Teb              : 0x7ffdd000 Void​


Teb 域是一个特殊的域,它指向进程地址空间中的一个TEB(线程环境块)结构。TEB结构包含了在用户地址空间中需要访问的各种信息,例如与线程相关的GDI 信息、系统支持的异常,甚至还有WinSock 的信息,等等.


View Code CPP

 


一个线程上的定时器,当一个线程在执行过程中需要定时器时,比如实现可超时的等待函数(KeWaitForSingleObject 或KeWaitForMultipleObjects),就会用到此定时器对象。紧接着Timer 域之后的是AutoAlignment 和DisableBoost两个位标记,线程的这两个标记直接继承自所属进程的同名标记.


View Code CPP

 


WaWaitBlock 域是一个包含4 个KWAIT_BLOCK 成员的数组,其中第4项专门用于可等待的定时器对象.如肯WaitBlockList所述,KWAIT_BLOCK 结构代表了一个线程正在等待一个分发器对象,或者说一个分发器对象正在被一个线程等待,它会被同时加入到两个双链表结构中.

WaitBlock 域是一个内置数组,内核在实现等待功能的时候,如果一个线程所等待的对象数量小于4(实际上应该是3 个分发器对象加上一个定时器对象),则内核无须另外分配KWAIT_BLOCK 对象内存,只需直接使用WaitBlock 中的数组成员即可。如果等待的对象数量大于4,则内核必须分配额外的KWAIT_BLOCK对象内存。由于等待操作在内核中非常频繁,所以,利用静态数组来满足大多数情况下的内存需求, 这一优化非常有意义.


1




​LIST_ENTRY QueueListEntry;​​​​//记录了线程在处理一个队列项时加入到队列对象的线程链表中的节点地址​



1




​+0x110 TrapFrame        : 0xf621cd64 _KTRAP_FRAME​


TrapFrame 域是一个线程中最为关键的部分了。在Windows 中,线程是系统调度的基础,它代表了一个进程中的一个控制流。当一个线程离开运行状态时,其当前的执行状态,比如现在的指令指针(IP)在哪里,各个寄存器中的值是什么,等等,都必须保存下来,以便下次再轮到这个线程运行时,可以恢复原来的执行状态。TrapFrame 域正是记录控制流状态的数据结构,它是一个指向KTRAP_FRAME 类型的指针,它包含了Intel x86 的所有常用寄存器.


View Code CPP

 


WD-线程KTHREAD结构(WRK)_链表_02


1




​+0x114 CallbackStack    : (null) ​​​​//包含了线程的回调(callback)栈地址,此栈在该线程从内核模式调用到用户模式时使用。​



1




​+0x118 ServiceTable     : 0x8089f7c0 Void​


ServiceTable 域指向该线程使用的系统服务表(全局变量KeServiceDescriptorTable),如果这是一个图形用户界面(GUI)线程,此域将指向另一个影子系统服务表(全局变量KeServiceDescriptorTableShadow),这里是calc,所以为ShadowSSDT


View Code CPP

 



1




​UCHAR​​ ​​IdealProcessor;​​​​//指明了在多处理器的机器上该线程的理想处理器​



1


2


3




​BOOLEAN​​ ​​Preempted;​


​BOOLEAN​​ ​​ProcessReadyQueue;​


​BOOLEAN​​ ​​KernelStackResident;​


Preempted域说明这个线程是否被高优先级的线程抢占了,只有当一个线程正在运行或者正在等待运行而被高优先级线程抢占的时候,此值才会置成TRUE。在其他情况下,该值总是FALSE.

ProcessReadyQueue域说明一个线程是否在所属进程KPROCESS对象的ReadyListHead 链表中,TRUE表示在此链表中,FALSE表示不在.

KernelStackResident域说明该线程的内核栈是否驻留在内存中,当内核栈被换出内存时,该值将被置成FALSE;当换入内存时,再置成TRUE.


1


2


3




​KAFFINITY UserAffinity;​​​​// 线程的用户亲和性​


​PKPROCESS Process;     ​​​​// 从属的进程​


​KAFFINITY Affinity;    ​​​​// 线程的处理器亲和性​



View Code CPP

 


ApcStateIndex 域是一个索引值,它指明了当前的APC 状态在ApcStatePointer 域中的索引。由于ApcStatePointer 是一个只有两个元素的数组,所以,ApcStateIndex 的值为0或者1。ApcStatePointer 数组元素的类型是指向KAPC_STATE 的指针,其两个元素分别指向线程对象的ApcState 和SavedApcState 域.


View Code CPP

 


Win32Thread 域是一个指针,指向由Windows 子系统管理的区域。SuspendApc 和SuspendSemaphore 两个union 域相互之间有联系,SuspendApc 被初始化成一个专门的APC。当该APC 被插入并交付时,KiSuspendThread 函数被执行,其执行结果是在线程的SuspendSemaphore 信号量上等待,直到该信号量对象有信号,然后线程被唤醒并继续执行。线程的挂起(suspend)操作正是通过这一机制来实现的,参见base\ntos\ke\thredobj.c 中的KeSuspendThread 和KeFreezeAllThreads 函数。自然地,线程的恢复(resume)操作则是通过控制SuspendSemaphore 信号量的计数来实现的,参见base\ntos\ke\thredobj.c 中的KeResumeThread 等函数。


1


2




​LIST_ENTRY ThreadListEntry;​​​​//代表了一个双链表上的节点,当一个线程被创建时,它会被加入到进程对象的ThreadListHead 链表中​


​PVOID​​ ​​SListFaultAddress;​​​​//记录了上一次用户模式互锁单链表POP 操作发生页面错误的地址​


线程对象提供了为参与线程调度而必需的各种信息及其维护控制流的状态.