作者:刘树伟

网上找到的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;
}

windows c++程序在崩溃时自动生成dump相关推荐

  1. 编写的windows程序,崩溃时产生crash dump文件的办法

    一.引言 dump文件是C++程序发生异常时,保存当时程序运行状态的文件,是调试异常程序重要的方法,所以程序崩溃时,除了日志文件,dump文件便成了我们查找错误的最后一根救命的稻草.windows程序 ...

  2. 如何在.NET程序崩溃时自动创建Dump?

  3. 关于Windows程序崩溃(Crash)以及生成dump文件的探究

    文章目录 关于Windows程序崩溃(Crash)以及生成dump文件的探究 什么是崩溃(Crash),崩溃(Crash)的现象 崩溃(Crash)的原因是什么 如何消除崩溃时出现的异常信息对话框 如 ...

  4. 让程序在崩溃时体面的退出之Dump文件

    在我的那篇< 让程序在崩溃时体面的退出之CallStack>中提供了一个在程序崩溃时得到CallStack的方法.可是要想得到CallStack,必须有pdb文件的支持.但是一般情况下,发 ...

  5. java 生成dump文件_程序自动生成Dump文件

    前言:通过drwtsn32.NTSD.CDB等调试工具生成Dump文件,drwtsn32存在的缺点虽然NTSD.CDB可以完全解决,但并不是所有的操作系统中都安装了NTSD.CDB等调试工具.了解了m ...

  6. IIS崩溃时自动抓取Dump

    原文:IIS崩溃时自动抓取Dump 背景:在客户现场,IIS有时会崩溃,开发环境没法重现这个bug,唯有抓取IIS的崩溃是的Dump文件分析. IIS崩溃时自动抓取Dump,需要满足下面几个条件 1. ...

  7. java gc时自动收dump_Full GC分析:设置Java VM参数实现在Full GC前后自动生成Dump

    本文讲解了如何设置JavaVM参数实现在Full GC前后自动生成Dump.共有三个VM参数需要设置: HeapDumpBeforeFullGC 实现在Full GC前dump. HeapDumpBe ...

  8. 让程序在崩溃时体面的退出之总结

    终于把<让程序在崩溃时体面的退出>这个系列的6篇文章全部发表出来了.         这6篇文章分别是:         < 让程序在崩溃时体面的退出之Unhandled Excep ...

  9. 安装Ruby、Sass在WebStrom添加Watcher实现编辑scss文件时自动生成.map和压缩后的.css文件...

    前言 这段时间一直在看Bootstrap,V3官方直接提供了Less版本的源码,就先将Less学完了,很简单的语法,学习写Demo都是在Webstorm里写的,配置了Watcher自动编译(详见< ...

最新文章

  1. 【Flutter】Dart 面向对象 ( 抽象类 | 抽象方法 )
  2. 使用SAP C4C的OData服务的deep insert操作创建Lead
  3. identifier __ldg is undefined
  4. MaxCompute规格详解 让您花更低的成本获得更高的业务价值
  5. mysql中文乱码解决_Stata 中文乱码顽疾解决方法
  6. Java面试之线程池详细
  7. 你和一位姑娘正在恋爱,很想知道她是不是喜欢自己
  8. 【软件周刊第 24 期】Ubuntu 17.04 正式发布,不再支持 32 位 PowerPC(PPC)架构
  9. vue-style,vue-style-scoped
  10. Tomcat环境设置
  11. Vue.js 关于router传参那点事儿
  12. 入门机器学习(西瓜书+南瓜书)模型选择与评估总结(python代码实现)
  13. D3D12渲染技术概述
  14. 华为数通笔记-QOS
  15. linux虚拟机安装教程
  16. Ubuntu Tty (字符终端) 显示中文,和字体大小设置
  17. 过去式-ed的发音规则
  18. 两张表之间进行数据库查询时的聚合函数用法
  19. Upgraded Edition
  20. 上网行为管理软件的主要功能、并简要说明上网行为管理类的软件的原理。

热门文章

  1. 2019年华为网络精英挑战赛-服务器
  2. 用vue代码实现随机产生5道数学题
  3. 风光互补路灯实验,风光互补发电系统,智能型路灯,QY-T12
  4. 文件上传与下载的场景梳理
  5. 关于ASoC中的aux设备及prefix(基于MTK mt6799 平台)
  6. 微信小程序WebSocket接口以及在小程序中的使用。
  7. 苹果魔术鼠标不工作的修复办法
  8. 解决 Uncaught TypeError: elem.getClientRects is not a function
  9. QQ登录界面模仿和事件监听(下)
  10. proe PTC Pro_Engineer wildfire4.0 M040野火版 DVD32位