スタックがカーネル境界を超えると、例外中に 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 のみ) では、ディスク上にページ アウトが存在する可能性があるため、カーネル スタックをウォークできないという印象を与えます。