1. 任务门
上一节已经基本掌握了使用 call/jmp
去访问一个任务段,来达到切换一堆寄存器的目的。但是,CPU同时又提供了另一种方法让我们访问任务段——任务门。
而任务门是安装在 IDT 表中的。之前学中断门的时候,就简单介绍了IDT表中可以安装中断门、陷阱门,还有一个当时只是稍微提了一下,那就是任务门。
说白了就是想表达,IDT 表中只可以安装 3 种门:中断门、陷阱门和任务门。
思考一下,既然都可以使用 call/jmp
来访问任务段了,为什么又弄个任务门来访问段,何必多此一举?仔细想想,使用 int
指令加索引号,是不是比call/jmp
加选择子要方便。因为一个 int 0x20
指令(假设我在 IDT[20]处安装了一个任务门描述符)就可以让我切换一堆寄存器。
到现在或许你已经明白门的含义了,所有的门描述符的里头都嵌入着另一个段的选择子。比如中断门和陷阱门中嵌入了代码段的选择子。任务门也不例外,它里头嵌入了任务段的选择子。
2. 任务门描述符
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 字节
|76543210|76543210|7 65 4 3210|76543210|76543210|76543210|76543210|76543210| 比特
|-----------------|1|--|0|0101|--------|--------|--------|--------|--------| 占位
| reserved |P|D |S|TYPE| |segment selector | reserved | 含义
| 31-16 |P | | | 15-0 |
|L |
任务门的 S=0, TYPE=0101。
在Windows中,典型任务门描述符就是安装在 IDT[8] 的位置,00008500`00501188.其中有用的就只有标红的部分。它告诉我们,该任务段的段选择子是0x0050.
3. 分析 xp 系统的 windows 任务门
- 实验
上图可以得出,TSS段的选择子为 0x0050。现在去查GDT表的0x0050选择子对应的描述符。
通过分析图2,可以看到该TSS段描述符的段基址是0x8054a100,段界限是104B. 现在去看看0x8054a100处的TSS段长什么样。
- 对齐到TSS段结构体
typedef struct TSS {
DWORD link; // 00000000
DWORD esp0; // 80547100
DWORD ss0; // 00000010
DWORD esp1; // 00000000
DWORD ss1; // 00000000
DWORD esp2; // 00000000
DWORD cr3; // 00b67000
DWORD eip; // 8053f69e
DWORD eflags; // 00000000
DWORD eax; // 00000000
DWORD ecx; // 00000000
DWORD edx; // 00000000
DWORD ebx; // 00000000
DWORD esp; // 80547100
DWORD ebp; // 00000000
DWORD esi; // 00000000
DWORD es; // 00000023
DWORD cs; // 00000008
DWORD ss; // 00000010
DWORD ds; // 00000023
DWORD fs; // 00000030
DWORD gs; // 00000000
DWORD ldt; // 00000000
DWORD io_map; // 20ac0000
} TSS;
4. 总结
CPU提供任务门,是为了方便访问任务段。在CPU发生二重错误的时候,会直接跳到 8 号中断,而 8 号中断就是任务门(这是Windows xp 系统设计的任务门),这意味着什么?
一旦进入 8 号中断,CPU 会切换一堆寄存器,这时候无论发生什么错误都没什么关系,通过一堆寄存器的切换,CPU 保证能跳到一个正确的地方去执行(除非那个地方也被破坏了),紧接着做一些后续处理(比如收集错误信息),系统蓝屏。