【问题标题】:Seg faults in Binary search in NASM assemblyNASM 程序集中二进制搜索中的段错误
【发布时间】:2020-10-24 14:13:52
【问题描述】:

您好,我正在努力找出为什么我的二进制搜索实现会出现段错误(我是 NASM 程序集的新手)

抱歉,我知道它不是一个 MVP,但我想不出一种合适的方法来组装它。

%define n [ebp+8]
%define list [ebp+12]
%define low [ebp+16]
%define high [ebp+20] ; Parameters loading from the stack
binary_search:
  push ebp
  mov ebp, esp

  mov ebx, n
  mov edi, list
  mov ecx, low
  mov edx, high

  cmp ecx, edx ; if (low > high)
  jg .FIRST

  mov eax, edx ; Next few lines for mid = low + (high - low)/2
  sub eax, ecx
  sar eax, 1 ; Will this give an appropriate index? (i.e is it floor division?)
  add eax, ecx
  lea esi, [edi+eax*4] ;Getting list[mid]
  cmp ebx, [esi]; if (n == list[mid])
  je .END
  jl .SECOND
  jg .THIRD

  jmp .END

.FIRST:
  mov eax, -1 ; return -1
  jmp .END

.SECOND:
  mov edx, eax ; return middle - 1
  dec edx
  jmp .CONTINUE

.THIRD:
  mov ecx, eax ; low = mid - 1
  dec ecx
  jmp .CONTINUE

.CONTINUE:
  push edx
  push ecx
  push edi
  push esi
  push ebx
  call binary_search ; recursive call, causing the segfault.
  pop ebx
  pop esi
  pop edi
  pop ecx
  pop edx
  jmp .END

.END:
  mov esp, ebp
  pop ebp
  ret

在注释掉不同的部分后,我确定这肯定与我对导致 seg 错误的 binary_search 的递归调用有关。 (在 .CONTINUE 中找到) 我在搞砸不同意多次递归调用的 binary_search 主体的原因是什么?

二分查找算法:

binary_search(n, list, low, high)

    if (low > high)
        return -1

    mid = low + (high - low) / 2

    if (n == list[mid])
       return middle;
    if (n < list[mid])
       high = mid - 1
    else
        low = mid + 1

    return binary_search(n, list, low, high)



我知道这是一个远射,谢谢:)

编辑:它的 32 位模式

【问题讨论】:

  • 对于您的评论,算术右移具有向负无穷大舍入的效果,所以是的,地板。你可以尝试一些例子来确认。
  • @liamod:你说得对,部分是对的。但是您将 ebx 作为 n 传递,esi 作为 list 传递,edi 作为 high 传递,而 ecx 作为 low 传递。您还推送和弹出 edx 但不使用该值作为参数。我认为edi不适合这个。不过,我必须检查更多细节才能找到修复方法。
  • 您应该放弃push esipop esi。我认为这对于正确的递归调用是必要的,尽管可能不足以修复所有极端情况下的函数。
  • 我认为在.THIRD 中你可能应该使用inc ecx 而不是dec
  • 谢谢@ecm,主要问题是推送/弹出esi,您能否更详细地解释一下为什么会这样?我会接受这个作为答案。

标签: assembly nasm binary-search


【解决方案1】:

这部分定义了你的函数的协议:

%define n [ebp+8]
%define list [ebp+12]
%define low [ebp+16]
%define high [ebp+20] ; Parameters loading from the stack
binary_search:
  push ebp
  mov ebp, esp

dword [ebp] 将保留旧的ebp 值,dword [ebp+4] 返回旧的eip,然后您的参数将跟随。它们依次是 n、list、low 和 high。随着堆栈向下增长,您需要按顺序推送到堆栈,以便首先推送最高寻址参数(恰好是“高”),然后是第二高(低),依此类推(列表,n, eip, ebp)。

下一部分确定您使用哪些寄存器来保存从这些堆栈帧参数初始化的变量:

  mov ebx, n
  mov edi, list
  mov ecx, low
  mov edx, high

ecxedx 在递归调用之前被修改。但它们仍然充当该代码中的“低”和“高”变量。)

现在检查您的递归调用站点。本质上,您想再次将您正在使用的所有寄存器传递给该函数。 ebx = n,edi = 列表,ecx = 低,edx = 高。这就是你要做的:

.CONTINUE:
  push edx
  push ecx
  push edi
  push esi
  push ebx
  call binary_search ; recursive call, causing the segfault.
  pop ebx
  pop esi
  pop edi
  pop ecx
  pop edx

如果你将推送的变量与你的函数协议所期望的堆栈帧参数相匹配,你会得到:

  push edx    ; (unused)
  push ecx    ; high
  push edi    ; low
  push esi    ; list
  push ebx    ; n
  call binary_search

esi 表示的变量仅在您的函数内部使用。它不需要传递给后续调用。更重要的是,它与您的函数协议相混淆。 edxecxedi 被推高一个堆栈槽,比它们应该传递这些变量传递给你的递归调用。解决办法是把push esipop esi放在这里。


您的代码还有一些潜在问题:

  • 正如我在 cmets 中提到的,您应该使用 inc ecx 而不是 dec ecx

  • 您的程序调用该函数的调用约定是什么?您似乎使用了很多寄存器,并且只保留了ebpesp

  • 如果您的调用约定允许无限制地更改堆栈帧参数内容,则可以将用于递归的文字 call 替换为“尾调用”,并将参数更改为您当前传递给 @ 987654349@。并重新使用整个堆栈帧。不过,此时它看起来与循环非常相似。也许您确实想要一个实际的(字面意思)递归实现。尾调用优化或迭代方法需要 O(1) 的堆栈空间,而不是 O(log2 n)。

【讨论】:

  • 感谢@ecm,提供详细答案,是的,我更改了 dec -> inc ,只是一个错字,我们使用的是 cdecl,我在函数调用之前推送它们时是否没有保留所有寄存器还是只是通过他们?我知道这种方法效率低下,但它必须是递归的。再次感谢!
  • @liamod:考虑到函数不会修改堆栈上的参数,它实际上同时传递和保留这 5 个寄存器(或 4 个,如果您删除 esi 操作)。但是,这只发生在递归调用周围。它不会发生在最外层的函数调用周围(来自您的代码,此处未显示)。看来you should preserve ebx, esi, and edi。为此,您应该添加以下内容:push 之后的每个寄存器 mov ebp, esppop 之前的每个 mov esp, ebp
猜你喜欢
  • 1970-01-01
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
  • 2020-07-28
  • 2023-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多