当我在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

这个程序在没有打印任何东西的情况下出现故障。只是一个悲伤的错误。
但我可以通过按下并弹出%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。所以问题一定在别处?

最佳答案

我怀疑这个问题与%rbp无关,而是与堆栈对齐有关。引用ABI:
ABI要求堆栈帧在16字节边界上对齐。具体来说,在
参数区域(%rbp+16)必须是16的倍数。这个要求意味着框架
大小应填充为16字节的倍数。
当您输入main()时,堆栈将对齐。调用printf()将返回地址推送到堆栈上,将堆栈指针移动8个字节。通过将另一个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,然后在结束时再加上8来处理对齐要求。
相反,您可以对您喜欢的任何寄存器进行虚拟的推/弹出操作,而不是直接操作%rspsome compilers do use a dummy push to align the stack,因为在现代CPU上,this can actually be cheaper可以节省代码大小。

07-24 09:45
查看更多