windows c++程序在崩溃时自动生成dump

作者:刘树伟

网上找到的windows在进程崩溃时自动抓dump的代码,几乎全部是由进程自己调用实现的,这个其实在一些情况下是有问题的。

原因1:程序已经崩溃,再创建dump,可能失败。
原因2:MiniDumpWriteDump在进行写dump文件的时候,首先挂起其它所有线程,然后再写dump。如果另一个线程中正在进行堆分配、释放、重新分配等操作。因为默认情况下,堆是同步的,分配堆内存时,将请求堆同步对象。如果这个时候调用MiniDumpWriteDump,MiniDumpWriteDump等待的同步对象被堆内存分配线程占用,而进行堆分配的线程已被挂起,这将导致调用MiniDumpWriteDump也被卡住。
参考:https://stackoverflow.com/questions/62632898/minidumpwritedump-hangs

MSDN也建议不要使用崩溃的那个进程创建dump,而是建议使用一个单独的进程来创建dump。本人在实践中,确实碰到过MiniDumpWriteDump被std::vector::resize调用和std::string::clear卡死的情况。
参考:https://www.it1352.com/457981.html


有时,使用MiniDumpWriteDump抓dump时,你还可能碰到程序在持续生成dump,这可能是由于你的代码中调用了OutputDebugString,OutputDebugString内部是通过抛出一个异常来实现的。所以也可以被你写的未处理异常回调捕获到。基于此,在未处理异常回调里要过滤掉值为DBG_PRINTEXCEPTION_C(0x40010006)和DBG_PRINTEXCEPTION_WIDE_C(0x4001000A)的异常。
参考:
https://www.cnblogs.com/yilang/p/12038397.html
https://www.cnblogs.com/yilang/p/12036228.html


我们通过windbg的
!analyze -v
分析dump的时候,如果看到异常码为0x40010006(DBG_PRINTEXCEPTION_C)或0x4001000A(DBG_PRINTEXCEPTION_WIDE_C),那么就不用分析了,可直接认定是由于调用OutputDebugString导致的。

另外:在本进程或单独进程抓dump时,MINIDUMP_EXCEPTION_INFORMATION::ClientPointers参考需要注意一下。


下面两篇是翻译自http://www.debuginfo.com/articles/effminidumps.html,标题为Effective minidump
https://blog.csdn.net/pkrobbie/article/details/6636310
https://blog.csdn.net/pkrobbie/article/details/6641081
这是两篇不错的文章。


公共库代码:
cautocriticalsection.h:
#pragma once

class CAutoCSInit
{
public:
    CAutoCSInit()
    {
        InitializeCriticalSection(&m_cs);
    }
    ~CAutoCSInit()
    {
        DeleteCriticalSection(&m_cs);
    }

public:
    CRITICAL_SECTION m_cs;
};

class CAutoCriticalSection
{
public:
    CAutoCriticalSection(CRITICAL_SECTION *pcs)
        : m_pcs(pcs)
    {
        EnterCriticalSection(m_pcs);
    }

    ~CAutoCriticalSection()
    {
        LeaveCriticalSection(m_pcs);
    }

private:
    CRITICAL_SECTION *m_pcs;
};


WriteMiniDumpException.h:
#pragma once

// 需要被抓dump的进程,包含此头文件
// 在进程入口调用下面两行代码:
//    WriteMiniDump::InitWatchDogPath(_T("C:\\YourWatchDog.exe"), _T("YourCustomFileMappingName"));
//    AddVectoredExceptionHandler(TRUE, WriteMiniDump::WriteMiniDumpFilter);

namespace WriteMiniDump
{

    // 每个变量定义两份,在崩溃后生成dump前,通过校验两份变量的值是否相同来判断它们是否被意外覆盖
    extern TCHAR g_szWatchDogPath1[MAX_PATH];
    extern TCHAR g_szWatchDogPath2[MAX_PATH];
    extern TCHAR g_szFileMappingName1[MAX_PATH];
    extern TCHAR g_szFileMappingName2[MAX_PATH];
    extern DWORD g_dwPID1;
    extern DWORD g_dwPID2;

