当前位置: 首页 > 知识库问答 >
问题:

为什么这个汇编代码会无限循环?

江洲
2023-03-14

该程序应该使用int 0x10在ASCII中打印一个具有给定字符的金字塔,3行的预期结果(下面代码中使用的数量)将是:

A.

a a

a a a

要编译和运行代码,我使用nasm编译它,然后使用qemu进行仿真:

nasm pyramid.asm
qemu-system-x86_64 -drive file=pyramid,format=raw,index=0,media=disk

然而,程序get无法打印所有ASCII值。此外,如果有任何针对nasm代码的调试器,可以让您逐行运行,允许您检查寄存器值,这对学习也很有帮助。

[bits 64]
[org 0x7c00]   

mov sil, CHAR            ; Save the char in the sil register.
add sil, 48              ; Adds 48 to display it as ASCII.
mov ah, 0x0e             ; Value in 'ah' needed to be able to use 'int 0x10'.
mov cl, 0x3              ; Setting the counter of lines remaining.
mov bx, 0x1              ; Setting the amount of characters to print.

pyramid: 
    mov dx,bx            ; Creates a copy bx in dx.
    cmp cl,0             ; If we already printed all the lines we exit the program.
    je exit              ;
    jmp printLine        ; Otherwise we print the next line.

printLine:
    cmp dx,0             ; If all characters from the line were printed goes to next line
    je endPrintLine      ; 
    printChar:
        mov al, sil      ; We move the counter to the 'al' register. 
        int 0x10         ; Interruption that prints the content of the register al.
        mov al,0x20      ; We move the value 0x20 (space) to the 'al' register.
        int 0x10         ; Interruption that prints the content of the register al.
        add dx,-1        ; Decrement by 1 the amount of characters remaining.
        jmp printLine    ; Print the next line.
    endPrintLine:        ;
        mov al,0xA       ; We move the vale 0xA (next line) to the 'al' register.
        int 0x10         ; Interruption that prints the content of the register al.
        add cl,-1        ; Decrement by 1 the amount of lines remaining.
        add bx,1         ; Icrement the amount of chars to print by 1.
        jmp pyramid      ;

exit:
    jmp $

CHAR: db "a",0           ; Character we want to make the pyramid of. 

times 510-($-$$) db 0    ; Fill with 0s.
dw 0xaa55                ; Save in 0x511 '0xaa55' to indicate it's bootable.

共有1个答案

凌永逸
2023-03-14

您不能仅仅将NASM的组装切换到模式,而期望qemu以长模式运行您的代码。您调用的方式似乎建议您使用Real 8086模式,即位16(NASM的默认值)。这可能是由于您使用了许多8位或大小无关的操作,因此代码确实以某种方式运行,但没有按预期运行。

此外,您还将mov al、sil注释为“将计数器移至”al,但这不是计数器。而初始的mov sil,CHAR并没有将“CHAR”指向的字符放入sil,而是将CHAR的地址放入寄存器中(预期为sil,但无论如何都会被解释为R86M)。和添加sil,48也没有任何意义。48(30h)是将十进制数(0到9)从数值转换为该数字的ASCII数字时要添加的正确值。它不是一种通用的“将其显示为ASCII”转换,它只适用于十进制单位数。

您还没有提到qemu运行会永远卡在循环中,在无限循环中显示各种字符。

以下是16位模式下的代码反汇编:

$ ndisasm -b 16 -k 0x3D,$((512 - 0x3D)) pyramid
00000000  40                inc ax
00000001  B63D              mov dh,0x3d
00000003  40                inc ax
00000004  80C630            add dh,0x30
00000007  B40E              mov ah,0xe
00000009  B103              mov cl,0x3
0000000B  66BB01006689      mov ebx,0x89660001
00000011  DA80F900          fiadd dword [bx+si+0xf9]
00000015  7424              jz 0x3b
00000017  EB00              jmp short 0x19
00000019  6683FA00          cmp edx,byte +0x0
0000001D  740F              jz 0x2e
0000001F  40                inc ax
00000020  88F0              mov al,dh
00000022  CD10              int 0x10
00000024  B020              mov al,0x20
00000026  CD10              int 0x10
00000028  6683C2FF          add edx,byte -0x1
0000002C  EBEB              jmp short 0x19
0000002E  B00A              mov al,0xa
00000030  CD10              int 0x10
00000032  80C1FF            add cl,0xff
00000035  6683C301          add ebx,byte +0x1
00000039  EBD4              jmp short 0xf
0000003B  EBFE              jmp short 0x3b
0000003D  skipping 0x1C3 bytes

