进程与线程 -- 线程切换(二)

c++

Posted by YiMiTuMi on May 28, 2021

进程与线程 – 线程切换(二)

3环模拟Windows线程切换

一个线程执行至少需要寄存器堆栈,线程切换本质就是堆栈的切换。

线程切换分为两种:主动切换和被动切换。只要调用API就会发生主动切换,系统时钟属于被动切换。

每一个线程最开始执行的总是同一个StartUp(开始函数),然后在 StartUp 中调用自己的执行函数。

中断一个正在执行的程序:

1)异常:缺页,或者 INT N 指令

2)中断:时钟中断

线程切换的几种情况:

1)主动调用API函数

2)时钟中断

3)异常处理

如果一个线程不调用API,在代码中屏蔽钟中断(CLI指令),并且不会出现异常,那么当前线程将永久占用CPU,单核占有率100%,双核就是50%。

线程切换与TSS的关系

内核堆栈:

StackLimit	→	          0000
				  0000
				  0000
				  0000
InitialStack →	                  0000
				  0000
				  0000
				  0000
				  0000
				  0000
InitialStack →	                  0000

InitialStack 栈底

KernelStack 栈顶

StackLimit 栈的边界 (这三个成员存储在_KTHREAD结构体中)

内核堆栈的结构:

内核堆栈有2部分组成,即 InitialStack + 0x210字节 和 _Trap_Frame结构组成

StackLimit	→  0000
		   0000
InitialStack →	   0000
	           0000
		   0000
		   0000
		   0000
0x210	→	   0000   ↑ _Trap_Frame结构
	           0000
		   0000
InitialStack →	   0000   ↑ 0x210字节 (浮点寄存器的值)

调用API进0环:

普通调用:通过 Tss.Esp0 得到0环堆栈

快速调用:从MSR得到一个临时0环栈,代码执行后仍然通过 Tss.Esp0 得到当前线程0环堆栈

TSS:

Inter设计TSS的目的是为了任务切换(线程切换),但Windows与Linux并没有使用,而是采用堆栈来保存线程的各种寄存器。

在线程切换时,会修改TSS中的 CR3 和 Esp0。

_Trap_Frame:

ps:从TSS中获取的ESP0是一个线程一个的,每个线程都不同,所以说_Trap_Frame结构也是和线程相关的一个线程一个,所以要保存寄存器。

ps:TSS里面永远都是当前执行线程的ESP0,在线程切换的时候会更新TSS。

即:_Trap_Frame 就是我们线程在内核层的结构体,ETHREAD -> InitialStack指向的就是这个结构体向下0x210字节的位置。

线程切换与FS的关系

FS:[0]寄存器在3环指向TEB,进入0环后FS:[0]执行KPCR。

在GDT表中 38H 对应FS的段描述符。

在线程切换时会修改FS为当前线程的值。

mov     eax, [ebx+18h]  //获取KPCR 中 _NT_TIB 结构体指针,Self: Ptr32 _NT_TIB(TEB)
mov     ecx, [ebx+3Ch]  //获取KPC中 GDT表,GDT: Ptr32 _KGDTENTRY
//重写FS寄存器段描述符指向线程TEB的地址,将该地址换为新线程的TEB的地址										
mov     [ecx+3Ah], ax  //更新GDT表中段描述符的Base,即 低 16 - 31位 ax = TEB的低 16位
shr     eax, 10h       //获取 TEB地址的 高 16位
mov     [ecx+3Ch], al  //更新Base TEB低8位,写入 段描述符的 高 0 - 7位
mov     [ecx+3Fh], ah  //更新Base TEB高8位,写入 段描述符的 高 24 - 31位
								//设置 FS 通过段描述符,指向TEB(3环)或_NT_TIB(同时也是_ETREAD的基址)

所以在3环时,线程的段选择字没有修改,但是其段选择子指向的段描述符的基址已经改变了。

线程切换优先级

操作系统通过 KiFindReadyThread 函数来查找下一个要切换的线程。

线程调度链表有32个,KiFindReadyThread查找方式:按照优先级别进行查找,即31-30-29-28…,如果在本次查找中,31的链表里面有线程,那么就不会查找级别为30的链表。(只是查找,并不是说所有线程都一点根据这个优先级进行)

高效查找调度链表:

调度链表有32个,每次都从头开始查找效率太低,所以Windows都会设置一个DWORD类型的变量来记录:

当调度链表(32个)中挂入或者摘除某个线程时,会判断当前级别的链表是否为空,为空将DWORD变量对应位置置为0,否则置1。若是挂入线程则直接置1。

DWORD变量为:_kiReadySummary

1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0

CPU判断链表为空:

在 _KiDispatcherReadyListHead 中保存了32个链表的链表头即:

当前链表头地址 = 下一个链表头地址 = 当前链表所在地址

则当前链表为空。

当_KiDispatcherReadyListHead的所有链表为空,则执行空闲线程。

	call    @KiFindReadyThread@8 ; KiFindReadyThread(x,x)
	test    eax, eax
	jz      loc_40EA85  //当eax 为空时,则说明 _KiDispatcherReadyListHead的所有链表为空 则跳转 

loc_40EA85:                             ; CODE XREF: KiSwapThread()+2A↑j
	mov     eax, [esi+0Ch]     //如果没有找到任何就绪的线程则切换到空闲线程  esi + 0Ch = _KPRCB + 0Ch  = IdleThread
	xor     edx, edx
	inc     edx
	mov     ecx, ebx
	shl     edx, cl
	or      ds:_KiIdleSummary, edx
	jmp     loc_4050EF

loc_4050EF 
		   .
		   .
		   .
	call    @KiSwapContext@4 ; KiSwapContext(x)	

多CPU会随机寻找 KiDispatcherReadyListHead 指向的数组中的线程。线程可以绑定某个CPU。(使用API:setThreadAffinityMask)

进程的挂靠

进程与线程的关系:

一个进程可以包含多个线程,一个进程至少要有一个线程。

进程为线程提供资源,也就是提供了CR3的值,CR3中存储的是页目录表基址,CR3确定了,线程能访问的内存也就确定了。

如解析当前地址:

mov eax, dword ptr ds:[0x12345678]

1)CPU解析线性地址时要通过页目录表来找对应的物理页,页目录表基址存在CR3寄存器中。

2)当前的CR3的值来源于当前的进程(_KPRCOESS.DirectoryTableBass(+0x018))

ETHREAD提供的Process:

在 ETHREAD 结构体中,有两个值表示了当前线程所在的进程,具体使用哪个呢?

	+x034 ApcState
		+0x000 ApcListHead
		+0x010 Process     // 进程 0x044的位置
	
	+0x220 ThreadsProcess  //进程

养父母负责提供CR3:

通过 SwapContext 函数可以看出:

线程切换的时候,会比较 _KTREAD 结构体 0x44 处指定的EPROCESS是否为同一个,如果不是同一个。会将0x044处指定的EPROCESS的 DirectoryTableBass 的值取出,赋值给CR3。

所以,线程需要的CR3的值来源于 0x044 处偏移指定的EPROCESS。

即:

0x220 亲生父母: 这个线程谁创建的

0x044 养父母:谁在为这个线程提供资源(CR3)

一般情况下,0x220 和 0x044指向的时同一个进程。

当发生进程挂靠的时候,亲生父母进程(0x220)不会发生变化,只会改变养父母进程(0x044),即只要发生进程挂靠0x044处的值就会变更。

