x86プロテクトモードと特権

最終更新: 2022-12-24 15:42:13 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

x86プロテクトモードは、ディスクリプタベースのセグメンテーション、制御された制御転送、ページング、特権命令を組み合わせます。ロングモードでは通常のセグメンテーションが縮小されていますが、ディスクリプタテーブル、特権レベル、割り込みゲート、タスク状態セグメント(TSS)は保持されます。オペレーティングシステムはこれらの機構を使用してカーネルへ移行し、ユーザー実行を隔離します。一方、現在のx86-64システムでは、ページテーブルが主要なメモリ保護境界を提供します。

テーブルレジスタ #

グローバルディスクリプタテーブル(GDT)には、論理プロセッサが共有するコード、データ、システムディスクリプタが含まれます。GDTRはGDTのリニアベースとバイト単位のリミットを格納します。GDTエントリ0は慣例的にヌルディスクリプタであり、インデックス0のセレクタを通常のコードまたはデータアクセスに使用できないようにします。

ローカルディスクリプタテーブル(LDT)は、LDTRを介して選択するオプションのテーブルです。LDTRには、GDT内のLDTシステムディスクリプタを指すセレクタが含まれ、解決済みのベース、リミット、属性をキャッシュします。レガシーアーキテクチャでは、タスクごとに独自のLDTを持てますが、現在の64ビットオペレーティングシステムでLDTが使われることはほとんどありません。

IDTRは割り込みディスクリプタテーブル(IDT)のリニアベースとバイト単位のリミットを格納します。IDTは通常のセグメントセレクタではなく、例外または割り込みベクタによってインデックスされます。エントリは、ハンドラコードを識別し、エントリの意味を制御するゲートです。

タスクレジスタ(TR)には、GDT内のTSSディスクリプタを指すセレクタが含まれ、そのディスクリプタをキャッシュします。LGDT、LIDT、LLDT、LTRは、アーキテクチャで定義された特権および型チェックの下でテーブル状態をロードします。

セレクタと隠しキャッシュ #

16ビットのセグメントセレクタには、インデックス、テーブルインジケータ(TI)、要求特権レベル(RPL)が含まれます。

text
15                              3 2   1 0
+--------------------------------+---+---+
|             index              | TI|RPL|
+--------------------------------+---+---+

TI=0はGDTを選択し、TI=1は現在のLDTを選択します。プロセッサはインデックスにディスクリプタサイズを乗算し、テーブルリミットを検査し、ディスクリプタを取得して、その型、存在状態、操作に対する特権を検証します。

CS、SS、DS、ES、FS、GSはセレクタを公開しますが、ベース、リミット、型、アクセス権の隠しキャッシュも保持します。メモリ参照では、ディスクリプタテーブルを再読み込みせずにキャッシュを使用します。したがって、GDTまたはLDTのエントリを書き換えても、ロード済みセグメントは変化しません。ソフトウェアはセレクタを再ロードするか、アーキテクチャで要求される遷移を実行する必要があります。

LDTRとTRも解決済みディスクリプタをキャッシュします。GDT、LDT、またはTSSを変更するコンテキストスイッチでは、可視セレクタだけが完全な状態であるとみなさず、これらのキャッシュを考慮する必要があります。

セグメントディスクリプタ #

レガシープロテクトモードのコードおよびデータディスクリプタは8バイトです。これらは、32ビットのベース、オプションで4 KiB粒度を使用する20ビットのリミット、ディスクリプタ特権レベル(DPL)、プレゼントビット、ディスクリプタクラス、型固有のフラグをエンコードします。

  • S=1はコードまたはデータディスクリプタを示し、S=0はシステムディスクリプタまたはゲートを示します。
  • コードディスクリプタは、実行可能、読み取り可能、コンフォーミングの動作を選択します。
  • データディスクリプタは、書き込み可能および拡張方向下向きの動作を選択します。
  • Gはリミットをスケールし、D/Bはディスクリプタ型に応じてデフォルトのオペランドサイズまたはスタックサイズを制御します。
  • ロングモードでは、Lが64ビットコードセグメントを示します。Dとの無効な組み合わせは拒否されます。

