Windows Error Reporting Part.3

Part.1 と Part.2 で WER の仕組みと前提となる SEH の概要をお伝えしました。Part.3 では、いよいよ WER の
API を紹介したいと思います。

Windows Error Reporting Part.1 で WER は Microsoft WER サイトに採取したデータを送信し、
その動きをレジストリで制御することができると紹介しましたが、API を使うと、一時的に現在のプロセスの設定のみを
変更することができます。

一つだけ注意があります。WerGetFlags() は、WerSetFlags() の結果を取得するときに使う用途になっているので
WerSetFlags() を呼び出さずに、いきなり WerGetFlags() を呼びだすと、エラーになります。

次に、WER の問題レポートの作成ですが、MSDN にある

に従い、以下の流れで処理します。

  1. WER_REPORT_INFORMATION 構造体を準備する
  2. WerReportCreate() を呼び出し、WER 問題レポートを作成する
  3. WerReportSetParameter() を呼び出して、パラメーターを追加する
  4. WerReportAddFile() を呼び出して、レポートに独自のファイルを追加する
  5. WerReportAddDump() を呼び出して、ミニダンプをレポートに追加する
  6. WerReportSubmit() を呼び出して、レポートを送信するか、ローカルキューに保存する
  7. WerReportCloseHandle() でリソースを解放して終了する

イメージがわかるように、実際の画面との対応を行ってみます。
まず、WER 問題レポートの表示です。

アクションセンター_問題を表示

アクションセンター_解決策の確認

「技術的な詳細の表示」がリンクになっているので、クリックすると、次のような画面になります。
先の流れで書いた API と構造体の対応を画面に書き込んでみました。

WERレポートカスタマイズ

1. WER_REPORT_INFORMATION 構造体

この構造体で今回作成する WER の問題レポートの情報を作成します。なお、WER 系の API はすべて Unicode 文字列
だけの対応になるので、注意してください。

WER_REPORT_INFORMATION reportInformation = {0};

reportInformation.dwSize = sizeof(reportInformation);
reportInformation.hProcess = NULL;

StringCchCopy(reportInformation.wzConsentKey,
   ARRAYSIZE(reportInformation.wzConsentKey), L"Sample Consent Key");

StringCchCopy(reportInformation.wzApplicationName,
   ARRAYSIZE(reportInformation.wzApplicationName), L"WER サンプル アプリケーション");

StringCchCopy(reportInformation.wzApplicationPath,
   ARRAYSIZE(reportInformation.wzApplicationPath), L"Sample Application Path");

StringCchCopy(reportInformation.wzDescription,
   ARRAYSIZE(reportInformation.wzDescription), L"WER のサンプルを作成しています。ここには独自の記述を行うことができます。");

StringCchCopy(reportInformation.wzFriendlyEventName,
   ARRAYSIZE(reportInformation.wzFriendlyEventName), L"WER サンプル アプリケーションのクラッシュエラー");

2. WerReportCreate()

この API は、WER の問題レポートを作成する際に使用します。この API を使うと、作成する問題レポートの
レポートハンドル(HREPORT)を取得でき、この後の WER 系 API で使用します。

hr = WerReportCreate(L"Application Crash Report",
        WerReportCritical, &reportInformation, &hReport);

3. WerReportSetParameter()

この API は、任意の情報を WER 問題レポートに追加することができます。ただし、最大で 10 個までの情報になります。
マクロで WER_P0 ~ WER_P9 までの 10 個のインデックスが用意されているので、それを順番に使います。

hr = WerReportSetParameter(hReport, WER_P0, L"アプリケーション名", szPath);
hr = WerReportSetParameter(hReport, WER_P1, L"パラメーター1", L"Value1");
hr = WerReportSetParameter(hReport, WER_P2, L"パラメーター2", L"Value2");
hr = WerReportSetParameter(hReport, WER_P3, L"パラメーター3", L"Value3");

4. WerReportAddFile()

