[VisualStudio/ダンプ] ProcDumpを使ったダンプファイルの作成と解析


**もくじ**
→https://qiita.com/tera1707/items/4fda73d86eded283ec4f

# ダンプ関連記事

https://tera1707.com/entry/2022/02/06/144447#WindowsDump



# やりたいこと

自分が作成したあるアプリが、他の人のPCで動かしたときに例外を吐いてクラッシュしてしまう。
その原因を調べたい。できれば自分のPCでVisualStudioでデバッグ実行して、落ちた部分でbreakさせて調査をしたいが、自分のPCでは起きてくれず、リモートデバッグも出来ない状況だが、なんとかして手がかりを得たい。

# やること

ダンプを採取して、VisualStudio(2019)で、落ちた時の状況を再現する。
ダンプの採取には、Microsoftのツール「Proc Dump」を使用する。

# 前提

今回ダンプのやり方を調べるきっかけとなった、アプリがクラッシュしてこまった時の状況は下記のようなものだった。

- 状況
    - アプリは、VisualStudio2019、C#、.netFramework4.0で作成している。
    - アプリで特定の操作を、かなりの回数行ったときにアプリがクラッシュする。(BSODではない)
    - 起きている例外は「AccessViolationException」だった。
    - 自分の(開発用の)PCでは起きない。
    - テスターの方のPCでのみ起きる。
    - 最初、起きた時はReleaseビルドだったが、試した結果Debugビルドでも起きる。
    - テスターの方のPCで、Debugビルドで再現させてもらうことは可能。

そのため、この状況に対してなにか情報を得られるようなProcDumpの使い方を調べる。
ProcDumpは、ここで調べたやり方以外にもいろいろなことができる様子。

### サンプルプログラム

調べるきっかけになったことは上記のようなことだが、実験的にアプリをクラッシュさせて、どういう動きをするのかを実験したかったので、実験の際は下記のサンプルプログラムでヒープの0番地にデータをコピーする、ということを行って、「**System.AccessViolationException**」でクラッシュするようにした。
このプログラムで、だいたい同じようなシチュエーションになっているはず。

```AccessViolationExceptionでクラッシュ.cs
using System.Runtime.InteropServices;
using System.Windows;

namespace WpfApp48
{
    public unsafe partial class MainWindow : Window
    {
        internal class NativeMethods
        {
            [DllImport("kernel32.dll")]
            public static extern unsafe void CopyMemory(void* dst, void* src, int size);
        }
        public MainWindow()
        {
            InitializeComponent();
        }
        private unsafe void Button_Click(object sender, RoutedEventArgs e)
        {
            byte[] realsrc = new byte[3] { 1, 2, 3};
            byte[] realdest = new byte[3];

            fixed (void* src = realsrc)
            fixed (void* dest = realdest)
            {
                // ここで無理やり0番地にデータをコピーするようにした
                NativeMethods.CopyMemory((void*)0, src, realsrc.Length);
            }
        }
    }
}
```



# やり方

## 何の例外で落ちたかを調べる

アプリが落ちた時に、何の例外で落ちたか？を確認する。


### 一般的な、クラッシュ原因の例外が何だったかの調べ方

イベントログに、該当のアプリがなぜクラッシュしたかが残るので、それを見て調べる。

- イベントログの開き方
    - [Windowsキー + R]を押す
    - eventvwrと入力し、Enterを押す
    - [Windowsログ]の中の[Application]を開く

上のサンプルプログラムがクラッシュした際には、下記のようなイベントログが残る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/acca66e1-5f27-7420-281f-ff6edae24f51.png)
これで、
「**System.AccessViolationException**」でクラッシュしたのだ、とわかる。


## ProcDumpを使ってダンプファイルを作成する

### ProcDumpをダウンロード

ProcDumpとは、指定したアプリが、指定した例外で落ちた時にダンプファイルを作成してくれるツール。
(色々な指定をして、ダンプを作成できる。詳しくは下記のページを参照)

下記のMicrosoftのページの「Downliad ProcDump」をクリックして、ProcDumpをダウンロードしてくる。
https://docs.microsoft.com/ja-jp/sysinternals/downloads/procdump

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/1fb5acfc-d607-e81b-70ca-4b2b93d14267.png)
※調べた時点では、バージョンはv9.0。


ダウンロードしたzipを解凍すると、下記のようなファイルがある。
ここにある「**procdump.exe**」がツール本体。

### ProcDump実行のためのパラメータを決める

こいつを実行する際に渡すパラメータで、何のアプリの、何の例外を取るか、というのを指定できる。
下記の欄に詳しく書かれている。
https://docs.microsoft.com/ja-jp/sysinternals/downloads/procdump#using-procdump
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/dc0aa2fe-3907-c419-ae33-356bcb2a83c5.png)


今回の、上の「前提」のところに書いた状況を鑑みて、ProcDumpには下記のパラメータを渡してダンプ作成させることにした。(動かす、クラッシュするアプリは実験用コードのものを使う)

- ProcDump実行のためのコマンド
    - `procdump -w -ma -e -f *Violation* WpfApp48.exe`

|パラメータ|説明|
|:--|:--|
|-w|対象のプロセスが起動するまで待つ。<br>指定しないと、procdump起動時「そんなプロセス起動してないよ」と言ってprocdump終わってまう|
|-e|例外が起きた時にダンプファイルを取るようにする。※|
|-f <例外の名前>|ダンプをとる例外を指定する。<br>指定の際は「*」も使える。上記のように指定すると、AccessViolationExceptionも対象になってくれる。|
|-ma|フルダンプ。<br>プロセスの仮想アドレス空間をすべて含む。※|