让我们一步一步地检查分解后的机器代码。

mov sil, CHAR位被解码为inc ax(REX前缀字节,40h),然后mov dh,3Dh

00000000  40                inc ax
00000001  B63D              mov dh,0x3d

然后另一个REX前缀字节和添加dh,30h

00000003  40                inc ax
00000004  80C630            add dh,0x30

现在dh等于6Dh('m')。

接下来的两条指令是不带REX前缀字节的8位操作,因此它们按照您的预期进行解释:

00000007  B40E              mov ah,0xe
00000009  B103              mov cl,0x3

然后您进入mov bx,1,它用O16前缀组装(32位或64位模式下的OSIZE)。这被解释为O32,而不是我们在16位代码段中:

0000000B  66BB01006689      mov ebx,0x89660001

现在,反汇编程序继续执行错误的指令,因为O32前缀BBh是mov ebx,imm32,而不是预期的mov bx,imm16。

00000011  DA80F900          fiadd dword [bx+si+0xf9]

在这种情况下,这本质上是一个无操作指令。然后我们进入跳转:

00000015  7424              jz 0x3b
00000017  EB00              jmp short 0x19

我相信inc ax很可能会使标志处于非零(NZ)状态(fiadd不会改变它),因此这里的jz不会分支。

00000019  6683FA00          cmp edx,byte +0x0
0000001D  740F              jz 0x2e

该比较是在整个edx上进行的。由于采用了经过优化的形式,符号扩展的8位立即数,从预期的O16到O32的唯一变化是将比较整个edx寄存器。然而,由于涉及edx的高位字,该循环可能会运行超过4GA的迭代。

0000001F  40                inc ax
00000020  88F0              mov al,dh

再次将sil寄存器解码为REX前缀字节(inc),然后访问dh。这就是无限循环显示不同字符的原因:您正在从循环计数器的中间字节初始化al

00000022  CD10              int 0x10
00000024  B020              mov al,0x20
00000026  CD10              int 0x10

这里没有任何意外,所有解释都是有意的。

00000028  6683C2FF          add edx,byte -0x1
0000002C  EBEB              jmp short 0x19

根据从qemu传递给您的程序的edx中的初始值,此添加会产生很长的循环。

0000002E  B00A              mov al,0xa
00000030  CD10              int 0x10
00000032  80C1FF            add cl,0xff
00000035  6683C301          add ebx,byte +0x1
00000039  EBD4              jmp short 0xf

这里没有太多惊喜。但是,ebx是递增的,而不是bx

0000003B  EBFE              jmp short 0x3b

此停止循环按您希望的方式进行解释。

返回到标签金字塔的循环对代码片段的解释如下:

$ ndisasm -b 16 -s 0xF -k 0x3D,$((512 - 0x3D)) pyramid
[...]
0000000F  6689DA            mov edx,ebx
00000012  80F900            cmp cl,0x0
00000015  7424              jz 0x3b
[...]

因此,它将循环计数器edx初始化为ebx的完整值。这又会导致一个很长的循环。cmp cl,0的解释符合预期。

这里有一个固定的程序重写。它不再使用sil,因为您无法在16位模式下使用sil,而且无论如何都不需要它。它不使用bx作为内环计数器重置值,因为中断10h ah=0Eh服务可能使用bx。此外,它使用完整的cx作为外循环计数器,这不是必需的,但允许使用循环指令而不是dec cl jnz。loop\u outer(外循环)。

此外,我还修复了程序中的两个错误:

