sysenter/sysexit 系统调用的机制: 在 Intel 的软件开发者手册第二、三卷(Vol.2B,Vol.3)中,4.8.7 节是关于 sysenter/sysexit 指令的详细描述。手册中说明,sysenter 指令可用于特权级 3 的用户代码调用特权级 0 的系统内核代码,而 SYSEXIT 指令则用于特权级 0 的系统代码返回用户空间中。sysenter 指令可以在 3,2,1 这三个特权级别调用(Linux 中只用到了特权级 3),而 SYSEXIT 指令只能从特权级 0 调用。 执行 sysenter 指令的系统必须满足两个条件:1.目标 Ring 0 代码段必须是平坦模式(Flat Mode)的 4GB 的可读可执行的非一致代码段。2.目标 RING0 堆栈段必须是平坦模式(Flat Mode)的 4GB 的可读可写向上扩展的栈段。 在 Intel 的手册中,还提到了 sysenter/sysexit 和 int n/iret 指令的一个区别,那就是 sysenter/sysexit 指令并不成对,sysenter 指令并不会把 SYSEXIT 所需的返回地址压栈,sysexit 返回的地址并不一定是 sysenter 指令的下一个指令地址。调用 sysenter/sysexit 指令地址的跳转是通过设置一组特殊寄存器实现的。这些寄存器包括: SYSENTER_CS_MSR - 用于指定要执行的 Ring 0 代码的代码段选择符,由它还能得出目标 Ring 0 所用堆栈段的段选择符; SYSENTER_EIP_MSR - 用于指定要执行的 Ring 0 代码的起始地址; SYSENTER_ESP_MSR-用于指定要执行的Ring 0代码所使用的栈指针 这些寄存器可以通过 wrmsr 指令来设置,执行 wrmsr 指令时,通过寄存器 edx、eax 指定设置的值,edx 指定值的高 32 位,eax 指定值的低 32 位,在设置上述寄存器时,edx 都是 0,通过寄存器 ecx 指定填充的 MSR 寄存器,sysenter_CS_MSR、sysenter_ESP_MSR、sysenter_EIP_MSR 寄存器分别对应 0x174、0x175、0x176,需要注意的是,wrmsr 指令只能在 Ring 0 执行。 这里还要介绍一个特性,就是 Ring0、Ring3 的代码段描述符和堆栈段描述符在全局描述符表 GDT 中是顺序排列的,这样只需知道 SYSENTER_CS_MSR 中指定的 Ring0 的代码段描述符,就可以推算出 Ring0 的堆栈段描述符以及 Ring3 的代码段描述符和堆栈段描述符。 在 Ring3 的代码调用了 sysenter 指令之后,CPU 会做出如下的操作: 1.将 SYSENTER_CS_MSR 的值装载到 cs 寄存器 2.将 SYSENTER_EIP_MSR 的值装载到 eip 寄存器 3.将 SYSENTER_CS_MSR 的值加 8(Ring0 的堆栈段描述符)装载到 ss 寄存器。 4.将 SYSENTER_ESP_MSR 的值装载到 esp 寄存器 5.将特权级切换到 Ring0 6.如果 EFLAGS 寄存器的 VM 标志被置位,则清除该标志 7.开始执行指定的 Ring0 代码 在 Ring0 代码执行完毕,调用 SYSEXIT 指令退回 Ring3 时,CPU 会做出如下操作: 1.将 SYSENTER_CS_MSR 的值加 16(Ring3 的代码段描述符)装载到 cs 寄存器 2.将寄存器 edx 的值装载到 eip 寄存器 3.将 SYSENTER_CS_MSR 的值加 24(Ring3 的堆栈段描述符)装载到 ss 寄存器 4.将寄存器 ecx 的值装载到 esp 寄存器 5.将特权级切换到 Ring3 6.继续执行 Ring3 的代码 由此可知,在调用 SYSENTER 进入 Ring0 之前,一定需要通过 wrmsr 指令设置好 Ring0 代码的相关信息,在调用 SYSEXIT 之前,还要保证寄存器edx、ecx 的正确性 |
|