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

Printing floating point numbers from x86-64 seems to require %rbp to be saved

提问人:Ray Toal 提问时间:4/19/2013 更新时间:4/19/2018 访问量:2950

问:

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

        .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

此程序在不打印任何内容的情况下出现 seg 错误。只是一个可悲的段错误。

但是我可以通过推动和弹出来解决这个问题。%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。

我的问题是:为什么推送和弹出使应用程序正常工作?根据 ABI 的说法,是被叫方必须保留的寄存器之一,因此不能搞砸它。事实上,在第一个程序中工作,当时只有一个整数传递给 .所以问题一定出在别处吗?%rbp%rbpprintfprintfprintf

程序集 浮点 x86-64

评论

0赞 NPE 4/19/2013
出于兴趣,那有什么目的?mov%rax
2赞 Carl Norum 4/19/2013
浮点参数计数,IIRC。
0赞 Peter Cordes 6/6/2018
相关:你不能直接打印 with , only (with ) 或因为可变参数函数的 C 升级规则:如何使用 printf 打印单精度浮点数floatprintfdouble"%f"long double
0赞 Peter Cordes 6/6/2018
也相关;glibc 只关心堆栈对齐 when ,因为这就是 gcc 编译可能接受 FP 参数的可变参数函数的方式。 NASM 汇编 64 位中的 printf float 显示,当使用未对齐的堆栈和 RAX=0 调用时,它不会崩溃,答案显示 gcc 的代码(仅对非零 AL 运行)转储 xmm0。7 到堆栈中(可变参数函数也可以接受参数,而不仅仅是 .)printf%al != 0printfmovaps__m128double

答:

10赞 NPE 4/19/2013 #1

我怀疑这个问题与 无关,而是与堆栈对齐有关。引用 ABI:%rbp

ABI 要求堆栈帧在 16 字节边界上对齐。具体来说,结束 参数区域 (%RBP+16) 必须是 16 的倍数。此要求意味着框架 size 应填充为 16 字节的倍数。

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

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

.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

正如你所看到的,它通过从开头减去 8 并在结尾处将其加回去来处理对齐要求。%rsp

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

评论

1赞 Carl Norum 4/19/2013
我认为你是对的——我自己也遇到过类似的问题。它对整数起作用的原因只是抽奖的运气。未定义的行为等等。如果没有堆栈调整,OP 的第一个示例也无法在我的机器上运行。不过,我只是用了。sub $8, %rsp
0赞 nrz 4/19/2013
有时,完成的推送次数可能取决于,因此堆栈可能对齐为 16 字节,也可能不对齐。逻辑 AND for or 始终有效,如下所示: .spspland spl,0xf0
0赞 Ray Toal 4/20/2013
@NPE 不错的答案。我希望是这样。我从用 C 编写代码并执行 .我非常了解 32 位汇编堆栈帧,并认为 gcc 的推动是过去的遗物,如果它真的重要,我会感到震惊。感谢您提供有关对齐的说明;我会回去更仔细地研究 ABI 文档!%push rbpgcc -Spush %ebp; mov %esp, %ebp%rbp
0赞 Peter Cordes 4/19/2018
这是某些平台的确切 gcc 输出吗?通常你会得到(带有一个前导点,所以它是一个 GAS 本地标签),而 Linux ELF 系统不使用前导下划线。这是来自 MacOS X,它使用 x86-64 SysV 调用约定和符号名称吗?匹配 Ubuntu 的 OP 代码可能会更好,因此它看起来不像是更正/答案的一部分。例如,godbolt.org/g/2PKKAP 具有 gcc4.6.4 的输出,并使用 和 no ,但除了指令顺序外,其他方面与您的答案相同。.LC1__mainmain-O3.LC..._