[迅速なお困りごと解決のために] 開発した .NET アプリケーションが異常終了して、その際イベントログに ID 1000 もしくは ID 1023 のイベントが表示されている場合の初期調査

初めまして、Visual Studio サポートチーム トミタです!
ブログでも、普段のサポート同様、有用な情報をお客様にご提供できればと思いますので、どうぞよろしくお願いします!

私達サポートチームではいろいろなお問い合わせをお受けし対応していますが、お問い合わせいただいた際のお問い合わせの種類によっては、調査前の切り分けとして、事前にご確認いただくことや、ご理解いただきたいことがある場合もございます。
そこで、今後、このブログを通じて、お客様側で問題が起きた際の切り分けの方法や、事前の情報採取手法などをご紹介したいと考えています。
事前に現象の切り分けを実施いただくことで、サポートサービスをご利用いただく際も、より効率的に利用いただけると思っています。

これらの情報を、今後「迅速なお困りごと解決のために」シリーズとして、順次公開させていただければと思います。
サポートエンジニアならではのコンテンツ、どうぞご期待ください!


さて、記念すべき第一回は 、「開発した .NET アプリケーションが異常終了して、その際イベントログに ID 1000 もしくは ID 1023 のイベントが表示されている」 という事象を確認された場合の切り分け方法についてお伝えしたいと思います。

[現象]

.NET Framework 上で動作しているアプリケーションが異常終了する際、イベント ID 1000 もしくはイベント ID 1023 のエラーが出力され、.NET Framework のコンポーネントにてエラーが発生していると通知される。

-例

image image

[原因]

イベントログに出力される ID 1000 もしくは ID 1023 のエラーは、.NET アプリケーションで、try-catch 節などで捕捉されない例外やエラーが発生した場合に記録されます。可能性は以下の通りです。

  1. 例外が発生しているが、エラーハンドルされず、異常終了した場合
  2. アプリケーション内で Windows API などのネイティブ API を使用しており、.NET Framework が管理できないメモリ領域で不正なメモリアクセス エラーが発生した場合
  3. .NETランタイム内部で問題発生し、内部のデータ不整合でそれ以上の継続動作が難しい場合

このような場合には、イベントログには上記例のような「致命的な実行エンジンエラー (Fatal Execution Engine Error) = エラー番号 80131506」という名前で記録されます。また、発生アドレスを示す 000007FEF894FA42 や 000006427F44AE16、32 ビット環境の場合に 7A05E2B3 の数値が出力されることがあります。なお、このイベントログでは、現象発生時の場所は特定できず、原因はわかりません。

注意点として、2, 3 の場合は、.NET コード外部で発生しているため、.NET コード中の try-catch 句を指定しても捕捉できない場合もあります。

そのため、原因追及のためにはイベントログのみならず、発生場所の特定や、もう少し詳細な情報を採取したうえでの調査が必要となります。

[対処方法]

現象発生時の状況を特定するために、一般的には、以下の方法が有効です。

(1) コードレビューし、不足している箇所に try-catch 句を挿入し必要なログ出力を行う
(2) System.Diagnostics 名前空間の Debug / Trace クラスをコード上に実装して、発生場所の特定を行う
(3) try-catch 句で捕捉されない例外を捕捉し、ログの出力を行うための実装をする (UnhandledException イベントなどの利用)
(4) アプリケーション クラッシュ時のダンプファイルの採取を行い解析する

ここで、(1)(2)(3) はアプリケーションの実装が変更になることと、発生している例外の情報によっては、必要な情報が取得できない場合もあるという懸念点があります。
しかし、方法自体は単純で、情報さえ取得できれば、どの箇所で問題が発生しているかを確認することも簡単にできます。

一方 (4) ではアプリケーションの実装は不要で確実に情報が取得できるものの、実際にアプリケーションを運用している環境で情報を取っていただく場合、アプリケーションのパフォーマンスに影響を与えることがあります。
また、特定のデバッグ用のツールが必要になり、ある程度そのツールを使っての解析方法を習得していただく必要があるという注意点もあります。

以下にそれぞれの情報採取方法について詳細をまとめてみました。
(1) (2) について
=================
try-catch 句の利用方法については、下記のドキュメントをご参考いただければと思います。