>

  • 构建金字塔的最后一个字符,每行,后面跟着一个尾随的空白。我更改了程序,首先显示空白,然后显示另一个字符。

    换行符只显示了一个10(0Ah,换行)字符的代码。对于中断10h服务级别,正确的是13(回车)然后10(换行)。

    还有一个问题是,您对其本身使用了一个普通的jmp来停止。这将消耗大量CPU时间,因为它将永远循环。我使用了一个sti序列,它在qemu进程停止时将CPU时间保持在接近零的位置。

    来源如下:

            ; cpu 386       ; no 32-bit registers used or needed here!
            cpu 8086
            bits 16
            org 0x7c00
    
    start:
            mov ah, 0Eh     ; value to call "display TTY" int 10h service
            mov cx, 3       ; outer loop counter
            mov di, 1       ; inner loop counter initialisation,
                            ;  incremented by each outer loop
            mov bx, 7       ; bx initialised to 7 for Int10.0E "page" and "colour".
    
                    ; Note: Do not use bp register, as it may be overwritten by
                    ;        the Int10.0E call.
    
    .loop_outer:
            mov dx, di      ; reset inner loop counter to di
    
    .loop_inner:
            mov al, ' '
            int 10h         ; display a blank
            mov al, 'a'
            int 10h         ; display the character we want to show
            dec dx
            jnz .loop_inner ; loop inner loop
    
            mov al, 13
            int 10h
            mov al, 10
            int 10h         ; display a line break
    
            inc di          ; increment reset value for inner loop counter
            loop .loop_outer; loop outer loop
    
    halt:
            sti
            hlt             ; halt the system (without using much CPU time)
            jmp halt
    
            times 510-($-$$) db 0
            dw 0AA55h
    

    按如下方式运行:

    $ nasm p.asm -l p.lst -o p.bin
    $ qemu-system-x86_64 -drive file=p.bin,format=raw,index=0,media=disk
    

    它在我的qemu上显示以下输出:

    [...]
    
    Booting from Hard Disk...
     a
     a a
     a a a
    [cursor here]
    

  •  类似资料:
    • 我写了这个简单的代码来获取一个double,并一直询问直到得到一个为止,但是当你给一个string时,它就变成了一个无限循环,我不知道为什么。为什么它会这样?

    • 问题内容: 这段代码使我凝视了几分钟: (这里的第137行) 我以前从未见过,而且我也不知道Java有一个“ loop”关键字(NetBeans甚至没有像关键字一样给它上色),并且它在JDK 6中可以很好地编译。 有什么解释? 问题答案: 它不是一个keyword,而是一个label。 用法:

    • 因此,我开始开发x86\u 64 hobby内核,我发现这段代码用于加载GDT(全局描述符表),但我不知道它是做什么的。 我知道它从rdi寄存器(sysv abi中函数调用的第一个参数的寄存器)加载我的gdt描述符,但我不知道为什么我需要将所有段寄存器设置为0x10,其余的是什么黑魔法?

    • 我有一个看起来很简单的问题,但由于某种原因我无法绕过它。基本上我的程序正在导致一个无限循环,我不知道为什么。 下面是我陷入的特定循环: 当我运行它时,它总是问我输入列#。我给它一个数字,它接受这个数字,$response变为True,但while循环继续运行,就好像<code>的$response</code>为false一样。我是Perl新手,所以可能我遗漏了一些东西,但是($response=

    • 我知道这行代码将某些内容移动到eax寄存器中,但是dword ptr [edx 15Ch]有什么作用? 它是否接受edx中的值并添加15C=Q,然后将地址Q中的值加载到eax中?如果是这样,这个conetxt中的dword ptr是什么意思?

    • 尝试编写一个程序,其中用户输入他们一周的费用。麻烦在于我希望程序重新询问用户是否输入了不可接受的输入。我想出了如何检测负值的方法,但是当尝试捕获输入不匹配异常(如果像输入字符或字符串一样)时,循环只是无限运行,要求“星期一费用:”我如何使它,以便用户有另一个回答的机会?我试着Rest一下;但是这也打破了做而循环,我不想要。 这是我目前为止的代码: 谢谢你的帮助