x86の命令どもを16ビットモードから眺めてます。今回は無条件ジャンプです。どのプロセッサも長いのとか短いのとか複数種類のジャンプ命令を持つことが多いですが、x86はやっぱりメンドクセーです。直接に間接、nearにfarそういえばshortもあったな、という感じ。アセンブラにもコマケー指示をせんとなりません。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
無条件(Unconditional) Jump
今回練習してみる命令のニーモニックはただ一つ
-
- JMP
なんでありますが、オペコードマップ(部分)上でみれば以下の4バイトを占めます。
そこには以下のような異なる「射程」異なる「トビ先指定」の命令どもがならんでおります。
-
- Intra-Segment
- 直接ジャンプ
- short(8ビット)
- near(16ビット)
- 間接ジャンプ
- 直接ジャンプ
- Inter-Segment (far)
- 直接ジャンプ
- 間接ジャンプ
- Intra-Segment
まず大きな分類、イントラ・セグメントとインター・セグメントの違いがあります。セグメント(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
デバッガで挙動を追ったものが以下に。まずはプログラム冒頭部分。
最初の short (8ビットのディスプレースメント)ジャンプが黄色、次のnear(16ビットのディスプレースメント)が赤です。ディスプレースメント(相対値)がIP(現在番地を指している)+該当命令のバイト長に加え合わされて新たなIPとして設定されているのが分かるかと思います。実際に計算するとメンドクセー。
続いて以下では、インター・セグメント・ジャンプを行ってます。こちらでは引数で指定されている値がそのままCS:IPに格納されているのでメンドーな計算不要。ただし、リトルエンディアンなので、LOWバイトが先に来るのでお間違えなく。
お次は、メモリ間接ジャンプです。ただ[BX]と指定したのでは、NEARなのかFARなのか分からないのでちゃんとFARと表示してくれてます。指定のメモリ番地にオフセット・アドレス2バイト、セグメント値2バイトが格納(もちろんリトルエンディアン)されているので、これを引っ張って来て飛びます。
ちゃんとジャンプできたみたいでよかった。