为什么内核代码不能使用Red Zone

mmk*_*mmk 18 x86-64 abi red-zone

强烈建议在创建64位内核(对于x86_64平台)时,指示编译器不要使用用户空间ABI所执行的128字节红区.(对于GCC,编译器标志是-mno-red-zone).

如果启用了内核,则内核不会是中断安全的.

但那是为什么呢?

mev*_*ets 14

可以在内核类型上下文中使用 red-zone。IDEntry 可以指定 0..7 的堆栈索引 (ist),其中 0 有点特殊。TSS 包含这些堆栈的表。1..7 被加载,用于异常/中断保存的初始寄存器,不嵌套。如果您按优先级划分各种异常条目(例如,NMI 是最高的并且随时可能发生)并将这些堆栈视为蹦床,您可以安全地处理内核类型上下文中的红色区域。也就是说,在启用可能导致异常的中断或代码之前,您可以从保存的堆栈指针中减去 128 以获得可用的内核堆栈。

零索引堆栈以更传统的方式运行,当没有特权转换时,将堆栈、标志、pc、错误推送到现有堆栈上。

蹦床中的代码必须小心(呃,它是一个内核)在清理机器状态时不要产生其他异常,但提供了一个很好的、安全的地方来检测病态的内核嵌套、堆栈损坏等...... [抱歉这么晚才回复,在寻找其他东西时注意到了这一点]。


qdo*_*dot 12

引自AMD64 ABI:

超出%rsp指向的位置的128字节区域被认为是保留的,不应被信号或中断处理程序修改.因此,函数可以将此区域用于函数调用不需要的临时数据.特别是,叶子函数可以将这个区域用于它们的整个堆栈帧,而不是调整序言和尾声中的堆栈指针.这个区域被称为红区.

从本质上讲,它是一种优化 - 用户态编译器确切地知道在任何给定时间(在最简单的实现中,局部变量的整个大小)使用了多少Red Zone,并且可以%rsp在调用子函数之前相应地调整.

特别是在叶函数中,这可以产生一些性能上的好处,而不必调整,%rsp因为我们可以确定在函数中不会运行任何不熟悉的代码.(POSIX信号处理程序可能被视为协同例程的一种形式,但您可以指示编译器在信号处理程序中使用堆栈变量之前调整寄存器).

在内核空间中,一旦你开始考虑中断,如果这些中断做出任何假设%rsp,它们可能是不正确的 - 关于红区的使用是不确定的.因此,您要么假设所有这些都是脏的,并且不必要地浪费堆栈空间(在每个函数中有效地运行128字节保证的局部变量),或者,您保证中断不做任何假设%rsp- 这是棘手的.

在用户空间中,上下文切换+堆栈的128字节分配为您处理.

  • 这不仅仅是节省空间.实际上不可能安全地实现正常的128字节红区,因为在中断处理程序的任何代码运行之前,中断总是会破坏`%rsp`下面的16个字节. (5认同)

Pet*_*des 10

在内核空间中,您使用的是中断使用的相同堆栈.当发生中断时,CPU会按下返回地址和RFLAGS.这个blobbers 16字节以下rsp.即使你想写一个中断处理程序,假设红区的完整128字节是有价值的,那也是不可能的.


也许你可以有一个内核内部ABI认为有一个红色的小区从rsp-16rsp-48什么的.(因为内核堆栈很有价值,并且大多数功能无论如何都不需要非常多的红区.)

中断处理程序必须sub rsp, 32在推送任何寄存器之前.(并在之前恢复iret).

如果中断处理程序本身在运行之前被中断sub rsp, 32,或者在它rsp之前恢复之后,这个想法将不起作用iret.将会有一个漏洞窗口,其中包含有价值的数据rsp .. rsp-16.


该方案的另一个实际问题是AFAIK gcc没有可配置的红区参数.无论是打开还是关闭.因此,如果你想利用它,你必须为gcc/clang添加对red-zone内核风格的支持.

即使嵌套中断是安全的,其好处也很小.在内核中证明它是安全的困难可能使它不值得.(正如我所说,我完全不确定它是否可以安全实施,因为我认为嵌套中断是可能的.)


(顺便说一句,请参阅标签wiki,了解指向红区的ABI链接以及其他内容.)