.NET Framework の内部処理で発生する例外について

こんにちは。Visual Studio サポート チームです。
今回は、.NET Framework アプリケーションで発生する異常終了の問題について、調査方法と有効な可能性のある回避策についてご案内します。

.NET Framework アプリケーションの異常終了は、大きく分けて以下の 2 種類の例外によって発生します。

 

a) アプリケーションでハンドルされない一般的なマネージ例外
b) .NET  Framework の内部処理で発生する例外

 

a) については、多くの方が経験されたことがありイメージしやすいかと思います。

例えば、未初期化のオブジェクトにアクセスしようとすると NullReferenceException がスローされますが、try-catch ステートメントで例外をハンドルしていない場合はアプリケーションが終了します。この問題は、Visual Studio や WinDbg などのデバッガを使用してアプリケーションをデバッグ実行するか、アプリケーション終了時のプロセスのダンプ ファイルを取得し、解析することで原因を調査することが可能です。デバッグ方法についての解説はここでは割愛します。

一方、b) については馴染みのない開発者の方も多いかもしれません。

これらは、通常、再現性が低く、問題発生時には「致命的な実行エンジン エラー」として ID 1000、および ID 1023 のイベント ログが記録されたり、アクセス違反の例外 (例外コード : 0xC0000005) として記録されたりします。これらの問題は、.NET Framework のコア ランタイム ライブラリ (mscorwks.dll または clr.dll) の内部処理でアクセス違反の例外が発生しているもので、イベント ログにもこれらのモジュールの名前が記録されます。(mscorwks.dll は .NET Framework 2.0/3.0/3.5 で使用される .NET Framework のコア ランタイム ライブラリです。clr.dll は .NET Framework 4 以降で使用されるものです。)

 

以下では、この b) の問題のうち、特に、ガベージ コレクション (GC) の実行中に発生する問題について取り上げ、原因、調査方法、有効な可能性のある対処策について解説します。

※ なお、GC 実行中の問題であるかどうかを判断するには、アプリケーション異常終了時のダンプ ファイルを解析するなどして、clr.dll や mscorwks.dll の GC 関連の処理 (WKS::gc_heap::mark_object_simple など) の最中に発生した問題であるかどうか確認する必要があります。

 

原因について

GC 実行時にアクセス違反の例外が発生する原因としては、「マネージ オブジェクトの破損」が挙げられます。

.NET Framework では、オブジェクトは GC ヒープ上に世代ごとに隙間なく並べられます。各オブジェクトのメモリ上のレイアウトはこちらの記事の Figure 6 のとおりです。各オブジェクトは自身の型を示す TypeHandle という領域を保持しており、型の情報を保持するグローバル領域である MethodTable をポイントしています。

ここで、なんらかの理由により、この MethodTable へのポインタが不正なアドレスをポイントするよう書き換えられると、GC 実行時にこの不正なアドレスが参照されアクセス違反となり、.NET Framework でハンドルできない致命的なエラーとしてアプリケーションが終了する結果となります。

また、何らかの理由により GC が使用するオブジェクトへのポインタが、スレッド間での整合性を失ってしまった場合も、同様に致命的なエラーとしてアプリケーションが異常終了します。

この「何らかの理由」の代表的なものとして、プラットフォーム呼び出し (P/Invoke) や COM 相互運用によるネイティブ関数の直接的な呼び出しにおける、呼び出し規約や引数のマーシャリングに関するコーディングのミスによるものが挙げられます。また、.NET Framework の未解決の問題が影響している可能性もあります。

 

調査方法について

マネージ オブジェクトの破損によりアプリケーションが異常終了する問題は、問題が混入したタイミングとアプリケーションが異常終了するタイミングが異なるため、アプリケーション異常終了時のダンプ ファイルからは原因を追究することはできません。ダンプ ファイルにはダンプ ファイルが出力された瞬間のメモリやレジスタの情報が記録されますが、根本的な原因は、アプリケーションが GC を実行してクラッシュするよりも前の時点で、オブジェクトのデータが何らかの処理により破壊されていることにあるからです。

 

