Windows Error Reporting Part.2

Part.1 では、WER の概要をお伝えしました。Part.2 では、実際に WER の API を紹介したいところですが
WER の前提として、知っておかなければならない知識があります。例外処理についてです。

C++ では、構造化例外処理(SEH: Structured Exception Handling)と C++ 例外処理の 2 種類を使うことが
できます。SEH は OS が提供しているもので、C++ 例外処理は名前の通り、C++ 言語が提供しています
WER を使いこなすためには、SEH を理解しておく必要があります。
C++ の例外処理は別の機会でネタにして、今回は SEH の最低限の知識を復習します。

SEH は、__try/__except や __try/__finally を使い実装しますが、代表的な使い方はこんな感じです。

void MyFunc (void)
{
    __try
    {
        __try
        {
            // 処理
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            // 例外ハンドラ
            // __try ブロック内で何かしらの例外処理が発生したときに 実行される
        }
    }
    __finally
    {
        // 終了ハンドラ
        // 例外が有ろうと無かろうと常に実行される。後処理で使うことが多い
    }
}

__except では、例外フィルターを指定するのですが、使うことができるのは

  • EXCEPTION_EXECUTE_HANDLER
    対応する __try ブロック内で例外処理が発生すれば、必ず実行される
  • EXCEPTION_CONTINUE_SEARCH
    呼び出しチェイン内の上位にある次の __except ブロックを実行させる
  • EXCEPTION_CONTINUE_EXECUTION
    対応する __try ブロック内で発生した例外を無視する

の 3 つです。図に書くと、次のような動きになります。

例外処理の仕組み

少し詳しく書くと、例外フィルターが EXCEPTION_CONTINUE_SEARCH と評価された場合、例外ハンドラは例外の
キャッチを拒否したことになり、上位に向かい例外ハンドラを求めて検索が継続されます。
EXCEPTION_CONTINUE_EXECUTION と評価された場合には、処理再開型の例外になり、例外を無視して、
例外の発生地点の直後から実行が再開します。
EXCEPTION_EXECUTE_HANDLER と評価された場合には、終了型の例外になります。例外の発生地点から
コールスタックを逆進行しながら、対応した終了ハンドラをそれぞれ実行します。逆進行は例外をキャッチした例外フィルター
を持った例外ハンドラーのところで停止して、そこのハンドラーに入り、処理が継続されます。

[補足]
実際には、EXCEPTION_EXECUTE_HANDLER のあとで、Global Unwind が動く仕組みがあるのですが
これについては、今回割愛します。なお、Vista 以降の OS では、デフォルト状態では、Global Unwind は
開始されず、__finally ブロックは実行されないので、注意してください。Vista 以降の OS で Global Unwind
を実行させるためには例外フィルターで必ず、EXCEPTION_EXECUTE_HANDLER を返す必要が
あります。

WER Part.1 で書いた図の②でカーネルがアプリケーションに対して例外を通知すると書きましたが、
それが SEH 部分です。アプリケーションが独自に例外ハンドラを実装していない、つまり、__try ブロックが
見つからなければ、カーネルは EXCEPTION_CONTINUE_SEARCH が返ったこととして、WER を動かします。
逆に、アプリケーションが例外ハンドラを実装して、EXCEPTION_EXECUTE_HANDLER で処理した場合には
独自に例外を処理したことにより、WER は動きません。
WER は、アプリケーションが例外を処理できなかった場合のみ動くというわけです。

__except 内では、GetExceptionCode() と使うことで、どんな例外が発生したのかを判別することが
できるので、例外の種類によって、どのような例外ハンドラを実装するのかを考えることができます。

上記リファレンスに例外の種類も記載されています。
なお、GetExceptionCode() は __except ブロック内からしか呼び出せませんので注意してください。

int divideExpCode(DWORD dwExceptCode)
{
    switch (dwExceptCode)
    {
    case EXCEPTION_ACCESS_VIOLATION:// アクセス違反
    case EXCEPTION_STACK_OVERFLOW:    // スタックオーバーフロー
    case EXCEPTION_INVALID_HANDLE:    // 無効なハンドル
        return EXCEPTION_EXECUTE_HANDLER;
    default:
        return EXCEPTION_CONTINUE_SEARCH;
    }
}

void MyFunc2 (void)
{
    __try
    {
        __try
        {
            // 処理
        }
        __except (divideExpCode(GetExceptionCode()))
        {
            // 例外ハンドラ
            // __try ブロック内で何かしらの例外処理が発生したときに 実行される
        }
    }
    __finally
    {
        // 終了ハンドラ
        // 例外が有ろうと無かろうと常に実行される。後処理で使うことが多い
    }
}

ちなみにですが、例外コードは 32bit の値で、COM の HRESULT と読み方は同じです。
上記に書いた EXCEPTION_ACCESS_VIOLATION (アクセス違反)は、WinBase.h で

#define EXCEPTION_ACCESS_VIOLATION          STATUS_ACCESS_VIOLATION

と定義されていて、STATUS_ACCESS_VIOLATION は、WinNT.h で

#define STATUS_ACCESS_VIOLATION          ((DWORD)0xC0000005L)

と定義されています。ダンプ解析のときによくでてくるアクセス違反の例外コード「0xC0000005」のことです。

SEH を使い、きちんと例外コードを判別して例外ハンドラを実装していれば、「0xC0000005」 が発生した時点で
独自の処理を行うことができ、EXCEPTION_EXECUTE_HANDLER で処理すれば WER が表示されずに
アプリケーションを終了させることもできてしまいます。

GetExceptionCode() の他に似たような API で GetExceptionInformation() があります。この API は
クラッシュした原因とそのときの CPU の状態を記録している EXCEPTION_POINTERS 構造体への
ポインタを返します。

上記を見るとわかるのですが、GetExceptionCode() で取得できる値と
*(GetExceptionInformation())->ExceptionReport->ExceptionCode の値は同じです。

int divideExpInfo(EXCEPTION_POINTERS* pExceptionPointers)
{
    switch (pExceptionPointers->ExceptionRecord->ExceptionCode)
    {
    case EXCEPTION_ACCESS_VIOLATION:// アクセス違反
    case EXCEPTION_STACK_OVERFLOW:    // スタックオーバーフロー
    case EXCEPTION_INVALID_HANDLE:    // 無効なハンドル
        return EXCEPTION_EXECUTE_HANDLER;
    default:
        return EXCEPTION_CONTINUE_SEARCH;
    }
}

void MyFunc3 (void)
{
    __try
    {
        __try
        {
            // 処理
        }
        __except (divideExpCode(GetExceptionInformation()))
        {
            // 例外ハンドラ
            // __try ブロック内で何かしらの例外処理が発生したときに 実行される
        }
    }
    __finally
    {
        // 終了ハンドラ
        // 例外が有ろうと無かろうと常に実行される。後処理で使うことが多い
    }
}

実際の製品開発では、GetExceptCode() よりも GetExceptionInformation() の方が取得できる情報量が多いので
GetExceptionInformation() を使った方が良いと思います。

SEH のことを書き続けると、いつまでもネタが尽きないのですが…。今回は、WER のネタなので、 ここで WER を
絡めてみます。 もう、想像通りだと思いますが、例外ハンドラの中で、EXCEPTION_EXECUTE_HANDLER を処理して
アプリケーションを優雅に終了させ、そこで 独自に WER を呼び出そうというわけです。

Part.3 では、いよいよ、WER の API を紹介します。

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中

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