当我在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来处理对齐要求。相反,您可以对您喜欢的任何寄存器进行虚拟的推/弹出操作,而不是直接操作
%rsp
;some compilers do use a dummy push to align the stack,因为在现代CPU上,this can actually be cheaper可以节省代码大小。