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

时间:2013-04-19 04:23:57

标签: assembly floating-point 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"

按预期打印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

此程序会在不打印任何内容的情况下发生错误。只是一个悲伤的段错误。

但我可以通过推送和弹出%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

现在可以使用,打印15.5000。

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

1 个答案:

答案 0 :(得分:10)

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

  

ABI要求堆栈帧在16字节边界上对齐。具体而言,结束了   参数区域(%rbp + 16)必须是16的倍数。此要求表示框架   应将大小填充为16个字节的倍数。

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

以下是gcc生成的代码(也是on the Godbolt compiler explorer):

.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

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

你可以改为对你喜欢的任何寄存器进行虚拟推/弹,而不是直接操纵%rsp; some compilers do use a dummy push to align the stack因为this can actually be cheaper在现代CPU上,并节省了代码大小。