【问题标题】:Compiler using local variables without adjusting RSP编译器使用局部变量而不调整 RSP
【发布时间】:2017-08-18 05:30:42
【问题描述】:

问题Compilers: Understanding assembly code generated from small programs编译器使用两个局部变量而不调整堆栈指针。

不调整RSP 以使用局部变量似乎中断安全,因此编译器似乎依赖硬件在中断发生时自动切换到系统堆栈。否则,出现的第一个中断会将指令指针压入堆栈并覆盖局部变量。

该问题的代码是:

#include <stdio.h>

int main()
{
    for(int i=0;i<10;i++){
        int k=0;
    }
}

该编译器生成的汇编代码是:

00000000004004d6 <main>:
  4004d6:       55                      push   rbp
  4004d7:       48 89 e5                mov    rbp,rsp
  4004da:       c7 45 f8 00 00 00 00    mov    DWORD PTR [rbp-0x8],0x0
  4004e1:       eb 0b                   jmp    4004ee <main+0x18>
  4004e3:       c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
  4004ea:       83 45 f8 01             add    DWORD PTR [rbp-0x8],0x1
  4004ee:       83 7d f8 09             cmp    DWORD PTR [rbp-0x8],0x9
  4004f2:       7e ef                   jle    4004e3 <main+0xd>
  4004f4:       b8 00 00 00 00          mov    eax,0x0
  4004f9:       5d                      pop    rbp
  4004fa:       c3                      ret    

局部变量是i[rbp-0x8]k[rbp-0x4]

谁能阐明这个中断问题?硬件是否确实切换到系统堆栈?如何?我的理解有误吗?

【问题讨论】:

标签: c linux assembly x86-64 red-zone


【解决方案1】:

这是x86-64 ABI 的所谓“红色区域”。来自wikipedia的总结:

在计算中,红色区域是函数堆栈帧中超出当前堆栈指针的固定大小区域,该函数不保留该区域。被调用函数可以使用红色区域来存储局部变量无需修改堆栈指针的额外开销。这个内存区域不能被中断/异常/信号处理程序修改。 System V 使用的 x86-64 ABI 要求有一个 128 字节的红色区域,该区域直接从堆栈指针的当前值开始。

在 64 位 Linux 用户代码中是可以的,只要使用不超过 128 个字节。这是叶函数最常用的优化,即不调用其他函数的函数,


如果您使用-mno-red-zone 选项将示例程序编译为带有 GCC(或兼容编译器)的 64 位 Linux 程序,您会看到生成的代码如下:

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16;     <<============  Observe RSP is now being adjusted.
        mov     DWORD PTR [rbp-4], 0
.L3:
        cmp     DWORD PTR [rbp-4], 9
        jg      .L2
        mov     DWORD PTR [rbp-8], 0
        add     DWORD PTR [rbp-4], 1
        jmp     .L3
.L2:
        mov     eax, 0
        leave
        ret

此代码生成可在此godbolt.org 链接中查看。


对于 32 位 Linux 用户程序,不调整堆栈指针将是一件坏事。如果您要将问题中的代码编译为 32 位代码(使用 -m32 选项)main 将出现类似于以下代码:

main:
        push    ebp
        mov     ebp, esp
        sub     esp, 16;     <<============  Observe ESP is being adjusted.
        mov     DWORD PTR [ebp-4], 0
.L3:
        cmp     DWORD PTR [ebp-4], 9
        jg      .L2
        mov     DWORD PTR [ebp-8], 0
        add     DWORD PTR [ebp-4], 1
        jmp     .L3
.L2:
        mov     eax, 0
        leave
        ret

此代码生成可在此gotbolt.org 链接中查看。

【讨论】:

  • 感谢您的回答。它解释了一切。因此,在中断时,硬件将指令指针向下推入堆栈 128 个字节,因此不会覆盖局部变量。如果函数需要超过 128 字节的本地存储,它必须调整堆栈指针。
  • @Paul - The kernel uses a separate stack,因此特权中断不会触及用户代码的堆栈。
  • @Paul - 我不是 System V 方面的专家,但相信操作系统不会允许您在用户级代码中安装中断处理程序。所以它永远不会发生。
  • 内核代码使用不同的ABI,没有红色区域。在信号处理过程中将堆栈调整128字节。当信号“中断”进程时,内核在执行信号处理程序之前将堆栈指针向下调整 128 个字节。
  • 我更改了答案以提供更新的 Wiki 条目(我最近修改了 Wiki 文章)。旧的错误地建议redzone从存储返回地址的位置下方开始。这是不准确的 - 它是 RSP 的当前值正下方的 128 个字节。 RSP 可能指向也可能不指向返回地址。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多