セグメンテーションは、命令の実効オフセットにディスクリプタベースを加算し、ページングの前にリミットとアクセス権を検査します。フラットな32ビットモデルでは、ベースがゼロでリミットが大きいセグメントを使用しながら、これらの検査を保持します。

64ビットモードでは、通常のアドレス生成においてCS、SS、DS、ESのベースはゼロとして扱われ、レガシーのリミットは適用されません。FSとGSは個別に制御される64ビットベースを保持します。コードセグメントの型、CSの特権、および選択されたSSの規則は、引き続きアーキテクチャ上重要です。

システムディスクリプタとゲート #

システムディスクリプタは、LDT、利用可能またはビジーなTSS、レガシーゲートを識別します。IA-32では、TSSおよびLDTディスクリプタは8バイトです。64ビットモードでは、TSSおよびLDTディスクリプタは64ビットのベースを格納できるよう16バイトを占有し、上位8バイトは次のGDTスロットを使用します。

コールゲートは、宛先コードセレクタとオフセットを指定し、同じレベルまたはより特権の高いレベルへの遠距離コールに対して検査済みの経路を提供します。レガシーコールゲートは、設定された数のパラメータを新しいスタックにコピーできます。遠距離リターンは、別の一連の検査の下で特権遷移を逆転させます。

IDTエントリは、割り込みゲート、トラップゲート、またはレガシーモードではタスクゲートにできます。割り込みゲートとトラップゲートには、ハンドラのセレクタとオフセットが含まれます。割り込みゲートはエントリ時にIFをクリアし、トラップゲートは保持します。64ビットの割り込みゲートまたはトラップゲートには、緊急スタックまたは専用スタックに使用するISTセレクタも含まれます。

特権値と実効チェック #

x86は、0から3までの4つの特権レベルを定義します。数値が小さいほど特権が高くなります。現在の特権レベル(CPL)はCSの下位2ビットで表されます。カーネルは通常CPL 0で実行され、アプリケーションはCPL 3で実行されます。リング1とリング2は存在しますが、オペレーティングシステム設計では必須ではありません。

各ディスクリプタにはDPLがあり、各セレクタにはRPLがあります。多くのデータアクセスでは、プロセッサは実効特権レベルを形成します。

text
EPL = max(CPL, RPL)

通常のデータセグメントへのアクセスには、型および存在状態の検査に加えて、EPL <= DPLが必要です。数値が大きいほど特権が低いため、RPLによってセレクタの権限を意図的に下げることができます。SSのロードにはより厳格な規則があり、セレクタRPLと書き込み可能なデータセグメントDPLはCPLと一致しなければなりません。

コード転送では、機構ごとに固有の規則が使用されます。

  • 非コンフォーミングコードセグメントへの直接の遠距離転送は、CPLを任意に下げません。そのDPLは、要求される現在の特権関係と一致しなければなりません。
  • コンフォーミングコードセグメントは、そのDPLが許可する場合、数値的に同じまたは低い特権のCPLからの実行を許可しますが、CPLは変化しません。この転送によって、対象DPLのデータへのアクセスが付与されることはありません。
  • コールゲートセレクタは、CPLとRPLを使用してゲートDPLに対して検査されます。その後、ゲートが宛先コードセグメントへのエントリを制御します。
  • ソフトウェアINT nは、CPLに対してIDTゲートDPLを検査します。プロセッサ例外および外部割り込みはINT nを介してソフトウェアが呼び出したものではないため、呼び出し元の認可検査としてゲートDPLを使用しません。

すべての操作を単一のCPL/DPL比較で表すことはできません。セグメントロード、コンフォーミングコード、ゲート、タスクスイッチ、IRET、ポートI/O、特権命令は、それぞれ独自の検査を定義します。

