使用 SYSENTER 和 SYSEXIT 指令执行对系统过程的快速调用

Posted rtoax

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 SYSENTER 和 SYSEXIT 指令执行对系统过程的快速调用相关的知识,希望对你有一定的参考价值。

SYSENTER 和 SYSEXIT 指令被引入奔腾 II 处理器的 IA-32 体系结构中,目的是为调用操作系统或执行程序提供一种快速(低开销)机制。

SYSENTER 供以权限级别 3 运行的用户代码使用以访问以权限级别 0 运行的操作系统或执行程序。SYSEXIT 供权限级别 0 操作系统或执行程序使用以快速返回权限级别 3 用户 代码。SYSENTER 可以从权限级别 3、2、1 或 0 执行;SYSEXIT 只能从权限级别 0 执行。

SYSENTER 和 SYSEXIT 指令是伴随指令,但它们不构成调用/返回对。这是因为 SYSENTER 不保存任何状态信息供 SYSEXIT 在返回时使用。

这些指令的目标指令和堆栈指针不是通过指令操作数指定的。相反,它们是通过在 MSR 和通用寄存器中输入的参数来指定的。

对于 SYSENTER,目标字段是使用以下源生成的:


  • 目标代码段:从 IA32_SYSENTER_CS 读取。

  • 目标指令:从 IA32_SYSENTER_EIP 读取。

  • 堆栈段:通过将 8 添加到 IA32_SYSENTER_CS 中的值来计算。

  • 堆栈指针:从 IA32_SYSENTER_ESP 中读取。


对于 SYSEXIT,目标字段是使用以下源生成的:


  • 目标代码段:通过将 16 添加到 IA32_SYSENTER_CS 中的值来计算。

  • 目标指令:从 EDX 读取。

  • 堆栈段:通过将 24 添加到 IA32_SYSENTER_CS 中的值来计算。

  • 堆栈指针:从 ECX 读取。


SYSENTER 和 SYSEXIT 指令执行“快速”调用和返回,因为它们在执行 SYSENTER 时强制处理器进入预定义的权限级别 0 状态,并在执行 SYSEXIT 时强制处理器进入预定义的权限级别 3 状态。通过强制预定义和一致的处理器状态,通常执行对另一个特权级别的远程调用所需的特权检查数量大大减少。此外,通过在 MSR (会有单独一篇讲述MSR寄存器)和通用寄存器中预定义目标上下文状态,消除了除获取目标代码之外的所有内存访问。

任何需要保存以允许返回调用过程的附加状态必须由调用过程显式保存或通过编程约定进行预定义。

IA-32e 模式下的 SYSENTER 和 SYSEXIT 指令


对于 Intel 64 位处理器,SYSENTER 和 SYSEXIT 指令得到增强,以允许从以权限级别 3(兼容模式或 64 位模式)运行的用户代码到以权限级别 0 运行的 64 位执行程序的快速系统调用。IA32_SYSENTER_EIP MSR 和 IA32_SYSENTER_ESP MSR 被扩展以保存 64 位地址。如果 IA-32e 模式未激活,则仅使用存储在这些 MSR 中的低 32 位地址。WRMSR 指令确保存储在这些 MSR 中的地址是规范的。请注意,在 64 位模式下,IA32_SYSENTER_CS 不得包含 NULL 选择器。

32bit模式不做过多讲解,感兴趣请参考《Intel® 64 and IA-32 Architectures Software Developer’s Manual》。

64 位模式下的快速系统调用


SYSCALL 和 SYSRET 指令是为使用平面内存模型(不使用分段)的操作系统设计的。这些指令与 SYSENTER 和 SYSEXIT 一起适用于 IA-32e 模式操作。但是,兼容模式(或保护模式)不支持 SYSCALL 和 SYSRET。使用 CPUID 检查 SYSCALL 和 SYSRET 是否可用(CPUID.80000001H.EDX[bit 11] = 1)。

SYSCALL 旨在供以特权级别 3 运行的用户代码用于访问以特权级别 0 运行的操作系统或执行过程。SYSRET 旨在供特权级别 0 操作系统或执行过程使用以快速返回到特权级别 3 用户 代码。

SYSCALL/SYSRET 的堆栈指针不是通过特定于模型的寄存器指定的。RFLAGS 中位的清除是可编程的,而不是固定的。SYSCALL/SYSRET 保存和恢复 RFLAGS 寄存器。

