Nik*_*Nik 4 assembly stack x86-64 calling-convention memory-alignment
对于下面的 C 代码,来自Compiler Explorer 的GCC x86-64 10.2发出我在下面进一步粘贴的程序集。
一个指令是subq $40, %rsp
。问题是,为什么减去 40 个字节%rsp
不会使堆栈未对齐?我的理解是:
call foo
,堆栈是 16 字节对齐的;call foo
在堆栈上放置一个 8 字节的返回地址,因此堆栈未对齐;pushq %rbp
atfoo
的 start 在堆栈上又放置了 8 个字节,因此它再次对齐了 16 个字节;subq $40, %rsp
。结果,减少%rsp
40个字节一定会破坏对齐吗?显然,就保持堆栈对齐而言,GCC 发出了有效的程序集,所以我一定遗漏了一些东西。
(我尝试用 CLANG 替换 GCC,并且 CLANG 发出了subq $48, %rsp
- 正如我直觉所期望的那样。)
那么,我在 GCC 生成的程序集中缺少什么?它如何保持堆栈 16 字节对齐?
int bar(int i) { return i; }
int foo(int p0, int p1, int p2, int p3, int p4, int p5, int p6) {
int sum = p0 + p1 + p2 + p3 + p4 + p5 + p6;
return bar(sum);
}
int main() {
return foo(0, 1, 2, 3, 4, 5, 6);
}
Run Code Online (Sandbox Code Playgroud)
bar:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
popq %rbp
ret
foo:
pushq %rbp
movq %rsp, %rbp
subq $40, %rsp
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
movl %edx, -28(%rbp)
movl %ecx, -32(%rbp)
movl %r8d, -36(%rbp)
movl %r9d, -40(%rbp)
movl -20(%rbp), %edx
movl -24(%rbp), %eax
addl %eax, %edx
movl -28(%rbp), %eax
addl %eax, %edx
movl -32(%rbp), %eax
addl %eax, %edx
movl -36(%rbp), %eax
addl %eax, %edx
movl -40(%rbp), %eax
addl %eax, %edx
movl 16(%rbp), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %edi
call bar
leave
ret
main:
pushq %rbp
movq %rsp, %rbp
pushq $6
movl $5, %r9d
movl $4, %r8d
movl $3, %ecx
movl $2, %edx
movl $1, %esi
movl $0, %edi
call foo
addq $8, %rsp
leave
ret
Run Code Online (Sandbox Code Playgroud)
16 字节对齐的目的是使在当前级别以下的任何级别调用的函数如果需要对齐的局部变量,则不必担心对齐它们的堆栈。
如果没有 ABI 保证,每个需要它的函数都必须and
使用一些值指向堆栈指针,以确保它正确对齐,例如:
and %rsp, $0xfffffffffffffff0
Run Code Online (Sandbox Code Playgroud)
但是,在这种特殊情况下没有理由这样做 - 该bar()
函数是叶函数,这意味着编译器完全了解其级别或以下级别的任何对齐要求(它没有局部变量,并且不调用任何函数,因此无要求)。
该foo()
函数在下面也没有要求,因为它唯一调用的是bar()
. 它还似乎决定它自己的当地人也不需要这种级别的对齐。
即使bar()
或foo()
是从直接翻译单元外部调用的(它们可以,因为它们没有被标记static
),这不会改变不需要对齐的事实。
例如,如果bar
在单独的翻译单元中,或者在无法确定不需要对齐的情况下调用其他函数,则情况会有所不同。
这意味着gcc
不会完全了解其对齐要求。而且,事实上,如果您bar
在 Godbolt 中注释掉定义行(有效地隐藏了定义),您将看到该行发生了变化:
// int bar(int i) { return i; }
--> subq $48, %rsp ; no longer $40
Run Code Online (Sandbox Code Playgroud)
顺便说一句,虽然在这种情况下16 字节对齐在技术上不是必需的,但我认为它可能会使gcc
使用 System V AMD64 ABI的声明无效。该 ABI 中似乎没有任何内容允许这种偏差,文本 ( PDF ) 指出(略有解释,并带有我的粗体):
输入参数区域的末尾应在 16(或 32,如果
__m256
在堆栈上传递)字节边界对齐。换句话说,该值%rsp + 8
是始终16(或32)时控制转移到函数入口点的倍数。堆栈指针%rsp
始终指向最新分配的堆栈帧的末尾。
以任何方式使观察到的行为兼容,似乎没有什么解释的余地,即使在这种情况下它不会引起问题。
是否有人认为这足够重要而值得担心超出了这个答案的范围,我对这一点不做任何判断:-)
归档时间: |
|
查看次数: |
236 次 |
最近记录: |