问题描述
我对esp指针有误解。
func: xor eax,eax
call L3
L1: call dword[esp]
inc eax
L2: ret
L3: call dword[esp]
L4: ret
现在,我将解释我的想法,并希望有人会纠正或批准我。 当我知道答案是什么时,我就是这样想的,因此我不确定自己是否在正确地思考。
- eax = 0
- 我们将堆栈的返回地址压入下一行,即标签L1。
- 我们跳到L3。
- 我们将堆栈的返回地址压入下一行,即标签L4。
- 我们跳到L1。
- 我们将堆栈返回地址压入下一行,即inc eax。
- 我们跳到L4。
- 我们跳到inc eax所在的行,并且堆栈现在为空。
- eax = 1。
- 我们在这里结束(标签L2)并返回1。
解决方法
我认为eax
= 2,并且func
的调用方从L1
的指令中被调用过一次。在下文中,我将跟踪执行过程以向您展示我的意思。
我重新整理了您的示例,以使其更具可读性。这是NASM来源。我认为这应该与原始版本相同(假设设置了the D bit and B bit,即您在正常的32位模式下运行)。
bits 32
func:
xor eax,eax
call L3
.returned:
L1:
call near [esp]
.returned:
inc eax
L2:
retn
L3:
call near [esp]
.returned:
L4:
retn
现在假设我们从执行此操作的某个函数开始:
foo:
call func
.returned:
X
retn
这是发生了什么
-
在
foo
,我们叫func
。foo.returned
的地址放在堆栈上(例如,堆栈插槽-1)。 -
在
func
,我们将eax
设置为零。 -
接下来,我们称为
L3
。func.returned
=L1
的地址放在堆栈中(插槽-2)。 -
在
L3
,我们将双字称为栈顶。这是func.returned
。L3.returned
=L4
的地址放在堆栈中(插槽-3)。 -
在
L1
,我们将双字称为栈顶。这是L3.returned
。L1.returned
的地址放在堆栈上(插槽-4)。 -
在
L4
,我们返回。这会将L1.returned
(从插槽-4弹出)到eip
中。 -
在
L1.returned
,我们进行inc eax
,将eax
设置为1。 -
然后在
L2
返回。这会将L3.returned
(从插槽-3)弹出到eip
中。 -
在
L4
,我们返回。这会将func.returned
(从插槽-2)弹出到eip
中。 -
在
L1
,我们将双字称为栈顶。这是foo.returned
。L1.returned
的地址放在堆栈上(插槽-2)。 -
在
foo.returned
,我们执行我标记为X
的所有内容。假设函数最终使用retn
返回... -
...我们回来了。这会将
L1.returned
(从插槽-2)弹出到eip
中。 -
在
L1.returned
,我们进行inc eax
。假设X
没有改变eax
,那么我们现在有eax
= 2。 -
然后在
L2
返回。这会将foo.returned
(从插槽-1)弹出到eip
中。
如果我的假设是正确的,那么eax
最终为2。
请注意,在栈顶调用返回地址真的很奇怪。我无法想象有什么实际用途。
还要注意,如果在调试器中继续在func
中对foo
的调用,则在步骤11中,调试器可能会将控制权返回给用户,其中eax
等于到1。但是,此时堆栈不平衡。