在 long mode 下,gate 是 16 字节的,并取消了对 task gate 的支持。即 IDT 的 entry 是 16 字节的,所以:gate = IDTR.base + vector * 16。
在 long mode 下,code segment descriptor 的 L、D、C 以及 DPL 有效,其它域无效。由于 base 强制为 0,中断服务例程的入口地址就是 gate.offset,这个 offset 是 64 位值。code segment descriptor 的 L = 1 && D = 0,代表 code segment 是 64 位代码。
1、索引 gate 和 code segment descriptor
gate = IDTR.base + vector * 16; temp_descriptor = get_descriptor(gate.selector, GDT /* LDT */); |
同样,以 gate.selector 可以找到 code segment descriptor。
2、gate descriptor 和 code segment descriptor 的检查
processor 将对 gate 和 code segment descriptor 进行检查
if (gate.type == INTERRUPT_GATE || gate.type == TRAP_GATE) { } else { /* #GP 异常 */ } if (IS_CANONICAL(gate.offset)) { /* is canonical-address ? */ } else { /* #GP */ } if (temp_descriptor.L == 1 && temp_descriptor.D == 0) { /* 64 bit */ } else { /* #GP 异常 */ } |
processor 对 gate 额外检查 offset 是否是 canonical-address 地址形式。processor 还将对 code segment descriptor 的 L 和 D 属性进行检查,是否为 64 位代码。
3、权限 check
DPLg 是 gate.DPL,DPLs 是 code segment descripotr.DPL
if (CPL <= DPLg && D >= DPLs) { /* 通过,允许访问 */ } else { /* 失败, #GP 异常 */ } |
4、stack 切换 在 long mode 下的 interrupt/trap gate 增加了 IST 域,代表 1 ~ 7 stack pointer ID 值。允许在 interrupt/trap 提供自定义的 stack pointer,分别为:IST1 ~ IST7。
old_SS = SS; old_RSP = RSP; if (gate.IST) { SS = NULL; RSP = TSS.IST[gate.IST].RSP; /* Intrrupt Stack Table */ } else { SS = NULL: RSP = TSS.stack[temp_descriptor.DPL].RSP; /* stack pointer */ } RSP |= 0xFFFFFFFF_FFFFFFF0; /* 调整 stack 为 16 字节对齐 */ push64(old_SS); push64(old_RSP); push64(RFlags); push64(CS); push64(RIP); if (ERROR_CODE) { push64(error_code); } |
(1)如果,interrupt/trap gate 的 IST 不为 0 无论是否有权限的改变,都将发生 stack 切换,以便使用 gate 提供的 IST 指针,若 gate.IST 为 0 的话,将沿有以前的的 stack 切换模式:在权限改变时发生 stack 切换。
(2)long mode 下的 stack 以 16 字节对齐,processor 额外将 RSP 调整为 16 字节对齐。
(3)SS 将被加载为 NULL selector,即:0x0000(selector),在 long mode 下的 stack 切换中 SS 被加载为 NULL-selector 是不会产生 #GP 异常的。
(4)旧的 SS & RSP、Eflags、CS & RIP 将被入栈,SS、CS 在 8 字节边界上,多余补 0 。Rflags 的高 32 位补 0 入栈。
(5)同样若是 exception 例程则入栈 error code
--------------------------------------------------------------------------------- 在 long mode 下的 TSS segment 是 64 位的,其中有 6 个 RSP 指针域,构成一个 Interrupt Stack Table 表,分别为:IST1 ~ IST7,这 6 个 RSP 由 interrupt / trap gate descriptor 中的 IST 域来索引获取。 gate 中的 IST 域值为 001 ~ 111,相应地指出在 TSS 中的 IST 值。当 gate.IST 为 0 时,表示在 gate 中不提供对 IST 的索引,还是按原来的获取 stack pointer 方式。
5、Rflags 寄存器的处理
Rflags.NT = 0; Rflags.RF = 0; Rflags.TF = 0; if (gate == INTERRUPT_GATE) { Rflags.IF = 0; } else if (gate == TRAP_GATE) { } |
同样,processor 需将 NT、TF、RF 清为 0,在 interuupt gate 下 IF 清 0, trap-gate 下不修改 IF 标志。long mode 下不支持 virtual-8086 mode,对 VM 标志不修改。
6、加载 code segment descriptor
和 x86 下一样,code segment selector 和 descriptor 被加载到 CS 中,但是仅 CS.L、CS.D、CS.P、CS.C 和 CS.DPL 有效,其它域无效,CS.base 被强制为 0,CS.limit 被强制为 FFFFFFFF_FFFFFFFF CS.RPL 被更新为 code segment descriptor 的 DPL,即为当前的 CPL。
7、执行中断服务例程 由于 CS.base 被强制为 0,因此 gate.offset 就是实际的中断服务例程入口地址。
RIP = gate.offset 然后执行 CS:RIP 处理的中断服务例程。
8、中断服务例程的返回
若返回到 64 bit 模式时,processor 处理将和 x86 的情形一样。但是在 64 bit 的中断服务例程里需使用 iretq 指令。
情景提示:
由于 iret 指令在 64 bit 模式下 default operand-size 是 32 位的,这不同于 ret 指令。ret 指令的 default operand-size 是 64 位。所以,中断返回时需使用 iretq 指令,即:使用指令前缀 REX.W(48) 将 iret 调整为 iretq |
7.1.3.7.1、 compatibility 模式与 64 bit 模式之间的切换
由于 long mode 下有 compatiblity 与 64 bit 两种子模式,那么在 x64 版本的 64 位 OS 里就有可能存在 compatibility 与 64 bit 模式之间的切换情况发生。 64 位 OS 核心运行在 64 bit 模式下,当系统加载的是原 x86 下的 32 位用户程序,processor 将从 64 bit 切换到 compatiblity 模式。32 位软件返回 OS 时,从 compatibility 模式切换回 64 bit 模式。
当原 x86 程序中使用 int 陷入系统服务例程时,processor 从 compatibility 模式切换到 64 bit 模式,从 3 级切换到 0 级代码。