nasm 中的嵌套循环

Nested loops in nasm

我在 Windows 10 64 位机器上 nasm 中编写了以下程序

extern _GetStdHandle@4
extern _WriteFile@20
extern _ReadFile@20
extern _ExitProcess@4

;ReadBufferSize
%define rbs 100

section .rodata
    w: dd 3
    h: dd 3
    text: db "abc", 10

section .data
    x: dd 4
    y: dd 4

section .bss
    stdout: resb 4
    stdin: resb 4
    readbuff: resb (rbs+2); allow for CR, LF
    bytecnt: resb 4

section .text
    _print:
        push ebp
        mov ebp, esp

        push 0
        lea eax, [ebp-4]
        push eax
        mov eax, [ebp+8]
        push dword [eax]
        push dword [ebp+12]
        push dword [stdout]
        call _WriteFile@20

        mov esp, ebp
        pop ebp
        ret 8

    _read:
        push ebp
        mov ebp, esp

        push 0
        push dword [ebp+8]
        push rbs
        push dword [ebp+12]
        push dword [stdin]
        call _ReadFile@20
        sub dword [bytecnt], 2; remove CR and LF

        mov esp, ebp
        pop ebp
        ret 8
    _draw:
        push ebp
        mov ebp, esp
        
        push dword [w]
        L1:
            push dword [h]
            L2:
                push text
                push x
                call _print
            dec dword [ebp-8]
            cmp dword [ebp-8], 0
            jg L2
        dec dword [ebp-4]
        cmp dword [ebp-4], 0
        jg L1

        mov esp, ebp
        pop ebp
        ret

    global _main
    _main:
        push -11
        call _GetStdHandle@4 ;Get Stdout handle
        mov [stdout], eax

        push -10
        call _GetStdHandle@4 ;Get Stdin handle
        mov [stdin], eax
        
        call _draw

        push 0
        call _ExitProcess@4

        hlt

我会清理它,但这可能会影响某些东西,所以我不会(抱歉)。 _draw函数应该在屏幕上输出“abc\n”9(3*3)(3和3,因为wh是3)次,但它只输出 5 次。据我检查,_print 按预期工作。如果有帮助,这是我的构建脚本:

@echo off
IF "%~2" EQU "" set f0rm4t=win32
IF "%~2" NEQ "" set f0rm4t=%~2
if exist %1.exe rm %1.exe
if exist %1.obj rm %1.obj
nasm -f %f0rm4t% %1.asm
if exist %1.exe goto run
if not exist %1.obj goto end
gcc %1.obj -o %1.exe
if %errorlevel% EQU 0 goto run
:run
%1.exe
echo ------------------------
if %errorlevel% EQU -1073741819 (
    echo exited with C0000005
    goto end
)
if %errorlevel% EQU 0 (
    echo exited successfully
    goto end
)
echo exited with return value %errorlevel%
:end

我使用 build filename 命令构建,它运行正常。 我已经检查了我的逻辑,它似乎很好,所以这是嵌套循环的问题,我不知道到底是什么。 完成 build 命令输出:

C:\Users\User\files>build filename
abc
abc
abc
abc
abc
------------------------
exited successfully

C:\Users\User\files>

如果循环正常,那么输出5次是不可能的,因为它应该是(外圈的重复)*(内圈的重复),但是5是质数.我想不通,所以我把它留给这个社区。祝你有美好的一天,提前致谢!

第一次通过外循环,[ebp-8]倒数到零。接下来两次通过外循环,[ebp-8] 已经为零(或更少),所以内循环只执行一次。总共五次调用打印。

请注意 push [h] 的后续执行不会更改 [ebp-8]。 在第二次迭代中,它写入 [ebp-12],在第三次迭代中它写入 [ebp-16],因为堆栈指针随着每次推送继续向下增长。

要解决此问题,我建议:

  1. mov ebp, esp 之后添加 sub esp, 8
  2. push [w] 更改为 mov eax, [w]; mov [ebp-4], eax
  3. push [h] 更改为 mov eax, [h]; mov [ebp-8], eax

在查看@prl 的回答开头后,我意识到,正如他们所说,[ebp-8] 在内循环的第一次迭代和下一次 push [h] 中递减为 0推送到 [ebp-12],因此 [ebp-8] 保持为 0。所以我要做的第一件事就是在跳转后添加一个 pop edx(使用 edx 作为垃圾寄存器),如下所示:

    _draw:
        push ebp
        mov ebp, esp
        
        push dword [w]
        L1:
            push dword [h]
            L2:
                push text
                push x
                call _print
            dec dword [ebp-8]
            cmp dword [ebp-8], 0
            jg L2
            pop edx
        dec dword [ebp-4]
        cmp dword [ebp-4], 0
        jg L1
        pop edx

        mov esp, ebp
        pop ebp
        ret

这将清除堆栈中已经置零的变量并为新变量腾出空间,并且它有效。

但我认为@prl 的回答通常更适用于在堆栈上分配变量的技术,所以如果可以的话,请使用它,因为它也不会那么令人困惑。