我正在尝试编写一个与this完全相同的程序。唯一的区别是我使用堆栈内存而不是.bss部分来保存我从调用函数获得的值。 在此更改之后,从汇编函数返回时出现总线错误。
任何想法为什么?
C-程序:
#include<stdio.h>
extern double func(double d);
int main(){
double d_1 = 1.22;
double d_2 = func(d_1);
printf("%lf\n", d_2);
return 0;
}
大会:
section .text
global func
func:
enter 0,0
sub rsp, 8
movq qword[rbp],xmm0 ; Store current value in memory
fld qword[rbp] ; Load current value from memory
fld qword[rbp] ; Load current value from memory again
fadd ; Add top two stack items
leave
ret
答案 0 :(得分:1)
movq [rbp],xmm0
会覆盖enter
推送的已保存RBP值。如果您没有使用enter
,这将更加明显,但[rbp+0]
不是您可以在具有堆栈帧的函数中使用的地址。
([rbp-8]
是您可以用于本地人的最高地址。[rsp]
会有效,因为您在enter
设置RBP = RSP之后递减了RSP,但您使用了RBP。)< / p>
当执行返回main
时,gcc -O0
(针对调试进行反优化)运行这些指令,将函数返回值从xmm0
存储到d_2
的堆栈空间中只是将它直接传递给printf
,而它仍然在寄存器中:
movq rax,xmm0
mov QWORD PTR [rbp-0x8],rax # Using RBP after you clobbered it.
未优化的gcc输出真的愚蠢:将FP数据复制到整数寄存器而不是直接与movsd
存储是没有意义的。但那不是问题。
RBP
保留1.22
0x3ff3851eb851eb85
的{{3}},因为这是func
对其进行破坏的原因。强>
地址rbp-8
不符合规范:高16位不匹配第47位,因此它不是符号扩展的48位虚拟地址。 (见IEEE double
precision bit-pattern)。
在当前x86-64硬件上使用非规范地址会生成#GP(0)
异常(根据this ASCII-art diagram),Linux会将此x86异常映射到SIGBUS。
这就是为什么在尝试使用虚假指针访问未映射的内存时出现总线错误而不是通常的分段错误。
您的代码过于复杂和错误
在两种主流x86-64调用约定(Linux / OS X使用x86-64 System V)中,double
都返回xmm0
。像普通人一样使用Intel's manual entry for mov
/ ret
,就像您关联的问题的答案一样。
func:
addsd xmm0,xmm0 ; first FP arg in (low 64 bits of) xmm0
ret ; return value in (low 64 bits of) xmm0
或者如果你坚持使用x87,那么看看你需要编写多少代码:
func:
movsd [rsp-8], xmm0 ; double arg in xmm0
fld qword [rsp-8]
fadd st0, st0 ; use x87 regs instead of uselessly loading twice.
fstp qword [rsp-8] ; empty the x87 stack
movsd xmm0, [rsp-8] ; return value in xmm0
ret
在RSP下使用8个字节作为临时空间,在addsd xmm0,xmm0
中存储/重新加载以获取SSE2寄存器和x87之间的数据,因为x86-64调用约定是围绕SSE2设计的,使用xmm寄存器。如果您不想使用红区,请使用sub rsp, 8
/ add rsp, 8
。
除非您需要80位浮点精度,否则不要在x86-64中使用x87。
(red-zone;如果你需要,可以使用push rbp
/ mov rbp,rsp
制作一个堆栈框架。leave
很好。制作堆栈框架是可选的;我离开了那个。)
printf
并不需要"%lf"
来打印double
,只需要scanf
个需要lf
。您不能 printf
单精度浮点数,因为C默认促销规则适用于可变函数的args,因此任何float
都会提升为{{1} }}
在大多数C实现(包括glibc)中,double
无论如何都会起作用,默默地忽略"%lf"
转换上无意义的l
修饰符。
如果您尝试使用%f
之后使用call printf
格式字符串从asm执行此操作,并且遇到enter
is slow and not recommended,我会提到这一点。