ぐだぐだ低レベルプログラミング(190)x86(16bit)、即値のMOV操作

Joseph Halfmoon

前回ようやくx86に悪名高いセグメンテーションに踏み込みました。でもセグメントレジスタへのロード、セーブだけっす。それでもメンドクセー感じがそこはかと。今回はMOV命令、それも即値データのMOVを練習。セグメンテーションなど関係ね~と言いたいところが、やっぱり影を落として?いるのです。16ビットx86は逃げられない。

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

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

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
今回練習する命令ども

以下にx86(16ビット)のオペコードマップ(一バイト目)の一部を掲げました。今回練習するのは、こんな感じの命令どもです。

MOV レジスタあるいはメモリ, 即値データ

レジスタあるいはメモリの特定番地に即値データを書き込むもの。これに関わるオペコードどもは以下の図の赤枠部分、合計18個もあります。MOV_IMM_OPMAP

このうち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ステップで使ってます。

    1. アドレシングで使われるベースレジスタに変数アドレス(オフセット)を立てるためのレジスタへの即値MOV
    2. 上記で設定したベースレジスタが指すメモリ上の変数への即値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:ラベルまで実行を進めたところからシングルステップした結果が以下に。movBXSIimm

赤枠のところで、BXレジスタに即値0をロードし、そのBXでアドレスされるメモリ番地に、即値0x1234を書き込んでます。どのセグメントに書き込まれるのかは「暗黙のお約束」通りっと。

同じく黄色枠のところでSIレジスタに2をロードし、SIでアドレスされるメモリ番地に、即値0x5678を書き込んでます。SI(ソースインデックス)レジスタは、[BX+SI]のように、インデックスレジスタとして使われることもありますが、単独のベースアドレシング用のレジスタとしても使用可能デス。

つづく2命令が以下に。movDIimm

緑枠では、DIレジスタに4をロードし、DIでアドレスされるメモリ番地に、即値0x9ABCを書き込んでます。DI(デスティネーションインデックス)レジスタも[BX+DI]のようにインデックスレジスタとして使われることがあります。しかし後の回で練習する予定のMOVS命令で登場するときと、セグメントレジスタの「暗黙のお約束」が異なるので要注意です。MOV命令のアドレシングで単独DI使うときはDSへ向かうのですが、MOVS命令などではESがデフォルトに変わります。覚えておいてね。

さて、上記の3メモリ書き込みの結果が以下に、DSセグメントの0番地、2番地、4番地にそれぞれ赤、黄、緑の結果が書き込まれてます。dumpDS

なお、0x1234をメモリに書き込むと、アドレス下から0x34、0x12 の順です。「リトルエンディアン」だからね。まあ、最近のArmも大体はそうだし、間違わんか。

つづく命令がこちら。こちらではベースレジスタはBPです。BPに即値4をロードしておいて、BPが指す変数に即値 0xDEF0を書き込むコードです。movBPimm

ここで「お約束が2つ」

    1. BPをベースレジスタとしてアドレシングに使うと暗黙でSS(スタックセグメント)が使われる
    2. BPをベースレジスタとして指定した場合、Displacementを不在にしておくアドレシングモード(2バイト目が 2進00xxxxxx となる)は使用できない。かならずDisplacementを伴うアドレシングモードが使われる。

アセンブラで[bx]、[si]、[di]と書いたときと、[bp]と書いたときで、コード生成のルールも微妙に変われば、セグメントも変わるっと。コマケー話だけれどもx86らしいところなのよ。

ぐだぐだ低レベルプログラミング(189)x86(16bit)、セグメントレジスタ操作 へ戻る

ぐだぐだ低レベルプログラミング(191)x86(16bit)、MOVS、所謂ブロック転送命令 へ進む