对于SYSCALL,处理器将RFLAGS存入R11,将下一条指令的RIP存入RCX;然后它获取特权级 0 目标代码段、指令指针、堆栈段和标志,如下所示:


  • 目标代码段:从 IA32_STAR[47:32] 读取一个非 NULL 选择器。

  • 目标指令指针:从IA32_LSTAR读取64位地址。(WRMSR 指令确保 IA32_LSTAR MSR 的值是规范的。)

  • 堆栈段:通过将 8 添加到 IA32_STAR[47:32] 中的值来计算。

  • 标志:处理器将 RFLAGS 设置为其当前值与 IA32_FMASK MSR 中值的补码的逻辑与。

当 SYSRET 使用 REX.W 将控制权转移到 64 位模式用户代码时,处理器获取特权级别 3 目标代码段、指令指针、堆栈段和标志如下:

  • 目标代码段:从 IA32_STAR[63:48] + 16 读取一个非 NULL 选择器。

  • 目标指令指针:将 RCX 中的值复制到 RIP 中。

  • 堆栈段:IA32_STAR[63:48] + 8。

  • EFLAGS:从 R11 加载。

当 SYSRET 使用 32 位操作数大小将控制权转移到 32 位模式用户代码时,处理器获取特权级别 3 目标代码段、指令指针、堆栈段和标志如下:

  • 目标代码段:从 IA32_STAR[63:48] 读取一个非 NULL 选择器。

  • 目标指令指针:将ECX中的值复制到EIP中。

  • 堆栈段:IA32_STAR[63:48] + 8。

  • EFLAGS :从 R11 加载。

操作系统有责任确保 GDT/LDT 中的描述符对应于 SYSCALL/SYSRET 加载的选择器(与指令强制的基值、限制值和属性值一致)。

IA32_STAR、IA32_LSTAR、IA32_FMASK 的布局见下图。



SYSCALL 指令不保存堆栈指针,SYSRET 指令不恢复它。操作系统系统调用处理程序很可能会将堆栈指针从用户堆栈更改为操作系统堆栈。如果是这样,首先保存用户堆栈指针是软件的责任。这可能在执行 SYSCALL 之前由用户代码完成,或者在 SYSCALL 之后由操作系统系统调用处理程序完成。

由于 SYSRET 指令不修改堆栈指针,因此需要软件切换回用户堆栈。操作系统可能会在执行 SYSRET 之前加载用户堆栈指针(如果它是在 SYSCALL 之后保存的);或者,用户代码可以在收到来自 SYSRET 的控制后加载堆栈指针(如果它在 SYSCALL 之前保存)。

如果操作系统在执行 SYSRET 之前加载堆栈指针,它必须确保在恢复堆栈指针和成功执行 SYSRET 之间传递的任何中断或异常的处理程序不被用户堆栈调用。它可以使用以下方法来做到这一点:


  • 外部中断:操作系统可以通过在加载用户堆栈指针之前清除 EFLAGS.IF 来防止传递外部中断。

  • 不可屏蔽中断 (NMI):操作系统可以通过使用 IDT 中的门 2 (NMI) 的中断堆栈表 (IST) 机制来确保使用正确的堆栈调用 NMI 处理程序。

  • 一般保护例外 (#GP):如果 RCX 的值不规范,SYSRET 指令会生成 #GP(0)。操作系统可以使用以下一种或多种方法来解决这种可能性:

    • 在执行 SYSRET 之前确认 RCX 的值是规范的。

    • 使用分页来确保 SYSCALL 指令永远不会将非规范值保存到 RCX 中。

    • 对 IDT 中的 13 号门 (#GP) 使用 IST 机制。

参考


《Intel® 64 and IA-32 Architectures Software Developer’s Manual》

CPL,RPL和DPL:这三个级别你搞懂了吗?

以上是关于使用 SYSENTER 和 SYSEXIT 指令执行对系统过程的快速调用的主要内容,如果未能解决你的问题,请参考以下文章

Linux-gate.so技术细节

windows7内核分析之x86&x64第二章系统调用

跟踪 Ring3 - Ring0 的运行流程

windows ssdt

windows ssdt

如何强制使用 int $0x80 而不是 sysenter 进行系统调用检测