当我编写一个简单的汇编语言程序,与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
。所以问题必须在其他地方吗?
答案 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上,并节省了代码大小。