37

スタックがカーネル境界を超えると、例外中に 64 ビット Windows がスタックをアンワインドできないのはなぜですか? 32 ビット Windows ではできるのに。

この質問全体のコンテキストは次のとおりです。

OnLoad 例外が消えるケース – x64 でのユーザーモード コールバック例外

バックグラウンド

32 ビット Windows で、カーネル モードコードからコールバックされたユーザー モードコードで例外をスローすると、ユーザーモードコードから呼び出されます。

User mode                     Kernel Mode
------------------            -------------------
CreateWindow(...);   ------>  NtCreateWindow(...)
                                   |
WindowProc   <---------------------+                                   

Windows の構造化例外処理 (SEH) はスタックを巻き戻し、カーネル モードを介して巻き戻し、ユーザー コードに戻ることができます。ここで例外を処理でき、有効なスタック トレースが表示されます。

ただし、64 ビット Windows にはありません

Windows の 64 ビット エディションでは、これを実行できません。

複雑な理由により、64 ビット オペレーティング システム(amd64 および IA64) で例外を伝播することはできません。これは、Server 2003 の最初の 64 ビット リリース以来ずっと当てはまりました。x86 では、これは当てはまりません。例外はカーネル境界を介して伝搬され、フレームを逆戻りさせます。

この場合、信頼できるスタック トレースをさかのぼる方法がないため、無意味な例外を表示するか、完全に非表示にするかを決定する必要がありました。

当時のカーネル アーキテクトは、保守的な AppCompat フレンドリーなアプローチを取ることを決定しました。つまり、例外を隠し、最善を尽くします。

この記事では、すべての 64 ビット Windows オペレーティング システムがどのように動作したかについて説明しています。

  • Windows XP 64 ビット
  • Windows Server 2003 64 ビット
  • Windows Vista 64 ビット
  • Windows Server 2008 64 ビット

しかし、Windows 7 (および Windows Server 2008) 以降、アーキテクトは考えを変えました。64 ビット アプリケーション (32 ビット アプリケーションではない) の場合のみ、(デフォルトで)これらユーザー カーネル ユーザー例外の抑制を停止します。したがって、デフォルトでは、次のようになります。

  • Windows 7 64 ビット
  • Windows Server 2008

すべての 64 ビット アプリケーションで、これまで見られなかったこれらの例外が表示されます

Windows 7 では、ネイティブ x64アプリケーションがこのようにクラッシュすると、Program Compatibility Assistantに通知されます。アプリケーションにWindows 7 マニフェストがない場合、PCA がアプリケーション互換性シムを適用したことを示すダイアログが表示されます。これは何を意味するのでしょうか?これは、次にアプリケーションを実行したときに、Windows が Server 2003 の動作をエミュレートし、例外が消えることを意味します。Server 2008 R2 には PCA が存在しないため、このアドバイスは適用されないことに注意してください。

だから質問

問題は、 32 ビット版の Windows ではできるのに、なぜ64 ビット版の Windows ではカーネル移行によってスタックを元に戻すことができないのかということです。

唯一のヒントは次のとおりです。

複雑な理由により、64 ビット オペレーティング システム(amd64 および IA64) で例外を伝播することはできません。

ヒントは複雑です。

私はオペレーティング システムの開発者ではないので、説明が理解できないかもしれませんが、その理由を知りたいのです。


更新: 32 ビット アプリの抑制を停止するホットフィックス

Microsoft は、32 ビット アプリケーションでも例外が抑制されないようにする修正プログラムをリリースしました。

KB976038: 64 ビット バージョンの Windows で実行されるアプリケーションからスローされる例外は無視されます

  • コールバック ルーチンでスローされる例外は、ユーザー モードで実行されます。

このシナリオでは、この例外によってアプリケーションがクラッシュすることはありません。代わりに、アプリケーションは一貫性のない状態に入ります。その後、アプリケーションは別の例外をスローしてクラッシュします。

ユーザー モード コールバック関数は通常、カーネル モード コンポーネントによって呼び出されるアプリケーション定義の関数です。ユーザー モード コールバック関数の例は、Windows プロシージャとフック プロシージャです。これらの関数は、Windows メッセージを処理するため、または Windows フック イベントを処理するために、Windows によって呼び出されます。

ホットフィックスを使用すると、Windows がグローバルに例外を食べるのを止めることができます。

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
DisableUserModeCallbackFilter: DWORD = 1

またはアプリケーションごと:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Notepad.exe
DisableUserModeCallbackFilter: DWORD = 1