この API を使うと、WER 問題レポートに独自のファイルを追加することができます。追加するファイルが、どのようなファイルなのか、ファイル種別を指定することになっているので、きちんと選んでください。

TCHAR szCurrentDir[MAX_PATH];
*szCurrentDir = ”;
GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
TCHAR szTraceFile[MAX_PATH];
// (注) szTraceFile がバッファオーバーならないように
wsprintf(szTraceFile, TEXT("%s\\trace.log"), szCurrentDir);
hr = WerReportAddFile(hReport, szTraceFile, WerFileTypeOther, WER_FILE_ANONYMOUS_DATA);

5. WerReportAddDump()

この API を呼び出すと、WER はミニダンプを作成して、WER 問題レポートに追加します。パラメ-ターにプロセスハンドルと
スレッドハンドルがあるので、外部プロセスからダンプを採取することもできます。さらに採取するダンプの種類も指定します。
通常は、WerDumpTypeMiniDump を指定しておけば大丈夫です。他に、SEH で入ってくる例外情報も指定します。

WER_EXCEPTION_INFORMATION werExceptionInformation;
werExceptionInformation.pExceptionPointers = inExceptionPointer;
werExceptionInformation.bClientPointers = FALSE; // TRUE にすると外部プロセスから採取する

hr = WerReportAddDump(hReport, GetCurrentProcess(),
    GetCurrentThread(), WerDumpTypeMiniDump, &werExceptionInformation, NULL, 0);

6. WerReportSubmit()

一通りの WER 問題レポートの処理が終了したら、必ず、この API を呼び出します。この API を呼ぶことで作成した
WER 問題レポートが登録されます。登録は、Microsoft の WER サイトに送信されるか、設定によってローカル環境に
キューイングされるかのどちらかになります。

hr = WerReportSubmit(hReport, WerConsentNotAsked,
    WER_SUBMIT_SHOW_DEBUG | WER_SUBMIT_QUEUE, &eSubmitResult);

7. WerReportCloseHandle()

この API で、WerReportCreate() で取得したレポートハンドルをクローズします。

hr = WerReportCloseHandle(hReport);

以上が一連の API とその使い方です。
これらをすべて入れたサンプルコードも載せておきます。(エラー処理は省略しています)

int AddWerDump(EXCEPTION_POINTERS* pExceptionPointer)
{
    HRESULT hr = S_OK;
    HREPORT hReport = NULL;
    WER_SUBMIT_RESULT eSubmitResult;

    // 1. WER_REPORT_INFORMATION 構造体を作成する
    WER_REPORT_INFORMATION reportInformation = {0};

    reportInformation.dwSize = sizeof(reportInformation);
    reportInformation.hProcess = NULL;

    StringCchCopy(reportInformation.wzConsentKey,
        ARRAYSIZE(reportInformation.wzConsentKey), L"Sample Consent Key");
  
    StringCchCopy(reportInformation.wzApplicationName,
        ARRAYSIZE(reportInformation.wzApplicationName), L"WER サンプル アプリケーション");

    StringCchCopy(reportInformation.wzApplicationPath,
        ARRAYSIZE(reportInformation.wzApplicationPath), L"Sample Application Path");

    StringCchCopy(reportInformation.wzDescription,
        ARRAYSIZE(reportInformation.wzDescription), L"WER のサンプルを作成しています。ここには独自の記述を行うことができます。");

    StringCchCopy(reportInformation.wzFriendlyEventName,
        ARRAYSIZE(reportInformation.wzFriendlyEventName), L"WER サンプル アプリケーションのクラッシュエラー");

    // 2. WerReportCreate() を呼び出し、レポートを作成する
    hr = WerReportCreate(L"Application Crash Report",
        WerReportCritical, &reportInformation, &hReport);

    // 3. WerReportSetParameter() を呼び出して、パラメーターを追加する
    TCHAR szPath[MAX_PATH];
    *szPath = ”;
    GetModuleFileName(NULL, szPath, sizeof(szPath));

    hr = WerReportSetParameter(hReport, WER_P0, L"アプリケーション名", szPath);
    hr = WerReportSetParameter(hReport, WER_P1, L"パラメーター1", L"Value1");
    hr = WerReportSetParameter(hReport, WER_P2, L"パラメーター2", L"Value2");
    hr = WerReportSetParameter(hReport, WER_P3, L"パラメーター3", L"Value3");

    // 4. WerReportAddFile() を呼び出して、レポートに独自のファイルを追加する
    TCHAR szCurrentDir[MAX_PATH];
    *szCurrentDir = ”;
    GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir);
    TCHAR szTraceFile[MAX_PATH];
    wsprintf(szTraceFile, TEXT("%s\\trace.log"), szCurrentDir);
    hr = WerReportAddFile(hReport, szTraceFile, WerFileTypeOther, WER_FILE_ANONYMOUS_DATA);

    // 5. WerReportAddDump() を呼び出して、ミニダンプをレポートに追加する
    WER_EXCEPTION_INFORMATION werExceptionInformation;
    werExceptionInformation.pExceptionPointers = inExceptionPointer;
    werExceptionInformation.bClientPointers = FALSE;

    hr = WerReportAddDump(hReport, GetCurrentProcess(),
        GetCurrentThread(),WerDumpTypeMiniDump, &werExceptionInformation, NULL, 0);

    // 6. WerReportSubmit() を呼び出して、レポートを送信するか、ローカルキューに保存する
    hr = WerReportSubmit(hReport, WerConsentNotAsked,
        WER_SUBMIT_SHOW_DEBUG | WER_SUBMIT_QUEUE, &eSubmitResult);
   
    // 7. WerReportCloseHandle() でリソースを解放して、レポートを終了する
    hr = WerReportCloseHandle(hReport);

    return EXCEPTION_EXECUTE_HANDLER;
}