制御された遷移 #

ユーザーコードは、リング0のコードセレクタをロードして任意のカーネルアドレスへジャンプすることはできません。ゲート、例外、割り込み、タスク機構、または設定済みのsyscall命令が、制御されたエントリポイントと保存済みの戻り状態を確立します。

数値的に低いCPLへ入る割り込みまたは例外ゲートでは、プロセッサは現在のTSSから宛先特権レベルのスタックポインタを取得します。スタックを切り替え、古いスタックコンテキストと戻り状態を保存し、ゲートのハンドラから実行を開始します。正確なフレーム、オプションのエラーコード、保存される値は、実行モードとベクタによって異なります。

同一特権のゲートは、通常、現在のスタックを保持します。64ビットモードでは、CPLが変化しなくても、ゼロ以外のIDT ISTフィールドによって設定済みのISTスタックが選択されます。その後、カーネルエントリコードは残りのレジスタを保存し、ABI不変条件を確立し、ユーザーが制御する状態を検証し、ネストしたエントリを安全に処理する必要があります。

IRETによる復帰では、保存された宛先を検証して復元します。特権の低いCPLへの復帰では、外側のスタックも復元されます。ソフトウェアは、検査されていないセレクタ、フラグ、命令ポインタを割り込み復帰フレームに配置してはなりません。復帰時の検証だけでは、カーネルポリシーを表現できないためです。

割り込みとシステムコール #

例外は、命令実行の同期的な結果です。外部割り込みは割り込みコントローラを介して到着し、優先度、マスキング、プロセッサ状態が許可した時点で配送されます。どちらも最終的にはIDTベクタを選択しますが、アーキテクチャ上のエラーコードをプッシュするのは一部の例外だけです。

SYSCALL/SYSRETおよびSYSENTER/SYSEXITは、一般的なIDTゲート検索を回避し、モデル固有レジスタを使用してエントリ状態とリターン状態を確立します。64ビットモードのSYSCALLは、次のRIPをRCXに保存し、RFLAGSをR11に保存し、設定されたコードセレクタとエントリRIPをロードして、選択されたフラグをマスクします。RSPは切り替えないため、カーネルエントリスタブは安全でない中間状態を公開せずに、信頼できるスタックへ移行する必要があります。SYSENTERの設定済みスタックセマンティクスは異なります。レジスタ引数とエラー規約は、x86自体ではなく、オペレーティングシステムのsyscall ABIに属します。

コールゲートはアーキテクチャとして残っていますが、現在のカーネルは一般にsyscall命令と割り込みゲートを使用します。これらの経路がフラットなアドレス空間とソフトウェア定義のエントリフレームに適合するためです。ページング、SMEP、SMAP、保護キー、その他の制御は、ディスクリプタ検査を補完します。

IA-32ハードウェアタスク #

80286およびIA-32のプロテクトモードアーキテクチャでは、ハードウェアタスクはTSSとTSSディスクリプタによって表される状態として定義されます。IA-32 TSSには、EIP、EFLAGS、汎用レジスタ、セグメントセレクタ、CR3、LDTセレクタ、特権レベル0から2のスタックポインタ、I/O許可ビットマップオフセットが格納されます。

ハードウェアタスクスイッチは、次の方法で開始できます。

  • TSSディスクリプタへの遠距離CALLまたはJMP。
  • タスクゲートを介した遠距離転送。
  • IDTエントリがタスクゲートである割り込みまたは例外。
  • EFLAGS.NTがネストしたタスクを示す場合のIRET。

プロセッサは宛先を検証し、現在のTSSに選択された出発側の状態を保存し、宛先TSSをロードし、必要に応じてディスクリプタのビジー状態を設定し、TRおよびその他のタスク状態をロードして、CR0のタスクスイッチフラグを設定します。ビジービット、リンク、例外の正確な動作は、開始機構によって異なります。

