プログラムは、int 0x10を使用してASCIIで指定された文字を使用してピラミッドを出力する必要があります。3行の期待される結果(以下のコードで使用される量)は次のようになります。
A
aa
aaa
コードをコンパイルして実行するには、コードをコンパイルしてnasm
から、次のようqemu
にエミュレートします。
nasm pyramid.asm
qemu-system-x86_64 -drive file=pyramid,format=raw,index=0,media=disk
ただし、プログラムはすべての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.
NASMのアセンブルを[bits 64]
モードに切り替えて、qemuがコードをロングモードで実行することを期待することはできません。呼び出す方法は、Real 8086モードbits 16
(NASMのデフォルト)を使用する必要があることを示唆しているようです。多くの8ビットまたはサイズに依存しない操作を使用しているため、コードは何らかの方法で実行されますが、期待どおりに実行されない可能性があります。
また、mov al, sil
「カウンターを移動する」とコメントしましたal
が、それはカウンターではありません。また、イニシャルmov sil, CHAR
は「CHAR」が指す文字をに入れません。sil
実際には、CHARのアドレスをレジスタに入れます(sil
これはR86Mのように解釈されます)。またadd sil, 48
、意味がありません。48(30h)は、10進数(0から9)を数値からその数値のASCII数字に変換するために追加する正しい値です。これは一般的な「ASCIIとして表示する」変換ではなく、10進数の1桁の数値に対してのみ機能します。
また、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プレフィックスバイトとadd dh, 30h
:
00000003 40 inc ax
00000004 80C630 add dh,0x30
これでdh
6Dh( 'm')になります。
次の2つの命令は、REXプレフィックスバイトを使用しない8ビット演算であるため、意図したとおりに解釈されます。
00000007 B40E mov ah,0xe
00000009 B103 mov cl,0x3
次にmov bx, 1
、O16プレフィックス(32ビットまたは64ビットモードのOSIZE)でアセンブルされたものを取得します。これは、16ビットコードセグメントにあるため、代わりにO32として解釈されます。
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
が含まれているため、このループは4ギガを超える反復で実行される可能性があります。
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
この追加によりedx
、qemuからプログラムに渡される初期値に応じて、非常に長いループが作成されます。
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
この停止ループは、希望どおりに解釈されます。
ラベルへのループバックは、pyramid
そのコードフラグメントを次のように解釈します。
$ 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
あなたが使用することはできませんので、もうsil
16ビットモードでは、それがとにかく必要とされていませんでした。割り込み10hah = 0Ehサービスで使用される可能性があるbx
ため、内部ループカウンタのリセット値としては使用されませんbx
。さらに、fullcx
を外部ループカウンターとして使用します。これは必須ではありませんがloop
、dec cl
\の代わりに命令を使用できますjnz .loop_outer
。
さらに、プログラムの2つのエラーを修正しました。
行ごとにピラミッドを構築する最後の文字の後に、末尾の空白が続きます。最初に空白を表示し、次に他の文字を表示するようにプログラムを変更しました。
改行には10(0Ah、改行)文字コードのみを表示しました。正しいのは、割り込み10時間のサービスレベルで13(キャリッジリターン)、次に10(ラインフィード)です。
もう1つの問題は、jmp
停止するためにプレーンを使用したことです。これは永久にループするため、多くのCPU時間を消費します。私が使用sti
\ hlt
\jmp
停止しながら、ゼロに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]
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加