第4章 フレームバッファで画面描画

「フレームバッファ」を使用してディスプレイへの描画を試してみます。ディスプレイの各ピクセルには、メモリ空間上に対応する領域があり、それが「フレームバッファ」と呼ばれる領域です。

poibootでは、kernel.binへジャンプする際、UEFIで取得したフレームバッファ情報を渡すようにしています。この章ではpoibootから渡されたフレームバッファ情報を取得し、取得したフレームバッファ情報を使用して、画面へドットを描画したり、塗りつぶしたりしてみます。

フレームバッファの各ピクセルは「B(青)」・「G(緑)」・「R(赤)」・「Reserved(予約領域)」の順に各1バイトずつ計4バイトで構成されています。フレームバッファ領域の最も若いアドレスが画面左上のピクセルに対応します。

4.1 フレームバッファのアドレスを取得する

まずは、フレームバッファ領域の先頭アドレスを取得してみます。

この節のサンプルディレクトリは「041_get_fb_addr」です。

フレームバッファに関する情報の在処

フレームバッファに関する情報は、ブートローダー(poiboot)から渡されます。

poibootでは、フレームバッファに関する情報を「struct fb(リスト4.1)」という構造でメモリ上に配置しています。そして、kernel.binを実行する際、その先頭アドレスをRSIレジスタ*1へ格納した上で、kernel.binへジャンプします。

[*1] RSIレジスタは、関数の第2引数として使われるレジスタです。そのため、普通にC言語などでカーネルを書く際は、kernel.binの一番先頭の関数の第2引数でstruct fbの先頭アドレスを取得できます。

リスト4.1: struct fbの構造

struct fb {
        /* フレームバッファの先頭アドレス */
        unsigned long long base;
        /* フレームバッファサイズ[バイト] */
        unsigned long long size;
        /* 画面の幅[ピクセル] */
        unsigned int hr;
        /* 画面の高さ[ピクセル] */
        unsigned int vr;
};

リスト4.1のコメントに記載の通り、フレームバッファの先頭アドレスは、先頭のメンバー変数「base」に格納されています。「先頭のメンバー変数のアドレス」は「構造体の先頭アドレス」と等しいので、RSIに格納されているアドレス(struct fbの先頭アドレス)は、メンバー変数baseのアドレスでもあります。そのため、RSIが指す先に「フレームバッファの先頭アドレス」があります。

レジスタ間接アドレッシング

「あるアドレスが指す先」のメモリを参照したい場合、例えばC言語では「ポインタ」がありますが、機械語(およびアセンブラ)では「レジスタ間接アドレッシング」(あるいは単に「レジスタ間接」)というオペランド指定方法があります。

これは、命令のオペランドとして「レジスタに格納されたアドレスの指す先」を指定するものです。アセンブラでは「(%レジスタ名)」の様に書きます。ここでは、RSIに格納されたアドレス先のメモリ上のデータ(フレームバッファ先頭アドレス)を、64ビットレジスタRAXへコピーしてみます。対応するmov命令は表4.1の通りです。

表4.1: mov命令: RSIに格納されたアドレス先の内容をRAXへコピー

アセンブラmov (%rsi),%rax
機械語48 8b 06

レジスタ間接で取得した値を64ビットレジスタへ格納する構文は表4.2の通りで、構文内の「reg_dst_src」は表4.3の通りです。

表4.2: 構文34: mov: 第1オペランドの指す先を第2オペランドへ格納

アセンブラmov (レジスタ(64bit)), レジスタ(64bit)
機械語48 8b 00+reg_dst_src
備考レジスタ間接がRSPの場合、末尾に0x24追加
レジスタ間接がRBPの場合、3バイト目に0x40加算、末尾に0x00追加

表4.3: 64ビットレジスタのreg_dst_src

dst/(src)(RAX)(RCX)(RDX)(RBX)(RSP)(RBP)(RSI)(RDI)
RAX0x000x010x020x030x040x050x060x07
RCX0x080x090x0a0x0b0x0c0x0d0x0e0x0f
RDX0x100x110x120x130x140x150x160x17
RBX0x180x190x1a0x1b0x1c0x1d0x1e0x1f
RSP0x200x210x220x230x240x250x260x27
RBP0x280x290x2a0x2b0x2c0x2d0x2e0x2f
RSI0x300x310x320x330x340x350x360x37
RDI0x380x390x3a0x3b0x3c0x3d0x3e0x3f

