从汇编程序返回 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到main
、gcc -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
存储是没有意义的。但这不是问题。
RBP
为 1.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),double
在 xmm0
中被 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
那是在red-zone到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"
格式字符串,并将 运行 转换为 .
我正在尝试编写一个与 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到main
、gcc -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
存储是没有意义的。但这不是问题。
RBP
为 1.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),double
在 xmm0
中被 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
那是在red-zone到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"
格式字符串,并将 运行 转换为