備忘録やめた

備忘録として使用していたけどやめた.このブログに載せてあるコードのライセンスは別途記載がない限りWTFPL OR NYSLです.

RamenOSに64ビットモードを実装した

要約

RamenOSに64ビットモードを実装しました.64ビットモード実装の際は,4階層ページングの実装,PAE(Physical Address Extension)の有効化,IA-32eモードの有効化,64ビットコードセグメントへのfar JMP,各種アドレスの変更,IDTの64ビットモード対応化,一部アセンブリコードの64ビットモード対応化が必要となります.デバッグにはQEMU以外にBochsも便利です.

はじめに

RamenOSが64ビットモードで動作するようになりました.64ビットモードでは,RAXRBXなどの64ビットのレジスタが使用できたり,4GB以上のメモリを使用できたりします.64ビットモードでは4階層ページングが必須となります.また,32ビットモードでは使用可能だった一部の命令が64ビットモードでは使用不可になります.この記事では,64ビットモードへの移行方法を説明します.

32ビットページングと4階層ページングの違い

重要な点は以下の通りです.これらはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 4.5の情報を基にしています.

PML4とPDPTの追加,CR3レジスタに保存されるアドレス変更

4階層ページングでの追加点:PML4(Page Map Level 4)とPDPT(Page-Directory Pointer Table)の追加

変更点 32ビットページング 4階層ページング
CR3レジスタに保存される内容 ページディレクトリ(以下PD)のアドレス PML4のアドレス

CR3レジスタにはアドレスに加えて,各種フラグも保存します.

PML4の各エントリはPDPTのアドレスを保存し,PDPTの各エントリはPDのアドレスを保存します.

PD等のエントリサイズ,総エントリ数変更
変更点 32ビットページング 4階層ページング
PD,ページテーブル(以下PT)のエントリの大きさ 4バイト 8バイト
1つのPDまたはPTの総エントリ数 1024個 512個

PD,PT自体の大きさは変更ありません.また,PML4,PDPTのエントリの大きさ,総エントリ数もそれぞれ8バイト,512個です.

仮想アドレスの解釈変更

4KBページの場合です.詳しくはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter4.5 Figure 4-8を参照してください.

32ビットページングの場合

第nビット 対応する変換表等
22 - 31 PD
12 - 21 PT
0 - 11 4KBページのオフセット

4階層ページングの場合

第nビット 対応する変換表等
39 - 47 PML4
30 - 38 PDPT
21 - 29 PD
12 - 20 PT
0 - 11 4KBページのオフセット

例として,仮想アドレス0xFFFF80000000を考えます.

第nビット 0xFFFF80000000の第nビット 解釈
39 - 47 0b111111111 = 0x1FF PML4の第0x1FF番目のエントリを指します
30 - 38 0b111111110 = 0x1FE PDPTの第0x1FE番目のエントリを指します
21 - 29 0 PDの第0番目のエントリを指します
12 - 20 0 PTの第0番目のエントリを指します
0 - 11 0 4KBページの先頭を指します

ソースコード

この記事は,以下のツリーを基にしています. github.com

やり方

paging_64.asmを作成し,32ビット保護モード移行後のコードにインクルードする

ramen/head.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

    %include "paging_64.asm"

インクルードの方法の詳細はこちらを参照してください.

tokuchan3515.hatenablog.com

ページングの設定をする

ramen/paging_64.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

基本的に32ビットページングを実装する場合と変わりません.32ビットページングの実装については以下の記事を参照してください.

tokuchan3515.hatenablog.com

64ビットコードセグメントを用意する

ramen/head.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

64ビットモードを含むIA-32eモードには,64ビットモードと互換モードの2つが存在します.Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 9.8.5.3 に記述されている内容を表にしました.

コードセグメントのLフィールドの値 コードセグメントのDフィールドの値 モード 既定のオペラントの大きさ(ビット) 既定のアドレスの大きさ(ビット)
0 0 互換 16 16
0 1 互換 32 32
1 0 64ビット 32 64
1 1 予約(使用不可) - -

GDTの各ビットが表す意味については以下の記事を参照してください.

tokuchan3515.hatenablog.com

64ビットモードを実装するために,GDTに新たにエントリを追加します.尚,IA-32eモードで使用されるGDTのエントリのフィールドは以下の通りです.これ以外のフィールドは無視されます.

  • Gフィールド
  • Dフィールド
  • Lフィールド
  • AVLフィールド
  • Pフィールド
  • DPLフィールド
  • Typeフィールド

また,Sフィールドと,Typeフィールドの最上位ビットは常に1でなければなりません.そして,Base Addressは0と見做されます.セグメントのLimit値はチェックされません.詳しくは,Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 5.2.1, Figure 5-2と,Chapter 5.3.1 を参照してください.

64ビットモードに移行する

ramen/paging_64.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

詳しい移行方法についてはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 9.8.5 を参照してください.

ページングを無効にする

Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 9.8.5 によれば,IA-32eモードへの移行の際は予めページングを有効にしている必要があるとされています.しかし,実際の移行の際は,最初にページングを無効にします.結局無効にするため,RamenOSでは最初からセグメンテーション方式のままIA-32eモードに移行します.もしページングを有効にしている場合,この時点でページングを無効にしてください.尚,ページングを無効にする命令は,物理アドレスと仮想アドレスが同一のメモリ上に配置してください.でないと,ページングを無効にした瞬間に命令ポインタが無効なアドレスを指してしまいます.

物理アドレス拡張を有効にする

CR4レジスタの第5ビットを1にします.CR4レジスタの各ビットの意味はCPU Registers x86-64 - OSDev Wikiで確認してください.