表4.2の備考について、レジスタ間接に使用するレジスタがRSPあるいはRBPの場合、機械語のパターンが少し変わります。例としては表4.4表4.5の通りです。

表4.4: 例: レジスタ間接のレジスタがRSPの場合

アセンブラmov (%rsp), %rax
機械語48 8b 04 24

表4.5: 例: レジスタ間接のレジスタがRBPの場合

アセンブラmov (%rbp), %rax
機械語48 8b 45 00

なお、一部の命令では、レジスタ間接に64ビット幅以外のレジスタを使う命令もあったりしますが、本書で使わないため省略します。

実装

サンプルコードはリスト4.2の通りです。

リスト4.2: 041_get_fb_addr/sample.txt

# フレームバッファ先頭アドレスをRAXへ格納
00: 48 8b 06    # mov (%rsi),%rax

# 無限Halt
03: f4          # hlt
04: eb fd       # jmp -1

フレームバッファの先頭アドレスをRAXへコピーした後、haltの無限ループで待機するようにしました。

動作確認

QEMUで動作させて、QEMUモニターでRAXを見てみると、フレームバッファの先頭アドレスが格納されています。

(qemu) info registers
RAX=00000000c0000000 RBX=00000000bef67f98 RCX=0000000000000000 RDX=0000000000000000
RSI=0000000000407180 RDI=00000000bfe9b018 RBP=00000000bff0eb10 RSP=0000000000210000
R8 =00000000bff0e9dc R9 =0000000000000078 R10=00000000bfe6e080 R11=00000000e7dc4657
R12=00000000bef69540 R13=00000000bef69548 R14=00000000bff24b60 R15=00000000bef6a018
RIP=0000000000110004 RFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=1

この場合、フレームバッファの先頭アドレスは「0xc0000000」であったと分かります。

4.2 画面の幅と高さを取得する

続いて、画面の幅と高さを取得します。

この節のサンプルディレクトリは「042_get_width_height」です。

ディスプレースメント付きレジスタ間接アドレッシング

画面の幅と高さもstruct fbの構造の中にあります。それぞれ、幅(hr)はstruct fbの先頭から16(0x10)バイト目、高さ(vr)は20(0x14)バイト目です。

これらのデータを参照する際、アドレスを格納したレジスタへオフセット分の値を都度足したり引いたりしても参照できるのですが、少し面倒です。

レジスタ間接アドレッシングには「ディスプレースメント(dsp)」と呼ばれるオフセット値を指定できるため、これを使うと「あるアドレスからNバイト先のデータ」へ1命令でアクセスできます。

ディスプレースメント付きの間接アドレスは、アセンブラでは「dsp(レジスタ名)」の様に書きます。「RSIに格納されたアドレスの16(0x10)バイト先」であれば「0x10(%rsi)」、「RSIに格納されたアドレスの20(0x14)バイト先」であれば「0x14(%rsi)」です。

幅(hr)と高さ(vr)はそれぞれ32ビットです。ここでは、幅の値を32ビットレジスタEBXへ、高さの値を32ビットレジスタECXへコピーすることにします。それぞれのアセンブラと機械語コードは表4.6表4.7の通りです。

表4.6: mov命令: 幅(RSI格納アドレスの0x10バイト先)をEBXへコピー

アセンブラmov 0x10(%rsi),%ebx
機械語コード8b 5e 10

表4.7: mov命令: 高さ(RSI格納アドレスの0x14バイト先)をECXへコピー

アセンブラmov 0x14(%rsi),%ecx
機械語コード8b 4e 14

それぞれ、末尾の1バイトがディスプレースメントです。

8ビットのディスプレースメント付きレジスタ間接の値を32ビットレジスタへ格納する構文は表4.8の通りで、構文内の「reg_dst_src」は表4.9の通りです。