    // 注:不同的App如果都需要使用本代码来抓dump,要保证lpszFileMappingName的唯一性
    int InitWatchDogPath(LPCTSTR lpszDogName, LPCTSTR lpszFileMappingName);

    LONG WINAPI WriteMiniDumpFilter(EXCEPTION_POINTERS *pExceptionInfo);

}

WriteMiniDumpException.cpp:
// write by Liushuwei
// 2020-11-12

#include "stdafx.h"
#include "WriteMiniDumpException.h"
#include "../CAutoCriticalSection/cautocriticalsection.h"
#include <strsafe.h>


// 每个变量定义两份,在崩溃后生成dump前,通过校验两份变量的值是否相同来判断它们是否被意外覆盖
TCHAR WriteMiniDump::g_szWatchDogPath1[MAX_PATH] = { 0 };
TCHAR WriteMiniDump::g_szWatchDogPath2[MAX_PATH] = { 0 };
TCHAR WriteMiniDump::g_szFileMappingName1[MAX_PATH] = { 0 };
TCHAR WriteMiniDump::g_szFileMappingName2[MAX_PATH] = { 0 };
DWORD WriteMiniDump::g_dwPID1 = 0;
DWORD WriteMiniDump::g_dwPID2 = 0;

int WriteMiniDump::InitWatchDogPath(LPCTSTR lpszDogName, LPCTSTR lpszFileMappingName)
{
    if (NULL == lpszDogName || NULL == lpszFileMappingName)
    {
        _ASSERT(FALSE);
        return -1;
    }

    // 把PID和看门狗路径做个备份。等崩溃的时候,验证一下path和pid这两个值
    // 在崩溃后没有被篡改(程序运行中的一些越界面问题,可能会导致其它数据被覆盖)
    StringCchCopy(g_szWatchDogPath1, MAX_PATH, lpszDogName);
    StringCchCopy(g_szWatchDogPath2, MAX_PATH, g_szWatchDogPath1);

    g_dwPID1 = g_dwPID2 = GetCurrentProcessId();

    _ASSERT(_tcslen(lpszFileMappingName) > 0);
    StringCchCopy(g_szFileMappingName1, MAX_PATH, lpszFileMappingName);
    StringCchCopy(g_szFileMappingName2, MAX_PATH, lpszFileMappingName);

    return 0;
}

int CallDogToWriteDump()
{
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    STARTUPINFO si;
    ZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    GetStartupInfo(&si);
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

    PROCESS_INFORMATION pi;
    if (!CreateProcess(NULL, WriteMiniDump::g_szWatchDogPath1,
            NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi))
    {
        return -2;
    }

    // 等待看门狗进程写dump,超时10分钟
    WaitForSingleObject(pi.hProcess, 10 * 60 * 1000);

    return 0;
}

CAutoCSInit g_cs;

LONG WINAPI WriteMiniDump::WriteMiniDumpFilter(EXCEPTION_POINTERS *pExceptionInfo)
{
    CAutoCriticalSection _cs(&g_cs.m_cs);

    // pExceptionInfo中包含异常代码,把DBG_PRINTEXCEPTION_C、
    // DBG_PRINTEXCEPTION_WIDE_C之类的异常过滤掉。这两个是OutputDebugString内部抛出的异常。
    // OutputDebugString的实现原理就是抛出这个异常,然后调试器捕获异常后显示字符串。
    if (DBG_PRINTEXCEPTION_C == pExceptionInfo->ExceptionRecord->ExceptionCode
        || /*DBG_PRINTEXCEPTION_WIDE_C*/0x4001000A == pExceptionInfo->ExceptionRecord->ExceptionCode)
    {
        return EXCEPTION_CONTINUE_SEARCH;
    }

    // 验证看门狗路径和PID等变量的值是否被篡改
    if (StrCmpN(g_szWatchDogPath1, g_szWatchDogPath2, MAX_PATH) != 0
        || StrCmpN(g_szFileMappingName1, g_szFileMappingName2, MAX_PATH) != 0
        || g_dwPID1 != g_dwPID2)
    {
        return EXCEPTION_EXECUTE_HANDLER;
    }

    LONG lr = EXCEPTION_EXECUTE_HANDLER;

    //
    // 把看门狗抓dump时需要的数据放到共享内存中。
    //
    HANDLE hFileMap = NULL;
    void *pView = NULL;
    do
    {
        // 注:不同的App如果都需要使用本代码来抓dump,要传递不同的g_szFileMappingName1
        hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
                PAGE_READWRITE, 0, 1024, g_szFileMappingName1);
        if (NULL == hFileMap)
        {
            break;
        }

        pView = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
        if (NULL == pView)
        {
            break;
        }

        // 给pView赋值
        memset(pView, 0, 1024);
        DWORD dwTID = GetCurrentThreadId();
        memcpy(pView, &g_dwPID1, sizeof(DWORD));
        memcpy((char *)pView + sizeof(DWORD), &dwTID, sizeof(DWORD));
        memcpy((char *)pView + sizeof(DWORD) * 2, &pExceptionInfo, sizeof(void *));
    }
    while (false);

    //
    // 通知看门狗去抓本进程的dump
    //
    CallDogToWriteDump();

    // 取消映射
    UnmapViewOfFile(pView);

    if (NULL != hFileMap)
    {
        CloseHandle(hFileMap);
        hFileMap = NULL;
    }

    return lr;
}

