13-任务状态段(TSS)

1. 任务状态段

不要被名字所吓倒,它不过是一块位于内存中的结构体而已。有一点需要注意的是,不要把它和任务切换关联起来(切记),否则你会被搞晕,它只是位于内存中的一段数据。

Intel 白皮书给出TSS在内存中的图是这样的,它保存了一些重要的值。

这里写图片描述
抽象成结构体就是下面这个样子。

typedef struct TSS {
    DWORD link; // 保存前一个 TSS 段选择子,使用 call 指令切换寄存器的时候由CPU填写。
    // 这 6 个值是固定不变的,用于提权,CPU 切换栈的时候用
    DWORD esp0; // 保存 0 环栈指针
    DWORD ss0;  // 保存 0 环栈段选择子
    DWORD esp1; // 保存 1 环栈指针
    DWORD ss1;  // 保存 1 环栈段选择子
    DWORD esp2; // 保存 2 环栈指针
    DWORD ss2;  // 保存 2 环栈段选择子
    // 下面这些都是用来做切换寄存器值用的,切换寄存器的时候由CPU自动填写。
    DWORD cr3; 
    DWORD eip;  
    DWORD eflags;
    DWORD eax;
    DWORD ecx;
    DWORD edx;
    DWORD ebx;
    DWORD esp;
    DWORD ebp;
    DWORD esi;
    DWORD edi;
    DWORD es;
    DWORD cs;
    DWORD ss;
    DWORD ds;
    DWORD fs;
    DWORD gs;
    DWORD ldt;
    // 这个暂时忽略
    DWORD io_map;
} TSS;

在学习调用门,中断门和陷阱门已经知道,代码发生提权的时候,是需要切换栈的。

之前遗留的一个问题是,栈段描述符和栈顶指针从哪里来?那时只是简单的讲了一下是从 TSS 中来的。

如果代码从3环跨到0环,现在观察上面的图或者结构体,可以看到确实存在这么一个 SS0 和 ESP0。提权的时候,CPU就从这个TSS里把SS0和ESP0取出来,放到 ss 和 esp 寄存器中。

2. CPU怎么找到TSS

前面已经知道,CPU可以通过 gdtr 寄存器来知道 GDT表在哪里,通过 idtr 寄存器知道 IDT 表在哪里。实际上,CPU是通过 tr 寄存器来确定 TSS 的位置的。

和 gdtr,idtr 这些不同的是,tr 寄存器是段寄存器,之前已经知道的段寄存器有 cs, ds, es, ss, fs, gs 。也知道段寄存器有96位,还做过实验验证。tr 寄存器中存放的就是描述了TSS段的相关信息,比如TSS段的基址,大小和属性。

可以通过 ltr指令跟上TSS段描述符的选择子来加载TSS段。该指令是特权指令,只能在特权级为0的情况下使用。

3. TSS 段描述符

|   7    |     6       |     5     |   4    |   3    |   2    |   1    |   0    |  字节
|76543210|7 6 5 4 3210 |7 65 4 3210|76543210|76543210|76543210|76543210|76543210|  比特
|--------|-|-|-|-|---- |-|--|-|----|--------|--------|--------|--------|--------|  占位
|  BASE  |G|D|0|A|LIMIT|P|D |S|TYPE|<------- BASE 23-0 ------>|<-- LIMIT 15-0 ->|  含义
|  31-24 | |/| |V|19-16| |P |
           |B| |L|     | |L |

当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 来切换任务。(你可参考这一篇:http://guojing.me/linux-kernel-architecture/posts/process-switch/)

下一小节,会对 TSS 的第二个功能做一个实验。有兴趣,可以自己尝试着挑战一下。

发布了398 篇原创文章 · 获赞 700 · 访问量 58万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览