【问题标题】:Infinite loop using triple nested for loop (32-bit Assembly)使用三重嵌套 for 循环的无限循环(32 位汇编)
【发布时间】:2017-02-02 15:25:46
【问题描述】:

下面我尝试在 32 位汇编中实现的 C++ 代码是这样编写的:

for(int ebx = 3; ebx < 10; ebx++){
      print("LO");
      for(int esi = 2; esi < ebx; esi++){
           PRINT("L1");
           for(int ebp = 0; ebp < esi; ebp++){
                PRINT("L2");
           }
      }
}

这是我的汇编代码:

SECTION .data                    ; Section containing initialized data
helloWorld0: dw "L1",10,0
helloWorld1: dw "L2",10,0
helloWorld2: dw "L3",10,0

SECTION .bss                     ; Section containing uninitialized data
SECTION .text                    ; Section containing code
extern printf                    ; Print function from glibc
global main                      ; Linker needs this to find the entry point
main:
nop                              ; This no-op keeps gdb happy
push     ebp                     ; Set up stack frame for debugger
mov      ebp,esp
push     ebx                     ; Must preserve EBP, EBX, ESI & EDI
push     esi
push     edi
; Everything before this is boilerplate; use it for all  apps

mov ebx, 3                      ;L1
mov esi, 2                      ;L2
mov ebp, 0                      ;L3
L1:
push    helloWorld0
call    printf

L2:
push    helloWorld1
call    printf

L3:
push    helloWorld2
call    printf
inc     ebp
cmp     ebp, esi
jne     L3

inc     esi
cmp     esi, ebx
jne     L2

mov     esi, 2
inc     ebx
cmp     ebx, 10
jne     L1
; Everything after this is boilerplate; use it for all apps
pop     edi                     ; Restore saved registers
pop     esi
pop     ebx
mov     esp,ebp                 ; Destroy stack frame before returning
pop     ebp
ret                              ; Return control to Linux

似乎代码永远无法确定何时 ebp = esi。我是汇编语言的新手,所以我的教授提供了样板文件。我使用了 EBP、ESI 和 EBX,因为它们被保留以供使用。关于导致第三个嵌套循环的无限循环的任何想法?

【问题讨论】:

  • 在循环内逐行调试时发现了什么?
  • 我用linux终端编码,需要什么软件调试?
  • 您的任务似乎与此类似:stackoverflow.com/questions/39680843/…。我那里的一些 cmets 将适用于此。
  • 使用 gdb 在 Linux 文本外壳中进行调试,就像 Michael 在 cmets 中对您上一个问题提出的建议一样。请参阅x86 tag wiki 的底部以了解有关将其用于 asm 的一些提示。
  • @Michael Petch,嘿,感谢您对我上一个问题的帮助!它把我带到了我的程序的这一点!

标签: assembly x86 nasm 32-bit


【解决方案1】:

在您的 C 中,int ebp = 0 位于两个外部循环中,并且在进入内部循环之前运行每次。那不是它在你的 asm 中的位置。

另外,不要在函数内部修改 EBP。您的教授的样板文件使用 EBP 作为框架指针创建了一个“堆栈框架”(google it)。在函数的最后,mov esp,ebp 用于清理。 (这也是一件好事,因为您的代码为 printf 推送了 args,但在 printf 返回后永远不会调整 ESP!)

大概未来的讲座将讨论使用[ebp + 8][ebp - 4] 或其他方式访问函数参数和本地变量。现在,只要知道这种帧指针废话正在浪费您的另一个宝贵寄存器,因此您不能使用它。 (即,在 8 个 x86 整数寄存器中,您可以使用且未被函数调用修改的仅有 3 个寄存器是 EDI、ESI 和 EBX。)


re: Cedric 关于分支条件的观点:

你的循环很好,除非我错过了什么。

您的循环都具有它们应该始终运行一次的便利属性,因此您可以将它们写为do{}while() 循环而无需在顶部进行额外检查。这意味着它们可以在 asm 中很好地实现,就像你正在做的那样在底部进行检查。

例如,printf(即循环体)应该在第一个内部循环上运行两次:一次 EBP=0,一次 EBP=1。之后,增量将使 EBP=2,因此 CMP/JNE 将下降,因为 EBP==ESI==2。

对于外部循环,逻辑是相同的。整个内部循环是“循环体”,它在您增加计数器之前运行。所以你没事。

您可以等效地使用 CMP/JL 进行有符号比较并在计数器小于限制时重新运行循环体。这将与 C 相匹配。它也会将故障模式从大约改变。 2^32 次通过内循环(直到 inc ebp 缠绕并达到 2)。使用 JL,如果循环计数器太高以至于我们一开始就不应该进入循环,则当前错误的故障模式将是只通过内部循环一次。


通常在 asm 中,倒数到零更容易,例如dec ebp / jnz 不需要 CMP。转换你的循环来做到这一点并非易事,因为你的外循环计数器充当内循环的上限。不过应该是可以的。您没有打印出循环内的计数器值,因此实际上它们是什么并不重要,只是循环行程计数。

【讨论】:

  • 这绝对是答案,尽管for 循环具有某些未包含在您的答案中的属性。我知道在编译器中将优化设置为高设置可能会如您所说重写它,将 C 直接转换为 asm 需要 for 循环可能根本不会运行,如果比较返回 false。此外,增量是在循环体之后完成的。最后,使用 jne 而不是 jl 令人困惑,因为据我所知,这样做没有任何好处,除了如果不了解 asm 的人试图将您的代码用于另一个项目时的意外行为。跨度>
  • @Cedric:正如我在回答中指出的那样,这里的增量在循环体之后完成的。 OP 代码中循环执行期间寄存器中的计数器值 do 与 C. 匹配
  • @Cedric:在 asm 中实现for() 循环的正常方法是在循环外使用 test&branch 来检查它是否需要运行 0 次,然后落入 do(){}while 结构中底部的测试和分支,就像 OP 一样。正如我在答案中指出的那样,由于 OP 代码的特殊情况,可以在循环之外省略初始测试。就 JNE 与 JL 而言,是的,我同意,通常最好在方便且不干扰英特尔 CPU 上的宏融合(将 cmp/jcc 转换为单个 uop)时缩小真实比较的范围。
  • 是的,但是比较是在循环体之后和增量之后进行的。这意味着,正如您所说,它更接近于 do-while 而不是 for。即使结果可能相同,但完成的方式却不同。在这种情况下,如果 OP 尝试尽可能多地复制初始 C 代码,那么即使没有调试器,调试也会变得相当容易。例如,jl 指令会阻止无限循环的发生。
猜你喜欢
  • 1970-01-01
  • 2020-08-09
  • 2021-12-11
  • 2018-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多