进程挂靠:

正常情况下,CR3的值是由养父母提供的,但CR3的值也可以改成和当前线程毫不相干的其他进程的 DirectoryTableBass。

mov cr3, A.DirectoryTableBass
mov eax, dword ptr ds:[0x12345678]  //A进程的0x12345678内存

mov cr3, B.DirectoryTableBass
mov eax, dword ptr ds:[0x12345678]  //B进程的0x12345678内存

mov cr3, C.DirectoryTableBass
mov eax, dword ptr ds:[0x12345678]  //C进程的0x12345678内存

将当前CR3的值改为其他进程, 称为“进程挂靠”。

NtReadVirtualMemory函数:

NtReadVirtualMemory获取其他线程的内存就是通过进程挂靠是现实的。

在进程挂靠中修改了 养父母和CR3的值。

NtReadVirtualMemory
	↓
  KiAttachProcess
	↓
    修改养父母
	↓
     修改CR3

不能只修改CR3而不修改养父母,如果不修改养父母的值,一旦在读取其他进程值的时候产生线程切换(当发生线程切换时不会变化,但当从其他线程切换回来时,会重新根据养父母的值赋值CR3),就会变成自己读自己的内存。

如果我们自己来写代码,在切换CR3后关闭中断,并且不调用会导致线程切换的API,就可以不用修改养父母的值。

跨进程读写内存

跨进程的本质是“进程挂靠”,正常情况下,A进程的线程只能访问A进程的地址空间,如果A进程的线程想访问B进程的地址空间,就要修改当前的CR3的值为B进程的页目录表基址(KPROCESS.DirectoryTableBase)。

跨进程操作:

//A进程代码:

mov cr3, B.DirectoryTableBase        //切换Cr3的值为B进程
mov eax, dword ptr ds:[0x12345678]   //获取进程B 0x12345678 的值存到 eax 中
mov dword ptr ds:[0x00401234], eax   //将数据存储到 0x00401234 中
mov cr3, A.DirectoryTableBase        //切换会Cr3的值

这段代码中 0x00401234 的地址仍然是进程B的,所以再切换会A进程时,0x00401234中不会是我们从B进程获取的,所以有问题。最好是将获取到的值保存到高2g的位置,因为不同进程之间高2g使用的物理页是一样的

NtReadVirtualMemory函数流程:

	1.切换CR3为要获取进程的CR3
	
	2.将获取到的数据复制到高2G(暂存区)
	
	3.切换会原进程的CR3
	
	4.从高2G(暂存区)将获取到的数据复制到目标位置

时钟中断导致的线程切换

(IDT表)中断号       IRQ        说明
	0x30         IRQ0     时钟中断

Windows系列操作系统时钟间隔: 10 - 20 毫秒

获取当前的时钟,用 Win32API: GetSystemTimeAdjustment

时钟中断执行流程:

KiStartUnexpectedRange        HalEndSystemInterrupt
		↓		     ↓
KiEndUnexpectedRange          KiDispatchInterrupt
		↓		     ↓
KiUnexpectedInterruptTail     SwapContext
		↓				
HalBeginSystemInterrupt

时钟中断会导致线程进行切换,但并不是说只要有时钟中断就一定会切换线程,时钟中断时,两种情况会导致线程切换:

1)当前的线程CPU时间片到期。

2)有备用线程(KPCR.PrcbData.NextThread)。

时间片管理

CPU时间片:

1)当一个新的线程开始执行时,初始化程序会在 _KTHREAD.Quantum 赋初始值(一般默认为6),该值的大小由 _KPROCESS.ThreadQuantum 决定的。

2)每次时钟中断会调用 KeUpdateRunTime 函数,该函数每次将当前进程 Quantum 减少3个单位,如果减到0(一般需要调用2次),则将 KPCR.PrcbData.QuantumEnd 的值设置为非0,说明CPU时间片已经到期。

KeUpdateRunTime:

sub     byte ptr [ebx+6Fh], 3  //ebx+6Fh = ebx + _KTHREAD.Quantun
jg      short loc_40C45D
cmp     ebx, [eax+12Ch]
jz      short loc_40C45D
mov     [eax+9ACh], esp        //ebx + 9ACh = eax + KPCR.PrcbData.QuantumEnd
mov     ecx, 2
call    ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)

3)KiDispatchinterrupt 函数,判断时间片到期:调用 KiQuantumEnd(重新设置时间片、找到要运行的线程)

KiDispatchinterrupt:

	sti
	cmp     dword ptr [ebx+9ACh], 0  //ebx + 9ACh = ebx + KPCR.PrcbData.QuantumEnd, 判断时间片是否到期
	jnz     short loc_404902         //到期跳转
	cmp     dword ptr [ebx+128h], 0
	jz      short locret_404901
	mov     eax, [ebx+128h]
			.
			.
			.

loc_404902:    //从上面跳转过来,重置标志位,查找下一个要执行的线程
	mov     dword ptr [ebx+9ACh], 0  //重置标志位 KPCR.PrcbData.QuantumEnd
	call    _KiQuantumEnd@0 ; KiQuantumEnd()
	or      eax, eax
	jnz     short loc_4048BB
	retn
			.
			.
			.

loc_4048BB:   //从上面跳转过来,这主要是线程切换                          
	sub     esp, 0Ch
	mov     [esp+0Ch+var_4], esi
	mov     [esp+0Ch+var_8], edi
	mov     [esp+0Ch+var_C], ebp
	mov     esi, eax
	mov     edi, [ebx+124h]
	mov     dword ptr [ebx+128h], 0
	mov     [ebx+124h], esi
	mov     ecx, edi
	mov     byte ptr [edi+50h], 1
	call    @KiReadyThread@4 ; KiReadyThread(x)
	mov     cl, 1
	call    SwapContext       //切换函数
	mov     ebp, [esp+0Ch+var_C]
	mov     edi, [esp+0Ch+var_8]
	mov     esi, [esp+0Ch+var_4]
	add     esp, 0Ch

KiQuantumEnd:

loc_41047B:                             
	movsx   edx, byte ptr [esi+33h]
	cmp     edx, 10h
	mov     al, [eax+63h]  //_KPROCESS.ThreadQuantum 重置这个标志位
	mov     [esi+6Fh], al
	mov     eax, edx
			.
			.
			.
loc_4104D2:                             
	movzx   ecx, byte ptr [esi+12Bh]
	call    @KiFindReadyThread@8 ; KiFindReadyThread(x,x) //查找下一个所要切换的线程
	cmp     eax, ebx
	jz      short loc_4104AF
	mov     byte ptr [eax+2Dh], 3
	mov     [edi+8], eax
	jmp     short loc_4104AF

存在备用线程(NextThread)

当 KPCR.PrcbData.NextThread(下一个要执行的线程)这个值被设置时,即使当前线程的CPU时间片没有到期,仍然会被切换。

KiDispatchinterrupt:

	sti
	cmp     dword ptr [ebx+9ACh], 0  //ebx + 9ACh = ebx + KPCR.PrcbData.QuantumEnd, 判断时间片是否到期
	jnz     short loc_404902         //到期跳转
	cmp     dword ptr [ebx+128h], 0  //ebx + 128h = ebx + KPCR.PrcbData.NextThread, 判断是否存在下一个可切换线程,没有则Return
	jz      short locret_404901
	mov     eax, [ebx+128h]

