assembly - System V ABI - AMD64 - GCC 发出的程序集中的堆栈对齐

标签 assembly stack x86-64 memory-alignment calling-convention

对于下面的 C 代码,来自 Compiler Explorer 的 GCC x86-64 10.2发出我在下面进一步粘贴的程序集。
一条指令是 subq $40, %rsp .问题是,如何从 %rsp 中减去 40 个字节?不会使堆栈错位?
我的理解是:

  • 就在之前 call foo , 栈是 16 字节对齐的;
  • call foo在堆栈上放置一个 8 字节的返回地址,因此堆栈未对齐;
  • 但是pushq %rbpfoo的 start 在堆栈上放置另外 8 个字节,因此它再次对齐 16 个字节;
  • 所以堆栈是在 subq $40, %rsp 之前对齐的 16 个字节.结果,递减%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);
    }
    
    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
    

    最佳答案

    16 字节对齐的目的是使在当前级别以下的任何级别调用的函数如果需要对齐的局部变量,则不必担心对齐它们的堆栈。
    如果没有 ABI 保证,每个需要它的函数都必须and带有一些值的堆栈指针以确保它正确对齐,例如:

    and %rsp, $0xfffffffffffffff0
    
    但是,在这种特殊情况下没有理由这样做 - bar()函数是叶函数,这意味着编译器完全了解其级别或以下级别的任何对齐要求(它没有局部变量,也没有调用函数,因此没有要求)。foo()函数在下面也没有要求,因为它唯一调用的是 bar() .它还似乎决定它自己的本地人也不需要这种级别的对齐。
    即使 bar()foo()是从直接翻译单元外部调用的(它们可以是,因为它们没有标记 static ),这不会改变不需要对齐它们的事实。
    例如,如果 bar,情况会有所不同。位于单独的翻译单元中,或者它调用了其他无法确定不需要对齐的函数。
    这意味着 gcc不会完全了解其对齐要求。事实上,如果你注释掉 bar在godbolt中定义行(有效隐藏定义),您将看到行更改:
    // int bar(int i) { return i; }
       --> subq $48, %rsp             ; no longer $40
    

    顺便说一句,虽然在这种情况下 16 字节对齐在技术上不是必需的,但我认为这可能会使 gcc 的说法无效。使用 System V AMD64 ABI。该 ABI 中似乎没有任何内容允许这种偏差,文本 ( PDF ) 指出(略有解释,并带有我的粗体):

    The end of the input argument area shall be aligned on a 16 (or 32 if __m256 is passed on stack) byte boundary. In other words, the value %rsp + 8 is always a multiple of 16 (or 32) when control is transferred to the function entry point. The stack pointer %rsp always points to the end of the latest allocated stack frame.


    以任何方式使观察到的行为兼容,似乎没有什么解释的余地​​,即使在这种情况下它不会引起问题。
    是否有人认为这足够重要而值得担心超出了这个答案的范围,我对这一点不做任何判断:-)

    关于assembly - System V ABI - AMD64 - GCC 发出的程序集中的堆栈对齐,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64627897/

    相关文章:

    c - 在汇编代码中,静态局部 C 变量后跟一个数字。这个数字是随机的吗?

    assembly - NASM 汇编器 - 生成的机器代码中不需要的 66

    程序集调用堆栈 - 术语问题

    c++ - 在 gcc linux x86-64 C++ 中,(p+x)-x 是否总是为指针 p 和整数 x 生成 p

    c++ - MIPS 和 x86_64 之间对象对齐的差异

    assembly - Turbo 汇编器 cs 部分解释

    c - nasm 中的链接 c 函数

    java - 堆栈行为的正确实现

    java - 打印堆栈而不弹出元素java

    pointers - 为什么 RBP 而不是另一个寄存器作为帧指针?