VCPI メモ 第1回: 最低限の使い方を思い出す
2024-07-26
タグ: #asm #DOSvpmxfer を作った時に VCPI を使ったものの詳細を忘れてしまったので色々調べ直すことにしました。 手始めにプロテクトモードに行って帰ってくるのに最低限程度のプログラムを作ります。
VCPI を使ってプロテクトモードに移行する場合、自力で行う必要がある事柄が大分多いです。 そのため、x86 のプロテクトモードでのセグメント管理やページング等について 事前に知識を入手しておいた方がとっつきやすいと思います。
「VCPI メモ」シリーズの記事は主に以下の資料を参考に作成しています。
- CQ出版 トラ技コンピュータ 1992年10月号 特集 MS-DOS からのプロテクト・モード活用法
- CQ出版 MS-DOS基本プログラミング集 6 X86プロテクトモード・プログラミング
- CQ出版 別冊インターフェース 実践的プロテクト・モード研究
- ZOB.Club The Advanced Assembler 386 上ノ巻
サンプルプログラム
VCPI を使ってプロテクトモードと V86 モードを行き来するサンプルプログラムのコードとアセンブル用のバッチファイルを以下のリンクからダウンロードできます。 以下4つのファイルのコードは PDS とします。適宜ご活用ください。
- vcpi_01.asm (プログラム本体)
- procs.inc (共通ルーチン)
- structs.inc (各種構造体定義)
- assemble.bat
assemble.bat
では、JWasm
と JWlink
でアセンブルとリンクをそれぞれ行うようにしています。
これらのプログラムを実行できる環境で、サンプルプログラムのコードと assemble.bat
を同じディレクトリに置いて assemble.bat
を実行してください。
この方法は次回以降のサンプルコードについても同様です。
また、 procs.inc
と structs.inc
も次回以降のサンプルコードでインクルードして使います。
JWasm
: https://github.com/Baron-von-Riedesel/JWasmJWlink
: https://github.com/Baron-von-Riedesel/jwlink
実行結果例
VCPI が使える環境でサンプルプログラムを実行すると以下のような内容が表示されます。
プログラム中での各種チェックや動作のメッセージと、test_val
の値と、プロテクトモードで保存した EFLAGS
の値を表示しています(test_val
はプロテクトモードに移行した後、動作確認でビット反転させている値です)。
VCPI が使えないなどの場合は、その旨を表示してそこでプログラムが終了します。
A:\> vcpi_01.exe
VCPI test program
EMS driver available
VCPI available
VCPI initialization OK
test_val: 55
Go to protect mode
Returned to V86 mode
test_val: aa
FEDCBA9866543210FEDCBA9866543210
II
VV OO
IIIAVR NPPODITSZ A P C
DPFCMF TLLFFFFFF F F F
00000000000000000011000010000010
Finished
VCPI の存在確認
VCPI が使えるか確認する前に EMS ドライバが存在しているか確認する必要があります。
EMS ドライバが存在する場合 int 67h (AX=0de00h)
を呼び出します。
VCPI が使える場合は AH
に0が、BX
に VCPI のバージョンがそれぞれ返ります。
バージョンは 1.00 なら BX=0100h
というような値となります。
VCPI が使えない場合は AH
に非0の値が返ります。
VCPI の初期化
以下の領域を事前に用意しておく必要があります。
- ページディレクトリ、ページテーブル
- GTD
- TSS
ページテーブルの用意
ページディレクトリが1個と、ページテーブルが最低1個必要です。 ページディレクトリとページテーブルともに 4KiB アライメントされている必要があります。
ページテーブルのうち1個は、ページディレクトリ中の最低位 4MiB (0~3fffffh)を担当するエントリが指すページテーブルとなります。 このページテーブルの(下位側の)初期化は VPCI が行います。
ここでは、簡単のため、下記のように必要なページディレクトリとページテーブルの個数より1個多い4KiB 単位の連続したメモリを確保しておき、その中でアライメントが 4KiB となる位置を探して使うことにします。
pagetbl_start label byte
org (N + 1) * 4 * 1024 ; N=必要なページディレクトリとページテーブルの個数
TSS の用意
以下のように TSS と IO 番地ビットマスクを連続して確保しておき、TSS の I/O Map Base Address に 0068h を入れておくのが簡単だと思います。
また、プロテクトモードのコードをすべて CPL <= IOPL
を満たしながら実行する場合は I/O 番地ビットマスクは不要になります。
+----------------------+ <- 0000h
| TSS |
+----------------------+ <- 0068h
| I/O 番地ビットマスク |
+----------------------+
GDT の用意
例えば以下のような GDT を用意すれば良いです。
用途 | limit | base | アクセスバイト | 備考 |
---|---|---|---|---|
NULL | 0 | 0 | 00h | |
TSS | TSS の大きさ | TSS 先頭 | 89h | |
16bit コード | 64KiB | CS 先頭 | 9ah | |
データ | 64KiB | DS 先頭 | 92h | |
スタック | 64KiB | SS 先頭 | 92h | |
V86 モード移行時用 | 後述 | 0 | 92h | 移行時に DS に設定 |
VCPI コード | - | - | - | VCPI サーバが設定 |
VCPI データ0 | - | - | - | 同上 |
VCPI データ1 | - | - | - | 同上 |
「V86 モード移行時用」のディスクリプタの説明は後述します。
VCPI 初期化処理の呼び出し
以下の引数を設定して int 67h (AX=0de01h)
を呼び出します。
ES:DI
: ページテーブルのアドレスDS:SI
: GDT 内の VCPI 用の3個連続したエントリの先頭アドレス
成功すると以下の値が戻ります。
AH
: 00hDI
: 未初期化のページテーブルエントリのオフセットDI
以降のエントリはクライアント側で好きな値を入れることができます
EBX
: プロテクトモード用の VPCI サーバのエントリポイントのオフセット- このオフセットが含まれるのは VCPI コードのセグメントです
最下位 1MiB の範囲のメモリのマッピングは VCPI サーバとクライアントで同一になります。
ページテーブルの準備
ページディレクトリ中の最低位 4MiB (0~3fffffh)を担当するエントリが、VCPI に初期化させたページテーブルを指すようにします。
追加でページングを行ってアクセスしたいメモリ領域があれば、ページディレクトリとページテーブルの追加の設定を適宜行います。
プロテクトモードへの移行
以下の作業を行い int 67h (AX=0de0ch)
を呼び出します。
- 割込を禁止する
- 以下のレジスタを設定する
ESI
: 下記の値を設定したメモリ領域のリニアアドレス (0fffdeh 以下のアドレス)
ESI
に設定するアドレスのメモリ領域の構造は以下の通りです。
GDTR の値と IDTR の値は最低位 1MiB の範囲に配置しておく必要があります。
31 15 0
+-----------+-----------+ <- +00h (ESI)
| CR3 の値 |
+-----------+-----------+ <- +04h
| GDTR の値のアドレス |
+-----------+-----------+ <- +08h
| IDTR の値のアドレス |
+-----------+-----------+ <- +0ch
| TR の値 | LDTR の値 |
+-----------+-----------+ <- +10h
| EIP の値 |
+-----------+-----------+ <- +14h
| CS の値 |
+-----------+
プロテクトモードに移行した時には以下の状態になっています。
ESI
に設定した各レジスタの値が対応するレジスタに設定されているESI
に設定したメモリ領域のCS:EIP
の位置の命令を実行している- 割込は禁止されている
EAX
,ESI
,DS
,ES
,FS
,GS
の値は不定になっている
V86 モードへの移行
以下の作業を行い、 AX=0de0ch
でプロテクトモード用の VCPI サーバのエントリポイントを far call します。
- 割込を禁止する
- 以下のレジスタを設定する
DS
: ベースアドレスが0で VCPI サーバが初期化時にページテーブルに登録したメモリ領域にアクセス可能なディスクリプタを示すセレクタSS:ESP
: 下記の値が積まれたスタック領域
DS
に設定するセレクタが示すディスクリプタの limit は、ページテーブルのうち VCPI サーバが初期化した範囲を走査して最も大きいアドレスを見つけることで計算できます。
ただし、limit が大きい分には問題無いようなので limit を 4GiB 固定にしてしまって計算を省くことも可能です。
今回の例では簡単のために limit は 4GiB 固定としています。
SS:ESP
が指すスタック領域の構造は以下の通りです。
31 15 0
+----------+----------+ <- +00h (SS:ESP)
| EIP の値 |
+----------+----------+ <- +04h
| 未使用 | CS の値 |
+----------+----------+ <- +08h
| EFLAGS のために予約 |
+----------+----------+ <- +0ch
| ESP の値 |
+----------+----------+ <- +10h
| 未使用 | SS の値 |
+----------+----------+ <- +14h
| 未使用 | ES の値 |
+----------+----------+ <- +18h
| 未使用 | DS の値 |
+----------+----------+ <- +1ch
| 未使用 | FS の値 |
+----------+----------+ <- +20h
| 未使用 | GS の値 |
+----------+----------+
V86 モードに移行した時には以下の状態になっています。
- スタックに積んだ
ES:EIP
の位置の命令を実行している - 割込は禁止されている
ESP
,SS
,ES
,DS
,FS
,GS
はスタックに積んだ値が設定されているGDTR
,IDTR
,LDTR
,TR
は VCPI サーバの値が設定されている
デバッグに関する補足
実機環境では、何かの間違いでリセットがかかってしまうなど、デバッグが難しいです。 そのため、実行中のプログラムをデバッグ可能なエミュレータ(QEMU 等)を使って開発を行った方が良いかもしれません。
次回予告
第2回はサンプルプログラムの改良をやります。