loc_4048BB:   //从上面跑下来,这主要是线程切换                          
	sub     esp, 0Ch
	mov     [esp+0Ch+var_4], esi
	mov     [esp+0Ch+var_8], edi
	mov     [esp+0Ch+var_C], ebp
	mov     esi, eax
	mov     edi, [ebx+124h]
	mov     dword ptr [ebx+128h], 0
	mov     [ebx+124h], esi
	mov     ecx, edi
	mov     byte ptr [edi+50h], 1
	call    @KiReadyThread@4 ; KiReadyThread(x)
	mov     cl, 1
	call    SwapContext       //切换函数
	mov     ebp, [esp+0Ch+var_C]
	mov     edi, [esp+0Ch+var_8]
	mov     esi, [esp+0Ch+var_4]
	add     esp, 0Ch

locret_404901:
	retn

当发生时钟中断时,先判断如果存在下一个要执行的线程就切换,如果不存在则继续执行当前线程。若不存在下一个要执行的线程,且当前线程的执行时间片用完,就查找32个调度链表,如果32个调度链表都为空,则说明没有可执行的线程,CPU将会执行空闲线程。

主动切换:

1)Windwos中绝大部分API都调用了SwapContext函数也就是说,当线程只要调用了API,就是导致了线程切换。

2)在SwapContext中,线程切换时会比较是否属于同一个进程,如果不是,切换Cr3,Cr3换了,进程也就切换了。

线程切换函数 KiSwapContext

线程切换都是通过函数 KiSwapContext 进行切换的:

1)当前线程主动调用API:

API函数 → KiSwapThread → KiSwapContext → SwapContext

2)当前线程时间片到期(时钟间隔每10-20毫秒检查一次):

KiDispatchinterrupt → KiQuantumEnd → KiSwapContext → SwapContext

2)当存在备用线程(时钟间隔每10-20毫秒检查一次):

KiDispatchinterrupt → SwapContext

线程切换的代码是不允许读取分页内存的,只能读取非分页内存,所以不会出现缺页异常。

线程切换 – 切换 – win7 x64

在Windows中只要调用 KiSwapContext 函数就好导致线程切换,无论是主动切换还是被动的时钟切换都会调用这个函数。

x64下fs寄存器的角色已经换成了gs。

gs:[0x30]                 TEB

gs:[0x40]                 Pid

gs:[0x48]                 Tid

gs:[0x60]                PEB

gs:[0x68]                 LastError

KiSwapContext:

	//调用 KiSwapContext
	call    KiQueueReadyThread  //返回等待队列的数据
    mov     rdx, rsi   //源线程的_ETHREA
    mov     rcx, rbx   //目标线程的 _ETHREA
    mov     [rbx+166h], r14b
    call    KiSwapContext

//KiSwapContext

KiSwapContext   proc near               ; CODE XREF: MiInsertPageInFreeOrZeroedList-8CBB4↑p
                               		 ; KeWaitForSingleObject-841AD↑p ...

var_108         = xmmword ptr -108h
var_F8          = xmmword ptr -0F8h
var_E8          = xmmword ptr -0E8h
var_D8          = xmmword ptr -0D8h
var_C8          = xmmword ptr -0C8h
var_38          = byte ptr -38h

             sub     rsp, 138h
             lea     rax, [rsp+138h+var_38]    //保存线程寄存器现场
             movaps  [rsp+138h+var_108], xmm6
             movaps  [rsp+138h+var_F8], xmm7
             movaps  [rsp+138h+var_E8], xmm8
             movaps  [rsp+138h+var_D8], xmm9
             movaps  [rsp+138h+var_C8], xmm10
            movaps  xmmword ptr [rax-80h], xmm11
             movaps  xmmword ptr [rax-70h], xmm12
             movaps  xmmword ptr [rax-60h], xmm13
             movaps  xmmword ptr [rax-50h], xmm14
             movaps  xmmword ptr [rax-40h], xmm15
             mov     [rax], rbx
             mov     [rax+8], rdi
             mov     [rax+10h], rsi
             mov     [rax+18h], r12
             mov     [rax+20h], r13
             mov     [rax+28h], r14
             mov     [rax+30h], r15
             mov     rbx, gs:20h
             mov     rdi, rcx     //rcx中保存要切换线程的 _ETHREA
             mov     rsi, rdx     //源线程的 _ETHREA
             movzx   ecx, byte ptr [rdi+166h]
             call    SwapContext
             lea     rcx, [rsp+138h+var_38]   //恢复新线程的各种寄存器
             movaps  xmm6, [rsp+138h+var_108]
             movaps  xmm7, [rsp+138h+var_F8]
             movaps  xmm8, [rsp+138h+var_E8]
             movaps  xmm9, [rsp+138h+var_D8]
             movaps  xmm10, [rsp+138h+var_C8]
             movaps  xmm11, xmmword ptr [rcx-80h]
             movaps  xmm12, xmmword ptr [rcx-70h]
             movaps  xmm13, xmmword ptr [rcx-60h]
             movaps  xmm14, xmmword ptr [rcx-50h]
             movaps  xmm15, xmmword ptr [rcx-40h]
             mov     rbx, [rcx]
             mov     rdi, [rcx+8]
             mov     rsi, [rcx+10h]
             mov     r12, [rcx+18h]
             mov     r13, [rcx+20h]
             mov     r14, [rcx+28h]
             mov     r15, [rcx+30h]
             add     rsp, 138h
             retn
KiSwapContext   endp

KiSwapContext 函数中没有发生切换,切换是在 SwapContext 函数中做的。线程切换本质就是堆栈的切换。堆栈切换前要先保存当前栈的ESP,然后修改ESP的值为目标栈的esp

