调用 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,我们打电话给funcfoo.returned 的地址被放入栈中(比如栈槽-1)。

    func,我们将eax 设置为零。

    接下来我们调用L3func.returned = L1 的地址入栈(槽-2)。

    L3,我们调用堆栈顶部的双字。这是func.returnedL3.returned = L4 的地址入栈(槽-3)。

    L1,我们调用堆栈顶部的双字。这是L3.returnedL1.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.returnedL1.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 以单步执行它,在步骤10call [esp] 使用传入的栈顶作为调用目标。在这种情况下,它是argc (1),所以它在那里出错,EAX=1。所以这段代码不是一个符合 ABI 的函数,而且没有真正的方法来说明它的作用!如果在调用后被执行leave 或类似操作的调用者调用,则额外的返回地址将被丢弃,否则会破坏堆栈(例如pop ebx)。它看起来很有趣,但事实证明这是一个非常糟糕的考试问题。

以上是关于调用 func 会返回哪个值的主要内容,如果未能解决你的问题,请参考以下文章

缓存其参数返回值的函数

C# 委托/Func() 中 GetInvocationList() 方法的使用 | 接收委托多个返回值

函数声明引用

函数的基本应用

委托代码func和Action的基本用法

如何使用具有多个返回值的python赋值运算符