为什么在分配没有后续函数调用的大数组时,GCC会将错误的值减去堆栈指针?

12 x86 assembly stack gcc red-zone

真奇怪的gcc怪癖.看一下这个:

main() { int a[100]; a[0]=1; }
Run Code Online (Sandbox Code Playgroud)

产生这个组件:

   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 81 ec 18 01 00 00    sub    $0x118,%rsp
   b:   c7 85 70 fe ff ff 01    movl   $0x1,-0x190(%rbp)
  12:   00 00 00 
  15:   c9                      leaveq 
  16:   c3                      retq
Run Code Online (Sandbox Code Playgroud)

堆栈的顶部显然是400,因为它是一个100*4阵列.因此,当它写入第一个条目时,它会执行rbp - 400(行'b').好.但是为什么它从堆栈(第4行)指针中减去280?那不指向数组的中间吗?

如果我们之后添加一个函数调用,gcc会做正确的事情:

b() {}
main() { int a[100]; a[0]=1; b(); }
Run Code Online (Sandbox Code Playgroud)

产生这个组件:

0000000000000000 <b>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   c9                      leaveq 
   5:   c3                      retq   

0000000000000006 <main>:
   6:   55                      push   %rbp
   7:   48 89 e5                mov    %rsp,%rbp
   a:   48 81 ec 90 01 00 00    sub    $0x190,%rsp
  11:   c7 85 70 fe ff ff 01    movl   $0x1,-0x190(%rbp)
  18:   00 00 00 
  1b:   b8 00 00 00 00          mov    $0x0,%eax
  20:   e8 00 00 00 00          callq  25 <main+0x1f>
  25:   c9                      leaveq 
  26:   c3                      retq 
Run Code Online (Sandbox Code Playgroud)

在这里,它适当地减去400(线'a').

添加函数调用时为什么要更改?gcc是不是很懒,而且做得不对,因为没关系?发生了什么?显然,这仅在编译x86_64时发生,而不是对于普通x86.这与x86_64的"redzone"有什么奇怪之处吗?发生了什么?

Evg*_*uev 12

你的猜测是正确的.这是一个"红区".红色区域是从rsp-128到rsp的空间,可以由函数用于局部变量和临时存储.中断和异常处理程序不会触及此空间.显然,红色区域被函数调用破坏,因此如果调用任何函数,则没有局部变量可以在红色区域中.

红色区域只能用于64位Linux,BSD和Mac.它在内核代码中不可用.

它可用于优化空间,因为使用红色区域,您可以使用短指令引用最多512个字节的局部变量,仅基于rsp和ebp.没有红色区域,只有384个字节可用.超出此限制的所有局部变量都可通过更长的代码或其他寄存器进行访问.

例如,使用红色区域不是必需的,但gcc更喜欢将它用于所有"叶子"功能.以这种方式实现编译器更容易.


Bre*_*ale 5

x86-64 ABI要求超出堆栈指针的128个字节的"红色区域",无需修改即可使用%rsp.在第一个示例中,main()是一个叶子函数,因此编译器正在优化堆栈空间的使用 - 即,没有函数调用,因此不会覆盖此区域.