表4.8: 構文35: mov: DSP(8bit)付きレジスタ間接の値をレジスタ(32bit)へ格納

アセンブラmov dsp(レジスタ(64bit)), レジスタ(32bit)
機械語8b 40+reg_dst_src dsp(8bit)
備考レジスタ間接がRSPの場合、3バイト目に0x24追加

表4.9: レジスタ間接とレジスタ(32bit)のオフセット値

dst/(src)(RAX)(RCX)(RDX)(RBX)(RSP)(RBP)(RSI)(RDI)
EAX0x000x010x020x030x040x050x060x07
ECX0x080x090x0a0x0b0x0c0x0d0x0e0x0f
EDX0x100x110x120x130x140x150x160x17
EBX0x180x190x1a0x1b0x1c0x1d0x1e0x1f
ESP0x200x210x220x230x240x250x260x27
EBP0x280x290x2a0x2b0x2c0x2d0x2e0x2f
ESI0x300x310x320x330x340x350x360x37
EDI0x380x390x3a0x3b0x3c0x3d0x3e0x3f

表4.8の備考に記載の通り、レジスタ間接のレジスタがRSPの場合、機械語のパターンが変わります。例としては表4.10の通りです。




表4.10: 例: レジスタ間接のレジスタがRSPの場合

アセンブラmov 0x12(%rsp), %rax
機械語8b 44 24 12

備考: DSPの有無による機械語の変化

ここで、構文33で紹介したDSP無しの構文では、レジスタを示すバイトは「00+reg_dst_src」という構文でした。今回の場合と比較すると、レジスタを示すバイトに0x40を足すと「DSP付き」の意味になり、末尾に追加した1バイトがDSPとして扱われるようです。

構文34より、「0x12」のDSP付きでRAXのレジスタ間接で取得した値をEAXへ格納する機械語は「8b 40 12」です。

試しに、2バイト目から0x40を引き、3バイト目のDSPを除いた「8b 00」をdas_dumpで逆アセンブルしてみます。

$ das_dump 8b 00

/tmp/tmp.pYcO1hU5Ag/a.bin:     ファイル形式 binary


セクション .data の逆アセンブル:

0000000000000000 <.data>:
   0:   8b 00                   mov    (%rax),%eax

8b 00」はDSP無しに対応する命令だと分かりました。これより、「レジスタ間接の対象レジスタを示すバイトに0x40を足すとDSP付きを示し、末尾の1バイトがDSPになる」と考えて良さそうです。

DSP無しの構文33の備考を改めて見てみると、『レジスタ間接のレジスタ(src)がRBPの場合、3バイト目に0x40を足し、末尾に「00」を追加』とありました。これは、RBPにはDSP無しの機械語が無いので、DSP無しのアセンブラからアセンブルすると、DSPを0とした機械語が生成されていたのですね。

実装

以上を踏まえて、前節のsample.txtに幅と高さの取得を追加してみます(リスト4.3)。

リスト4.3: 042_get_width_height/sample.txt

# フレームバッファ先頭アドレスをRAXへ格納
00: 48 8b 06    # mov (%rsi),%rax

# 追加(ここから)
# 画面の幅をEBXへ、高さをECXへ格納
03: 8b 5e 10    # mov 0x10(%rsi),%ebx
06: 8b 4e 14    # mov 0x14(%rsi),%ecx
# 追加(ここまで)

# 無限Halt
09: f4          # hlt
0a: eb fd       # jmp -1

動作確認

QEMU上で実行し、QEMUモニターでレジスタ値を確認すると、EBXとECXにそれぞれ、フレームバッファの幅と高さが格納されています。

(qemu) info registers
RAX=00000000c0000000 RBX=0000000000000320 RCX=0000000000000258 RDX=0000000000000000
RSI=0000000000407180 RDI=00000000bfe9b018 RBP=00000000bff0eb10 RSP=0000000000210000
R8 =00000000bff0e9dc R9 =0000000000000078 R10=00000000bfe6e080 R11=00000000e7dc4657
R12=00000000bef69540 R13=00000000bef69548 R14=00000000bff24b60 R15=00000000bef6a018
RIP=000000000011000a RFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=1

