进程与线程 – 线程切换(二)
一个线程执行至少需要寄存器和堆栈,线程切换本质就是堆栈的切换。
线程切换分为两种:主动切换和被动切换。只要调用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