SwapContext:

 SwapContext     proc near               ; CODE XREF: KiIdleLoop+108↑p
                                         ; KiSwapContext+75↑p ...

 BugCheckParameter4= qword ptr -18h
 var_10          = byte ptr -10h
 var_8           = qword ptr -8

                 sub     rsp, 38h
                 mov     [rsp+38h+var_8], rbp
                 prefetchw byte ptr [rsi+49h]
                 mov     [rsp+38h+var_10], cl
                 cmp     byte ptr [rsi+49h], 0
                 jnz     loc_140083403

 loc_14008319B:                          ; CODE XREF: SwapContext+289↓j
                 mov     byte ptr [rsi+49h], 1
                 cli
                 rdtsc
                 shl     rdx, 20h
                 or      rax, rdx
                 sub     rax, [rbx+4740h]
                 add     [rbx+4778h], rax
                 add     [rbx+4740h], rax
                 test    byte ptr [rbx+6], 0FFh
                 jz      short loc_1400831D9
                 and     byte ptr [rbx+6], 0
                 cmp     [rbx+18h], rsi
                 jz      short loc_1400831D9
                 mov     ecx, 2
                 call    cs:__imp_HalRequestSoftwareInterrupt

 loc_1400831D9:                          ; CODE XREF: SwapContext+42↑j
                                         ; SwapContext+4C↑j
                 test    byte ptr [rsi+2], 5
                 jnz     loc_1400833AA

 loc_1400831E3:                          ; CODE XREF: SwapContext+23E↓j
                 dec     byte ptr [rbx+20h]
                 sti

 loc_1400831E7:                          ; CODE XREF: SwapContext+255↓j
                 inc     dword ptr [rbx+20BCh]
                 movsx   eax, byte ptr [rdi+165h]
                 and     al, 0FDh
                 jz      short loc_140083216
                 mov     rbp, [rdi+28h]
                 lea     rcx, [rbp+50h]
                 cdq

 SwapContext_PatchXSave:                 ; DATA XREF: .data:KiXSaveOptPatchPointers↓o
                 fxsave  dword ptr [rcx]
                 test    al, 1
                 jz      short loc_140083216
                 test    word ptr [rcx+2], 80h
                 jz      short loc_140083212
                 fnclex

 loc_140083212:                          ; CODE XREF: SwapContext+8E↑j
                 ffree   st(7)
                 fild    dword ptr [rcx]

 loc_140083216:                          ; CODE XREF: SwapContext+76↑j
                                         ; SwapContext+86↑j
                 mov     [rdi+38h], rsp
                 mov     rsp, [rsi+38h]
                 test    byte ptr [rdi+3], 80h
                 jz      short loc_140083240
                 mov     ecx, 0C0000102h
                 rdmsr
                 mov     r14, [rdi+1B8h]
                 mov     [r14+80h], eax
                 mov     [r14+84h], edx

 loc_140083240:                          ; CODE XREF: SwapContext+A2↑j
                 mov     r14, [rsi+210h]
                 cmp     qword ptr [r14+118h], 0
                 jz      short loc_140083278
                 mov     rcx, [rbx-180h]
                 mov     rdx, [r14+108h]
                 mov     [rcx+70h], rdx
                 mov     rdx, [r14+110h]
                 mov     [rcx+78h], rdx
                 mov     rcx, 70h ; 'p'
                 lldt    cx

 loc_140083278:                          ; CODE XREF: SwapContext+CF↑j
                 mov     r14, [rsi+70h]
                 cmp     r14, [rdi+70h]
                 jz      short loc_1400832AB
                 mov     rdx, [rdi+70h]
                 lea     rcx, [rdx+88h]
                 mov     edx, [rbx+24h]
                 call    KeInterlockedClearProcessorAffinityEx
                 lea     rcx, [r14+88h]
                 mov     edx, [rbx+24h]
                 call    KeInterlockedSetProcessorAffinityEx
                 mov     rdx, [r14+28h]
                 mov     cr3, rdx

 loc_1400832AB:                          ; CODE XREF: SwapContext+100↑j
                 mov     r15, [rbx-178h]
                 mov     rbp, [rsi+28h]
                 mov     [r15+4], rbp
                 mov     [rbx+28h], rbp
                 test    cs:dword_140207684, 4
                 jnz     loc_1400833DA

 loc_1400832CE:                          ; CODE XREF: SwapContext+265↓j
                 mov     byte ptr [rdi+49h], 0
                 movsx   eax, byte ptr [rsi+165h]
                 and     al, 0FDh
                 jz      short loc_1400832E5
                 lea     rcx, [rbp+50h]
                 cdq

 SwapContext_PatchXRstor:                ; DATA XREF: .data:KiXRstorPatchPointers↓o
                 fxrstor dword ptr [rcx]

 loc_1400832E5:                          ; CODE XREF: SwapContext+15B↑j
                 bt      dword ptr [rsi+4Ch], 0Dh
                 jb      short loc_14008336B
                 mov     eax, [rsi+0B8h]
                 add     eax, 2000h
                 mov     rcx, [rbx-180h]
                 mov     [rcx+52h], ax
                 shr     eax, 10h
                 mov     [rcx+54h], al
                 mov     [rcx+57h], ah
                 mov     eax, ds
                 mov     ecx, es
                 and     eax, ecx
                 mov     ecx, gs
                 and     eax, ecx
                 cmp     ax, 2Bh ; '+'
                 jz      short loc_14008332E
                 mov     ecx, 2Bh ; '+'
                 mov     ds, ecx
                 assume ds:nothing
                 mov     es, ecx
                 assume es:nothing
                 cli
                 swapgs
                 mov     gs, ecx
                 assume gs:nothing
                 swapgs
                 sti

 loc_14008332E:                          ; CODE XREF: SwapContext+199↑j
                 mov     eax, 53h ; 'S'
                 mov     fs, eax
                 assume fs:nothing
                 mov     ecx, 0C0000102h
                 mov     rax, [rsi+0B8h]
                 mov     edx, [rsi+0BCh]
                 mov     [rbx-150h], rax
                 test    byte ptr [rsi+3], 80h
                 jz      short loc_140083369
                 mov     r8, [rsi+1B8h]
                 mov     eax, [r8+80h]
                 mov     edx, [r8+84h]

 loc_140083369:                          ; CODE XREF: SwapContext+1D2↑j
                 wrmsr

 loc_14008336B:                          ; CODE XREF: SwapContext+16A↑j
                 cmp     byte ptr [rbx+21DAh], 0
                 jnz     short loc_1400833EA
                 inc     dword ptr [rsi+134h]
                 cmp     byte ptr [rsi+79h], 1
                 jnz     short loc_14008339D
                 movzx   ax, [rsp+38h+var_10]
                 or      ax, [rsi+1C6h]
                 jz      short loc_14008339D
                 mov     ecx, 1
                 call    cs:__imp_HalRequestSoftwareInterrupt
                 or      rcx, rsp

 loc_14008339D:                          ; CODE XREF: SwapContext+1FE↑j
                                         ; SwapContext+20D↑j
                 setz    al
                 mov     rbp, [rsp+38h+var_8]
                 add     rsp, 38h
                 retn
 ;---------------------------------------------------------------------------

 loc_1400833AA:                          ; CODE XREF: SwapContext+5D↑j
                 test    byte ptr [rsi+2], 4
                 jz      short loc_1400833BA
                 mov     rcx, rsi
                 mov     dl, 1
                 call    KiBeginCounterAccumulation

 loc_1400833BA:                          ; CODE XREF: SwapContext+22E↑j
                 test    byte ptr [rsi+2], 1
                 jz      loc_1400831E3
                 dec     byte ptr [rbx+20h]
                 sti
                 mov     r8, rsi
                 mov     dl, 1
                 mov     rcx, rbx
                 call    PsCheckThreadCpuQuota
                 jmp     loc_1400831E7
 ;---------------------------------------------------------------------------

 loc_1400833DA:                          ; CODE XREF: SwapContext+148↑j
                 mov     rcx, rdi
                 mov     rdx, rsi
                 call    EtwTraceContextSwap
                 jmp     loc_1400832CE
 ;---------------------------------------------------------------------------

 loc_1400833EA:                          ; CODE XREF: SwapContext+1F2↑j
                 xor     r9, r9          ; BugCheckParameter3
                 mov     [rsp+38h+BugCheckParameter4], r9 ; BugCheckParameter4
                 mov     r8, rsi         ; BugCheckParameter2
                 mov     rdx, rdi        ; BugCheckParameter1
                 mov     ecx, 0B8h       ; BugCheckCode
                 call    KeBugCheckEx
 ;---------------------------------------------------------------------------
                 retn
 ;---------------------------------------------------------------------------

 loc_140083403:                          ; CODE XREF: SwapContext+15↑j
                                         ; SwapContext+28F↓j
                 pause
                 cmp     byte ptr [rsi+49h], 0
                 jz      loc_14008319B
                 jmp     short loc_140083403
 SwapContext     endp

线程切换 – 切换 – XP

新线程 == 要切换的线程,当前线程 == 正在执行中的线程,即KPCR中保存的线程。