EBXに格納されている「0x320」は10進数で「800」、ECXに格納されている「0x258」は10進数で「600」です。QEMUのフレームバッファ解像度はデフォルトで800x600なので、正常に取得できているとわかります。

4.3 画面へ1ピクセル描画してみる

前節までで、フレームバッファで画面描画を行うために必要な情報は揃いました。この節からはそれらを使って画面描画を行ってみます。まずは1ピクセルだけ画面に描画してみます。

この節のサンプルディレクトリは「043_draw_pixel」です。

即値をレジスタ間接で書き込む

前節までで確認したレジスタ間接による方法で、メモリ上にマップされたフレームバッファの領域へ書き込みを行います。

フレームバッファのピクセルフォーマットは「青(B), 緑(G), 赤(R), 予約(RSV)」が各1バイトずつで、1ピクセル当たり4バイトです。フレームバッファには画面左上から順にピクセルが並んでいるため、フレームバッファ先頭の4バイトに書き込みを行うと画面左上の1ピクセルを操作できます。

ここではフレームバッファ先頭の4バイトに即値で「B=0x00, G=0xff, R=0x00, RSV=0x00」を書き込んで、画面左上の1ピクセルを緑で塗ってみます。対応する機械語コードは表4.11の通りです。

表4.11: mov命令: 画面左上(0,0)を緑(0x00,0xff,0x00,0x00)で塗る

アセンブラmovl $0x0000ff00,(%rax)
機械語コードc7 00 00 ff 00 00

即値をレジスタ間接で書き込むの場合、アセンブラ上では何ビットの書き込みかが分からない*2ため、書き込みのビット幅を明示する必要があります。

[*2] アセンブラでは即値で「0x0000ff00」と書いていても操作するビット幅に応じて上の位のビットを良しなに0(あるいは符号ビット)で補完します。そのため、「0x0000ff00(計32ビット)」と書いているからといって、「32ビットの即値書き込み」の意思がアセンブラへ伝わるわけではないです。「mov $0x0000ff00,(%rax)」でアセンブルしようとすると「サイズが分からない」といったエラーが出ます。

ここでは、32ビットアクセスであることを明示するためmovに接尾辞として"l"*3を付けています。

[*3] 他にも、8ビットアクセスは"b"、16ビットアクセスは"w"、64ビットアクセスは"q"といった接尾辞があります。

機械語コードの末尾4バイトが書き込む即値データです。

構文をまとめると以下の通りです。

表4.12: 構文36: mov: 即値(32bit)をレジスタ間接で書き込む

アセンブラmovl 即値(32bit), (レジスタ(64bit))
機械語c7 00+reg_ofs 即値(32bit)
備考レジスタ間接がRSPの場合、3バイト目に0x24追加
レジスタ間接がRBPの場合、2バイト目に0x40加算、3バイト目に0x00追加

実装

前節までのsample.txtへ、画面左上(0,0)のピクセルを緑で塗りつぶす処理を追加してみます(リスト4.4)。

リスト4.4: 043_draw_pixel/sample.txt

# フレームバッファ先頭アドレスをRAXへ
# 画面の幅をEBXへ、高さをECXへ格納
00: 48 8b 06            # mov (%rsi),%rax
03: 8b 5e 10            # mov 0x10(%rsi),%ebx
06: 8b 4e 14            # mov 0x14(%rsi),%ecx

# 追加(ここから)
# フレームバッファ先頭ピクセル(画面左上)を緑で塗る
09: c7 00 00 ff 00 00   # movl $0x0000ff00,(%rax)
# 追加(ここまで)

# 無限Halt
0f: f4                  # hlt
10: eb fd               # jmp -1

動作確認

GUIモードで実行すると、たった1ピクセルなので見難いですが、一番左上の1ピクセルが緑で塗られていることを確認できます。

また、QEMUモニターでフレームバッファのデータをダンプすることでも確認できます。

(qemu) x/4bx 0xc0000000
00000000c0000000: 0x00 0xff 0x00 0x00

筆者の場合、フレームバッファのアドレスは0xc0000000だったので、そこから1バイトずつ、4個分のデータを16進数でダンプしてみました。