MDA の活用

前述の P/Invoke や COM 相互運用に原因があるような場合、MDA (Managed Debugging Assistants) の gcManagedToUnmanaged、gcUnmanagedToManaged、invalidGCHandleCookie、invalidOverlappedToPinvoke、pInvokeLog、および pInvokeStackImbalance といった項目を指定して現象の再現性を高めたり問題個所を絞り込むことができる可能性があります。MDA の利用方法や各オプションの詳細については、以下の MSDN ライブラリを参照してください。

Diagnosing Errors with Managed Debugging Assistants
https://msdn.microsoft.com/en-us/library/d21c150d.aspx

 

 

Stress Log の取得

MDA を使用しても現象の再現性を高められない場合、Stress Log と呼ばれる特殊な設定を有効にした状態でアプリケーションを実行し、現象が再現した際のダンプ ファイルを取得して解析することで原因を追究できる場合があります。この解析作業は弊社のサポート サービスで承ります。
調査に有効な情報を採取するためには Stress Log と合わせて Heap Verify、GC Stress といった設定も有効にする必要があります。具体的にはレジストリを以下のように設定します。

キー : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework

名前 : StressLog
種類 : REG_DWORD
値 : 1

名前 : HeapVerify
種類 : REG_DWORD
値 : 1

名前 : GCStress
種類 : REG_DWORD
値 : 3

名前 : LogFacility
種類 : REG_DWORD
値 : 0xffffffff

名前 : StressLogSize
種類 : REG_DWORD
値 : 65536

なお、これらを有効にした場合、GC が高頻度で実行され、GC 実行時のオブジェクトの移動内容をすべてメモリ上にログするような動作となることから、アプリケーションのパフォーマンスが大幅に低下します。このため、運用環境への適用は避け、可能な限りテスト環境をご利用いただくようご注意ください。また、パフォーマンスが低下することにより現象の再現性が低下する場合もある点につきまして、予めご了承ください。
ダンプ ファイルの取得方法については以前のブログ記事でもご案内していますのでこちらをご参照ください。

 

有効な可能性のある対処策について

上述のとおり、Stress Log の採取設定によりアプリケーションのパフォーマンスが低下し、マルチスレッドの処理のタイミングも影響を受けることから、再現性が大幅に低下して難航することが多く、残念ながら原因が特定できないことのほうが圧倒的に多いのが実情です。
そのため、根本原因の特定とは別に、現象の回避という観点で、「同時実行 GC の無効化」をお試しいただくこともご検討ください。

.NET Framework を使用してデスクトップ アプリケーションを開発すると、GC のモードは既定で、Workstation モードとなり、合わせて、同時実行 GC という機能が有効となります。この同時実行 GC では、GC を複数のスレッドで並行して、細切れに実行することで、アプリケーションの応答性を向上させるよう複雑な同期処理を行っています。

この同時実行 GC を無効化することで、複数スレッドによる複雑な GC 処理を単純化することができ、.NET Framework の未解決の問題や、マネージ オブジェクト破損時の不正なアドレス参照によりアプリケーションが異常終了する現象を回避、または問題の発生確率を大幅に低減できる可能性があります。

同時実行 GC を無効化するにはアプリケーション構成ファイルなどで <gcConcurrent> 要素を追加して enabled 属性を false に設定します。<gcConcurrent> 要素については、以下のドキュメントをご参照ください。

<gcConcurrent>
https://msdn.microsoft.com/ja-jp/library/yhwwzef8.aspx

 

以上となります。

.NET Framework の致命的な実行エンジン エラーなど内部処理中に発生する問題は、現象の再現性によっては原因を特定することが難しい場合がありますが、同時実行 GC を無効にすることで現象の発生を抑止できる場合がありますので、原因調査とあわせて、こちらの利用もご検討ください。