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

Bus error when returning double from assembly program

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

movq [rbp],xmm0 覆盖 enter 推送的保存的 RBP 值。如果您没有使用 enter,这会更明显,但是 [rbp+0] 不是您可以在具有堆栈帧的函数中使用的地址。

([rbp-8] 是您可以为本地人使用的最高地址。[rsp] 会起作用,因为您在 enter 设置 RBP=RSP 后减少了 RSP,但您使用了 RBP。 )


当执行returns到maingcc -O0(为了调试反优化)运行s这些指令存储函数return值从 xmm0 进入堆栈 space 以获得 d_2 而不是直接将它传递给 printf 而它仍在寄存器中:

movq   rax,xmm0
mov    QWORD PTR [rbp-0x8],rax    # Using RBP after you clobbered it.

未优化的 gcc 输出 确实 愚蠢:将 FP 数据复制到整数寄存器而不是直接使用 movsd 存储是没有意义的。但这不是问题。


RBP1.22 (0x3ff3851eb851eb85) 保留 IEEE double precision bit-pattern 因为那是你的 func 破坏它的原因。

地址rbp-8不规范:高16位与位47不匹配,因此不是符号扩展的48位虚拟地址。 (参见 )。

在当前 x86-64 硬件上使用非规范地址会生成 #GP(0) 异常(根据 Intel's manual entry for mov),并且 Linux 将此 x86 异常映射到 SIGBUS。

这就是为什么您在尝试使用虚假指针访问未映射内存时会收到总线错误而不是通常的分段错误。


您的代码过于复杂且错误

在两个主流的 x86-64 调用约定中(Linux/OS X 使用 x86-64 系统 V),doublexmm0 中被 return 编辑。像正常人一样使用 addsd xmm0,xmm0 / 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

那是在到store/reload之间使用RSP下面的8个字节作为scratchspace来获取SSE2寄存器和x87之间的数据,因为x86-64调用约定被设计围绕 SSE2,使用 xmm 寄存器。如果您不想使用红色区域,请使用 sub rsp, 8 / add rsp, 8

除非您需要 80 位浮点精度,否则不要在 x86-64 中使用 x87。

(enter is slow and not recommended; 如果需要,可以使用 push rbp / mov rbp,rsp 创建一个堆栈框架。不过 leave 很好。创建堆栈框架是可选的;我把它忘了。)


printf 不需要 "%lf" 来打印 double,只有 scanf 需要 lf。您 不能 printf 单精度浮点数,因为 C 默认提升规则适用于可变函数的参数,因此任何 float 都被提升为 double.

在大多数 C 实现中(包括 glibc),"%lf" 仍然有效,默默地忽略 %f 转换中无意义的 l 修饰符。

我提到这一点是为了防止你稍后尝试使用 call printf 和来自 asm 的 "%f" 格式字符串,并将 运行 转换为 .