「0x00(B) 0xff(G) 0x00(R) 0x00(RSV)」と、意図通りにフレームバッファへ書き込みができていることを確認できます。

4.4 画面を塗りつぶしてみる

前節で画面の左上(0,0)の1ピクセルを塗ることができました。フレームバッファ上の書き込み先を1ピクセル(4バイト)ずつずらしながら、全ピクセル(幅 * 高さ)分を書き込むことで、画面全体を単色で塗りつぶしてみます。

この節のサンプルディレクトリは「044_fill」です。

実装の流れ

jmp命令を使うことでループを作ることはできるので「書き込み先を4バイトずつずらしながら、即値を書き込む」事はできそうです。

ただ、フレームバッファの領域を超えて書き込みを行ってしまうとフレームバッファ外の領域のメモリ破壊につながります。そのため、レジスタEDXを「書き込んだピクセル数を管理するカウンタ」、レジスタESIを「画面の総ピクセル数(幅 * 高さ)」とし、「EDX >= ESI」のときループを脱出するようにします。

ここで主に使用する命令が、数値比較を行う「cmp」命令と、「jbe」命令です。

等しいか小さければジャンプ(命令24「jbe」)

jbeは「Jump if Below or Equal」の略です。既に登場した「je(Jump if Equal)命令」等と同じく条件付きジャンプ命令の一種です。

対応するフラグレジスタはキャリーフラグとゼロフラグで、いずれかがセットされていた場合にジャンプします。

キャリーフラグは、算術演算の結果、レジスタサイズを超える桁上がり(キャリー)あるいはボロー(桁借り)が発生した場合にセットされます。今回の場合、キャリーフラグがセットされるのは「ESI - EDXが負となる場合」で、ゼロフラグがセットされるのは「ESIとEDXが等しい場合」なので、「EDX >= ESI」のときジャンプすることになります。

jbe命令の構文は表4.13の通りです。




表4.13: 構文37: jbe: 等しいか小さければジャンプ

アセンブラjbe <ラベルあるいは".">
機械語コード76 fe+rel_addr

末尾の1バイトがジャンプ先を決める相対アドレスで、jmp命令等と同様、基準は0xfeです。

実装

前節までのsample.txtへ画面塗りつぶし処理を追加してみました(リスト4.5)。

リスト4.5: 044_fill/sample.txt

# フレームバッファ先頭アドレスをRAXへ
# 画面の幅をEBXへ、高さをECXへ格納
00: 48 8b 06            # mov (%rsi),%rax
03: 8b 5e 10            # mov 0x10(%rsi),%ebx
06: 8b 4e 14            # mov 0x14(%rsi),%ecx

# 追加・変更(ここから)
# EDXを「カウンタ」、ESIを「画面ピクセル数(幅*高さ)」で初期化
09: 31 d2               # xor %edx,%edx
0b: 89 ce               # mov %ecx,%esi
0d: 0f af f3            # imul %ebx,%esi

# カウンタ(EDX)が画面ピクセル数(ESI)へ達したら「無限Halt」へジャンプ(*1)
10: 39 d6               # cmp %edx,%esi
12: 76 0e               # jbe +0x10

# 現在のピクセルを緑で描画し次のピクセルへ移動
14: c7 00 00 ff 00 00   # movl $0x0000ff00,(%rax)
1a: 48 83 c0 04         # add $4,%rax

# カウンタ(EDX)をインクリメントし(*1)へジャンプ
1e: ff c2               # inc %edx
20: eb ee               # jmp -0x10
# 追加・変更(ここまで)

# 無限Halt
22: f4                  # hlt
23: eb fd               # jmp -1

初出の構文については後述しますが、やっていることとしてはコメントに記載のとおりです。

気をつける点としては、1ピクセルの描画を行っている以下の命令です。

リスト4.6: 即値のバイトオーダーに注意

# 現在のピクセルを緑で描画し次のピクセルへ移動
14: c7 00 00 ff 00 00   # movl $0x0000ff00,(%rax)

