【问题标题】:Recursion in Assembly x86: Fibonacci汇编 x86 中的递归:斐波那契
【发布时间】:2021-07-18 23:13:41
【问题描述】:

我正在尝试在汇编中编写递归斐波那契序列,但由于某种原因它无法正常工作。

没有报错,但是输出的编号总是错的。

section .bss
  _bss_start:
    ; store max iterations
    max_iterations resb 4
  _bss_end:

section .text
  global _start

; initialise the function variables
_start:
  mov dword [max_iterations], 11
  mov edx, 0
  push 0
  push 1
  jmp fib

fib:
  ; clear registers
  mov eax, 0
  mov ebx, 0
  mov ecx, 0
  ; handle fibonacci math
  pop ebx
  pop eax
  add ecx, eax
  add ecx, ebx
  push eax
  push ebx
  push ecx
  ; incriment counter / exit contitions
  inc edx
  cmp edx, [max_iterations]
  je print
  ; recursive step
  call fib
  ret

print:
  mov eax, 1
  pop ebx
  int 0x80

例如,上面的代码打印的值是 79 而不是 144(第 11 个斐波那契数)。

或者,如果我做

mov dword [max_iterations], 4

然后上面的代码打印 178 而不是 5(第 5 个斐波那契数)。

有人有想法吗?

K

【问题讨论】:

  • 错了怎么办?总是0?对于适合 Linux 进程的 1 字节退出状态的小数字是否正确,而对于较大的数字则截断? (mod 256)minimal reproducible example 需要这样的详细信息,以便人们知道他们正在寻找什么样的错误。
  • 对,对不起,会更新的
  • 您已将 max_iterations 声明为单个字节的存储空间,但使用它时就像有 4 个字节一样。
  • 您将max_iterations 声明为 1 个字节,然后将其视为 DWORD:mov dword [max_iterations], 11
  • fib 函数所做的第一件事是从堆栈中弹出内容,这意味着它将删除返回地址。您不应该弹出函数参数,只需正常从内存中读取它们(或使用寄存器)。

标签: recursion assembly x86 fibonacci


【解决方案1】:

作为一种方法,您应该尝试使用尽可能少的输入进行调试,例如 1 次迭代。这将是最具启发性的,因为您可以非常详细地看到它做错事,而不必担心多次递归。如果可行,请进行 2 次迭代。

当您使用复杂的寻址模式时,调试起来会更加困难,因为我们看不到处理器在做什么。因此,当使用复杂寻址模式的指令不起作用时,您想对其进行调试,然后将该指令拆分为 2 条指令,如下所示:

mov   dword [fibonacci_seq + edx + 4], ecx
---
lea   esi, [fibonacci_seq + edx + 4]
mov   [esi], ecx

通过备用代码序列,您可以观察寻址模式计算的价值,这将为您提供额外的调试洞察力。

再举一个例子:

cmp edx, [max_iterations]
---
mov edi, [max_iterations]
cmp edx, edi

使用 2 指令版本,您将能够看到处理器将 edx 与什么值进行比较。

或者更好的是,在循环之前执行一次 mov 加载,这样您就可以一直将循环绑定在寄存器中。这就是当你有足够的寄存器时你通常应该做的,只有当你用完时才使用内存。


您从代码中的一个地方jmping 到fib,从另一个地方calling 它。尽管您的逻辑应该可以工作,因为当您达到限制时,您不会返回主代码,这是非常糟糕的形式:将主代码与函数混合。更多内容如下...


mov dword [fibonacci_seq + edx + 4], ecx

这对你有用吗?您只是将 edx 增加 1。也许您想要:

mov dword [fibonacci_seq + edx * 4], ecx

我认为您的代码并不是真正的递归。

call fib      ; jumps to fib, pushes a return address
ret           ; never, ever reached, so, pointless
---
jmp fib       ; iterate w/o pushing unwanted return address onto the stack

1 指令 jmp 将优于 call 作为一种迭代机制,在 b/c 部分它不会将不必要的返回地址压入堆栈。

当您使用 2 次迭代进行调试时,您可能会看到调用推送的未使用的返回地址扰乱了您的“参数”传递,弹出。

为了扩展“递归”,当迭代停止并且控制转移到print 时,堆栈上将有大约 11 个(取决于迭代次数)未使用的返回地址(以弹出和推送的干扰为模) .

递归调用仅用于迭代,递归永远不会展开。因此,我认为它不是递归的(甚至不是尾递归)——它只是错误地将一些未使用的返回地址推入堆栈——这不是递归。

【讨论】:

  • 好点,我已经编辑了代码以尝试使问题更清晰
【解决方案2】:

这一行是你的主要问题:

mov dword [fibonacci_seq + edx + 4], ecx

由于 +4,您永远不会写入“数组”的第一个条目。而且因为您只将 EDX 增加 1,所以每次写入数组都会覆盖前一个条目的 3 个字节。试试这个:

    mov dword [fibonacci_seq + edx * 4], ecx

【讨论】:

  • 对,我将删除它并用结果更新描述
【解决方案3】:

有点重新设计,因为我没有意识到调用指令是这样使用堆栈的,解决方法在这里

section .bss
  _bss_start:
    ; store max iterations and current iteration
    max_iterations resb 4
    iteration resb 4
    ; store arguments
    n_0 resb 4
    n_1 resb 4
  _bss_end:

section .text
  global _start

; initialise the function variables
_start:
  mov dword [max_iterations], 11
  mov dword [iteration], 0
  mov dword [n_0], 0
  mov dword [n_1], 1
  jmp fib

fib:
  mov ecx, 0
  mov edx, 0
  mov eax, [n_0]
  mov ebx, [n_1]
  add ecx, eax
  add ecx, ebx
  mov edx, [n_1]
  mov dword [n_0], edx
  mov dword [n_1], ecx

  mov edx, [iteration]
  inc edx
  mov dword [iteration], edx

  cmp edx, [max_iterations]
  je print

  call fib
  ret

print:
  mov eax, 1
  mov ebx, [n_1]
  int 0x80

【讨论】:

  • 虽然修复了调用指令和 pop/push 参数传递之间的干扰,但这仍然不是递归的。 call 无条件地将控制权转移到(顶部)fibret 永远不会被执行。当它到达print 时,堆栈上将有未使用的返回地址,只要有超过 1 次迭代。最好将jmp 转为fib,而不是使用call
猜你喜欢
  • 2011-08-02
  • 2012-11-06
  • 2013-10-13
  • 2014-06-24
  • 2016-06-05
  • 1970-01-01
  • 1970-01-01
  • 2012-03-25
  • 2020-08-11
相关资源
最近更新 更多