编译器使用局部变量而不调整RSP

Pau*_*vie 9 c linux x86 assembly x86-64

问题编译器:了解从小程序生成的汇编代码,编译器使用两个局部变量而不调整堆栈指针.

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

该问题的代码是:

#include <stdio.h>

int main()
{
    for(int i=0;i<10;i++){
        int k=0;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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    
Run Code Online (Sandbox Code Playgroud)

局部变量i位于[rbp-0x8]k[rbp-0x4].

谁能照亮这个中断问题呢?硬件确实切换到系统堆栈吗?怎么样?我理解错了吗?

ead*_*ead 10

这就是x86-64 ABI所谓的"红区" .来自维基百科的摘要:

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

在64位Linux用户代码中,只要使用不超过128个字节就可以了.它是叶函数最突出的优化,即不调用其他函数的函数,


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

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
Run Code Online (Sandbox Code Playgroud)

可以在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
Run Code Online (Sandbox Code Playgroud)

可以在此gotbolt.org链接中观察到此代码生成.

  • @Paul - [内核使用单独的堆栈](http://stackoverflow.com/questions/15168822/intel-x86-vs-x64-system-call/15169141#15169141),因此特权中断不会触及堆栈的用户代码。 (2认同)
  • 内核代码使用不同的ABI,其中不存在红色区域.在信号处理期间,堆栈调整为128个字节.当信号"中断"进程时,内核在执行信号处理程序之前将堆栈指针向下调整128个字节. (2认同)