KiSwapContext:

 KiSwapContext@4 proc near              ; CODE XREF: KiSwapThread()+33↓p

 var_10          = dword ptr -10h
 var_C           = dword ptr -0Ch
 var_8           = dword ptr -8
 var_4           = dword ptr -4

                 sub     esp, 10h
                 mov     [esp+10h+var_4], ebx   //保存堆栈了
                 mov     [esp+10h+var_8], esi
                 mov     [esp+10h+var_C], edi
                 mov     [esp+10h+var_10], ebp
                 mov     ebx, ds:0FFDFF01Ch   //在XP中, FFDFF000H 指向 KPCR, KPCR 01c指向自己,ebx = KPCR
                 mov     esi, ecx             //新线程的 ETHREAD
                 mov     edi, [ebx+124h]      //获取当前线程的ETHREAD,ebx+124h = KPCR+124h = CurrentThread:Ptr32 _ETHREAD 

                 mov     [ebx+124h], esi      //将新线程的ETHREAD写入KPCR,
                 mov     cl, [edi+58h]        //当前线程的获取等待优先级
                 call    SwapContext
                 mov     ebp, [esp+10h+var_10]
                 mov     edi, [esp+10h+var_C]
                 mov     esi, [esp+10h+var_8]
                 mov     ebx, [esp+10h+var_4]
                 add     esp, 10h
                 retn
 @KiSwapContext@4 endp

SwapContext:

SwapContext     proc near               ; CODE XREF: KiUnlockDispatcherDatabase(x)+72↑p
                                         ; KiSwapContext(x)+29↑p ...
                 or      cl, cl
                 mov     byte ptr es:[esi+2Dh], 2   //线程状态 -- 就绪、等待、运行。 2 == 运行,
													设置新线程的状态 ETHREAD+2Dh = 0x02d State:UChar
                 pushf

 loc_40492C:                             ; CODE XREF: KiIdleLoop()+5A↓j
                 mov     ecx, [ebx]     //获取异常链表 
                 cmp     dword ptr [ebx+994h], 0
                 push    ecx            //保存到当前进程堆栈
                 jnz     loc_404A70
                 cmp     ds:_PPerfGlobalGroupMask, 0
                 jnz     loc_404A47

 loc_404949:                             ; CODE XREF: SwapContext+12B↓j
                                         ; SwapContext+13C↓j ...
                 mov     ebp, cr0
                 mov     edx, ebp
                 mov     cl, [esi+2Ch]   //获取新线程调试状态 0x02c DebugActive:UChar
                 mov     [ebx+50h], cl   //将新线程的调试状态写入 KPCR 
                 cli
                 mov     [edi+28h], esp   //保存堆栈: edi(ETHREAD)+28h  =  +0x028 KernelStack:Ptr32 Void(内核堆栈栈顶即ESP0),
										  edi中是当前的 ETHREAD 每个线程都有一个ESP0,从 KernelStack 中获取,
										  从其他线程切换到当前线程时会将该值写到TSS中,
										  当从当前线程切换到其他线程时,它就会更新 KernelStack 保存新的栈顶。
					  
                 mov     eax, [esi+18h]   //获取新线程的起始堆栈 ETHREAD -> InitialStack:Ptr32 Void
                 mov     ecx, [esi+1Ch]   //获取新线程的堆栈范围 ETHREAD -> StackLimit:Ptr32 Void
                 sub     eax, 210h        //减去浮点寄存器的大小,此时 eax 指向 _Trap_Frame(内核栈) 结构开始的地方
                 mov     [ebx+8], ecx     //将新线程的堆栈范围写入到KPCR中,ebx+8 = KPCR + 8 = _NT_TIB + 8 = StackLimit:Ptr32 Void
                 mov     [ebx+4], eax     //将新线程的起始堆栈写入到KPCR中,ebx+4 = KPCR + 8 = _NT_TIB + 4 = StackBase:Ptr32 Void
                 xor     ecx, ecx
                 mov     cl, [esi+31h]   //获取新线程的 NpxState
                 and     edx, 0FFFFFFF1h
                 or      ecx, edx
                 or      ecx, [eax+20Ch]
                 cmp     ebp, ecx
                 jnz     loc_404A3F
                 lea     ecx, [ecx]

 loc_404983:                             ; CODE XREF: SwapContext+11E↓j
                 test    dword ptr [eax-1Ch], 20000h  //设置 eax 其值位 ESP0
                 jnz     short loc_40498F
                 sub     eax, 10h         //跳过 _Trap_Frame 开始的4个成员,那是虚拟8086使用的

 loc_40498F:                             ; CODE XREF: SwapContext+66↑j
                 mov     ecx, [ebx+40h]  //获取TSS的值 ebx + 40h = KPCR + 40h = TSS:Ptr32 _KTSS 
                 mov     [ecx+4], eax    //写入值到TSS + 4的位置,ecx+4 = TSS + 4 = ESP0 ,
										 将新线程的ESP0保存到TSS,TSS中一直保存当前正在执行线程中的ESP0

     重点:      mov     esp, [esi+28h]    //切换堆栈: esi+28h = KernelStack(ESP0),
											将新线程的ESP0赋值给ESP,即现在ESP指向的是新线程的堆栈,完成线程切换,
											从现在开始下面的esp都是新线程的值

                 mov     eax, [esi+20h]   //获取新线程 TEB 的地址,esi+20h = ETHREAD + 20H = Teb:Ptr32 Void
                 mov     [ebx+18h], eax   //将新线程的 TEB 写入到KPCR中,
											ebx + 18h = KPCR + 18h = _NT_TIB + 8 = Self: Ptr32 _NT_TIB(保存指向自己TIB的指针)
                 sti
                 mov     eax, [edi+44h]   //获取当前线程的进程值_KPROCESS, 
											edi+44h = _KTHREAD + 44H = (+034 ApcState:_KAPC_STATE) + 10H = Process:Ptr32_KPROCESS

判断是否切换进程   cmp     eax, [esi+44h]   //同 esi + 44h 保存线程所在进程的_KPROCESS,
											所以 CMP 比较两个线程是否所在同一个进程中,不在则切换进程,即切换CR3,否则切换 

                 mov     byte ptr [edi+50h], 0 //是否存在用户apc函数,存在为1,不存在为0

切换进程跳转函数   jz      short loc_4049D7  //如果切换了进程则不跳转,否则跳转

                 mov     edi, [esi+44h]    //将新线程所在的进程值(_KPROCESS),写入到edi
                 test    word ptr [edi+20h], 0FFFFh //判断LDT存在?,edi+20h = _KPROCESS + 20h = LdtDescriptor:_KGDTENTRY
                 jnz     short loc_404A11
                 xor     eax, eax

 loc_4049B8:                             ; CODE XREF: SwapContext+116↓j
                 lldt    ax              //加载中断描述符
                 xor     eax, eax        //清空eax
                 mov     gs, eax         //清空 GS 寄存器
                 assume gs:GAP
                 mov     eax, [edi+18h]  //获取新进程的页目录表基址(CR3),
										  edi+18h =  _KPROCESS + 18h = DirectoryTableBase:[2] Uint4B


                 mov     ebp, [ebx+40h]  //获取TSS的值 ebx + 40h = KPCR + 40h = TSS:Ptr32 _KTSS 
                 mov     ecx, [edi+30h]  //获取新线程的IopmOffset, edi+30h =  _KPROCESS + 30h = IopmOffset:Uint2B