機械語の末尾4バイトには、描画したいピクセル情報がB(0x00)・G(0xff)・R(0x00)・RSV(0x00)の順に並んでいますが、この命令自体は「32ビット即値をレジスタ間接で書き込む」です。そのため、アセンブラでは、バイトオーダーをリトルエンディアンで並べると「B・G・R・RSV」の順になるように、「0x0000ff00」という即値を指定しています。

初出の構文について

今回、「xor」・「mov」・「imul」・「cmp」・「add」・「inc」で新しい構文がでてきましたので、紹介しておきます。

xor

32ビットレジスタを使う構文が新しく登場しました。

表4.14: 構文38: xor: レジスタ(32bit)同士のXORを第2オペランドへ格納

アセンブラxor レジスタ(32bit), レジスタ(32bit)
機械語31 c0+reg_src_dst

reg_src_dst」は以下の通りです。

表4.15: 32ビットレジスタのreg_src_dst

src/dstEAXECXEDXEBXESPEBPESIEDI
EAX0x000x010x020x030x040x050x060x07
ECX0x080x090x0a0x0b0x0c0x0d0x0e0x0f
EDX0x100x110x120x130x140x150x160x17
EBX0x180x190x1a0x1b0x1c0x1d0x1e0x1f
ESP0x200x210x220x230x240x250x260x27
EBP0x280x290x2a0x2b0x2c0x2d0x2e0x2f
ESI0x300x310x320x330x340x350x360x37
EDI0x380x390x3a0x3b0x3c0x3d0x3e0x3f

今回、xorはEDXレジスタをゼロクリアするために使用しています。mov命令で即値0を格納しても良いのですが、xorを使用しているのは機械語のバイト数が2バイトと小さいからです。

試しにas_dumpで確認してみます。

$ as_dump mov \$0,%edx

/tmp/tmp.0Sw3Al6M9z/a.out:     ファイル形式 elf64-x86-64


セクション .text の逆アセンブル:

0000000000000000 <.text>:
   0:   ba 00 00 00 00          mov    $0x0,%edx

mov命令の場合、即値部分で4バイト使ってしまう5バイトの命令でした。

mov

movも32ビットレジスタに関する構文が初出です。

表4.16: 構文39: mov: 第1から第2オペランドへコピー(共に32bitレジスタ)

アセンブラmov レジスタ(32bit), レジスタ(32bit)
機械語89 c0+reg_src_dst

「reg_src_dst」はXORと同じです。

imul

imulも32ビットレジスタに関する構文です。

表4.17: 構文40: imul: レジスタ(32bit)同士の積を第2オペランドへ格納

アセンブラimul レジスタ(32bit), レジスタ(32bit)
機械語0f af c0+reg_dst_src

reg_dst_src」は以下の通りです。

表4.18: 32ビットレジスタのreg_dst_src

dst/srcEAXECXEDXEBXESPEBPESIEDI
EAX0x000x010x020x030x040x050x060x07
ECX0x080x090x0a0x0b0x0c0x0d0x0e0x0f
EDX0x100x110x120x130x140x150x160x17
EBX0x180x190x1a0x1b0x1c0x1d0x1e0x1f
ESP0x200x210x220x230x240x250x260x27
EBP0x280x290x2a0x2b0x2c0x2d0x2e0x2f
ESI0x300x310x320x330x340x350x360x37
EDI0x380x390x3a0x3b0x3c0x3d0x3e0x3f

先に紹介したxor・movとは行・列が異なるのでご注意ください。

cmp

cmpも32ビットレジスタに関する構文です。

表4.19: 構文41: cmp: レジスタ(32bit)同士の比較

アセンブラcmp レジスタ(32bit), レジスタ(32bit)
機械語39 c0+reg_src_dst

「reg_src_dst」は、xor・movと同じです。

add

addは8ビット即値を64ビットレジスタへ加算する構文が初出でした。

表4.20: 構文42: add: 即値(8bit)をレジスタ(64bit)へ加算

アセンブラadd 即値(8bit), レジスタ(64bit)
機械語48 83 c0+reg_ofs 即値(8bit)

inc

incも32ビットレジスタについてです。

表4.21: 構文43: inc: レジスタ(32bit)をインクリメント