例外処理ステートメント (C# リファレンス)
https://msdn.microsoft.com/ja-jp/library/s7fekhdy.aspx

Try...Catch...Finally ステートメント (Visual Basic)
https://msdn.microsoft.com/ja-jp/library/fk6t46tz.aspx

デバッグのための System.Diagnostics クラスの利用方法については、こちらをご参考ください。

アプリケーションのデバッグとプロファイリング
https://msdn.microsoft.com/ja-jp/library/7fe0dd2y(v=VS.90).aspx

アプリケーションのトレースとインストルメント (VS2008)
https://msdn.microsoft.com/ja-jp/library/zs6s4h68(v=VS.90).aspx

(3) について
=================
アプリケーションで try-catch句が実装された以外の箇所で発生し、捕捉されていない例外がある場合、それについては、AppDomain.UnhandledException イベントを利用する方法で例外情報のログを出力できます。

こちらの方法を利用すると、アプリケーションで発生した全てのハンドルされない例外を捕捉し、アプリケーション終了前に処理することが可能です。例えば、System.Diagnostics.EventLog クラスを使用して、イベントログに出力するよう実装することで、例外発生後にイベントログから例外の詳細を参照することができます。(もちろん、ログファイルに出力したりしていただいても構いません)

以下に AppDomain.UnhandledException イベントを使う方法についてご案内します。

- 実装例 (C#)
1. 以下の using 句を追加します。

using System.Diagnostics;
using System.Threading;

2. 以下のメソッドを Main メソッドの定義されているクラスへ実装します。

static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
Exception e = (Exception)args.ExceptionObject;
Console.WriteLine(e.ToString());
}

3. Main 関数開始直後に、以下のイベントハンドラの定義を追加します。

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);

4. 例えば以下のようなアプリケーションをビルドして実行します。

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace ErrorSample
{
class Program
{
static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
Exception e = (Exception)args.ExceptionObject;
Console.WriteLine(e.ToString());
}

        static void Main(string[] args)
{
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);

            throw new Exception("My error");
}
}
}

この実装により、ハンドルされていない例外が発生した場合には、その発生場所まで含んだ情報がイベントログに出力されます。下記の例は、実際に取得されたコールスタックの例です。

System.Exception: My error
場所 ErrorSample.Program.Main(String[] args) 場所 Z:\Documents\Visual Studio
2010\Projects\ErrorSample\ErrorSample\Program.cs:行 23
場所 System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
場所 System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblyS
ecurity, String[] args)
場所 Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
場所 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext,
ContextCallback callback, Object state)
場所 System.Threading.ThreadHelper.ThreadStart()

AppDomain.UnhandleException イベントの詳細については以下のドキュメントをご参照ください。

AppDomain.UnhandledException イベント
https://msdn.microsoft.com/ja-jp/library/system.appdomain.unhandledexception.aspx

なお、アプリケーションがコンソールベースではなく、ダイアログ形式のフォームを使っている場合には、フォームを呼びだしているスレッドに対して ThreadException イベントを設定する必要があります。フォームを呼び出すアプリケーションにて同様の処理を行う場合には、以下のイベントについても設定が可能ですのでご確認ください。

Application.ThreadException イベント (System.Windows.Forms)
https://msdn.microsoft.com/ja-jp/library/system.windows.forms.application.threadexception(VS.80).aspx

ただし、これらの例外ハンドラで例外を捕捉されましても、捕捉される例外の種類によっては、例外の捕捉までは可能であってもアプリケーションの継続が不可能となる場合もありますので、その点のみご留意ください。

(4)について
=================
本方法は、専用ツールを使用しアプリケーションが異常終了した際にダンプ ファイルを取得する方法です。具体的には、アプリケーションの起動後に、動作を継続中のタイミングで、アプリケーションのプロセス ID を確認して、デバッガをアプリケーションにアタッチする作業が必要となります。

以下にダンプファイル採取手順をご案内しますのでご確認ください。

※注意すべき点※
本方法は対象のアプリケーションにデバッグツールをアタッチすることとなりますので、アプリケーションの動作に多少の負荷を与えることとなります。運用環境でご実施いただく場合には、パフォーマンスが許容範囲となるかについても事前にご確認ください。

1. ダンプファイル採取の手順
----------------------------------
ダンプファイル採取には Adplus と呼ばれるツールを使用します。Adplus を対象のアプリケーションにアタッチさせ、異常終了時にダンプ ファイルを取得します。

以下に Adplus を使用したダンプ ファイルの取得方法についてお伝えします。

- 手順
1) Debugging Tools for Windows のダウンロードおよびインストール
以下の Web サイトより、Debugging Tools for Windows をインストールします。
なお、インストールは別の環境で行っていただき、情報採取環境にはツールをフォルダごとコピーする形でも結構です。この場合、インストール処理によるレジストリ書き込みを防ぐ事が可能です。

(※ 以下の手順は本ツールを c:\Debuggers にインストールしたと仮定したものとなります)

本ツールは、バージョン 6.11.1.404 より以前では単独でご提供していましたが、最新版では、Windows SDK に統合された形となっており、Windows SDK のインストールによって、他のツールと同時にもしくは単独でのインストールが可能となっております。
そのため、下記のいずれかの方法で、Debugging Tools for Windows を入手してくださいますようお願いします。

