调用 func 会返回哪个值
Posted
技术标签:
【中文标题】调用 func 会返回哪个值【英文标题】:Which value will be returned by calling func 【发布时间】:2020-12-04 11:02:30 【问题描述】:我对 esp 指针有一些误解。
以下是以前的考试中显示的代码。 返回值为 1。
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 的指令中被调用一次。
第10步后需要继续。10.返回到哪里?
retn
表示“将 dword 从堆栈顶部弹出到 eip
”。它不会无条件地“返回调用该函数的函数”,而是返回到堆栈顶部的任何地址。此外,您的第 8 步“堆栈现在为空”的评论是错误的。
这不是一个有效的函数;不能说它“返回”,因为它最终调用它的返回地址,并在堆栈上用 2 个额外的双字(包括原始返回地址)跳转到那里,而不是它是否返回。跨度>
【参考方案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。
注意,在栈顶调用返回地址真的很奇怪。我无法想象它的实际用途。
还请注意,如果在调试器中,您在foo
中继续执行对func
的调用,那么在第11 步调试器可能会将控制权返回给用户,其中eax
等于1。但是,此时堆栈不平衡。
【讨论】:
你是对的,如果你把它作为一个静态可执行文件放入_start
以单步执行它,在步骤10
call [esp]
使用传入的栈顶作为调用目标。在这种情况下,它是argc
(1),所以它在那里出错,EAX=1。所以这段代码不是一个符合 ABI 的函数,而且没有真正的方法来说明它的作用!如果在调用后被执行leave
或类似操作的调用者调用,则额外的返回地址将被丢弃,否则会破坏堆栈(例如pop ebx
)。它看起来很有趣,但事实证明这是一个非常糟糕的考试问题。以上是关于调用 func 会返回哪个值的主要内容,如果未能解决你的问题,请参考以下文章