
8087FPUの制御命令の説明に前回、前々回と2回を費やしました。今回こそ命令を「動かしたい」と思います。しかし、その前に確認しておかないとならないんだな、16ビット実行環境であるFreeDOSから「みえる」FPUの命令がどのレベルのものなのか?「ピュアな」8087ではないけれども、雰囲気は出してる?なんだそれ。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
今回は、8087系FPUの state (environmentと全レジスタスタック)をメモリ上にセーブする FSAVE 命令を動かしてその挙動を観察します。しかし第233回でやったとおり、FSAVE命令は「ピュア16ビット環境に適合した8087」と「32ビット仮想記憶対応の80387以降」ではそのセーブする内容(バイト数)が異なります。上の「※印」の註釈で書いている通りの屋上屋を重ねるような環境で、FreeDOS上の実行オブジェクトからはどんなFPUとして見えているのか? それが問題だ(今頃、それに気づいたお惚け老人はお間抜け。)
FreeDOS実行環境から見えるFPU
結論から言えば、FreeDOS(QEMU上のゲストOS)上で実行されるオブジェクトが「実行」できるFPUの命令セットは、
建前8087と互換動作(具体的にはFSAVE命令のメモリレイアウトは8087コンパチ)だけれども、80387以降で追加された命令や定義域も利用可能
という若干中途半端なものとなります。
FreeDOSには CPUSTAT というCPUの諸元を表示してくれるコマンドがあるので、まずそれを使って見てみます(以下はCPUSTAT出力の最初の部分。)
CPUベンダ文字列としては、 AuthenticAMDとAMDを主張。そして FPU integratedなので、FPUをふくんでます。そして注目すべきは V86-modeです。ゲストOSであるFreeDOSは仮想86モードで動いているということだと思います。
CPUは64ビットのサポートのあるx64に見えるけれども、QEMUの仮想CPUのバージョン2.5+だと。ちゃんとエミュレータであるQEMUが作り出したかりそめの存在だ、と認識されとるみたいです。
ここで80386以降x86の32ビット・プロテクテッドモードで使用可能なV86モードを想定し、FreeDOS上でのFPU命令の処理についてお惚け老人が推測するに、
-
- V86モードでは、直接FPU命令は実行できない(これは元々の仕様ね。)
- FPU命令は無効命令例外を引き起こす
- QEMUは無効命令例外を捕捉して、ゲストOS(FreeDOS)の実行を中断し、QEMU内部のコードで8087エミュレーションを行ってから制御を戻す
- その結果 FreeDOS上では、あたかも8087であるかのような挙動を示す
結局、命令は全てQEMU側で実行されとるのですが、FPUについては普通の整数命令とちょっと処理経路が違うみたい。そして今回環境では「なんちゃって8087(機能的には上よ)」としてFPU命令が実行できているように見えるっと。よってFSAVEでセーブされるバイト数は94バイト(8087式)となる筈。
今回実験のプログラム
今回は以下のような処理を行ってみます。
-
- π(3.14…)をレジスタスタックに8個詰め込む
- FSAVE命令で FPUステート(全レジスタ)をメモリに書き出す
なお以下は「強力なx86用アセンブラNASM」用のソースです(MSのMASMともインテルASM86とも微妙に異なるけど、まあ分かるっしょ。)
%use fp segment code ..start: mov ax, data mov ds, ax mov ax, stack mov ss, ax mov sp, stacktop test: fninit fldpi fldpi fldpi fldpi fldpi fldpi fldpi fldpi mov bx, dstAREA fsave [bx] nop fin: mov ax, 0x4c00 int 0x21 resb 2048 segment data align=16 resb 1024 * 63 dstAREA: db 256 dup (0) segment stack class=STACK align=16 resb 2048 stacktop: dw 1024 dup (0) stackend:
アセンブルして実行
MS-DOS互換で機能強化されているフリーなFreeDOS上、以下のステップで上記のアセンブラソース fsave.asm から実行可能なオブジェクトファイルを生成して実行することができます(nasmとwatcom Cがインストール済であること。)
なお、FreeDOS付属の「debugx」デバッガは、8087のレジスタ内容を浮動小数で見せてくれます
nasm -f obj -l fsave.lst fsave.asm wlink name fsave.exe format dos file fsave.obj debugx fsave.exe
また、念のため、FSAVEのセーブエリアのメモリ領域を確認。
色付の枠がついている部分がFPUからセーブされたデータです。先頭からこんな感じ。
7F 03 <- CW 00 00 <- SW 00 00 <- TAG 64 01 <- IPLOW C6 02 <- IPH + OPCODE 76 6C <- OPLOW C6 02 <- OPH + 0
その後はスタックトップST(0)からボトムのST(7)まで、同じπを意味する浮動小数値が10バイトx8回くりかえされてます。そしてその合計が94バイトね。
FSAVEすると、レジスタどもは、もれなくFINIT実行後と同じ状態(デフォルト初期値)にクリアされてしまいます。
インテルのマニュアルみても、以下引用のように素っ気ない説明です。
FSAVE/FNSAVE initializes the 8087 as if FINIT/FNINIT has been executed.
ここまで「世話焼いて」くれるのはFPUのコンテキスト・スイッチにかかる時間を可能な限り短縮したかったってことかな?でもFINIT1個くらい大勢に影響ない感じもするんだが。。。