質問: 現在、「作りながら学ぶOSカーネル 保護モードプログラミングの基本と実践」という本でOSについて学んでいます。 そこで、x86について、リアルモードから保護モードへ移行する際にCR0の最下位ビット(PEビット)を1にすることでCPUが保護モードとして動作するようになるという記載がありました。 しかし、その後の32ビットコード部分へのfarジャンプ命令では、prefix(0x66, 0x67)をつけているようです。 CPUはすでに保護モード(32ビット)として動いているはずなので、オペランドを32ビットと解釈させるためのprefixは不要と思えるのですが、なぜ必要となるのでしょうか? 確かに、prefixを外して実行するとうまくいきませんでした。 実際に本で説明されていた移行部分のコードは下記となります。 ``` mov eax, cr0 or eax, 0x00000001 mov cr0, eax jmp $+2 nop nop db 0x66 # <- ここ db 0x67 # <- ここ db 0xEA dd PM_Start dw SysCodeSelector ``` また、PEビットを1にした時点で保護モードとしてCPUが動作するのであれば、 その直後の命令ポインタが指すアドレスがくるってしまう気がするのですが、そんなことはないのでしょうか。 というのも、保護モードではCSレジスタの値からGDTを参照して対象のセグメントを探し出し、 EIPレジスタに入っているオフセットを足して次の命令をフェッチしていると思うのですが、 PEビットを1にした直後はCSレジスタにはリアルモードで使用していたセグメント情報が入っており、 それを元にGDTを参照しようとするとわけのわからないところを参照してしまいそうな気がしました。
回答: IA-32 インテル® アーキテクチャ ソフトウェア・デベロッパーズ・マニュアルを参照されることをお勧めします。質問の内容であれば 第9章 プロセッサの管理と初期化 で説明されています。 > > CPUはすでに保護モード(32ビット)として動いているはずなので、オペランドを32ビットと解釈させるためのprefixは不要と思えるのですが、なぜ必要となるのでしょうか? > > > また、PEビットを1にした時点で保護モードとしてCPUが動作するのであれば、その直後の命令ポインタが指すアドレスがくるってしまう気がするのですが、そんなことはないのでしょうか。 > > > そのため、次のfar JMPまたはfar CALLが行われるまでは16bitリアルモードが維持されます。これらの命令で`CS`レジスタが設定された段階で保護モードが有効になります。 具体的には9.9.1. 保護モードへの切り替え辺りで説明されているので引用します。 > > 3. 制御レジスタ CR0 の PE フラグ(またオプションで PG フラグ)を設定する MOV CR0 命令を実行する。 > 4. MOV CR0 命令のすぐ後で、far JMP 命令または far CALL 命令を実行する。(この操作は通常、命令ストリーム内の次の命令への far ジャンプまたは far コールになる。) > 5. MOV CR0 命令の直後に JMP 命令または CALL 命令を実行すると、実行の流れが変化し、プロセッサが順次処理される。 > 6. ページングがイネーブルな場合、MOV CR0 命令のコードと、JMP 命令または CALL 命令のコードは、アイデンティティ・マッピングされたページから得たものでなければならない(つまり、ジャンプ前のリニアアドレスが、ページングと保護モードがイネーブルになった後の物理アドレスと同一でなければならない)。JMP 命令または CALL 命令のターゲット命令は、アイデンティティ・マッピングされる必要はない。 > > > > > CSレジスタが設定された段階で32bitデフォルトになるという明確な記述はなかったような気がします > > > 質問文はPEフラグを立てた後の命令についてでしたので、保護モード移行後については言及しませんでした。この点については 第17章 16 ビット・コードと32 ビット・コードの混在 で説明されています。 具体的には 17.1. 16 ビット・プログラム・モジュールと 32 ビット・プログラム・モジュールの定義 から引用します。 > > コード・セグメント・ディスクリプタのDフラグは、コード・セグメントの命令に対するデフォルトのオペランド・サイズとアドレスサイズを決定する。(実アドレスモードと仮想8086モードでは、セグメント・ディスクリプタは使用されず、デフォルトは16 ビットである)。D フラグがセットされているコード・セグメントは 32 ビット・セグメントであり、Dフラグがクリアされているコード・セグメントは16ビット・セグメントである。 > > > とあります。質問文の手前でGDTを初期化していると思いますが、その中の該当コード・セグメント・ディスクリプタのDフラグが 1 となっているのではないでしょうか?