从汇编程序返回double时出现总线错误

时间:2018-04-18 07:33:37

标签: gcc assembly nasm sse bus-error

我正在尝试编写一个与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 

1 个答案:

答案 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。

;如果你需要,可以使用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,我会提到这一点。