【问题标题】:Which value will be returned by calling func调用 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

现在,我将解释我的想法,并希望有人纠正我或批准我。 当我知道答案是什么时,我就是这样想的,所以我不确定我的想法是否正确。

  1. eax = 0
  2. 我们推送到堆栈返回地址,即下一行,即标签 L1。
  3. 我们跳到 L3。
  4. 我们推送到堆栈返回地址,即下一行,即标签 L4。
  5. 我们跳到 L1。
  6. 我们推送到堆栈返回地址,即下一行,即 inc eax。
  7. 我们跳到 L4。
  8. 我们跳转到 inc eax 所在的行,堆栈现在为空。
  9. eax = 1。
  10. 我们到此结束(在标签 L2 处)并返回 1。

【问题讨论】:

  • 为什么问题被否决?请告诉我,我会改进的。
  • 我认为 eax = 2 并且 func 的调用者在 L1 的指令中被调用一次。
  • 第10步后需要继续。10.返回到哪里?
  • retn 表示“将 dword 从堆栈顶部弹出到 eip”。它不会无条件地“返回调用该函数的函数”,而是返回到堆栈顶部的任何地址。此外,您的第 8 步“堆栈现在为空”的评论是错误的。
  • 这不是一个有效的函数;不能说它“返回”,因为它最终调用它的返回地址,并在堆栈上用 2 个额外的双字(包括原始返回地址)跳转到那里,而不是它是否返回。跨度>

标签: pointers assembly x86 stack nasm


【解决方案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

会发生这样的事情:

  1. foo,我们打电话给funcfoo.returned 的地址被放入栈中(比如栈槽-1)。

  2. func,我们将eax 设置为零。

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

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

  5. L1,我们调用堆栈顶部的双字。这是L3.returnedL1.returned 的地址被放入堆栈(槽-4)。

  6. L4 我们回来了。这会将L1.returned(从插槽-4)弹出到eip

  7. L1.returned,我们执行inc eax,将eax 设置为1。

  8. 然后在L2 我们返回。这会将L3.returned(从插槽-3)弹出到eip

  9. L4 我们回来了。这会将func.returned(从插槽-2)弹出到eip

  10. L1,我们调用堆栈顶部的双字。这是foo.returnedL1.returned 的地址被放入堆栈(槽-2)。

  11. foo.returned,我们执行我标记为X 的任何内容。假设函数最终使用retn 返回,那么...

  12. ...我们回来了。这会将L1.returned(从插槽-2)弹出到eip

  13. L1.returned,我们在inc eax。假设X 没有改变eax 那么我们现在有eax = 2。

  14. 然后在L2 我们返回。这会将foo.returned(从插槽-1)弹出到eip

如果我的假设是正确的,那么 eax 最后是 2。


注意,在栈顶调用返回地址真的很奇怪。我无法想象它的实际用途。

还请注意,如果在调试器中,您在foo 中继续执行对func 的调用,那么在第11 步调试器可能会将控制权返回给用户,其中eax 等于1。但是,此时堆栈不平衡。

【讨论】:

  • 你是对的,如果你把它作为一个静态可执行文件放入_start 以单步执行它,在步骤10call [esp] 使用传入的栈顶作为调用目标。在这种情况下,它是argc (1),所以它在那里出错,EAX=1。所以这段代码不是一个符合 ABI 的函数,而且没有真正的方法来说明它的作用!如果在调用后被执行leave 或类似操作的调用者调用,则额外的返回地址将被丢弃,否则会破坏堆栈(例如pop ebx)。它看起来很有趣,但事实证明这是一个非常糟糕的考试问题。
猜你喜欢
  • 2014-01-10
  • 2013-03-16
  • 1970-01-01
  • 2022-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多