x86-64 代码生成上的堆栈红色分区不正确

Posted

技术标签:

【中文标题】x86-64 代码生成上的堆栈红色分区不正确【英文标题】:Incorrect stack red-zoning on x86-64 code generation 【发布时间】:2014-09-21 09:39:52 【问题描述】:

这是来自 Linux 内核函数的编译器输出(使用 -mno-red-zone 编译):

load_balance:
.LFB2408:
        .loc 2 6487 0
        .cfi_startproc
.LVL1355:
        pushq   %rbp    #
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp      #,
        .cfi_def_cfa_register 6
        pushq   %r15    #
        pushq   %r14    #
        pushq   %r13    #
        pushq   %r12    #
        .cfi_offset 15, -24
        .cfi_offset 14, -32
        .cfi_offset 13, -40
        .cfi_offset 12, -48
        movq    %rdx, %r12      # sd, sd
        pushq   %rbx    #
.LBB2877:
        .loc 2 6493 0
        movq    $load_balance_mask, -136(%rbp)  #, %sfp
.LBE2877:
        .loc 2 6487 0
        subq    $184, %rsp      #,
        .cfi_offset 3, -56
        .loc 2 6489 0
     ....

注意编译器已经溢出的“subq $184, %rsp”之后 到堆栈(溢出是疯狂的,顺便说一句,因为它溢出一个常数 价值!)

Linus 2 天前向 gcc 报告了这个错误。但我不明白错误是什么。 为什么subq 错了?

编辑: 错误报告在这里:抱歉之前没有包含这个 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61904

【问题讨论】:

这个问题似乎是题外话,因为它是关于当前事件的讨论,而不是一个正在遇到的特定编程问题。 我认为过去在主题问题的描述中或多或少逐字逐句地描述“一个正在遇到的特定编程问题”的描述很棒的一件事是,您遇到自动成为问题主题的正确级别。如果您正在为 x86-64 编写编译器,您应该已经知道有一个推荐的 ABI,在编译内核代码时必须对 ABI 进行异常处理,并且如果您不理解这些指令是如何破坏此 ABI 的+例外,这可以简单地回答。 不幸的是,这不是您遇到的特定编程问题,而是您在新闻网站上读到的事件。你没有读过它,因为它的难度正好适合你,让你有一个“哈哈”的时刻。你读到它是因为它涉及 Linux Torvalds。任何想要将其解释为问题中显示的理解水平的人都需要提供指向大量外部文档的链接(x86-64.org/documentation/abi.pdf),或者写一本书的章节来解释所有内容。两者都不适合 ***。 @PascalCuoq 你是对的。你可以关闭它。 我做了一个编辑,将它改写为一个关于 asm 编程的真正问题。 (并添加一个关键事实,即这是使用 -mno-red-zone 编译的。) 【参考方案1】:

我不明白为什么subq 是错误的?

问题在于它相对于movq $load_balance_mask, -136(%rbp) 指令的顺序。 subq 通过修改堆栈指针在堆栈上分配空间,movq 写入该分配区域内的位置。但在这种情况下,movq 出现在subq 之前,即它正在写入(到目前为止)未分配的堆栈空间。现在如果在movqsubq 之间发生中断并且中断处理程序试图触及堆栈的同一区域怎么办?结果可能会发生各种奇怪的事情,其中​​大部分可能是坏事。

在存在红色区域的情况下,首先使用movq 是可以的。引用***:

红色区域是内存中超出堆栈指针但尚未“分配”的固定大小区域。这个内存区域不能被中断/异常/信号处理程序修改。这允许将空间用于临时数据,而无需修改堆栈指针的额外开销。 x86-64 ABI 要求使用 128 字节的红色区域。

然而,正如 Linus 在 the email thread about this bug 中所写:“但是我们使用 -mno-red-zone 构建内核。我们*不*遵循 x86-64 ABI wrt redzoning”。在禁用红色区域的情况下,不应允许代码生成器在 subq 之前输出 movq

【讨论】:

【参考方案2】:

我认为那里没有问题。常量并没有真正溢出,它正在初始化一个局部变量。红色区域是堆栈指针下的 128 个字节,因此 -136(%rbp) 在限制范围内,因为在五次推送之前,rbp 的值是 rsp,它减少了 40。允许编译器在感觉时调整 rsp喜欢。也可能是 alloca 调用。

您可以提供链接或至少提供错误报告的摘要。我在 gcc bugzilla 中找不到任何相关内容。原始的 C 源代码也会很有用。

【讨论】:

内核禁用红色区域(-mno-red-zone 编译器选项),因为中断处理程序可能会破坏堆栈指针之外的任何内容。 lkml.org/lkml/2014/7/24/584 - 解决方法是,如果您不想破坏它,请从堆栈指针中减去! (正如我所期望的 alloca 所做的那样)。 @asveikau 谢谢。最初的问题从未提及此代码是在禁用红色区域的情况下编译的。 gcc.gnu.org/bugzilla/show_bug.cgi?id=61904 错误报告在这里。抱歉迟到了 用常量初始化变量的后备存储看起来像糟糕的代码生成。稍后,变量将在使用时加载到寄存器中:此时,可以将常量加载到寄存器中(直接常量传播)。如果变量的第一次使用是获取其地址并将指向它的指针传递给其他函数,则初始化后备内存是有意义的。该功能似乎没有发生这种情况。 @Kaz 我不会是那种专制主义者。如果这是仅在循环中使用的变量的后备存储,并且 RA 在迭代之间出于任何原因选择将其溢出,则使用常量初始化可能比剥离一次迭代并通过它进行 const-prop 更好。事实上,这里似乎就是这种情况:Linus 有一个循环,其中有几个函数调用,而 GCC 的代码生成在这里是有问题的。

以上是关于x86-64 代码生成上的堆栈红色分区不正确的主要内容,如果未能解决你的问题,请参考以下文章

简单的 x86-64 分区不起作用

在 linux 上根据 x86-64 调用约定设置本地堆栈

当我们有一个红色区域时,为啥我们需要堆栈分配?

堆栈红色区域的实际大小是多少? [复制]

gcc x86-32堆栈对齐并调用printf

如何在 x86-64 汇编中使用堆栈?