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