更新TSS中CR3      mov     [ebp+1Ch], eax  //将新进程CR3保存到TSS中存储CR3的位置 CR3 = TSSBase + 1Ch
切换进程,切换CR3  mov     cr3, eax        //将新进程的CR3 写到 CR3寄存器中
                 mov     [ebp+66h], cx   //将IopmOffset的值写入到TSS的IoMapBase位置 
                 jmp     short loc_4049D7
 ; ---------------------------------------------------------------------------
                 db 8Dh, 49h, 0
 ; ---------------------------------------------------------------------------

 loc_4049D7:                             ; CODE XREF: SwapContext+85↑j
                                         ; SwapContext+AE↑j
                 mov     eax, [ebx+18h]  //获取KPCR 中 _NT_TIB 结构体指针,Self: Ptr32 _NT_TIB(TEB)
				 mov     ecx, [ebx+3Ch]  //获取KPCR中 GDT表,GDT: Ptr32 _KGDTENTRY
	//重写FS寄存器段描述符指向线程TEB的地址,将该地址换为新线程的TEB的地址										
				 mov     [ecx+3Ah], ax  //更新GDT表中段描述符的Base,写入 段描述符的 低 16 - 31位 ,ax = TEB的低 16位
				 shr     eax, 10h       //获取 TEB地址的 高 16位
				 mov     [ecx+3Ch], al  //更新Base TEB低8位,写入 段描述符的 高 0 - 7位
				 mov     [ecx+3Fh], ah  //更新Base TEB高8位,写入 段描述符的 高 24 - 31位
								//设置 FS 通过段描述符,指向TEB(3环)或_NT_TIB(同时也是_ETREAD的基址)

										//设置 FS 通过段描述符,指向TEB(3环)或_NT_TIB(同时也是_ETREAD的基址)

                 inc     dword ptr [esi+4Ch]  //esi+4Ch =  _KTHREAD + 4cH = ContextSwitches:Uint4B
                 inc     dword ptr [ebx+61Ch] //ebx+61Ch = ????
                 pop     ecx                  //读取新堆栈中的异常量表
                 mov     [ebx], ecx           //将新堆栈中的异常量表赋值给KPCR的第一个成员
                 cmp     byte ptr [esi+49h], 0
                 jnz     short loc_404A00
                 popf
                 xor     eax, eax             //清空eax
                 retn
 ; ---------------------------------------------------------------------------

 loc_404A00:                             ; CODE XREF: SwapContext+D6↑j
                 popf
                 jnz     short loc_404A06
                 mov     al, 1
                 retn    //注意Retn 并不会返回到KiSwapContext,而是会返回到要切换线程所执行函数的地址
 ; ---------------------------------------------------------------------------

 loc_404A06:                             ; CODE XREF: SwapContext+DD↑j
                 mov     cl, 1
                 call    ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)
                 xor     eax, eax
                 retn    //注意Retn 并不会返回到KiSwapContext,而是会返回到要切换线程所执行函数的地址
 ; ---------------------------------------------------------------------------
 // ebx = _KPCR  \ edi = _KPROCESS(如果没有切换进程则是当前进程的_KPROCESS,如果切换了则是新进程的_KPROCESS)
 loc_404A11:                             ; CODE XREF: SwapContext+90↑j
                 mov     ebp, [ebx+3Ch]  //获取KPC中 GDT表,GDT: Ptr32 _KGDTENTRY
                 mov     eax, [edi+20h]  //获取 LDT表的基址,edi+20h = _KPROCESS + 20h = LdtDescriptor:_KGDTENTRY
                 mov     [ebp+48h], eax  //
                 mov     eax, [edi+24h]  //获取 Access,edi+24h = _KPROCESS + 20H + 4H 
											= _KIDTENTRY + 4H = Access: Uint2B 

                 mov     [ebp+4Ch], eax
                 mov     eax, 48h ; 
                 mov     ebp, [ebx+38h]  //获取KPCR中IDT表的值,下面是构建中断描述符
                 mov     ecx, [edi+28h]  //获取 Int21Descriptor 的值, _KIDTENTRY + 28H = Int21Descriptor:_KIDTENTRY
                 mov     [ebp+108h], ecx
                 mov     ecx, [edi+2Ch]  //????
                 mov     [ebp+10Ch], ecx
                 jmp     loc_4049B8
 ; ---------------------------------------------------------------------------

 loc_404A3F:                             ; CODE XREF: SwapContext+57↑j
                 mov     cr0, ecx
                 jmp     loc_404983
 ; ---------------------------------------------------------------------------

 loc_404A47:                             ; CODE XREF: SwapContext+1F↑j
                 mov     eax, ds:_PPerfGlobalGroupMask
                 cmp     eax, 0
                 jz      loc_404949
                 mov     edx, esi
                 mov     ecx, edi
                 test    dword ptr [eax+4], 4
                 jz      loc_404949
                 call    @WmiTraceContextSwap@8 ; WmiTraceContextSwap(x,x)
                 jmp     loc_404949
 ; ---------------------------------------------------------------------------

 loc_404A70:                             ; CODE XREF: SwapContext+12↑j
                 push    0B8h            ; BugCheckCode
                 call    _KeBugCheck@4   ; KeBugCheck(x)
SwapContext     endp

流程:

若线程A和C线程调用函数,A的线程函数y和C的线程函数n:

A -> y.SwapContext(发生线程切换,切换到C)-> C(y.SwapContext retu到C线程所调用的函数N) -> n.SwapContext(发生线程切换,切换回到A) 此时A线程会接着y.SwapContext继续执行。 

也就说当A线程调用y函数时,在y函数中发生线程切换,切换到C线程,此时A线程的 SwapContext 函数将会直接 Retu 到C线程的函数n中开始执行(因为堆栈换了,当前堆栈已经切换到C线程的堆栈了,Retu指令pop出来的返回地址已经变成C线程堆栈中函数n的地址了),当在n函数中发生线程切换,切换回到A线程,此时将会直接Retu到A进程上次执行的位置(此时的堆栈已经从C线程切换到A线程了),即接着 SwapContext 向后继续运行。即 Retu 返回的地址是不固定的主要看要切换线程的堆栈中返回地址的值。

					          设置新线程状态为运行
							   ↓
				        将当前线程的异常链表保存到自己的堆栈
							   ↓
			                      将新线程的调试状态写入 KPCR 
						           ↓
					       保存堆栈将当前线程的ESP0 
							   ↓
                                更新KPCR中线程堆栈信息为要切换线程信息(起始堆栈、堆栈范围)
							   ↓
	                                       将新线程的 ESP0 保存到TSS中
							   ↓
        将新线程的ESP0赋值给ESP,即现在ESP指向的是新线程的堆栈,完成线程切换 注:从现在开始向下堆栈已经切换成新的线程的了
							   ↓
				              将新线程的 TEB 写入到KPCR中
				                           ↓
	                       比较两个线程是否所在同一个进程中,不在则切换进程,即切换CR3,否则切换  →   →  →  →   → 
							   ↓
							   ↓Y(不在同一进程)                                        ↓   N(在同一进程)              
							   ↓
					      获取新进程的页目录表基址(CR3)                                        ↓
							   ↓
				                 获取新线程的IopmOffset                                            ↓
							   ↓
				            将新进程CR3保存到TSS中存储CR3的位置                                    ↓
							   ↓
				            将新进程的CR3 写到 CR3寄存器中  (切换CR3)                            ↓
							   ↓
			                 将IopmOffset的值写入到TSS的IoMapBase位置                                  ↓
							   ↓
							   ↓  ←  ←     ←      ←   ←    ←   ←     ←    ←   ←    ←        
							   ↓
					       获取KPCR 中 _NT_TIB 结构体指针
						           ↓	
					             获取KPCR中 GDT表
							   ↓
                               重写FS寄存器段描述符中的Base值,使其指向TEB(即三环的FS:[0])
							   ↓
				            从新堆栈中将异常链表保存到KPCR中
							   ↓
          Retn (注意Retn 并不会返回到KiSwapContext,返回的地址是不固定的主要看要切换线程的堆栈中返回地址的值,EIP)