int _tmain(int argc, _TCHAR* argv[])
{
    printf("WER Sample Start…\n");

    __try
    {
        int *pBadPtr = NULL;
        *pBadPtr = 0;
    }
    __except(AddWerDump(GetExceptionInformation()))
    {
    }

    printf("WER Sample End.\n");
    return 0;
}

上記コードを走らせ、WER で採取したミニダンプ(拡張子 .mdmp)を WinDBG で見ると

EXCEPTION_RECORD:  ffffffff — (.exr 0xffffffffffffffff)
ExceptionAddress: 00401495 (BadPtrCrash!wmain+0x00000045)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 00000000
Attempt to write to address 00000000

DEFAULT_BUCKET_ID:  NULL_POINTER_READ
PROCESS_NAME:  BadPtrCrash.exe
ERROR_CODE: (NTSTATUS) 0xc0000005 – 0x%08lx
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 – 0x%08lx
EXCEPTION_PARAMETER1:  00000001
EXCEPTION_PARAMETER2:  00000000
WRITE_ADDRESS:  00000000

となり、アドレス 00000000 への書き込みで例外コード 0xc0000005 が発生とわかります。

上記は基本的な WER の使い方ですが、WER は他にもカスタマイズすることができます。
例えば、

  • WerReportSetUIOption() : WER 問題レポートの送信時に表示するタスクダイアログの文字列を変更する。
  • WerRegisterMemoryBlock() : 64KB までの指定したメモリブロックをミニダンプに登録する。
  • WerRegisterRuntimeExceptionModule() : 例外発生時に独自の処理を行う外部モジュールを WER に登録する。
  • WerRegisterFile() : 実行中のプロセスに対して WER が動作した際に任意のファイルを追加する(上限は 512 個)。
    なお、WerReportAddFile() は、作成する WER 問題レポートのみに任意のファイルを追加する。
  • WerAddExcludedApplication() : 指定したアプリケーションを WER から除外する。

などもできます。

WER は、ワトソン博士から多くの機能/仕組みがエンハンスされていて、うまく使うと、製品のフィールドトラブルシュートで
かなり役立つと思います。ぜひ、製品 に SEH と WER を活用してみてください。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。