アセンブラinc レジスタ(32bit)
機械語ff c0+reg_ofs

動作確認

GUIで実行すると、画面が緑一色で塗りつぶされることが確認できます(図4.1)。(白黒印刷なので印刷上はわからないですが。。)

044_fill/sample.txtの実行結果

図4.1: 044_fill/sample.txtの実行結果

4.5 ランダム色で塗りつぶしてみる

各ピクセルを塗る色をランダムに決めることでカラーノイズを描画してみます。

この節のサンプルディレクトリは「045_color_noise」です。

rdrand命令で乱数取得(命令25)

実はx86 CPUには乱数を取得する命令があります。それが「rdrand命令」です。rdrandはオペランドにレジスタを指定することで、そのレジスタへ乱数を格納してくれます。

ここでは、レジスタEDIへ乱数を格納し、この4バイトレジスタの内容をピクセルデータとしてフレームバッファへ書き込んでみます。

rdrand命令の今回使用する構文は以下の通りです。

表4.22: 構文44: rdrand: 乱数をレジスタ(32bit)へ格納

アセンブラrdrand レジスタ(32bit)
機械語0f c7 f0+reg_ofs

実装

早速実装してみます(リスト4.7)。

リスト4.7: 045_color_noise/sample.txt

# ・・・ 省略 ・・・

# カウンタ(EDX)が画面ピクセル数(ESI)へ達したら「無限Halt」へジャンプ(*1)
10: 39 d6       # cmp %edx,%esi
12: 76 0d       # jbe +0x0f     (変更)

# 変更(ここから)
# 現在のピクセルをランダム色で描画し次のピクセルへ移動
14: 0f c7 f7    # rdrand %edi
17: 89 38       # mov %edi,(%rax)
# 変更(ここまで)
19: 48 83 c0 04 # add $4,%rax

# カウンタ(EDX)をインクリメントし(*1)へジャンプ
1d: ff c2       # inc %edx
1f: eb ef       # jmp -0x0f     (変更)

# ・・・ 省略 ・・・

rdrand命令で32ビットレジスタEDIへ乱数を格納し、それをmov命令でフレームバッファへ書き込むように変更しました。

なお、32ビットレジスタをレジスタ間接で書き込むmov命令は新しい構文なので、紹介しておきます。

表4.23: 構文45: mov: レジスタ(32bit)をレジスタ間接で書き込む

アセンブラmov レジスタ(32bit), (レジスタ(64bit))
機械語89 00+reg_src_dst
備考レジスタ間接がRSPの場合、末尾に0x24追加
レジスタ間接がRBPの場合、2バイト目に0x40加算、末尾に0x00追加

「reg_src_dst」は以下の通りです。

表4.24: 32ビットレジスタ間接でのreg_src_dst

src/(dst)(RAX)(RCX)(RDX)(RBX)(RSP)(RBP)(RSI)(RDI)
EAX0x000x010x020x030x040x050x060x07
ECX0x080x090x0a0x0b0x0c0x0d0x0e0x0f
EDX0x100x110x120x130x140x150x160x17
EBX0x180x190x1a0x1b0x1c0x1d0x1e0x1f
ESP0x200x210x220x230x240x250x260x27
EBP0x280x290x2a0x2b0x2c0x2d0x2e0x2f
ESI0x300x310x320x330x340x350x360x37
EDI0x380x390x3a0x3b0x3c0x3d0x3e0x3f

備考について、例えばEAXからRSPのレジスタ間接で書き込む場合、以下のようになります。

表4.25: 例: EAXからRSPのレジスタ間接で書き込む

アセンブラmov %eax, (%rsp)
機械語89 04 24

また、EAXからRBPのレジスタ間接で書き込む場合は以下のようになります。

表4.26: 例: EAXからRBPのレジスタ間接で書き込む

アセンブラmov %eax, (%rbp)
機械語89 45 00

動作確認

実行すると図4.2の様に、ランダム色の描画により、ノイズ画像のようなものが表示されます。(これも白黒印刷なのでわかりにくいですが)

045_color_noise/sample.txtの実行結果

図4.2: 045_color_noise/sample.txtの実行結果