总结:

线程就是:EIP,EIP在哪哪就是线程。

线程切换就是:ESp

进程就是:CR3

访问内存如果发生缺页,就会进入缺页异常处理函数,发生线程切换。

寄存器FS和GS是段寄存器。 它们没有处理器定义的目的,而是由操作系统运行它们来实现目的。 在Windows 64位中,GS寄存器用于指向操作系统定义的结构。 OS内核通常使用FS和GS来访问特定于线程的内存。 在Windows中,GS寄存器用于管理特定于线程的内存, 通常用作指向线程本地存储(TLS)的指针。。 linux内核使用GS来访问特定于CPU的内存。

正常情况下,当前线程使用的CR3是由其所属进程提供的,正是因为如此,A进程中的线程只能访问A的内存。如果要让A进程中的线程能够访问B进程的内存,就必须要修改CR3的值为B进程的页目录表基址,这就是所谓的“进程挂靠”。

备注:XP中_ETHREAD _KTHREAD KPCR KPCRB _NT_TIB TSS _KAPC_STATE结构体

_KPCR:

kd> dt _KPCR
nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x01c SelfPcr          : Ptr32 _KPCR
   +0x020 Prcb             : Ptr32 _KPRCB
   +0x024 Irql             : UChar
   +0x028 IRR              : Uint4B
   +0x02c IrrActive        : Uint4B
   +0x030 IDR              : Uint4B
   +0x034 KdVersionBlock   : Ptr32 Void
   +0x038 IDT              : Ptr32 _KIDTENTRY
   +0x03c GDT              : Ptr32 _KGDTENTRY
   +0x040 TSS              : Ptr32 _KTSS
   +0x044 MajorVersion     : Uint2B
   +0x046 MinorVersion     : Uint2B
   +0x048 SetMember        : Uint4B
   +0x04c StallScaleFactor : Uint4B
   +0x050 DebugActive      : UChar
   +0x051 Number           : UChar
   +0x052 Spare0           : UChar
   +0x053 SecondLevelCacheAssociativity : UChar
   +0x054 VdmAlert         : Uint4B
   +0x058 KernelReserved   : [14] Uint4B
   +0x090 SecondLevelCacheSize : Uint4B
   +0x094 HalReserved      : [16] Uint4B
   +0x0d4 InterruptMode    : Uint4B
   +0x0d8 Spare1           : UChar
   +0x0dc KernelReserved2  : [17] Uint4B
   +0x120 PrcbData         : _KPRCB

_KPRCB:

kd> dt _KPRCB
ntdll!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD
   +0x008 NextThread       : Ptr32 _KTHREAD
   +0x00c IdleThread       : Ptr32 _KTHREAD
   +0x010 Number           : Char
   +0x011 Reserved         : Char
   +0x012 BuildType        : Uint2B
   +0x014 SetMember        : Uint4B
   +0x018 CpuType          : Char
   +0x019 CpuID            : Char
   +0x01a CpuStep          : Uint2B
   +0x01c ProcessorState   : _KPROCESSOR_STATE
   +0x33c KernelReserved   : [16] Uint4B
   +0x37c HalReserved      : [16] Uint4B
   +0x3bc PrcbPad0         : [92] UChar
   +0x418 LockQueue        : [16] _KSPIN_LOCK_QUEUE
   +0x498 PrcbPad1         : [8] UChar
   +0x4a0 NpxThread        : Ptr32 _KTHREAD
   +0x4a4 InterruptCount   : Uint4B
   +0x4a8 KernelTime       : Uint4B
   +0x4ac UserTime         : Uint4B
   +0x4b0 DpcTime          : Uint4B
   +0x4b4 DebugDpcTime     : Uint4B
   +0x4b8 InterruptTime    : Uint4B
   +0x4bc AdjustDpcThreshold : Uint4B
   +0x4c0 PageColor        : Uint4B
   +0x4c4 SkipTick         : Uint4B
   +0x4c8 MultiThreadSetBusy : UChar
   +0x4c9 Spare2           : [3] UChar
   +0x4cc ParentNode       : Ptr32 _KNODE
   +0x4d0 MultiThreadProcessorSet : Uint4B
   +0x4d4 MultiThreadSetMaster : Ptr32 _KPRCB
   +0x4d8 ThreadStartCount : [2] Uint4B
   +0x4e0 CcFastReadNoWait : Uint4B
   +0x4e4 CcFastReadWait   : Uint4B
   +0x4e8 CcFastReadNotPossible : Uint4B
   +0x4ec CcCopyReadNoWait : Uint4B
   +0x4f0 CcCopyReadWait   : Uint4B
   +0x4f4 CcCopyReadNoWaitMiss : Uint4B
   +0x4f8 KeAlignmentFixupCount : Uint4B
   +0x4fc KeContextSwitches : Uint4B
   +0x500 KeDcacheFlushCount : Uint4B
   +0x504 KeExceptionDispatchCount : Uint4B
   +0x508 KeFirstLevelTbFills : Uint4B
   +0x50c KeFloatingEmulationCount : Uint4B
   +0x510 KeIcacheFlushCount : Uint4B
   +0x514 KeSecondLevelTbFills : Uint4B
   +0x518 KeSystemCalls    : Uint4B
   +0x51c SpareCounter0    : [1] Uint4B
   +0x520 PPLookasideList  : [16] _PP_LOOKASIDE_LIST
   +0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x7a0 PacketBarrier    : Uint4B
   +0x7a4 ReverseStall     : Uint4B
   +0x7a8 IpiFrame         : Ptr32 Void
   +0x7ac PrcbPad2         : [52] UChar
   +0x7e0 CurrentPacket    : [3] Ptr32 Void
   +0x7ec TargetSet        : Uint4B
   +0x7f0 WorkerRoutine    : Ptr32     void 
   +0x7f4 IpiFrozen        : Uint4B
   +0x7f8 PrcbPad3         : [40] UChar
   +0x820 RequestSummary   : Uint4B
   +0x824 SignalDone       : Ptr32 _KPRCB
   +0x828 PrcbPad4         : [56] UChar
   +0x860 DpcListHead      : _LIST_ENTRY
   +0x868 DpcStack         : Ptr32 Void
   +0x86c DpcCount         : Uint4B
   +0x870 DpcQueueDepth    : Uint4B
   +0x874 DpcRoutineActive : Uint4B
   +0x878 DpcInterruptRequested : Uint4B
   +0x87c DpcLastCount     : Uint4B
   +0x880 DpcRequestRate   : Uint4B
   +0x884 MaximumDpcQueueDepth : Uint4B
   +0x888 MinimumDpcRate   : Uint4B
   +0x88c QuantumEnd       : Uint4B
   +0x890 PrcbPad5         : [16] UChar
   +0x8a0 DpcLock          : Uint4B
   +0x8a4 PrcbPad6         : [28] UChar
   +0x8c0 CallDpc          : _KDPC
   +0x8e0 ChainedInterruptList : Ptr32 Void
   +0x8e4 LookasideIrpFloat : Int4B
   +0x8e8 SpareFields0     : [6] Uint4B
   +0x900 VendorString     : [13] UChar
   +0x90d InitialApicId    : UChar
   +0x90e LogicalProcessorsPerPhysicalProcessor : UChar
   +0x910 MHz              : Uint4B
   +0x914 FeatureBits      : Uint4B
   +0x918 UpdateSignature  : _LARGE_INTEGER
   +0x920 NpxSaveArea      : _FX_SAVE_AREA
   +0xb30 PowerState       : _PROCESSOR_POWER_STATE

