从x86-64打印浮点数似乎需要保存%rbp

Ray*_*oal 9 floating-point assembly x86-64

当我编写一个简单的汇编语言程序,与C库链接,在Ubuntu上使用gcc 4.6.1,我尝试打印一个整数,它工作正常:

        .global main
        .text
main:
        mov     $format, %rdi
        mov     $5, %rsi
        mov     $0, %rax
        call    printf
        ret
format:
        .asciz  "%10d\n"
Run Code Online (Sandbox Code Playgroud)

按预期打印5.

但是现在如果我进行一些小改动,并尝试打印浮点值:

        .global main
        .text
main:
        mov     $format, %rdi
        movsd   x, %xmm0
        mov     $1, %rax
        call    printf
        ret
format:
        .asciz  "%10.4f\n"
x:
        .double 15.5
Run Code Online (Sandbox Code Playgroud)

这个程序赛格故障不打印任何东西.只是一个悲伤的段错误.

但我可以通过推动和弹出来解决这个问题%rbp.

        .global main
        .text
main:
        push    %rbp
        mov     $format, %rdi
        movsd   x, %xmm0
        mov     $1, %rax
        call    printf
        pop     %rbp
        ret
format:
        .asciz  "%10.4f\n"
x:
        .double 15.5
Run Code Online (Sandbox Code Playgroud)

现在它可以工作,打印15.5000.

我的问题是:为什么推送和弹出%rbp使应用程序工作?根据ABI,%rbp是被调用者必须保留的寄存器之一,因此printf不能搞砸它.事实上,printf在第一个程序中工作时,只传递了一个整数printf.所以问题必定在其他地方吗?

NPE*_*NPE 10

我怀疑这个问题没有任何关系%rbp,而是与堆栈对齐有关.引用ABI:

ABI要求堆栈帧在16字节边界上对齐.具体而言,参数区域的末尾(%rbp + 16)必须是16的倍数.此要求意味着帧大小应填充为16个字节的倍数.

输入时堆栈已对齐main().调用printf()将返回地址压入堆栈,将堆栈指针移动8个字节.您可以通过将另外八个字节压入堆栈来恢复对齐(恰好%rbp可能是其他东西).

这是gcc生成的代码(也在Godbolt编译器资源管理器上):

.LC1:
        .ascii "%10.4f\12\0"
main:
        leaq    .LC1(%rip), %rdi   # format string address
        subq    $8, %rsp           ### align the stack by 16 before a CALL
        movl    $1, %eax           ### 1 FP arg being passed in a register to a variadic function
        movsd   .LC0(%rip), %xmm0  # load the double itself
        call    printf
        xorl    %eax, %eax         # return 0 from main
        addq    $8, %rsp
        ret
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它通过从%rsp开头减去8 并在最后添加它来处理对齐要求.

你可以改为对你喜欢的任何寄存器进行虚拟推/弹而不是%rsp直接操作; 一些编译器确实使用虚拟推送来对齐堆栈,因为在现代CPU上实际上可以更便宜,并节省了代码大小.