借り初めのひみつきち

仮ブログです。

誰も教えてくれない AMD64 と Intel64 の違い (ページング編)

Intel64 は AMD64 を参考に実装したので、9割以上のアプリケーションにおいて概ね互換性があります。 一方、船頭多くしてなんとやら、両社の政治的な思惑などが絡んで意図的に非互換になっている部分があったり、細部の互換性をとることができていない部分があったりします。

AMD64 と Intel64 の代表的な差異は色々なところでまとめられているのですぐ見つけることができますが、中には一般に知られていない差異もあります。

以前 EFER.LMA の差異を発見したことがありましたが、今回新しい差異を発見したのでここにまとめます。

neriring.hatenablog.jp

事件編

myos は、現代では Intel64 PC の方が手に入りやすいので Intel64 機で開発されてきました。 そして、ある程度動くようになってきたので家にあった唯一の AMD64 機でも動作確認をすることにしました。

普段使ってないマシンなので埃を落としていざ電源オン、 BIOS のかっこいいロゴが表示された後で myos を起動しているメッセージが一瞬表示されましたが、すぐに暗転して次の瞬間また BIOS のかっこいいロゴが表示されました。

これは起動時に何か問題が起きて強制再起動しているようですね・・・。

犯人さがし

OS を開発している初期の段階では何か設定を間違って例外が発生することが稀によくあります。 一方、そもそも例外処理がうまく動いてないのでそのまま CPU がシャットダウン状態になって再起動してしまうケースが非常によくあります。

このような場合、どうやってデバッグすればいいでしょうか?

答えは簡単、まずは適当なところに無限ループを仕込んで起動テスト。 無事に動作が止まれば無限ループのところまでは動いてるので OK、 無限ループする前に再起動したらループより前に問題があることがわかります。 そして、無限ループを仕込む場所を少しずつ移動しながら起動テストを繰り返すことで問題が起きている場所を絞り込むことができます。

これで、カーネルの序盤にページングを初期化してる部分が怪しいところまでは絞れました。

他の機種では動いているのでページ設定の大枠に問題はないはずです。 しかし、実際にはページ設定で問題が起きています。 このような場合、ページングの拡張属性の使い方が正しくない可能性が高いです。

システム起動時には RECURSIVE PAGE TABLE AREA (ページテーブル操作用に再帰しているページテーブル領域) と DIRECT MAPPING AREA (物理メモリに直接アクセスするための領域) の設定を行っています。

ここで怪しい拡張属性を考えてみると、どちらもシステム全体からアクセスする領域なので GLOBAL 属性を付与していました。 *1 試しに GLOBAL を設定していたビットを0に変えてビルドしてみます。なんと、起動しました。

GLOBAL 属性は i386 よりも後 (P6 世代) になって追加された属性で、 CR4 レジスタの PGE ビットを有効にしないと使えません。 そこで、 CR4 レジスタの設定が間違っているかと思いブートローダーで明示的に CR4.PGE ビットを立てるコードを追加してみました。

しかし、予想に反してこの修正を加えても起動しませんでした。

Global ビットが絡んでいることは明白なのに、これは一体どういうことでしょうか。

解決編

では、分厚いマニュアルを開いて確認してみましょう。

Global ビットはページテーブルエントリの bit8 に存在します。

f:id:neriring16:20210925180451p:plain

AMD64 では PML4E の bit8 は Must be Zero と定義されています。

f:id:neriring16:20210925180544p:plain

一方、 Intel64 では PML4E の bit8 は Ignored と定義されています。

f:id:neriring16:20210925180639p:plain

もっと詳しくマニュアルを見てみると、 Global Page 属性は PTE, PDE, PDPTE のみでサポートされ、 PML4E ではサポートされていないことがわかります。

f:id:neriring16:20210925180809p:plain

また、 PML4E 以外でも Global ビットが実際に有効になるのは最下位レベルページテーブルエントリ (4KBページなら PTE、2MBページなら PDE) のみで、それ以外のレベルのページテーブルエントリ内の Global ビットは無視されます。

f:id:neriring16:20210925182349p:plain

つまり、 PML4E の Global に相当するビットはもともと存在しないビットなので PML4E に Global ビットを設定しようとしているコードがそもそも間違いです。 両者の仕様の違いにより、 Intel64 では無視され、 AMD64 では不正なビットでページフォルトが発生するという現象が起きていました。

このように PTE, PDE, PDPTE, PML4E は基本的な考え方やビット配置は概ね共通していますが、微妙にサポートされている機能に差異があります。 また、 Intel64 では無視されても AMD64 では厳しくチェックされるといった具合に、両者の細かい挙動は異なるようです。

後日談

PML4E に Global ビットを設定していたコードを修正して無事起動するようになりました。

しかし、起動しても USB をうまく認識せず myos を操作することができませんでした。 Windows を起動してデバイスマネージャで確認してみると、なんと USB デバイスは全部 EHCI に繋がっていました。解散。

*1:追記: Recursive Area に Global を設定すると CR3 フラッシュで消せなくなってめんどくさいことになるのでそもそも使うべきではないと思います。