_KTHREAD:

kd> dt _KTHREAD
ntdll!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   +0x020 Teb              : Ptr32 Void
   +0x024 TlsArray         : Ptr32 Void
   +0x028 KernelStack      : Ptr32 Void
   +0x02c DebugActive      : UChar
   +0x02d State            : UChar
   +0x02e Alerted          : [2] UChar
   +0x030 Iopl             : UChar
   +0x031 NpxState         : UChar
   +0x032 Saturation       : Char
   +0x033 Priority         : Char
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : Uint4B
   +0x050 IdleSwapBlock    : UChar
   +0x051 Spare0           : [3] UChar
   +0x054 WaitStatus       : Int4B
   +0x058 WaitIrql         : UChar
   +0x059 WaitMode         : Char
   +0x05a WaitNext         : UChar
   +0x05b WaitReason       : UChar
   +0x05c WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : Uint4B
   +0x06c BasePriority     : Char
   +0x06d DecrementCount   : UChar
   +0x06e PriorityDecrement : Char
   +0x06f Quantum          : Char
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : Ptr32 Void
   +0x0d4 KernelApcDisable : Uint4B
   +0x0d8 UserAffinity     : Uint4B
   +0x0dc SystemAffinityActive : UChar
   +0x0dd PowerState       : UChar
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY
   +0x120 SoftAffinity     : Uint4B
   +0x124 Affinity         : Uint4B
   +0x128 Preempted        : UChar
   +0x129 ProcessReadyQueue : UChar
   +0x12a KernelStackResident : UChar
   +0x12b NextProcessor    : UChar
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void
   +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x140 PreviousMode     : Char
   +0x141 EnableStackSwap  : UChar
   +0x142 LargeStack       : UChar
   +0x143 ResourceIndex    : UChar
   +0x144 KernelTime       : Uint4B
   +0x148 UserTime         : Uint4B
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : UChar
   +0x165 ApcStateIndex    : UChar
   +0x166 ApcQueueable     : UChar
   +0x167 AutoAlignment    : UChar
   +0x168 StackBase        : Ptr32 Void
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY
   +0x1b8 FreezeCount      : Char
   +0x1b9 SuspendCount     : Char
   +0x1ba IdealProcessor   : UChar
   +0x1bb DisableBoost     : UChar

_ETHREAD:

kd> dt _ETHREAD
ntdll!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x1c0 CreateTime       : _LARGE_INTEGER
   +0x1c0 NestedFaultCount : Pos 0, 2 Bits
   +0x1c0 ApcNeeded        : Pos 2, 1 Bit
   +0x1c8 ExitTime         : _LARGE_INTEGER
   +0x1c8 LpcReplyChain    : _LIST_ENTRY
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY
   +0x1d0 ExitStatus       : Int4B
   +0x1d0 OfsChain         : Ptr32 Void
   +0x1d4 PostBlockList    : _LIST_ENTRY
   +0x1dc TerminationPort  : Ptr32 _TERMINATION_PORT
   +0x1dc ReaperLink       : Ptr32 _ETHREAD
   +0x1dc KeyedWaitValue   : Ptr32 Void
   +0x1e0 ActiveTimerListLock : Uint4B
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : Ptr32 Void
   +0x208 LpcWaitingOnPort : Ptr32 Void
   +0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
   +0x210 IrpList          : _LIST_ENTRY
   +0x218 TopLevelIrp      : Uint4B
   +0x21c DeviceToVerify   : Ptr32 _DEVICE_OBJECT
   +0x220 ThreadsProcess   : Ptr32 _EPROCESS
   +0x224 StartAddress     : Ptr32 Void
   +0x228 Win32StartAddress : Ptr32 Void
   +0x228 LpcReceivedMessageId : Uint4B
   +0x22c ThreadListEntry  : _LIST_ENTRY
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : Uint4B
   +0x240 ReadClusterSize  : Uint4B
   +0x244 GrantedAccess    : Uint4B
   +0x248 CrossThreadFlags : Uint4B
   +0x248 Terminated       : Pos 0, 1 Bit
   +0x248 DeadThread       : Pos 1, 1 Bit
   +0x248 HideFromDebugger : Pos 2, 1 Bit
   +0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
   +0x248 SystemThread     : Pos 4, 1 Bit
   +0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
   +0x248 BreakOnTermination : Pos 6, 1 Bit
   +0x248 SkipCreationMsg  : Pos 7, 1 Bit
   +0x248 SkipTerminationMsg : Pos 8, 1 Bit
   +0x24c SameThreadPassiveFlags : Uint4B
   +0x24c ActiveExWorker   : Pos 0, 1 Bit
   +0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
   +0x24c MemoryMaker      : Pos 2, 1 Bit
   +0x250 SameThreadApcFlags : Uint4B
   +0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
   +0x250 LpcExitThreadCalled : Pos 1, 1 Bit
   +0x250 AddressSpaceOwner : Pos 2, 1 Bit
   +0x254 ForwardClusterOnly : UChar
   +0x255 DisablePageFaultClustering : UChar

_NT_TIB:

kd> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

_KTSS:

kd> dt _KTSS
nt!_KTSS
   +0x000 Backlink         : Uint2B
   +0x002 Reserved0        : Uint2B
   +0x004 Esp0             : Uint4B
   +0x008 Ss0              : Uint2B
   +0x00a Reserved1        : Uint2B
   +0x00c NotUsed1         : [4] Uint4B
   +0x01c CR3              : Uint4B
   +0x020 Eip              : Uint4B
   +0x024 EFlags           : Uint4B
   +0x028 Eax              : Uint4B
   +0x02c Ecx              : Uint4B
   +0x030 Edx              : Uint4B
   +0x034 Ebx              : Uint4B
   +0x038 Esp              : Uint4B
   +0x03c Ebp              : Uint4B
   +0x040 Esi              : Uint4B
   +0x044 Edi              : Uint4B
   +0x048 Es               : Uint2B
   +0x04a Reserved2        : Uint2B
   +0x04c Cs               : Uint2B
   +0x04e Reserved3        : Uint2B
   +0x050 Ss               : Uint2B
   +0x052 Reserved4        : Uint2B
   +0x054 Ds               : Uint2B
   +0x056 Reserved5        : Uint2B
   +0x058 Fs               : Uint2B
   +0x05a Reserved6        : Uint2B
   +0x05c Gs               : Uint2B
   +0x05e Reserved7        : Uint2B
   +0x060 LDT              : Uint2B
   +0x062 Reserved8        : Uint2B
   +0x064 Flags            : Uint2B
   +0x066 IoMapBase        : Uint2B
   +0x068 IoMaps           : [1] _KiIoAccessMap
   +0x208c IntDirectionMap  : [32] UChar

_KAPC_STATE:

ntdll!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : UChar