CALL、タスクゲート、または割り込みタスクゲートによって開始されたネストしたタスクスイッチでは、古いTSSセレクタを宛先TSSのバックリンクに書き込み、ネストタスク(NT)フラグを設定できます。NT=1のIRETはバックリンクに従って、以前のタスクを再開します。バックリンクはアーキテクチャ上の制御状態であり、スケジューラが管理するプロセスリストではありません。不正または一貫性のないタスク状態は、タスクセグメント例外を発生させる可能性があります。

現在のオペレーティングシステムは、ハードウェアタスクスイッチが固定された状態レイアウトと副作用を持つため、スケジュール対象スレッドをソフトウェアで切り替えます。カーネルは、スケジューラメタデータ、アドレス空間ポリシー、CPUごとの状態、浮動小数点およびベクトル状態、デバッグ状態、実装固有の緩和策も管理する必要があります。ソフトウェアスイッチでは、カーネルのコンテキストとABIに必要な状態だけを正確に保存できます。IA-32カーネルは、スケジューリングにハードウェアタスクスイッチを使用せず、特権スタックおよびI/Oビットマップ機能のためにTSSを引き続き使用します。

ロングモードでのTSS使用 #

64ビットモードはハードウェアタスクスイッチをサポートしませんが、通常のカーネル動作には各論理プロセッサで有効な64ビットTSSが必要です。TSSには次が含まれます。

  • RSP0、RSP1、RSP2。ISTを選択せずに割り込みまたは例外が対応する、より特権の高いCPLへ入る場合に使用されます。
  • IST1からIST7までの7つの割り込みスタックテーブルポインタ。
  • I/O許可ビットマップオフセット。

IDTゲートの3ビットISTフィールドは、7つのISTポインタのいずれかを選択します。ISTは、現在のスタックが使用不能な場合、またはハンドラに専用スタックが必要な場合に有用です。たとえば、ダブルフォールトの処理経路が該当します。ISTハードウェアは新しいスタックポインタをロードしますが、完全なソフトウェアネストポリシーは提供しません。エントリコードは、有限サイズの緊急スタックが制御なく再利用されたりオーバーフローしたりしないように、引き続き対策する必要があります。

I/O許可ビットマップは、IOPLおよびモード規則に従い、より特権の低いコードによるポートごとのINおよびOUTを許可または拒否できます。その位置と範囲はTSSディスクリプタのリミットから導出されるため、ディスクリプタ、オフセット、ビットマップ、終端バイトは一貫して構築する必要があります。

TSSディスクリプタは、GDT内の16バイトのシステムディスクリプタです。LTRはそれをTRにロードし、定義されたビジー型を設定または確認して、ベースとリミットをキャッシュします。特権エントリにはプロセッサローカルの信頼されたスタックが必要であるため、通常、各論理プロセッサは独自のTSSを持ちます。TRのロードはエントリ処理の初期化であり、スケジューラのコンテキストスイッチではありません。

ロングモードに関する考慮事項 #

ロングモードは、標準的なCPL 0/CPL 3のカーネルとユーザーの分離、コードディスクリプタ、IDTゲート、GDT、TR、TSSを保持します。一方、通常の64ビットアドレス処理におけるセグメントベースとリミットの効果の大部分を除去し、ハードウェアタスクスイッチを省略します。したがって、ページテーブルのユーザー/スーパーバイザ、書き込み可能、実行禁止の権限が、主要なアドレス空間境界を形成します。

ディスクリプタ状態を省略することはできません。64ビットコードセグメントには有効なディスクリプタが必要であり、割り込みゲートにはコードセレクタが含まれ、特権エントリではTSSのスタック状態が使用され、互換モードでは追加のレガシーディスクリプタセマンティクスが適用されます。ブートコードとカーネルコードは、予約フィールドとモード遷移の順序がアーキテクチャ上の要件であるため、プロセッサベンダーの最新のシステムプログラミングマニュアルに従う必要があります。

主要な参考資料 #

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)