Cygwin64 gcc C/assembler 使用 -O2 与 -O1 时崩溃

Cygwin64 gcc C/assembler crash when using -O2 vs -O1

我有以下代码(两个文件):

main.c

#include <stdio.h>

void print();
void asm_print();

int main() {
    asm_print();
    printf("done\n");

    return 0;
}

void print() {
    printf("printing with number: %d\n", 1);
    // printf("printing without number\n");
}

lib.s

    .intel_syntax noprefix
    .text

    .globl asm_print
asm_print:
    push    rbp
    mov     rbp, rsp
    call    print
    mov     rsp, rbp
    pop     rbp
    ret

预期输出

printing with number: 1
done

如果我使用 gcc4.9.3 和命令行在 linux 上编译:

gcc -O2 -m64 -Wall -Wextra -Werror -std=gnu99 main.c lib.s

一切正常。如果我使用 –O1 或 –O3,这也有效。如果我使用 gcc4.9.3 和命令行在 cygwin64 上编译:

gcc –O1 -m64 -Wall -Wextra -Werror -std=gnu99 main.c lib.s

一切正常。

但是,如果我将上面的内容更改为使用 –O2 或 –O3,则只会生成第一行输出。如果在函数 print() 中注释掉第一行并取消注释第二行,我会得到预期的输出:

printing without number
done

无论我使用多少优化。任何人都可以建议代码有什么问题,以便无论在 cygwin64 上使用多少优化,我都能得到预期的输出?

问题是windows64位ABI与32位ABI不同,需要调用者在栈上分配32字节的scratch参数(home)space被调用者使用。

http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ https://msdn.microsoft.com/en-us/library/tawsa7cb.aspx

所以你需要做的是在调用之前将堆栈至少减32。此外,x64 要求将堆栈指针保持在 16 字节的倍数上。 64位return地址是8个字节,所以实际需要将rsp移动40、56等

asm_print:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 40
    call    print
    add     rsp, 40
    pop     rbp
    ret

据推测,当您仅使用一个字符串常量调用 print / printf 时,它实际上并没有使用任何划痕 space,所以没有什么不好的事情发生。另一方面,当您使用 %d 格式时,它需要参数 space,并破坏您在堆栈上保存的寄存器。

它在禁用优化的情况下工作的原因是打印函数不使用 home space,并在调用 printf 时分配参数 space。如果使用 -O2,编译器会执行尾调用消除并​​用 "jmp printf" 替换 "call printf" 指令。这实质上导致重新使用本应由 asm_print.

分配的参数 space