强烈建议在创建 64 位内核(对于 x86_64 平台)时,指示编译器不要使用用户空间 ABI 所使用的 128 字节红色区域。(对于 GCC,编译器标志是-mno-red-zone
)。
如果启用,内核将不是中断安全的。
但这是为什么呢?
引用 AMD64 ABI:
%rsp 指向的位置之外的 128 字节区域被认为是保留的,不应被信号或中断处理程序修改。因此,函数可以将此区域用于函数调用之间不需要的临时数据。特别是,叶函数可以将这个区域用于它们的整个堆栈帧,而不是在序言和尾声中调整堆栈指针。这个区域被称为红色区域。
本质上,这是一种优化——用户态编译器确切地知道在任何给定时间使用了多少红区(在最简单的实现中,局部变量的整个大小),并且可以%rsp
在调用子函数之前进行相应的调整。
特别是在叶函数中,这可以产生一些无需调整的性能优势,%rsp
因为我们可以确定在函数中不会运行不熟悉的代码。(POSIX 信号处理程序可能被视为协程的一种形式,但您可以指示编译器在信号处理程序中使用堆栈变量之前调整寄存器)。
在内核空间中,一旦您开始考虑中断,如果这些中断对%rsp
. 因此,您要么假设所有这些都是脏的,并且不必要地浪费堆栈空间(有效地在每个函数中使用 128 字节保证的局部变量运行),或者您保证中断不做任何假设%rsp
- 这很棘手。
在用户空间中,上下文切换 + 128 字节的堆栈过度分配会为您处理它。
在内核空间中,您使用的堆栈与中断使用的堆栈相同。当中断发生时,CPU 会推送一个返回地址和 RFLAGS。这破坏了以下 16 个字节rsp
。即使您想编写一个中断处理程序,假设红色区域的全部 128 个字节都是有价值的,这也是不可能的。
你可能有一个内核内部的 ABI,它有一个小的红色区域 from rsp-16
torsp-48
或其他东西。(小是因为内核堆栈很有价值,而且大多数函数都不需要太多的红色区域。)
中断处理程序必须sub rsp, 32
在推送任何寄存器之前。(并在之前恢复它iret
)。
如果中断处理程序本身可以在它运行之前被中断sub rsp, 32
,或者在它恢复rsp
之后在iret
. 在有价值的数据所在的地方会有一个漏洞窗口rsp .. rsp-16
。
该方案的另一个实际问题是 AFAIK gcc 没有可配置的红色区域参数。它是打开还是关闭。因此,如果您想利用它,您必须向 gcc / clang 添加对 red-zone 内核风格的支持。
即使它不受嵌套中断的影响,好处也很小。证明它在内核中安全的困难可能使它不值得。(正如我所说,我完全不确定它是否可以安全地实现,因为我认为嵌套中断是可能的。)
(顺便说一句,请参阅x86标记 wiki 以获取指向 ABI 记录红区和其他内容的链接。)
可以在内核类型上下文中使用红色区域。IDTentry 可以指定堆栈索引 (ist) 为 0..7,其中 0 有点特殊。TSS 包含这些堆栈的表。1..7被加载,用于异常/中断保存的初始寄存器,不嵌套。如果您按优先级划分各种异常条目(例如,NMI 是最高的并且可能随时发生)并将这些堆栈视为蹦床,您可以安全地处理内核类型上下文中的红色区域。也就是说,在启用可能导致异常的中断或代码之前,您可以从保存的堆栈指针中减去 128 以获得可用的内核堆栈。
零索引堆栈以更传统的方式运行,当没有特权转换时,将堆栈、标志、pc、错误推入现有堆栈。
trampoline 中的代码必须小心(呃,它是一个内核),在清理机器状态时不要生成其他异常,但提供了一个很好、安全的位置来检测病态内核嵌套、堆栈损坏等...... [抱歉这么晚才回复,在搜索其他内容时注意到了这一点]。