※**その他のパラメータについては、[こちら](https://docs.microsoft.com/ja-jp/sysinternals/downloads/procdump#using-procdump)を参照。**

**※`-e`パラメータについて**
`-e 1`などとして、1を付けるとファーストチャンス例外をとる、とか公式に説明があるが、要するに、
1を付けるとtry catchで自前の例外処理をしてる部分でもダンプをとる、ということをする。
(1がないと、try catchしてるとこではダンプしない)
今回は、trycatchできていない(アプリがクラッシュしてしまう)例外のときにダンプしたいので、1は付けない。

**※`-ma`フルダンプについて**
`-ma`のほかに、`-mm`や`-mp`がある様子だが、msdocsにある説明だけではそれらがどう異なるのかよくわからないので別途実験をしてみた。→[こちら参照。](https://qiita.com/tera1707/items/112b491a196acd6d24ec)

# ProcDumpを実行する

コマンドプロンプトから、上で決めたパラメータを渡してProcDumpを実行する。
※毎回コマンドプロンプトを開いてコマンド入力したくないので、バッチファイルを作って行う。

同じ階層に**godebug.bat**というファイルを作成し、下記のように書く。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/55fc0c78-6526-1d1e-f75c-d8a8c0604c36.png)

```godebug.bat
procdump -w -ma -e -f *Violation* WpfApp48.exe
pause
```

このバッチファイルをダブルクリックして実行すると、下記のような画面が開く。
※コマンドプロンプト中の`WpfApp48.exe`というのは、今回の実験のためのクラッシュさせるアプリの名前

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/1074bf01-91d0-8ea6-bba5-9800a29d16ef.png)

これで、対象のアプリが動き出すのを待っている状態になった。

## 対象アプリを起動し、例外が起きる操作を行う

対象のアプリを起動すると、コマンドプロンプトのの表示が下記のように変化する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/855d8b0e-7d8d-5c85-b432-b6570b1e4497.png)
※対象のアプリのexeパスや、指定した例外の名前(*Violation*)、出力するダンプの名前などいろんな情報が載っている。

さらに、その状態で指定の例外が発生すると、下記のような表示になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/ee760ce6-5616-13c4-0969-1b00eca650a6.png)

上記表示がでると、例外が発生したのでダンプを出力した状態。
ProcDump.exeのフォルダに、ダンプファイルが出力されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/3935a5b2-1f08-5f88-9437-71476565a89e.png)

## VisualStudio2019でダンプのデバッグを行う

### 開く

作成されたダンプファイルをVisualStudio2019で開く。
VisualStudio2019のメニューから、
- [ファイル]
- [開く]
- [ファイル] 
- →作成されたダンプファイルを選択する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/d5c5edee-cbeb-2d2d-944d-154cd77f91bc.png)
※.dmpファイルがVisualStudio2019に関連づけられているのなら、ダブルクリックするだけでもOK。

ダンプファイルを開くと、下記のような画面になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/0f2f6a3f-d808-09f5-eaa5-d9a6877198df.png)

ここで、右上のほうの「マネージドのみでデバッグ」をクリックする。
もし開発を行ったPCで、ソースコードが開発(コードを書いてビルドした)ときと同じパスに置いてあるなら、下記のように、クラッシュしたときのコード上の位置が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/5a4e3a09-3b16-ebf2-ad80-2b96c911d900.png)

※もし開発用のPCではなく、ソースコードがビルド時のパスに無い場合は、下記のような画面になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/3fe5db6b-5faf-3161-3741-60df4ac551a7.png)

これは、ダンプファイルの中に実装時のコードのパスや、実行時のexeのパスの情報を覚えていて、そのパスにコードやexeが見つからないから出ているエラーっぽい。そのあたり、別ページで実験してみたので[こちら参照](https://qiita.com/tera1707/items/112b491a196acd6d24ec)。

### デバッグする

これで、VisualStudioでデバッグ実行中にbreakを貼って、そこで止めたときと似たことができる。
つまり、クラッシュしたときに、プロパティにどういう値が入っていたのか、とか、スタックフレームを遡ったりできる。(さかのぼった先で、そこのプロパティがどういう値なのか、も見ることができる)

こんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/e289fda4-d242-d870-0ec2-5d80b4c67497.png)
ただし、デバッグ実行の時のように、F5を押して続きを実行するということはできない。

### 注意点

今回はDebugビルドのexeを実行して、そのダンプファイルを作成し解析したが、Releaseビルドだと、できることに制限がある。

今回試した限りでは、最適化がかかっているために、クラッシュ箇所で止まった時のプロパティの値が見れないものがあった。(メソッド内のローカル変数は見れなかった。クラスのプロパティは見れるっぽい)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/7dc2692e-7a12-a7bc-eda7-381417fceb1a.png)

## 以上

以上が、ProcDunpを使ったダンプファイルの取得とVisualStudio2019での解析のやり方。
アンマネージドのコードで同じことをしたいときなどはまた別の手順があるかもしれないが、それはそういう場面に出会ったらまた調べる。

# 参照

procdumpの仕様（コマンドのパラメータなど）
https://docs.microsoft.com/ja-jp/sysinternals/downloads/procdump

VisualStudioでのダンプについて
https://docs.microsoft.com/ja-jp/visualstudio/debugger/using-dump-files?view=vs-2019

-mm、-ma、-mpの違い
https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2011/december/sysinternals-procdump-v4-0-writing-a-plug-in-for-sysinternals-procdump-v4-0#%E7%94%A8%E8%AA%9E