ぐだぐだ低レベルプログラミング(195)x86(16bit)、無条件JMP

Joseph Halfmoon

x86の命令どもを16ビットモードから眺めてます。今回は無条件ジャンプです。どのプロセッサも長いのとか短いのとか複数種類のジャンプ命令を持つことが多いですが、x86はやっぱりメンドクセーです。直接に間接、nearにfarそういえばshortもあったな、という感じ。アセンブラにもコマケー指示をせんとなりません。

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

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

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
無条件(Unconditional) Jump

今回練習してみる命令のニーモニックはただ一つ

    • JMP

なんでありますが、オペコードマップ(部分)上でみれば以下の4バイトを占めます。JMPopcodeMAP

そこには以下のような異なる「射程」異なる「トビ先指定」の命令どもがならんでおります。

    • Intra-Segment
      • 直接ジャンプ
        • short(8ビット)
        • near(16ビット)
      • 間接ジャンプ
    • Inter-Segment (far)
      • 直接ジャンプ
      • 間接ジャンプ

まず大きな分類、イントラ・セグメントとインター・セグメントの違いがあります。セグメント(CSレジスタ)を変更することなく同じコードセグメント内で飛ぶイントラ・セグメント・ジャンプとCSレジスタを書き替え「異なる」コードセグメントを再設定してその中へ飛ぶインター・セグメント・ジャンプの2種があります。16ビットのx86の場合、セグメントの大きさは64Kバイトであるので、イントラ系は最大でも64Kバイト、インター系はアドレス空間全体で1Mバイトということになります。ここで1M射程のジャンプどもを far、64K以内射程のジャンプどもを near などと呼ぶこともありです。このうち

far

は後でアセンブラ指定に使うので覚えておかないとなりませぬ。

また、トビ先の指定方法にも2種類あります。直接(direct)と間接(indirect)です。直接ジャンプは、ジャンプ命令につづく1バイトもしくは2バイトもしくは4バイトでトビ先アドレスを指定します。このうち1バイトと2バイトは「イントラ」、4バイトは「インター」となります。アセンブラやデバッガの表示を見ていると気づかないかもですが、1バイトと2バイトのトビ先指定の時はIP相対アドレシング(IP、インストラクション・ポインタ、他所ではPC、プログラムカウンタと呼ぶ)なので相対値をコードしてますが、4バイトのインターセグメントでは、2バイトのオフセットアドレス(セグメント内のアドレス)と2バイトのセグメント値の組み合わせです。いろいろコマケー話があるのよ。

また間接ジャンプは、ジャンプ命令の中でメモリアドレスまたはレジスタを指定してそこに置かれているアドレスへジャンプします。メモリの場合、置かれているアドレスがイントラの2バイトのオフセットアドレスのみなのか、インターの2バイトのオフセット+2バイトのセグメントなのかを指定しないとならないので、インターセグメントジャンプであれば、さきほどでてきた far をつかってメモリアドレスが far pointer である、と言っておかねばなりませぬ。何も言わないとアセンブラは near(イントラ)だと判断するハズ。

さらに間接ジャンプではレジスタを引数に指定できるので、レジスタ中に格納されているオフセット・アドレスに飛ぶこともできます。ただし「レジスタ間接」の場合は、セグメントを指定する方法が無いので、near のみとなります。

この辺の「簡素な(同じ16ビットx86でも286プロテクトモードに比べたらということ)」ルールを踏まえて以下の練習コードをごらんくだされや。

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

以下ちょこっとJMPしてみるだけのソースです。強力なx86用アセンブラNASM用のソースです。

segment code

..start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, stacktop
test:
    jmp ntarget1
    mov ax, 1
    mov ax, 2
ntarget1:
    mov ax, 3
    jmp ntarget2

    resb    2048
ntarget2:
    mov ax, 4
    jmp far ftarget1
ftarget2:
    mov ax, 6

fin:
    mov ax, 0x4c00
    int 0x21
    resb    2048

segment data    align=16
    resb    1024 * 63
jaddr:
    dw  ftarget2
    dw  code

segment code2   align=16
    resb    1024 * 63
ftarget1:    
    mov ax, 5
    mov bx, jaddr
    jmp far [bx]

segment stack   class=STACK
    resb    2048
stacktop:

あちこち飛び回って、その度にお印にAXレジスタに値をたてるだけのコードです。

アセンブルして実行

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

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

デバッガで挙動を追ったものが以下に。まずはプログラム冒頭部分。jmpDebug0

最初の short (8ビットのディスプレースメント)ジャンプが黄色、次のnear(16ビットのディスプレースメント)が赤です。ディスプレースメント(相対値)がIP(現在番地を指している)+該当命令のバイト長に加え合わされて新たなIPとして設定されているのが分かるかと思います。実際に計算するとメンドクセー。jmpDebug1

続いて以下では、インター・セグメント・ジャンプを行ってます。こちらでは引数で指定されている値がそのままCS:IPに格納されているのでメンドーな計算不要。ただし、リトルエンディアンなので、LOWバイトが先に来るのでお間違えなく。jmpDebug2

お次は、メモリ間接ジャンプです。ただ[BX]と指定したのでは、NEARなのかFARなのか分からないのでちゃんとFARと表示してくれてます。指定のメモリ番地にオフセット・アドレス2バイト、セグメント値2バイトが格納(もちろんリトルエンディアン)されているので、これを引っ張って来て飛びます。jmpDebug3

ちゃんとジャンプできたみたいでよかった。

ぐだぐだ低レベルプログラミング(194)x86(16bit)、LEA 実効アドレス へ戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です