- 最新版を利用する場合
下記のサイトより、Windows SDK をインストールし、インストールフォルダ配下の Debugging Tools for Windows フォルダ内のツールを利用します。
なお、単独で本ツールのみをインストールいただく場合は、インストール対象のコンポーネントが選択可能な "Installation Options" 画面で、"Common Utilities" 配下の "Debugging Tools for Windows" オプションのみを選択します。
※インストール環境によっては、その他のオプションについて、選択が無効とならないオプションが存在している場合ありますのでご了承ください。

Microsoft Windows SDK for Windows 7 and .NET Framework 4
https://www.microsoft.com/downloads/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b&displaylang=en

- 旧バージョン(6.11.1.404) を利用する場合
通常のダンプ採取では、バージョン 6.11.1.404 のものでもご利用できますので、当該ツールを下記のサイトから dbg_x86_6.11.1.404.msi (x86 版)、dbg_amd64_6.11.1.404.msi (x64 版)、 dbg_ia64_6.11.1.404.msi (ia64 版) のいずれかをダウンロードしてください。

(a) 32bit OS の場合
32 ビット版 Debugging Tools for Windows
https://msdn.microsoft.com/ja-jp/subscriptions/gg463016.aspx
(b) 64bit OS の場合
64 ビット版 Debugging Tools for Windows
https://msdn.microsoft.com/ja-jp/subscriptions/gg463012.aspx
ダウンロードできたセットアップファイル ( msi ファイル)をダンプ採取 PC の適用なフォルダに格納し、ダブルクリックでインストールしてください。

2) 現象が発生するアプリケーションを起動します。

3) コマンド プロンプトを起動し、Debugging Tools for Windows がインストールされたディレクトリに移動します。

> cd C:\Debuggers

4) tasklist コマンド、もしくはタスクマネージャを実行して対象アプリケーションのプロセス名とプロセス ID を確認します。

5) Crash モードで ADPlus コマンドを起動します。ダイアログが表示されますが、OK を押下して進めてください。

> cscript adplus_old.vbs -crash -FullOnFirst -p <PID>
※ <PID> は、tasklist コマンド で確認したプロセス ID を指定してください。< > は必要ありません。
※ 6.11.1.404 以前のバージョンの Debugging Tools for Windows をご利用される場合、adplus_old.vbs を adplus.vbs と読み替えてください。

起動後、自動的に新規にコマンド プロンプト ウィンドウが起動されます。

6) 致命的なエンジンエラーが発生するまで処理を継続してください。
Microsoft Debugging Tools がインストールされたディレクトリ以下に、日付と時間を含んだフォルダ内とその中にダンプ ファイル (.dmp) が作成されます。

以上で、アプリケーション異常終了のダンプファイル自動的生成についての方法となります。一般的な異常終了については、上記にてダンプ採取可能となります。

- 参考資料
How to use ADPlus.vbs to troubleshoot "hangs" and "crashes"
https://support.microsoft.com/kb/286350/

 

[ダンプファイルの解析について]

ここまで、致命的実行エラーが起きた際のダンプファイル採取の手順方法をお伝えしましたが、採取いただきましたら次に、そのファイルの解析が必要になります。
解析は Windbg というツールを使用しますが、これが非常に奥が深く、一言では語れないようなツールとなっています。
私たち、サポートエンジニアは、そんなツールを駆使してお困りごとの解決に日夜努力しておりますが、実際にダンプファイルの解析を行ってみたいというお客様もいらっしゃるかと思います。
そのような方向けに、Visual Studio サポートチームのお隣、ILM チームのブログにて詳しく Windbgの基本的な使用方法等について記載がありますので、それらをご参考にしていただけますと幸いです。

[Debugging] Windbg を使ってご機嫌ナナメな彼女の心を激しくデバッグ!(1) / 3
<https://blogs.technet.com/jpilmblg/archive/2009/02/21/debugging-windbg-1.aspx>

[Debugging] Windbg を使ってご機嫌ナナメな彼女の心を激しくデバッグ!(2) Ver 1.1
<https://blogs.technet.com/jpilmblg/archive/2009/02/25/debugging-windbg-2.aspx>

[Debugging] Windbg を使ってご機嫌ナナメな彼女の心を激しくデバッグ!(3)
<https://blogs.technet.com/jpilmblg/archive/2009/03/06/debugging-windbg-3-tips.aspx>

もし、ダンプファイルは取れたものの、解析が難しい…といったことがありましたら、弊社「Premier サポート」もしくは、「アドバイザリー サービス」にて、弊社エンジニアによる調査ご支援が可能でございますので、ご検討ください。

**

本記事が、今後 .NET Framework にてアプリケーションを開発されるお客様の有用な情報となりましたら幸いでございます。