一.什么是TSS:
TSS全称Task State Segment,中文名任务状态段,储存在内存中,大小104个字节,结构如下:(每个CPU,就是每个核,都有一个TSS,但是每个线程都有自己的ESP0和Ktrap_frame 结构体)
二.TSS原本的作用:
TSS在任务(线程)切换时起着重要的作用,通过它保存CPU中各寄存器的值,实现任务的挂起和恢复。
比如说,当CPU执行A进程的时间片用完,要切换到B进程时,CPU会先把当前寄存器里的值保存到A进程的TSS里(任务寄存器TR指向当前进程的TSS),比如CS,EIP,ESP,标志寄存器等等,然后挂起A进程。执行B进程。这样,在CPU下次执行A进程的时候,就可以从其TSS中取出,CPU就知道上一次A进程执行到了什么位置,执行状态等等,这样就恢复了上次A进程的执行现场。(但是Windows和Linux并没有使用TSS进行任务切换的工作,是使用的堆栈来实现的任务(也就是线程)的切换.但是在执行调用门操作的时候,ESP和SS的值就是通过TSS来自动进行切换的.)
三.CPU如何调用TSS:
CPU中有一个TR段寄存器,存出一个段描述符,存在于GDT段描述符表中,属于系统段描述符的一种,base指向TSS表的地址,limit表示他的偏移范围.
四.我们对于TSS的调用:
1.申请内存空间,自己写一个TSS表(104字节),并为表中的寄存器赋值.
2.修改GDT表中的段描述符,让其Base指向我们自己的TSS表
3.CALL FAR 或者 JMP FAR,跳到段描述符,使用我们的TSS表.(会先自动改写TR寄存器,不需要LTR在改了.)
1)也可以通过LTR指令:修改TR寄存器的段选择子,让TR寄存器指向GDT表中我们改写的TR段描述符.(LTR是系统指令,只有在R0层,才能使用)
mov ax,4B(GDT中,自己写的段描述符)
ltr ax
*加载后TSS段描述符状态会更改(就是上图的B位),TYPE的值由1001变为1011.
*如果要读TR寄存器的值,也就是段选择子,可以用STR指令.
上述操作完成,可以一次性改写一堆寄存器,在指向TSS表的时候,我们可以使用CALL FAR 和JMP FAR指令来进行,需要注意的是二者的区别, CALL FAR后NT位为1,返回时,EIP会取Previous Task Link的值,JMP FAR后NT位为0,返回时,EIP从堆栈中取值.
五.Intel设计TSS的目的是为了任务切换(线程切换),但Windows与Linux , 并没有使用。而是采用堆栈来保存线程的各种寄存器。
一个CPU只有一个TSS.但是线程很多,如何用一个TSS来保存所有线程的ESP0呢?
下面来看看SwapContext代码分析:
SwapContext proc near ; CODE XREF: KiUnlockDispatcherDatabase(x)+72p ; KiSwapContext(x)+29p ... or cl, cl mov es:[esi+_ETHREAD.Tcb.State],2 ; _KTHREAD(+0x2d state)改为2 pushf ; 存储当前的EFLAGS寄存器 loc_40492C: ; CODE XREF: KiIdleLoop()+5Aj mov ecx,[ebx+KPCR+NtTib.ExceptionList] ; ebx存储KPCR的地址 此处是读出异常链表 3环的FS:[0] cmp [ebx+KPCR.PrcbData.DpcRoutineActive],0 ; 是否有DPC有就蓝屏 push ecx jnz loc_404A70 cmp ds:_PPerfGlobalGroupMask, 0 ; LOG用的 Windows自己调试用的别的地方没用 jnz loc_404A47 loc_404949: ; CODE XREF: SwapContext+12Bj ; SwapContext+13Cj ... mov ebp, cr0 ; CR0中的保护控制位 mov edx, ebp mov cl, [esi+2Ch] mov [ebx+50h], cl cli mov [edi+_ETHREAD.Tcb.KernelStack],esp ; 将当前的esp存储到原线程结构中 mov eax,[_ETHREAD.Tcb.InitialStack] ; 目标线程栈底 mov ecx,[esi+_ETHREAD.Tcb.StackLimt] sub eax, 210h ; -210h 浮点寄存器 mov [ebx+KPCR.NtTib.StackLimit],ecx mov [ebx+KPCR.NtTib.StackBase],eax xor ecx, ecx mov cl,[esi+_ETHREAD.Tcb.NpxState] ; NpxState 浮点寄存器 运行浮点用这个 没运行就不用 and edx, 0FFFFFFF1h ; 判断NpxState有没有浮点支持 ; 如果上一个线程和要替换的线程对浮点支持是一样的那就不用换CP0 如果不一样 or ecx, edx or ecx, [eax+20Ch] cmp ebp, ecx jnz loc_404A3F lea ecx, [ecx] loc_404983: test dword ptr [eax-1Ch], 20000h jnz short loc_40498F ; 通过KPCR取出TSS sub eax, 10h ; 再减去10h(虚拟8086模式下使用 TrapFrame结构) loc_40498F: mov ecx,[ebx+KPCR.TSS] ; 通过KPCR取出TSS mov [ecx+4], eax ; 将修正后的栈底储存到TSS中 mov esp,[_ETHREAD.Tcb.KernelStatck] ; 将目标线程的ESP存储到ESP中 mov eax,[esi+_ETHREAD.Tcb.Teb] ; 当前线程有很多状态一份在ETHREAD里面 ; 还有一个备份在FS中 mov ecx,[ebx+KPCR.TSS] ; 通过KPCR取TSS sti mov eax,[edi+_ETHREAD.Tcb.ApcState.Process] cmp eax,[esi+_ETHREAD.Tcb.ApcState.Process] mov [edi+_ETHREAD.Tcb.IdleSwapBlock],0 jz short loc_4049D7 ; 如果是一个进程内的线程切换 跳转 mov edi,[esi+_ETHREAD.Tcb.ApcState.Process] ; 如果不是一个进程 取出目标线程_KPROCESS test [edi+_ETHREAD.pcb.LdtDescriptor.LimitLow],0FFFFh ; 判断LdtDescriptor是否为-1 jnz short loc_404A11 xor eax, eax loc_4049B8: lldt ax xor eax, eax mov gs, eax ; GS段寄存器清0 这个能解释清除 位什么在3环单步执行 GS会清0 assume gs:GAP mov eax,[edi+_EPROCESS.Pcb.DirectoryTableBase] ; 得到目标线程CR3 mov ebp,[ebx+KPCR.TSS] ; TSS寄存器 mov ecx,dword ptr[edi+_EPROCESS.Pcb.IopmOffset] mov [ebp+1Ch], eax ; 将当前TSS中的CR3修改为目标进程的CR3 mov cr3, eax ; 切换CR3 mov [ebp+66h], cx ; 存储IO权限位图到TSS 当前线程的IO权限位图 Windows2000以后不用了 jmp short loc_4049D7 ; --------------------------------------------------------------------------- db 8Dh, 49h, 0 ; --------------------------------------------------------------------------- loc_4049D7: mov eax, [ebx+18h] mov ecx, [ebx+3Ch] mov [ecx+3Ah], ax shr eax, 10h mov [ecx+3Ch], al mov [ecx+3Fh], ah inc dword ptr [esi+4Ch] inc dword ptr [ebx+61Ch] pop ecx mov [ebx], ecx cmp byte ptr [esi+49h], 0 jnz short loc_404A00 popf xor eax, eax retn