1、任务状态段
可以将任务状态段理解为一块内存,站在CPU设计的角度可以理解成每一个任务都拥有一个TSS结构,它占用104个字节。
Intel白皮书给出的图:
可以将该图理解为一个结构,它在内存中也是连续存储的。
typedef struct TSS {
DWORD link; // 保存前一个 TSS 段选择子,使用 call 指令切换寄存器的时候由CPU填写。
// 下面这些都是用来做切换寄存器值用的,切换寄存器的时候由CPU自动填写。
DWORD esp0; // 保存 0 环栈指针
DWORD ss0; // 保存 0 环栈段选择子
DWORD esp1; // 保存 1 环栈指针
DWORD ss1; // 保存 1 环栈段选择子
DWORD esp2; // 保存 2 环栈指针
DWORD ss2; // 保存 2 环栈段选择子
DWORD cr3; //这里涉及到分页。
DWORD eip;
DWORD eflags;
DWORD eax;
DWORD ecx;
DWORD edx;
DWORD ebx;
DWORD esp;
DWORD ebp;
DWORD esi;
DWORD edi;//8个通用寄存器是可以随便改动的
DWORD es;
DWORD cs;
DWORD ss;
DWORD ds;
DWORD fs;
DWORD gs;
DWORD ldt;
DWORD io_map;//IO映射,没什么用,有需要的可以百度拓展一下
} TSS;
学习调用门、陷阱门后在发生提权的情况下,堆栈会做切换的动作,那么堆栈的ESP就是从TSS中获取的。
2、CPU如何定位TSS
CPU通过tr寄存器找到tss结构的
TR寄存器读写:
LTR:使用LTR装载的话,仅仅是改变TR寄存器中的值(96位),没有真正改变TSS。切三环是不可以使用LTR指令。
STR:如果使用STR去读TR寄存器,则只能读取选择子部分(16位)。
当S=0, TYPE=1001或者TYPE=1011的时候,表示这是一个TSS段描述符。当TSS段没被加载进 tr 寄存器时,TYPE=1001,一旦TSS被加载进 tr 寄存器,TYPE就变成了1011.
4. TSS的用途
- 保存0环、1环和2环的栈段选择子和栈顶指针
前面讲到了,在跨段提权的时候,需要切换栈,CPU会通过 tr 寄存器找到 TSS,取出其中的 SS0 和 ESP0 复制到 ss 和 esp 寄存器中。这只是 TSS 的一个用途,也是现代 Windows 操作系统使用到的功能。
- 一次性切换一堆寄存器
TSS 的另一个用途是什么?通过观察 TSS 的结构还发现 TSS 不仅存储了不同特权级下的 SS 和 ESP,还有 cs, esp, ss, esp 等等,这些后面不带数字的变量名,有着各自的用途。可以通过 call/jmp + TSS段选择子
指令一次性把这些值加载到 CPU 对应的寄存器中。同时,旧值将保存在旧的 TSS 中。
GDT 表中可以存放多个TSS描述符,这意味着内存中可以存在多份不同的TSS。总有一个 TSS 是在当前使用中的,也就是 tr 寄存器指向的那个 TSS。当使用 call/jmp + TSS段选择子
的时候,CPU做了以下几件事情。
- 把当前所有寄存器(TSS结构中有的那些寄存器)的值填写到当前 tr 段寄存器指向的 TSS 中
- 把新的 TSS 段选择子指向的段描述符加载到 tr 段寄存器中
- 把新的 TSS 段中的值覆盖到当前所有寄存器(TSS结构中有的那些寄存器)中
总结
本节主要讲了 TSS 的两个功能:
- 提权时栈切换用到了 TSS
- 切换一堆寄存器
本文始终没有把 TSS 和任务切换关联起来,只是为了避免给初学者造成困扰。虽然 Intel 设计的初衷是用它来做任务切换,然而,在现代操作系统中(无论是 Windows 还是 Linux),都没有使用这种方式来执行任务切换,比如线程切换和进程切换。主要原因是这种切换速度非常慢,一条指令要消耗200多个时钟周期。
至于现代操作系统如何进行线程或进程的切换,确实是用到了 TSS,但却不是靠切换call/jmp TSS 来切换任务。
标签:状态,tr,CPU,任务,切换,寄存器,DWORD,TSS From: https://www.cnblogs.com/zhongyongzixue/p/18065018