この動作は、XP および Server 2003 の KB973460 にも記載されています。


ヒント

xperf を使用して 64 ビット Windows でスタック トレースをキャプチャすることを調査しているときに、別のヒントを見つけました。

Xperf でのスタック ウォーキング

ページングエグゼクティブを無効にする

64 ビット Windows でトレースを機能させるには、DisablePagingExecutiveレジストリ キーを設定する必要があります。これは、カーネル モード ドライバーとシステム コードをディスクにページングしないようにオペレーティング システムに指示します。これは、xperf を使用して 64 ビット コール スタックを取得するための前提条件です。 xperfスタック ウォーク コードは、ページ アウトされたページにアクセスできません。管理者特権でのコマンド プロンプトから次のコマンドを実行すると、このレジストリ キーが設定されます。

 REG ADD "HKLM\System\CurrentControlSet\Control\Session Manager\Memory Management" -v 
 DisablePagingExecutive -d 0x1 -t REG_DWORD -f

このレジストリ キーを設定したら、コール スタックを記録する前にシステムを再起動する必要があります。このフラグを設定すると、Windows カーネルがより多くのページを RAM にロックすることになるため、おそらく約 10 MB の追加の物理メモリが消費されます。

これは、64 ビット Windows (および 64 ビット Windows のみ) では、ディスク上にページ アウトが存在する可能性があるため、カーネル スタックをウォークできないという印象を与えます。

4

2 に答える 2

9

とても良い質問です。

カーネルとユーザーの境界を越えて例外を「伝播」することが問題になる理由のヒントを与えることができます。

あなたの質問からの引用:

スタックがカーネル境界を超えると、例外中に 64 ビット Windows がスタックをアンワインドできないのはなぜですか? 32 ビット Windows ではできるのに。

その理由は非常に単純です。「スタックがカーネル境界を越える」というようなことはありません。カーネル モード関数の呼び出しは、標準の関数呼び出しに匹敵するものではありません。実際には、コール スタックとは何の関係もありません。ご存じのとおり、カーネル モード メモリは、ユーザー モードからはアクセスできません。

カーネルモード関数 (別名syscall ) の呼び出しは、ソフトウェア割り込み (または同様のメカニズム) をトリガーすることによって実装されます。sysenterユーザー モード コードは、いくつかの値を (必要なカーネル モード サービスを識別する) レジスタに入れ、CPU をカーネル モードに転送して制御を OS に渡すCPU 命令 (など) を呼び出します。

次に、要求された syscall を処理するカーネル モード コードがあります。別のカーネル モード スタック (ユーザー モード スタックとは関係ありません) で実行されます。要求が処理された後、制御はユーザー モード コードに返されます。特定の syscall によっては、ユーザー モードのリターン アドレスが、カーネル モード トランザクションを呼び出したアドレスである場合もあれば、別のアドレスである場合もあります。

場合によっては、「途中で」ユーザーモード呼び出しを呼び出すカーネルモード関数を呼び出します。ユーザー-カーネル-ユーザー コードで構成されるコール スタックのように見えるかもしれませんが、これは単なるエミュレーションです。このような場合、カーネル モード コードは、ユーザー モード関数をラップするユーザー モード コードに制御を転送します。このラッパー コードは関数を呼び出し、関数が返されるとすぐにカーネル モード トランザクションをトリガーします。

ここで、「kernelmode から呼び出された」ユーザー モード コードで例外が発生した場合、次のようになります。

  1. ラッパーのユーザー モード コードは、SEH 例外を処理します (つまり、その伝播を停止しますが、スタックの巻き戻しはまだ実行しません)。
  2. 通常のプログラム フローの場合と同様に、制御をカーネル モード (OS) に渡します。
  3. Kenrel モードのコードは適切に応答します。要求されたサービスを終了します。ユーザーモードの例外があったかどうかによって、処理が異なる場合があります。
  4. ユーザーモードに戻ると、ネストされた例外があったかどうかをカーネルモードコードで指定できます。例外が発生した場合、スタックは元の状態に復元されません (巻き戻しがまだ行われていないため)。
  5. ユーザー モード コードは、そのような例外があったかどうかを確認します。そうであった場合、コール スタックはネストされたユーザー モード コールを含むように偽造され、例外が伝達されます。

そのため、カーネルとユーザーの境界を越える例外はエミュレーションです。本来はそんなことはありません。

于 2012-08-20T11:54:12.027 に答える