前回ようやくx86に悪名高いセグメンテーションに踏み込みました。でもセグメントレジスタへのロード、セーブだけっす。それでもメンドクセー感じがそこはかと。今回はMOV命令、それも即値データのMOVを練習。セグメンテーションなど関係ね~と言いたいところが、やっぱり影を落として?いるのです。16ビットx86は逃げられない。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
今回練習する命令ども
以下にx86(16ビット)のオペコードマップ(一バイト目)の一部を掲げました。今回練習するのは、こんな感じの命令どもです。
MOV レジスタあるいはメモリ, 即値データ
レジスタあるいはメモリの特定番地に即値データを書き込むもの。これに関わるオペコードどもは以下の図の赤枠部分、合計18個もあります。
このうち16個は、16ビットレジスタAX、CX、DX、BX、SP、BP、SI、DIの8本および、8ビットレジスタAL、CL、DL、BL(それぞれAX、CX、DX、BXの下位8ビット)およびAH、CH、DH、BH(それぞれAX、CX、DX、BXの上位8ビット)へ値を立てるための命令です。「地価の高い」目抜き通りのファーストオペコードに16個も場所をとるというのは、親世代の8ビットとの連続性に配慮したに違いありませぬ。知らんけど。
一方その下に2バイト分のオペコードが使われてます。実はこちらのエンコード法でもまったく同じレジスタへの即値の代入も可能なハズ(アセンブラは短いコードに生成しようとしてくれるので、冗長なコードは生成してくれないケド。)しかしこちらのコードの主目的は、メモリに即値を転送するための命令です。RISCじゃできない操作です。CISCだな、x86。メモリということは当然アドレシングモードの適用あり、アドレシングモードあれば、その裏にはセグメンテーションが息づいて?ます。
即値MOV命令のアセンブリ言語ソース
本来は、8ビット幅と16ビット幅の両方があるのですが、簡単のため16ビット幅のみっす。手抜き。即値MOV命令は以下の2ステップで使ってます。
-
- アドレシングで使われるベースレジスタに変数アドレス(オフセット)を立てるためのレジスタへの即値MOV
- 上記で設定したベースレジスタが指すメモリ上の変数への即値MOV
この際、ベースレジスタのセグメンテーションについてのお約束を頭に入れておいてね、という感じです。強力なx86用アセンブラである、nasm アセンブラのちょいクセありなソースが以下に。
segment code ..start: mov ax, data mov ds, ax mov ax, stack mov ss, ax mov sp, stacktop mov ax, edata mov es, ax test: mov bx, save0 mov word [bx], 0x1234 mov si, save1 mov word [si], 0x5678 mov di, save2 mov word [di], 0x9ABC mov bp, save0s mov word [bp], 0xDEF0 fin: mov ax, 0x4c00 int 0x21 resb 2048 segment data align=16 save0: dw 0 save1: dw 0 save2: dw 0 resb 1024 * 63 segment edata align=16 save0e: dw 0 save1e: dw 0 resb 1024 * 63 segment stack class=STACK save0s: dw 0 save1s: dw 0 resb 2048 stacktop:
今回はDSとSSしか使ってないのですが、後のこともあるのでESも定義してます。後って何だ?
アセンブルして実行
さて、以下のステップで上記アセンブラソース movimm.asm から実行可能なオブジェクトファイルを得ることができます。
nasm -f obj movimm.asm wlink name movimm.exe format dos file movimm.obj
なお、wlinkリンカは、OpenWatcomコンパイラのツールチェーンのものです。
実行は御本家 MS-DOSのdebugとクリソツな debug(かなり拡張されているケド)で。
debug movimm.exe
アセンブラのtest:ラベルまで実行を進めたところからシングルステップした結果が以下に。
赤枠のところで、BXレジスタに即値0をロードし、そのBXでアドレスされるメモリ番地に、即値0x1234を書き込んでます。どのセグメントに書き込まれるのかは「暗黙のお約束」通りっと。
同じく黄色枠のところでSIレジスタに2をロードし、SIでアドレスされるメモリ番地に、即値0x5678を書き込んでます。SI(ソースインデックス)レジスタは、[BX+SI]のように、インデックスレジスタとして使われることもありますが、単独のベースアドレシング用のレジスタとしても使用可能デス。
緑枠では、DIレジスタに4をロードし、DIでアドレスされるメモリ番地に、即値0x9ABCを書き込んでます。DI(デスティネーションインデックス)レジスタも[BX+DI]のようにインデックスレジスタとして使われることがあります。しかし後の回で練習する予定のMOVS命令で登場するときと、セグメントレジスタの「暗黙のお約束」が異なるので要注意です。MOV命令のアドレシングで単独DI使うときはDSへ向かうのですが、MOVS命令などではESがデフォルトに変わります。覚えておいてね。
さて、上記の3メモリ書き込みの結果が以下に、DSセグメントの0番地、2番地、4番地にそれぞれ赤、黄、緑の結果が書き込まれてます。
なお、0x1234をメモリに書き込むと、アドレス下から0x34、0x12 の順です。「リトルエンディアン」だからね。まあ、最近のArmも大体はそうだし、間違わんか。
つづく命令がこちら。こちらではベースレジスタはBPです。BPに即値4をロードしておいて、BPが指す変数に即値 0xDEF0を書き込むコードです。
ここで「お約束が2つ」
-
- BPをベースレジスタとしてアドレシングに使うと暗黙でSS(スタックセグメント)が使われる
- BPをベースレジスタとして指定した場合、Displacementを不在にしておくアドレシングモード(2バイト目が 2進00xxxxxx となる)は使用できない。かならずDisplacementを伴うアドレシングモードが使われる。
アセンブラで[bx]、[si]、[di]と書いたときと、[bp]と書いたときで、コード生成のルールも微妙に変われば、セグメントも変わるっと。コマケー話だけれどもx86らしいところなのよ。