CR3レジスタにPML4のアドレスを保存する

32ビットページングの場合と同様です.

IA-32eモードを有効にする
    MOV                  ECX, 0xC0000080
    RDMSR
    OR                   EAX, 0x00000100
    WRMSR

IA32_EFERの第8ビットを1にします.IA32_EFERの各ビットの意味はIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 4, Chapter 2.1 Table 2-2で確認してください.

このレジスタを操作するには,RDMSR命令とWRMSR命令を使用します.これらの命令は,IA32_EFERを含むMSR (Model-Specific Register)を操作します.MSRの一覧はIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 4, Chapter2にあります.

使用するレジスタ RDMSR WRMSR
EDX MSRの上位32ビットが書き込まれる MSRの上位32ビットに書き込む内容
EAX MSRの下位32ビットが書き込まれる MSRの下位32ビットに書き込む内容
ECX 読み込むMSRののインデックス 書き込むMSRのインデックス

IA32_EFERの場合,ECXには0xC0000080を指定します.

4階層ページングを有効にする

32ビットページングの場合と同様,CR0レジスタの第31ビットを1にします.

64ビットコードセグメントにジャンプする

先程用意したセグメントにfar JMPします.これは32ビット保護モードに移行する際と同様です.

これで,64ビットモードに移行することが出来ます.

カーネルへのジャンプのアドレス,スタックポインタの初期値を変更する

ramen/head.asm at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

共に仮想アドレスに変更します.

JMP命令では,64ビットの即値アドレスを引数に取ることが出来ず,代わりにレジスタに64ビットのアドレスを保存し,そのレジスタを引数に取る必要があります.詳しくは,Intel® 64 and IA-32 Architectures Software Developer Manuals, Volume 2, Chapter 3.2のJMP命令のページを確認してください.

SIMDを無効にする

ramen/cargo_settings.json at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

OSでは,SIMDを無効にするべきです.詳細はこちらで確認してください.尚,SIMDを無効にしない場合,大きな配列へのアクセスなどでOSがクラッシュする場合があります.

os.phil-opp.com

IDTを64ビットモードに対応させる

ramen/descriptor_table.rs at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

64ビットモードでは,IDTの各エントリの大きさは16バイトとなります.各ビットの意味は以下の通りです.尚,括弧内の数値は使用するビット数です.これはIntel® 64 and IA-32 Architectures Software Developer Manuals, Volume 3, Chapter 6.14.1の情報を基にしています.

Reserved (32)
Offset 63..32 (32)
Offset 31..16 (16) P (1) DPL (2) 0 (1) TYPE (4) 0 (5) IST (3)
Segment Selector (16) Offset 15..0 (16)

ISTの値は,Interrupt Stack Tableを利用する場合に用いられますが,今回は使用しないため,値を0にします.

また,LIDT命令で指定するアドレスをIDTの仮想アドレスに変更する必要があります.そして,引数が指すメモリアドレスには,以下の情報を格納しなければなりません.

32ビットモード 64ビットモード
16ビットのlimit値と32ビットのIDTのアドレス 16ビットのlimit値と64ビットのIDTのアドレス

割り込みハンドラを修正する

ramen/asm.rs at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

異なる点 32ビットモード 64ビットモード
ESDSSS 使用する 使用しない
PUSHPOP 16ビットレジスタ,32ビットレジスタに対して使用可能 16ビットレジスタ,64ビットレジスタに対して使用可能
PUSHADPOPAD 使用可能 使用不可能
割り込みからの復帰 IRETD IRETQ

64ビットモードでは,割り込みハンドラで汎用レジスタRAXRDI等8つのレジスタと,64ビットモードで新たに追加されたR8R9,... R15の8つのレジスタで,計16個)を保存・復元します.

デバッグ方法

デバッグ方法についてはこの記事の最後でも紹介しています.

tokuchan3515.hatenablog.com

Bochsのデバッガ

Bochsエミュレータですが,QEMUよりもデバッグがしやすいです.但し,QEMUよりも速度が遅いため,使い分けが必要です.

デバッガを有効にする方法

Gentooの場合,debuggerのUSEフラグを有効にしてください.

# /etc/portage/package.use/bochs(package.useがディレクトリの場合)あるいは /etc/portage/package.use(package.useがディレクトリではない場合)
app-emulation/bochs debugger

それ以外のディストリビューションでは,パッケージマネージャでインストールしたBochsのデバッガが有効になっていない場合,ソースコードからビルドする必要があります.詳しくは以下のリンクで確認して下さい.

bochs.sourceforge.net

bochsrc

ramen/bochsrc at 85928a1f5943defb8bf6a068d2228dfc9bfd3dc4 · toku-sa-n/ramen · GitHub

Bochsを利用する場合,bochsrcファイルが必要となります.floppya: 1_44=にはフロッピーのイメージファイルを指定して下さい.

作成したbochsrcファイルがあるディレクトリでbochs -qを実行すると,Bochsが起動します.

pbreak, vbreak
pbreak 物理アドレス
vbreak セグメント:オフセット

設定したアドレスに到達した場合,処理を中断します.後述するtrace onと組み合わせると便利です.中断した処理を再開させるには,continue命令を使用して下さい.

trace on

このコマンドは,実行される全ての機械語命令をコンソールに表示します.出力するとその分処理速度が遅くなるため,pbreakvbreak等と併用するのがおすすめです.

show int

このコマンドは,発生した割り込み,例外をコンソールに表示します.trace onでもこれらは表示されますが,割り込みや例外を確認する場合はこちらの方が表示する内容が少ないため高速です.

page
page 仮想アドレス

このコマンドは,仮想アドレスに対応する物理アドレスを表示します.