📘

Releaseビルドのアプリがクラッシュしたときに原因ソース行を特定する方法

2023/12/03に公開

若干タイトル詐欺です。
事前に設定しておけば、あとからソース行を特定できます。

想定環境

  • OS: Windows11
  • Compiler: MSVC
  • ビルド設定ツール: CMake
  • デスクトップアプリ開発
  • 管理者権限を取得可能

要約

  1. レジストリ設定でdmpファイル(プロセスダンプ)出力をON
  2. ビルド設定でpdbファイル(デバッグシンボル)出力をON
  3. Visual Studioにdmpファイルを読み込ませる
  4. Releaseビルドでもクラッシュ箇所がソース行レベルで特定できる

プロセスダンプの出力設定

参考:
https://jpwinsup.github.io/blog/2021/02/15/Performance/Hang_BSOD/AppDump/

Windows検索窓にregと入力するとレジストリエディターが表示されるので起動する。

Ctrl+Fで検索ボックスを開き、Windows Error Reportingについて検索する。
検索対象はキーのみ、また完全一致のみを条件とする。

\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reportingという項目にマッチするはず。
別の項目にマッチした場合はF3を押すことで次の検索結果に移動できる。

Windows Error Reportingを右クリック→新規→キーからLocalDumpsを作成する。

Dumpファイルの出力先を設定

LocalDumpsを右クリック→新規→展開可能な文字列を作成する。

作成した値の名前をDumpFolder、値のデータをC:\Dumps(任意)に設定する。

Dumpファイルに出力する内容を設定

LocalDumpsを右クリック→新規→DWORDを作成する。

作成した値の名前をDumpType、値のデータを2に設定する。
2はフルダンプを意味しており、出来得る限りすべての情報をDumpファイルに出力する。

Dumpファイル数の最大数を設定

LocalDumpsを右クリック→新規→DWORDを作成する。

作成した値の名前をDumpCount、値のデータを10(1以上)に設定する。
Dumpファイルは1つが数GBになることも多いので、ストレージ容量に応じて値を変更することを推奨。

最終確認

下図のような値になっているか確認する。

ビルド設定でPDBファイル出力をONにする

参考:
https://stackoverflow.com/questions/28178978/how-to-generate-pdb-files-for-release-build-with-cmake-flags

PDBファイルはデバッグシンボルと呼ばれ、バイナリとソースコードのファイル名や行番号やシンボル情報 (変数名等) の対応関係を保持している。
デバッグ実行時、PDBファイルを参照してプログラムのステップ実行を可能にしている。

Relaseビルドでは通常PDBファイルは出力されないが、CMakeスクリプトに以下の設定を記述することで出力可能。

set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi" CACHE STRING "" FORCE)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF" CACHE STRING "" FORCE)
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF" CACHE STRING "" FORCE)
set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF" CACHE STRING "" FORCE)

サンプルプログラムを用意

Qtで作成。
ボタンを押したらNullアクセスでクラッシュする。

#include <QApplication>
#include <QPushButton>

int main(int argc, char* argv[])
{
    QApplication a(argc, argv);

    auto button = new QPushButton("Push");

    QObject::connect(button, &QPushButton::clicked, []() {
        QWidget* widget{nullptr};
        widget->size();
    });

    button->show();

    return a.exec();
}

ビルドする。
出力フォルダにPDBファイルが生成されていることを確認する。

プログラムを実行し、ボタンを押してクラッシュさせる。

レジストリエディターで設定したC:\DumpsフォルダにDumpファイルが生成されている。

Visual StudioでDumpファイル解析

Visual Studioを起動し、「コードなしで続行」をクリックする。

ファイル→開く→ファイル を選択し、Dumpファイルを開く。

Dumpファイルの概要が表示される。
具体的にソースコードのどの行でクラッシュしたかを表示するため、シンボルパスの設定をクリックする。

今回はQtのバイナリが配置されているフォルダと、先程ビルドしたプログラムとPDBファイルがあるフォルダを追加する。

ネイティブのみでデバッグをクリックする。

プログラムがクラッシュした箇所がソースコードの行レベルで表示される。

感想

Releaseビルドで実行中に突然クラッシュしてもクラッシュ箇所を特定できるので便利。

GitHubで編集を提案

Discussion