提问人:Ray Toal 提问时间:4/19/2013 更新时间:4/19/2018 访问量:2950
从 x86-64 打印浮点数似乎需要保存 %rbp
Printing floating point numbers from x86-64 seems to require %rbp to be saved
问:
当我在 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
%rbp
printf
printf
printf
答:
我怀疑这个问题与 无关,而是与堆栈对齐有关。引用 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
评论
sub $8, %rsp
sp
spl
and spl,0xf0
%push rbp
gcc -S
push %ebp; mov %esp, %ebp
%rbp
.LC1
_
_main
main
-O3
.LC...
_
评论
mov
%rax
float
printf
double
"%f"
long double
printf
%al != 0
printf
movaps
__m128
double