ぐだぐだ低レベルプログラミング(197)x86(16bit)、LOOPとJCXZ

Joseph Halfmoon

前回は条件フラグ共を「見て飛ぶ」条件ジャンプ命令Jccでした。今回はカウンタ・レジスタCXを「見て飛ぶ」(ついでの操作もあるけど)条件ジャンプ命令どもです。16ビットのx86はこのCXの扱いといい、ぜんぜん汎用レジスタでないです。御先祖から引きづっているものが多過ぎる?でもま、ループなど作る時には便利な命令どもです。

※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら

※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
カウンタCXを「見て」飛ぶJumpども

今回扱う命令は以下の4つのオペコード(第1バイト)です。LoopJCXZopcodemap

例によって、1個のオペコードに二種類のニーモニックが割り当てられている命令がありますが、ゼロといってもよいし、イコールと言っても良いと。哲学的?

上記の4命令とも全て「汎用」レジスタCXの値を見て飛びます。シンプルなJCXZは「CXが0だったら飛ぶ」だけですが、LOOPの名を持つ命令どもは、まずCXをデクリメントして、その結果が0でなければ「飛ぶ」かもしれんです。「かも」とうのは、LOOPEとLOOPNEの場合、ゼロフラグもあわせて確認するからです。それら条件を表にしたものが以下です。

mnemonic CX ZF
JCXZ =0
LOOP DEC CX; ≠0
LOOPZ/LOOPE DEC CX; ≠0 1
LOOPNZ/LOOPNE DEC CX; ≠0 0

ありがちなFORループのようなものを作るには向いている(かもしれない)命令どもです。RISC派閥からは余計なお世話だ、シバリが多過ぎる、と文句を言われそうな気もしないでもないです。

なお上記の命令どもは全て8ビットのディスプレースメントをとり、「飛ぶ」場合には、符号拡張された16ビット値としてIPに加えられます。

今回練習のアセンブリ言語ソース

強力なx86用アセンブラNASM用のソースです。JCXZについては飛ぶのと飛ばないの、両方通してますが、LOOPは2回「回る」ときだけ、LOOPEはノンゼロなので飛ばないときだけです。手抜きだよ。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
segment code
..start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, stacktop
test:
mov cx, 0
jcxz lbl_cx0
mov ax, 999
lbl_cx0:
mov ax, 0
mov cx, 2
jcxz fin
loop_target:
inc ax
loop loop_target
mov cx, 1000
loop_target2:
cmp ax, 0
loope loop_target2
fin:
mov ax, 0x4c00
int 0x21
resb 2048
segment data align=16
resb 1024 * 63
segment stack class=STACK
resb 2048
stacktop:
segment code ..start: mov ax, data mov ds, ax mov ax, stack mov ss, ax mov sp, stacktop test: mov cx, 0 jcxz lbl_cx0 mov ax, 999 lbl_cx0: mov ax, 0 mov cx, 2 jcxz fin loop_target: inc ax loop loop_target mov cx, 1000 loop_target2: cmp ax, 0 loope loop_target2 fin: mov ax, 0x4c00 int 0x21 resb 2048 segment data align=16 resb 1024 * 63 segment stack class=STACK resb 2048 stacktop:
segment code

..start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, stacktop
test:
    mov cx, 0
    jcxz lbl_cx0
    mov ax, 999
lbl_cx0:
    mov ax, 0
    mov cx, 2
    jcxz fin
loop_target:
    inc ax
    loop loop_target
    mov cx, 1000
loop_target2:
    cmp ax, 0
    loope loop_target2
fin:
    mov ax, 0x4c00
    int 0x21
    resb    2048

segment data    align=16
    resb    1024 * 63

segment stack   class=STACK
    resb    2048
stacktop:
アセンブルして実行

MS-DOS互換で機能強化されてるフリーなFreeDOS上、以下のステップで上記アセンブラソース jcxz.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)

nasm -f obj jcxz.asm
wlink name jcxz.exe format dos file jcxz.obj
debug jcxz.exe

まずデバッガでのディスアセンブルから。JCXZu

赤枠で囲ったのが今回ターゲットの命令どもです。インテル式ではLOOPあるいはLOOPZと表記する筈が、後ろにWついているのはAT&Tが混じっている?よくわからんのう。。。

まずは1個めのJCXZから。CXレジスタが0であれば オフセット0x0015番地へ飛ぶと。ただしコード上はJCXZ命令の次の命令の先頭番地を基点とする相対番地なので、0x0010+2+3で0x0015っす。JCXZは0だったら何もしないでループを抜ける、ってなときにお役立ちとインテルのデータシートには書いてあるけどホントか?JCXZ1

お次は2個目のJCXZ、CXはゼロでないので飛びません。JCXZ2

さらにCX=0x0002で2回ループする LOOP命令です。1回目に関係するところが赤色、2回目がオレンジ色の枠つけてあります。LOOP1

CXがカウントダウンされていき、0になったら飛ばずに下に落ちるっと。

最後はLOOPZ(LOOPE)です。CXが0でなく、かつZFが立っていたら飛ぶ命令です。以下の赤枠ではCXにはデカイ値が入ってますが、NZになっているので飛ぶ条件を満たしませぬ。LOOP2

下に落っこちて完了と。まあ、思った通りの動作だったな。

ぐだぐだ低レベルプログラミング(196)x86(16bit)、条件ジャンプ命令群 Jcc へ戻る

ぐだぐだ低レベルプログラミング(198)x86(16bit)、CALLとRET へ進む