Assembly program to compute sum of given sequence: 1+(1/1!)+(1/2!)+...+(1/10!) using floating point registers. Why do I get a segmentation fault(Core dumped) error in the following program?
global main
main:
mov eax,2
mov ebx, 2
mov edx, 2
.fact:
fld dword [ecx]
fmul dword [ebx]
fstp dword [ecx]
fld dword [ebx]
fadd dword [one]
fstp dword [ebx]
fld dword [ebx]
fld dword [eax]
fcomi st0,st1
ja .fact
.sum:
fld dword [one]
fdiv dword [ecx]
fadd dword [edx]
fstp dword [edx]
fld dword [ten]
fld dword [eax]
fcomi st0,st1
ja .exit
fadd dword [one]
mov ebx,2
jmp .fact
.exit:
push edx
push dword msg
call printf
add sp, 8
答案 0 :(得分:2)
您有正确的想法,但我可以看到此代码存在许多问题,导致其无法按预期运行:
正如在评论中已经提到的那样,这些浮点指令中的括号表示加载/存储到内存。例如,语句fld dword [ecx]
不会加载内容ecx
st0
。ecx
。它将地址 st0
处的32位浮点值加载到2
。现在,在某些情况下,您尝试从地址eax
进行读/写。
因此,要使所有这些加载和存储工作,您需要在堆栈上分配变量。您使用其中的四个(ecx
,edx
,ebx
和sub esp, 0x10
lea eax, [esp]
lea ecx, [esp+4]
lea edx, [esp+8]
lea ebx, [esp+0xC]
),每个大小为四个字节,因此您可以保持寄存器的使用方式目前,如果你开始做例如:
lea
如果您不熟悉lea
,则会在未从内存中读取的情况下加载地址。 mov eax, esp
是唯一的 1 x86指令,其中括号不代表内存访问。例如,第二行在功能上等同于add esp, 0x10
。
最后,记得在离开例程(mov ebx, 2
)之前将堆栈指针放回原位。
正如上一节所述,像ebx
这样的指令不能达到你想要的效果,因为我们希望mov dword [ebx], 2
指向内存中我们可以存储浮点数的地址。
但即使你写2
将ebx
存储到地址 [ebx]
,这仍然是错误的,因为在这个例行程序中,我们会对待{{} 1}}作为一个浮点数,而你存储到该位置的2
是 2的整数表示。
fild
指令从内存加载一个整数,将其转换为浮点值,并将其存储在FPU堆栈的顶部。例如,以下指令将整数转换为float:
mov dword [ebx], 2 ; Now [ebx] contains 2
fild dword [ebx] ; Now st0 contains 2.0
fstp dword [ebx] ; Now [ebx] contains 2.0
每次使用fld
时,都会将值推送到FPU堆栈。每个fstp
都会弹出FPU堆栈的值。 (那是&{39; p'在fstp
中的含义)您使用fadd
,fmul
和fdiv
的方式只需替换当前位于FPU堆栈顶部的值(又名st0
)。这一切都很好,但是fcomi
并没有从FPU堆栈中弹出任何东西。因此,每当您完成循环并执行两个fld
s后跟fcomi
时,您就会在堆栈中添加两个值并将其保留在那里。
FPU堆栈只能存储8个值(st0
到st7
)。因此,如果所有八个都被填充并且您尝试将另一个值推送到FPU堆栈(例如,使用fld
),那么您将获得 FPU堆栈溢出异常和负载会失败。
还有另一条比较指令fcomip
,但它只会从FPU堆栈中弹出一个值。要弹出另一个,您可以使用fstp st0
。该指令基本上意味着"将st0
存储到st0
,然后将最高值从FPU堆栈中弹出。"
printf
不支持浮动 When passing a float
to a variadic function, it is automatically promoted to a double
.由于printf
是一个可变函数,%f
说明符(当然然后)也需要double
。因此,在函数结束时,您希望将[edx]
(不是edx
!)的内容打印为double。由于printf
需要一个双倍(64位宽),push dword [edx]
之类的东西不会削减它。
将值加载到FPU堆栈时,它们以尽可能高的精度存储。将它们存储到内存中时(通过fstp
),您可以指定精度(以及格式和宽度)。当您说fstp dword [address]
时,您指定dword
宽度,即32位浮点数。如果您需要double
,只需指定qword
。
我们可以将值直接存储到堆栈中。所以尝试类似的事情:
sub esp, 8 ; Make space for the 64-bit double we're about to store
fld dword [edx] ; Load the 32-bit float we want to print
fstp qword [esp] ; Store the 64-bit double to the space we just allocated
请记住,在printf
之后,您需要重新调整堆栈指针(不仅仅是8个字节!)。另请注意,任何32位模式下的堆栈指针都是 esp
,而不是 sp
。
你在那里的第四条指令是fld dword [ecx]
。我们尚未在此例程中初始化ecx
。由于您使用[ecx]
来存储针对您的阶乘的产品,因此您希望在每次开始1.0
之前将其设置为.fact
}循环。
您似乎正在通过使用[eax]
启动主循环计数器(2
)并使用2 = 1/0! + 1/1!
初始化您的总和来进行优化。在您运行.fact
循环之前,您将[ebx]
初始化为2
,可能是另一个优化,以避免乘以1.在伪代码中,您的算法如下所示:
// B is initialized to 2
.fact:
C = C * B
B = B + 1
if A > B: goto .fact
基本上(如果C
从1开始),C
是从2
到A-1
的所有整数的乘积,总是包括2
无论如何什么。
这并不是你想要的。请记住,您希望在总和中加入1/10!
,如上所述,需要使用.fact
执行A=11
。另外,请考虑此算法中C
对A
的不同值的结果:
A: 2 3 4 5 6 ...
C: 2 2 6 24 120 ...
参考上面的图表,根据算法的其余部分,最终总和将是2 + 1/2 + 1/2 + 1/6 + 1/24 + 1/120 ...
。这将由1/2
关闭。
您可以通过更改一行来解决这两个问题。
fldz
加载+0.0
和fld1
加载1.0
1 这是您唯一需要关心的问题。要迂腐,nop
指令也可以采用地址参数,但正如您所料,它实际上并不访问内存,因为它是 no-op 。