Assembly 系统V ABI-AMD64-GCC组件中的堆栈对齐

Assembly 系统V ABI-AMD64-GCC组件中的堆栈对齐,assembly,stack,x86-64,memory-alignment,calling-convention,Assembly,Stack,X86 64,Memory Alignment,Calling Convention,对于下面的C代码,GCC x86-64 10.2来自我在下面进一步粘贴的emits程序集 一条指令是subq$40,%rsp。问题是,为什么从%rsp中减去40字节不会使堆栈错位? 我的理解是: 就在调用foo之前,堆栈是16字节对齐的 callfoo在堆栈上放置一个8字节的返回地址,因此堆栈未对齐 但是pushq%rbp在foo的开始处将另外8个字节放在堆栈上,因此它再次对齐16个字节 因此,堆栈在subq$40之前对齐16字节,%rsp。因此,将%rsp减少40个字节必须中断对齐 显然,

对于下面的C代码,GCC x86-64 10.2来自我在下面进一步粘贴的emits程序集

一条指令是
subq$40,%rsp
。问题是,为什么从
%rsp
中减去40字节不会使堆栈错位? 我的理解是:

  • 就在调用foo之前,堆栈是16字节对齐的
  • callfoo
    在堆栈上放置一个8字节的返回地址,因此堆栈未对齐
  • 但是
    pushq%rbp
    foo
    的开始处将另外8个字节放在堆栈上,因此它再次对齐16个字节
  • 因此,堆栈在subq$40之前对齐16字节,%rsp。因此,将
    %rsp
    减少40个字节必须中断对齐
显然,GCC在保持堆栈对齐方面发出了有效的程序集,所以我肯定遗漏了什么

(我试着用叮当声替换GCC,叮当声会发出48美元的subq,%rsp——正如我直觉所预期的那样。)

那么,我在GCC生成的程序集中缺少了什么呢?它如何保持堆栈16字节对齐

intbar(inti){return i;}
int foo(int p0、int p1、int p2、int p3、int p4、int p5、int p6){
整数和=p0+p1+p2+p3+p4+p5+p6;
返回条(总和);
}
int main(){
返回foo(0,1,2,3,4,5,6);
}
条:
pushq%rbp
movq%rsp,%rbp
移动%edi,-4(%rbp)
movl-4(%rbp),%eax
popq%rbp
ret
傅:
pushq%rbp
movq%rsp,%rbp
subq$40,%rsp
移动百分比edi,-20(%rbp)
移动%esi,-24(%rbp)
移动%edx,-28(%rbp)
移动%ecx,-32(%rbp)
动产%r8d,-36(%rbp)
移动%r9d,-40%(rbp)
movl-20(%rbp),%edx
movl-24(%rbp),%eax
添加%eax,%edx
movl-28(%rbp),%eax
添加%eax,%edx
movl-32(%rbp),%eax
添加%eax,%edx
movl-36(%rbp),%eax
添加%eax,%edx
movl-40(%rbp),%eax
添加%eax,%edx
movl 16(%rbp),%eax
添加%edx,%eax
移动百分比eax,-4(%rbp)
movl-4(%rbp),%eax
移动%eax,%edi
呼叫栏
离开
ret
主要内容:
pushq%rbp
movq%rsp,%rbp
普什克6美元
movl$5,%9d南非兰特
movl$4,%8d南非兰特
movl$3,%ecx
movl$2,%edx
movl$1,%esi
movl$0,%edi
打电话给福
加成$8,%rsp
离开
ret

16字节对齐的目的是,如果函数需要对齐的局部变量,则在当前级别以下的任何级别调用的函数都不必担心对齐堆栈

如果没有ABI保证,每个需要这个的函数都必须
堆栈指针加上一些值,以确保它正确对齐,比如:

and %rsp, $0xfffffffffffffff0
但是,在这种特殊情况下,没有理由需要这样做,
bar()
函数是一个leaf函数,这意味着编译器完全了解其级别或更低级别的任何对齐要求(它没有局部变量,也没有调用函数,因此没有要求)

foo()
函数也没有下面的要求,因为它调用的唯一东西是
bar()
。它似乎也在决定,它自己的本地人也不需要这种水平的对齐

即使从直接翻译单元外部调用了
bar()
foo()
(而且它们可以是,因为它们没有标记为
static
),这也不会改变它们不需要对齐的事实

例如,
bar
位于一个单独的翻译单元中,或者它调用了其他函数,但无法确定是否需要对齐,则情况会有所不同

这意味着,
gcc
不完全了解其对齐要求。事实上,如果您注释掉godbolt中的
定义行(有效地隐藏了定义),您将看到行的更改:

// int bar(int i) { return i; }
   --> subq $48, %rsp             ; no longer $40

另一方面,尽管16字节对齐在本例中在技术上不是必需的,但我认为它可能会使
gcc
使用SystemV AMD64 ABI的说法无效。该ABI中似乎没有允许这种偏差的内容,文本()表示(稍加解释,并用粗体):

输入参数区域的末端应在16字节边界上对齐(如果堆栈上传递了
\uuuum256
,则为32字节)。换句话说,当控制转移到函数入口点时,值
%rsp+8
始终是16(或32)的倍数。堆栈指针
%rsp
始终指向最新分配的堆栈帧的末尾

在解释这一点时,似乎没有多少余地,以任何方式使观察到的行为兼容,即使已知在这种情况下不会造成问题


是否有人认为重要到足以让人担心的程度超出了这个答案的范围,我对这一点不作任何判断:-)

有趣的发现。显然,编译器认为
bar
不需要堆栈对齐,因此不需要麻烦。如果将其设置为
extern内部条(inti)
则堆栈将正确对齐。此外,如果您更改
,则它确实需要对齐,例如,因为它本身调用了另一个函数,编译器也会注意到这一点。我对在
-O0
中进行的此优化感到好奇。显然,这是GCC中默认的ipa堆栈对齐功能。在GCC版本>=9.0中,您可以使用
-fipa堆栈对齐
-fno-ipa堆栈对齐
打开/关闭它。将输出与GCC中的on/off选项进行比较:函数是否可以调用f