ProcDumpで、なんとなくtry catchで握りつぶしている例外(ファーストチャンス例外)発生を検知する

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

C#やりはじめのころ、

  • なぜかははっきりとはわからないが「例外」がおきる
  • でもそれでプログラムがクラッシュすると困る
  • だからtry catchで囲んで、なかったことにしてしまおう(握りつぶしてしまおう)

ということが正直あった。

ただそれは自分だけではなく、製品コードでも多くの個所にそういうのがあったりする。

そうなると、実は致命的なエラーが起きていても、握りつぶされてしまいプログラムがクラッシュすることもなく、 時間がたってから、(直接的にはその握りつぶした例外が原因なのに)見た目全然違う現象になって現れたりする。

そうならないように、日々握りつぶした例外を目視したい。
日々まで見なくても、なんかあったときに、実は握りつぶした例外があるのでは?ということを見たい。

※また、検知した例外は一個一個ダンプが出力されては大変なので、ダンプは取らずに例外があったことだけを知りたい。

使うもの

ProcDumpを使う。

learn.microsoft.com

ProcDumpは、「例外が起きてアプリがクラッシュしたときにダンプを取ることで、クラッシュ時のデバッグを行えるようにする」が王道の使い道だと思っているが、 クラッシュしない、try catchで握りつぶしたような例外を検知することもできる。

例外がthrowされたときを、ファーストチャンス
例外がcatchされなかったとき(クラッシュしたとき)をセカンドチャンス
というらしい。

learn.microsoft.com

-e 1 は、そのファーストチャンス例外を検知せよ、というパラメータ。

アプリクラッシュダンプを出力していた時のパラメータ-maは、今回ダンプは出力しないので付けない。(つけなくてもダンプはとる。デフォルトで-mm(ミニダンプ)扱いになる。)

やったこと

ProcDumpを下記のようなコマンドで起動する。(ConsoleApp17.exeは今回使った例外握りつぶしサンプルアプリ=例外を見たい対象アプリ)

procdump -w -e 1 -f "" ConsoleApp17.exe

これを実行すると、

こういう表示が出て待ちに入る。

で、例外を握りつぶすようなプログラムを走らせると、下記のような表示がでる。
赤マークを付けた部分が、握りつぶした例外表示。

「例外を握りつぶすようなプログラム」はこちら。

namespace ConsoleApp17;

internal class Program
{
    static void Main(string[] args)
    {
        Thread.Sleep(1000);

        try
        {
            // 1. NullReferenceException
            string nullString = null;
            Console.WriteLine(nullString.Length);
        }
        catch (NullReferenceException ex)
        {
            Console.WriteLine($"NullReferenceException: {ex.Message}");
        }

        try
        {
            // 2. IndexOutOfRangeException
            int[] numbers = { 1, 2, 3 };
            Console.WriteLine(numbers[5]);
        }
        catch (IndexOutOfRangeException ex)
        {
            Console.WriteLine($"IndexOutOfRangeException: {ex.Message}");
        }

        try
        {
            // 3. DivideByZeroException
            int zero = 0;
            int result = 10 / zero;
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"DivideByZeroException: {ex.Message}");
        }


        try
        {
            // 6. FormatException
            int invalidNumber = int.Parse("NotANumber");
        }
        catch (FormatException ex)
        {
            Console.WriteLine($"FormatException: {ex.Message}");
        }

        try
        {
            // 7. OverflowException
            int tooLarge = int.MaxValue;
            int overflow = checked(tooLarge + 1);
        }
        catch (OverflowException ex)
        {
            Console.WriteLine($"OverflowException: {ex.Message}");
        }

        Console.WriteLine("例外のサンプルが終了しました。");
    }
}

※実験コードを作るうえでだけの注意だが、このコードの冒頭にある「Thread.Sleep(1000)」で待ちを入れておかないと、 起動後一瞬で終わってしまうようなプログラムだと、ProcDumpがアタッチ?する前にプログラムが終わってしまい、うまくいかなかった。 実験時は、1秒程度Sleepしたほうがよさそう。

気になったこと(-f "" はなぜ必要か?)

今回、ProcDumpのパラメータのサンプルを参考に、

procdump -w -e 1 -f "" ConsoleApp17.exe

で試してうまくいったのだが、-f ""がなぜ必要なのか?がよくわからなかった。

MSDOCでの-f の説明は下記。

例外名称や例外Msgなどに、-fで指定した文字列が含まれているとダンプを行うらしい。

色々試した結果、-f ""は、「ダンプファイルを出力させない」ために付けているっぽい。

付けないと、1個目のファーストチャンス例外時にダンプが出力されて、procdumpが終了した。 (終了するのは、デフォルトの設定でダンプ回数が1になってるからっぽい?未確認)

で、-f ""を付けると、フィルター文字列に引っかからなくなることで、ダンプが出力されなくなり、引っかかった旨の文言だけprocdump上に出てくるようになるものと思われる。

※ためしに-f "AAAAAAAAAAAAAAAA"とかすると、これも引っかからなくなって、-f ""と同じ結果になった。

-f に付けるパラメータ実験

-f "" を付けなかったときのprocdumpのアタッチ時の出力 (Excludeに*`が入っている=すべての例外を除外する=ダンプを出力しない)

`-f "" を付けなかったときのprocdumpのアタッチ時の出力
(Excludeに何も入っていない=除外する例外なし=すべての例外でダンプを出力する)

`-f "AAAAAAAAAAAAAA" を付けたときのprocdumpのアタッチ時の出力
(Includeに"AAAAAAAAAAAA"が入っている=AAAAAAAAAAAという文言を含む例外でダンプを出力する=そんな例外はないので、実質ダンプを出力しない)

参考

ProcDump
https://learn.microsoft.com/en-us/sysinternals/downloads/procdump

ファーストチャンス例外とは?MS開発ブログ
https://learn.microsoft.com/en-us/archive/blogs/davidklinems/what-is-a-first-chance-exception