请参阅OSdev wiki for details on sysenter,其中包含有关如何避免安全/安全问题的说明。另请参阅 Intel / AMD 手册。他们详细介绍了操作系统开发人员需要的许多细节。有关链接,请参阅 x86 标签 wiki。
各种系统调用指令概览:
-
int:永远可用 (8086)
-
通过执行无效指令进行陷阱,显然是the fastest way to enter the kernel on 80386。 (但现在已经不是这样了)。
-
call gate(即
far call)。有关详细信息和陷阱,请参阅 OSdev 链接。
-
sysenter: (http://wiki.osdev.org/Sysenter) 在 x86-64 出现之前由 Intel 引入,不久之后(多年前)被 AMD 采用。适用于所有现代 x86 CPU。非常简约的设计,需要用户空间合作才能让内核返回,因为它不会在任何地方保存 EIP、ESP 或 EFLAGS。
Linux 在 32 位和 64 位内核中仅支持来自 32 位进程的系统调用。 IDK,如果您可以设计一个将其用于 64 位系统调用的内核 / 代替。 (我知道这不是问题,但它是相关的。)
使用sysenter需要用户空间合作提供返回地址并保存自己的ESP和EFLAGS。在 Linux 中,内核导出一页代码,其中包含此舞蹈的用户空间方面。用户空间应该call 这个代码而不是直接使用sysenter,但是你可以随意设计你的操作系统。如果您在其他地方没有找到示例,查看 Linux 的代码可能会有所帮助。
-
syscall 来自 64 位用户空间:随处可用,因为 Intel 与 AMD64 的其余部分一起实现了它。精心设计的界面,在进入内核之前屏蔽 RFLAGS(使用可配置的掩码),因此您可以避免竞争窗口(如果您必须使用 cli 手动禁用中断)。与swapgs 一起使用,内核可以访问其堆栈等。
在主流 x86 操作系统(如 Linux)上,syscall 是进行 64 位系统调用的唯一方法。
-
syscall 来自 32 位用户空间:与长模式 syscall 完全不同的指令,仅适用于 AMD CPU。 32 位内核(传统模式)与运行 32 位用户空间(兼容模式)的 64 位内核的内核端接口不同。
Linux 内核上有一些有用的 cmets:
entry_64_compat.S 32-bit SYSCALL entry(32 位 syscall 进入 64 位内核的入口点)
/* ...
* - Most programmers do not directly target AMD CPUs, and the 32-bit
* SYSCALL instruction does not exist on Intel CPUs. Even on AMD
* CPUs, Linux disables the SYSCALL instruction on 32-bit kernels
* because the SYSCALL instruction in legacy/native 32-bit mode (as
* opposed to compat mode) is sufficiently poorly designed as to be
* essentially unusable.
也许一个玩具操作系统可以使用它而不用担心任何问题使它不适合 Linux,IDK。但除非你只是单纯的好奇,否则不要浪费时间。 OTOH,如果您对操作系统和 CPU 设计感兴趣,那么找出 ISA 设计的问题可能会很有趣。
顺便说一句,当 AMD 设计 AMD64 时,他们在 amd64 邮件列表上从 Linux 内核开发人员那里得到了一些反馈,这些反馈改进了 64 位 syscall 的设计(以可配置地屏蔽 RFLAGS),因为他们的初始设计对于Linux。那些归档邮件列表帖子的链接in this answer。
建议:将sysenter 用于您的 32 位内核。它应该可以在任何地方使用,包括多年来在 AMD CPU 上的使用。如果您想添加第二个兼容性 ABI,不支持它的古代 CPU 可以使用 int 0x80 ABI(或您为操作系统选择的任何编号)。
Linux 内核入口点有很好的注释,并且写得相当易读。在编写What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? 时,我很容易使用syscall(本机64 位系统调用)或int 0x80 或sysenter(32 -bit 系统调用,通常来自兼容模式,但 int 0x80 支持 64 位进程。但它仍然调用 32 位 ABI!)如果启用各种跟踪/调试,则会发生很多复杂的事情,但其他部分相当容易理解。请参阅该答案,了解 Linux 的一些系统调用处理内部结构。
在arch/x86/entry 中,这些是感兴趣的主要文件:
-
entry_32.S:用于从用户空间进入的 32 位内核代码。 (传统模式)
-
entry_64_compat.S:用于从 32 位用户空间进入的 64 位内核代码(兼容模式 -> 长模式)。
-
entry_64.S:用于从 64 位用户空间(长模式 -> 长模式)进入的 64 位内核代码。
您应该能够找到用于sysenter 舞蹈的用户空间端的 Linux 的 VDSO 代码,它将向内核传递返回用户空间所需的值。 (What is better "int 0x80" or "syscall"?)。相关:What is better "int 0x80" or "syscall"? 和 The Definitive Guide to Linux System Calls 将提供一些有关 Linux 设计选择的有用信息。
sysret 指令不安全是真的吗?
返回 64 位用户空间时,英特尔和 AMD 都存在与非规范 RIP 不同的错误。例如在 Intel 上,Linux's entry_64.S 是这样描述的:
/*
* On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP
* in kernel space. This essentially lets the user take over
* the kernel, since userspace controls RSP.
如果ptrace 系统调用(例如由调试器进行)将进程的RIP 的保存值更改为非规范地址,则可能发生这种情况。
Linux 检查它是否可以使用sysret,如果不能使用它的iret 返回路径。 (sysret 路径足够快,值得做额外的工作来检查它是否安全)。
请注意,如果系统调用阻塞/休眠,则用户空间整数寄存器状态的“主副本”位于其内核堆栈上,系统调用入口点将其推送到该堆栈。 (在 Linux 中。其他设计也是可能的!)但无论如何,这就是为什么最终可能会出现奇怪的保存状态,即用户空间无法运行 syscall (因为它会在 jmp 上出现故障)非规范地址),或使用saved_rcx != saved_RIP(64 位syscall 设置 RCX=RIP 和 R11=RFLAGS(在屏蔽之前),因此它会破坏 RCX 和 R11,但允许内核恢复 RIP 和 RFLAGS。)
我不知道 32 位 syscall 是如何工作的,抱歉我跑题了。但我怀疑您可能读到的关于 sysret 不安全的内容是在谈论 64 位内核。
如果 32 位内核 sysret 或 64 位内核 sysret-to-compat-mode 中存在任何类似错误,请 IDK。