【问题标题】:Need help understanding recursion in x86 assembly需要帮助了解 x86 程序集中的递归
【发布时间】:2022-01-01 16:21:19
【问题描述】:

所以我在大学里有这个东西,我需要做一个简单的递归。似乎很好,我可以在 20 分钟内用 C 语言做到这一点。但是他们没有在课程中教我们关于汇编中的递归函数的课程,所以我在谷歌上查到了它。我只找到了这个 - https://scottc130.medium.com/recusive-functions-in-x86-assembly-5ba412bc7957 - 我有几个问题:

  • 为什么将 ebp 推到这里?我看到了 pop ,所以对我来说将某些东西推入堆栈而不弹出它是没有意义的。
  • 在解释部分,那个 eip 是什么,它在哪里被压入堆栈?

我想我可能已经稍微理解了,但我宁愿把它弄糊涂一点。



factorial:
    pushl %ebp
    movl %esp, %ebp

    movl 8(%ebp), %eax
    cmpl $1, %eax
    je end_factorial

    decl %eax
    pushl %eax
    call factorial

    movl 8(%ebp), %ebx
    imul %ebx, %eax

end_factorial:
    movl %ebp, %esp
    popl %ebp
    ret

【问题讨论】:

  • 末尾有pop ebpeip为指令指针,由call自动推送。
  • edit您的问题并将相关代码sn-ps从外部资源复制到您的帖子中,以便即使链接断开也可以理解您的问题。
  • 因为每次调用都有返回,所以是平衡的。
  • 您似乎认为end_factorial: 就像一个单独的函数,但事实并非如此。这只是一个标签。如果je 被占用,我们会到达end_factorial 并弹出;如果不是,则在imul %ebx, %eax 之后到达end_factorial,并且在这种情况下也完成了pop。没有不平衡。
  • 在汇编语言和其他语言中,递归的巧妙之处在于没有什么特别的事情可做。函数调用的编码完全相同,无论是对不同函数的调用还是对这个函数的递归调用。

标签: recursion assembly x86


【解决方案1】:

为什么ebp被推到这里?

程序员/编译器使用%ebp 作为frame pointer。由于%ebp是一个保留调用的寄存器,为了使用它,必须先保留它的原始值,这样才能在返回之前将这个原始值恢复到那个寄存器中。

帧指针在displacement addressing mode 中用于访问参数和局部变量。它不是 在 32 位 x86 代码中总是需要使用帧指针,因为堆栈指针可以用作基址寄存器(具有调整的位移值),但仍然是常见的做法。历史上,16 位 8086 不支持堆栈指针作为位移寻址的基址寄存器,因此需要一个帧指针。更多讨论请参见here,尤其是关于在 16 位 8086 上如何需要帧指针。

ebp 在 end_factorial 中弹出一次,并在调用 factorial 时被推送,其余的推送会发生什么?

处理器看不到标签——它们在机器代码的生产过程中被汇编器删除。

处理器只能看到机器代码指令,因此,只有机器代码指令会影响控制流。

每条机器代码指令都会通知处理器接下来要运行什么指令。许多指令只是告诉处理器继续执行下一条顺序指令——imull 就是这样做的。 je 指令表示要么跳转到标签(条件为真),要么按顺序继续(条件为假)。

因此,当您在汇编语言中解释控制流时,请注意标签对处理器没有任何作用 - 标签只会告知汇编器在编码使用标签的真实机器代码指令时要使用的数字,并且它们否则会被删除,并且不会被处理器看到。

end_factorialfactorial 的一部分,将由je end_factorial 引起的控制流变化执行,或由@987654331 之后的顺序控制流执行@,取决于条件逻辑(无论是否处于基本条件)。

在解释部分,那个eip是什么,它在哪里被压入堆栈?

%eip是指令指针,在其他架构中也称为程序计数器。它是对指令进行排序的寄存器。 call 指令将返回地址压入堆栈,有效地暂停调用者的执行,并将控制权转移给被调用者,这至少完成了函数调用的控制流部分。当被调用者准备好返回给调用者时,它使用ret 将返回地址值从堆栈中取出并将其放入指令指针寄存器 - 这有效地终止被调用者并在之后的指令处恢复调用者通话。返回地址可以认为是从调用者传递给被调用者的(隐藏在 C 中的)参数,因此同一个被调用者可以在任何调用站点返回给任何调用者。

例如,main 可以从main 中的两个不同位置调用factorial 两次,并且每次调用都会提供不同的返回地址值,因此暂停的main 在正确的调用站点之后恢复(调用这个调用阶乘的那个)。此外,main 可以调用factorialmain 也可以调用foo,后者可以调用factorial。返回地址机制支持通用返回给正确的调用者。

【讨论】:

    【解决方案2】:

    ebp 的推送和弹出是 x86-64 机器中调用约定的一部分,但我真的不确定你需要多少它来制作一个简单的递归函数。

    当您在 x86 中调用函数时,遵循调用者和被调用者约定,其中一些寄存器由调用者保存,一些由被调用者保存。此外,在调用函数时\之后还有一些堆栈使用和恢复过程。

    link 解释了有关 x86-64 中堆栈和调用约定的所有内容

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-06
      • 1970-01-01
      • 2018-08-28
      • 2013-05-04
      相关资源
      最近更新 更多