Windows上でアプリケーションクラッシュ時にミニダンプを自動生成する

アプリケーションが予期せぬクラッシュを起こした際、その状態を再現可能な形で保存する手段として、ミニダンプ(MiniDump)は極めて有効です。特にC++開発において、デバッグ情報が失われると原因の特定が困難になるため、クラッシュ直後のメモリ状態を自動記録する仕組みを導入することは、リリース環境での安定性向上に不可欠です。

以下に、Windows APIを用いて例外発生時に自動的にミニダンプファイルを生成する実装を示します。この実装は、SetUnhandledExceptionFilterを用いて未処理例外を捕捉し、dbghelp.libMiniDumpWriteDump関数でダンプファイルを書き出す仕組みです。

ミニダンプ生成のためのヘッダファイル(minidump.h)

#include <windows.h>
#include <dbghelp.h>
#include <wchar.h>
#include <stdlib.h>

#pragma comment(lib, "dbghelp.lib")

bool IsDataSectionNeeded(const WCHAR* pModuleName)
{
    if (pModuleName == nullptr)
        return false;

    WCHAR moduleName[_MAX_FNAME] = { 0 };
    _wsplitpath(pModuleName, nullptr, nullptr, moduleName, nullptr);

    // システムモジュールのデータセクションを含めるか判定
    if (_wcsicmp(moduleName, L"ntdll") == 0)
        return true;
    return false;
}

BOOL CALLBACK MiniDumpCallback(
    PVOID                            pParam,
    const PMINIDUMP_CALLBACK_INPUT   pInput,
    PMINIDUMP_CALLBACK_OUTPUT        pOutput
)
{
    if (!pInput || !pOutput)
        return FALSE;

    switch (pInput->CallbackType)
    {
    case ModuleCallback:
        if (pOutput->ModuleWriteFlags & ModuleWriteDataSeg)
        {
            if (!IsDataSectionNeeded(pInput->Module.FullPath))
                pOutput->ModuleWriteFlags &= ~ModuleWriteDataSeg;
        }
        // フォールスルー
    case IncludeModuleCallback:
    case IncludeThreadCallback:
    case ThreadCallback:
    case ThreadExCallback:
        return TRUE;
    default:
        break;
    }
    return FALSE;
}

void GenerateMiniDump(EXCEPTION_POINTERS* pExceptionInfo, const char* dumpFilePath)
{
    HANDLE hFile = CreateFileA(
        dumpFilePath,
        GENERIC_READ | GENERIC_WRITE,
        0,
        nullptr,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        nullptr
    );

    if (hFile == INVALID_HANDLE_VALUE)
        return;

    MINIDUMP_EXCEPTION_INFORMATION mdei = {};
    mdei.ThreadId = GetCurrentThreadId();
    mdei.ExceptionPointers = pExceptionInfo;
    mdei.ClientPointers = FALSE;

    MINIDUMP_CALLBACK_INFORMATION mci = {};
    mci.CallbackRoutine = MiniDumpCallback;
    mci.CallbackParam = nullptr;

    MINIDUMP_TYPE dumpType = static_cast<MINIDUMP_TYPE>(
        MiniDumpWithPrivateReadWriteMemory |
        MiniDumpWithHandleData |
        MiniDumpWithUnloadedModules |
        0x00000800 | // MiniDumpWithIndirectlyReferencedMemory
        0x00001000   // MiniDumpScanMemory
    );

    MiniDumpWriteDump(
        GetCurrentProcess(),
        GetCurrentProcessId(),
        hFile,
        dumpType,
        pExceptionInfo ? &mdei : nullptr,
        nullptr,
        &mci
    );

    CloseHandle(hFile);
}

メインアプリケーション(main.cpp)

#include "minidump.h"
#include <iostream>
#include <direct.h> // _getcwd

LONG WINAPI UnhandledExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo)
{
    char buffer[256] = {};
    _getcwd(buffer, sizeof(buffer));
    strcat_s(buffer, sizeof(buffer), "\\crash_dump.dmp");

    GenerateMiniDump(pExceptionInfo, buffer);
    return EXCEPTION_EXECUTE_HANDLER;
}

int main(int argc, char* argv[])
{
    SetUnhandledExceptionFilter(UnhandledExceptionFilter);

    // クラッシュを発生させるテストコード(実際の運用では削除)
    // int* p = nullptr;
    // *p = 42; // ここでのアクセス違反がダンプをトリガー

    std::cout << "アプリケーション起動中... クラッシュを発生させるにはコメントを解除してください。\n";
    system("pause");

    return 0;
}

この実装では、GenerateMiniDump関数が例外情報をもとにダンプファイルを生成し、UnhandledExceptionFilterがシステムレベルの未処理例外を捕捉してその関数を呼び出します。ダンプファイルはアプリケーションの実行ディレクトリにcrash_dump.dmpとして保存されます。

ダンプファイルは、Visual StudioやWinDbgなどで開くことで、クラッシュ時のスタックトレース、変数値、スレッド状態などを詳細に分析できます。特に、リリースビルドでもデバッグ情報(PDB)が同一ディレクトリに存在すれば、ソースコードレベルのデバッグが可能になります。

注意点として、MiniDumpWithPrivateReadWriteMemoryMiniDumpWithUnloadedModulesなどのフラグは、ダンプファイルのサイズを大きくするため、運用環境では必要最小限の情報のみを含めるよう調整することを推奨します。

タグ: Windows MiniDump dbghelp C++ ExceptionHandling

5月17日 23:29 投稿