WriteMiniDumpWG.h:
#pragma once

// 看门狗进程包含本头文件,调用WriteMiniDump来写dump
namespace WriteMiniDump
{
    LONG WriteMiniDump(LPCTSTR lpszDumpName, LPCTSTR lpszFileMappingName);
}

WriteMiniDumpWG.cpp:
// write by Liushuwei
// 2020-11-12

#include "stdafx.h"
#pragma warning(disable: 4091)
#include "WriteMiniDumpWG.h"
#include <Dbghelp.h>
#include <ShlObj.h>
#include <Shlwapi.h>
#include <atlstr.h>
#include "../CAutoCriticalSection/cautocriticalsection.h"

typedef BOOL(WINAPI *_MiniDumpWriteDump)(
    _In_ HANDLE hProcess,
    _In_ DWORD ProcessId,
    _In_ HANDLE hFile,
    _In_ MINIDUMP_TYPE DumpType,
    _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
    _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
    _In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

CAutoCSInit g_cs;
LONG WriteMiniDump::WriteMiniDump(LPCTSTR lpszDumpName, LPCTSTR lpszFileMappingName)
{
    // 接受常驻进程传过来的数据
    DWORD dwPID = 0;
    DWORD dwTID = 0;
    EXCEPTION_POINTERS *pExceptionInfo = NULL;

    // 接受并解析常驻进程传过来的数据是否成功
    BOOL bParseOK = FALSE;

    //
    // 通过内存映射文件,拿到引发异常的常驻进程的信息。
    //
    HANDLE hFileMap = NULL;
    VOID *pView = NULL;
    do
    {
        hFileMap = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE,
                lpszFileMappingName);
        if (NULL == hFileMap)
        {
            break;
        }

        pView = MapViewOfFile(hFileMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
        if (NULL == pView)
        {
            break;
        }

        // 解析pView中的数据
        memcpy(&dwPID, pView, sizeof(DWORD));
        memcpy(&dwTID, (char *)pView + sizeof(DWORD), sizeof(DWORD));
        memcpy(&pExceptionInfo, (char *)pView + sizeof(DWORD) * 2, sizeof(void *));

        bParseOK = TRUE;
    }
    while (false);

    //
    // 写dump
    //
    LONG lr = -1;
    HMODULE hDll = NULL;
    HANDLE hFile = INVALID_HANDLE_VALUE;

    if (bParseOK)
    {
        do
        {
            hDll = ::LoadLibrary(_T("DbgHelp.dll"));
            if (NULL == hDll)
            {
                break;
            }

            _MiniDumpWriteDump fnMiniDumpWriteDump = (_MiniDumpWriteDump)::GetProcAddress(hDll,
                    "MiniDumpWriteDump");
            if (NULL == fnMiniDumpWriteDump)
            {
                break;
            }

            TCHAR szPath[MAX_PATH] = { 0 };
            SHGetSpecialFolderPath(NULL, szPath, CSIDL_APPDATA, TRUE);
            PathAppend(szPath, _T("ssx"));
            SHCreateDirectoryEx(NULL, szPath, NULL);
            SYSTEMTIME st;
            GetLocalTime(&st);//当地时间
            CString strName;
            strName.Format(_T("%s_%04d%02d%02d-%02d%02d%02d-%ld.%ld.dmp"),
                lpszDumpName,
                st.wYear, st.wMonth, st.wDay,
                st.wHour, st.wMinute, st.wSecond,
                dwPID, dwTID);
            PathAppend(szPath, strName);
            hFile = ::CreateFile(szPath,
                    GENERIC_READ | GENERIC_WRITE,
                    FILE_SHARE_WRITE | FILE_SHARE_READ,
                    NULL,
                    CREATE_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
            if (INVALID_HANDLE_VALUE == hFile)
            {
                break;
            }

            MINIDUMP_EXCEPTION_INFORMATION mei;
            mei.ThreadId = dwTID;
            mei.ExceptionPointers = pExceptionInfo;
            // 如果内存位于调用程序的地址空间中,则设置为FALSE(调试器进程)
            // 如果内存驻留在要调试的进程(调试器的目标进程)中,则设置为TRUE。
            mei.ClientPointers = TRUE;

            HANDLE hDumpProcess = OpenProcess(
                    PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE,
                    FALSE, dwPID);
            if (NULL == hDumpProcess)
            {
                break;
            }

            // DbgHelp.dll中的函数都是非线程安全的。如果多个线程同时崩溃需要写dump,就会有问题,
            // 所以需要做下线程同步。
            BOOL bOk = FALSE;
            {
                CAutoCriticalSection _cs(&g_cs.m_cs);
                bOk = fnMiniDumpWriteDump(
                        hDumpProcess,
                        dwPID,
                        hFile,
                        MiniDumpWithFullMemory,
                        (NULL == pExceptionInfo) ? NULL : &mei,
                        NULL,
                        NULL);
            }

            if (NULL != hDumpProcess)
            {
                CloseHandle(hDumpProcess);
                hDumpProcess = NULL;
            }

            if (bOk)
            {
                lr = 0;
            }
        }
        while (0);
    }

    //
    // 清理释放
    //
    if (NULL != NULL)
    {
        UnmapViewOfFile(pView);
        pView = NULL;
    }
    if (INVALID_HANDLE_VALUE != hFile)
    {
        ::CloseHandle(hFile);
        hFile = INVALID_HANDLE_VALUE;
    }
    if (NULL != hDll)
    {
        FreeLibrary(hDll);
        hDll = NULL;
    }
    if (NULL != hFileMap)
    {
        CloseHandle(hFileMap);
        hFileMap = NULL;
    }

    return lr;
}


需要被抓dump的进程:
#include "stdafx.h"
#include "WriteMiniDumpException.h"

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPTSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    // 初始化看门狗路径,当异常发生时,尽可能少执行代码,所以这些初始化操作提前进行。
    TCHAR szDogPath[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, szDogPath, MAX_PATH);
    PathRemoveFileSpec(szDogPath);
    PathAppend(szDogPath, _T("ResidentWG.exe"));
    WriteMiniDump::InitWatchDogPath(szDogPath, _T("ResidentPluginDumpData"));

    // 使用AddVectoredExceptionHandler可以在debug状态下生成dump,而
    // 使用SetUnhandledExceptionFilter只能在运行时生成。
    //SetUnhandledExceptionFilter(WriteMiniDumpFilter);
    AddVectoredExceptionHandler(TRUE, WriteMiniDump::WriteMiniDumpFilter);

    // 可引发异常的测试代码
#ifdef _DEBUG
    char *p = NULL;
    *p = 1;
#endif
}


看门狗多代码:
#include "stdafx.h"
#pragma warning (disable: 4091)
#include "WriteMiniDumpWG.h"

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    // 生成完dump就退出本进程,常驻进程还在等待本进程退出后做清理工作。
    WriteMiniDump::WriteMiniDump(_T("ResidentPlug"), _T("ResidentPluginDumpData"));
    return 0;
}
 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值