Windows下生成dump文件的三种方式


前言

提示:本文为描述windows平台下的dump文件生成:

windows程序当遇到异常,没有try-catch或者try-catch也无法捕获到的异常时,程序就会自动退出。
windows系统默认是不产生程序dmp文件的。dump文件是C++程序发生异常时,保存当时程序运行状态的文件。 是调试异常程序重要的方法。


一、什么是dump文件?

概述

简单来说
就是程序崩溃时,产生的文件,它记录着程序崩溃时侯的一些信息、片段。

复杂来说
Dump文件是内存转储文件,也称为内存快照文件(内存镜像)。它是一个进程或系统在某一给定的时间的快照。比如在进程崩溃时或则进程有其他问题时(比如蓝屏),甚至是任何时候,我们都可以通过工具将系统或某进程的内存备份出来供调试分析用。dump文件中包含了程序运行的模块信息、线程信息、堆栈调用信息、异常信息等数据,方便系统技术人员进行错误排查。

dump分类

分类

Windows下Dump文件分为两大类:内核模式Dump和用户模式Dump。

内核模式Dump

是操作系统创建的崩溃转储,最经典的就是系统蓝屏,这时候会自动创建内核模式的Dump。
如果你抓取整个系统的内存dump文件, 那么你抓取的是内核态的dump文件。

用户模式Dump

如果你抓一个进程的dump文件, 那么你抓取的是用户态的dump文件。
进一步可以分为完整Dump(Full Dump)和迷你Dump(Minidump)。

Full Dump:包含了某个进程完整的地址空间数据,以及许多用于调试的信息
Minidump:随着Windows XP,微软发布了一组新的被称为“minidump”的崩溃转存技术。Minidump很容易定制。按照最常用的配置,一个minidump只包括了最必要的信息,用于恢复故障进程的所有线程的调用堆栈,以及查看故障时刻局部变量的值。这样的dump文件(.dmp)通常很小(只有几KB)。所以,很容易通过电子方式发送给软件开发人员。一旦需要,minidump甚至可以包含比原来的crash dump更多的信息。minidump可以定制,给我们带来了一个问题,保存多少应用程序状态信息才能既保证调试有效,又能够尽量保证minidump文件尽可能小?尽管调试简单的异常访问只需要调用堆栈和局部变量的信息,但是解决更复杂的问题需要更多的信息。例如,我们可能需要查看全局变量的值、检查堆的完整性和分析进程虚拟内存的布局。同时,可执行程序的代码段往往是多余的,开发用的机器上可以很容易找到这些执行程序。

二、dump生成方式

实现方法

方法一:修改注册表

使用管理员权限,执行一下脚本内容,运行后: 任何程序崩溃都会在C:\xxx 产生dmp文件(full dmp)。
[bat脚本示例]:

@echo 启用Dump

reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpFolder /t REG_EXPAND_SZ /d "C:\xxx" /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpCount /t REG_DWORD /d 10 /f

@echo Dump已经启用
参数说明

DumpFolder

dump文件生成路径 eg:C:\xxx

DumpType

0 = Create a custom dump //自定义dump 
1 = Mini dump  //mini核心dump文件
2 = Full dum  //所有dump文件,产生的文件较大

DumpCount

默认:10

方法二:生动创建转储文件

当windows应用程序出现无响应时,可以打开任务管理器,找到无响应的进程,右键选择创建转储文件即可,即生成崩溃对应的dmp文件,该文件一般位于 C:\Users\happy\AppData\Local\Temp目录(显然这需要用户自己动手,不合适)

方法三:通过代码设置异常回调函数

使用windows系统api,程序中加入存储Dump的代码。通过SetUnhandledExceptionFilter
设置捕获dump的入口,然后通过MiniDumpWriteDump生成dump文件

设计一个记录dump功能的类

代码如下(示例):

dumpFileManager.h
#ifndef __DUMPFILEMANAGER_H__
#define __DUMPFILEMANAGER_H__

#include <string>
//#include "safeQuit.h" //自定义的事件不需要可以不加
#include <Windows.h>

namespace koal {
namespace ztaClient {
namespace dump {

class DumpFileManager {
   public:
    static DumpFileManager* Instance() {
        static DumpFileManager ins;
        return &ins;
    }
   public:
    DumpFileManager();
    ~DumpFileManager();
   public:
    // 设置dump文件路径名
    void* setDumpFilePath(const char* rootPath, const char* pAppName);

    // 注册异常处理回调函数
    void installDumpCollect(void* pInstallEvent = NULL);

    // 注册程序安全退出回调函数
    void installProcessQuit();
    
   public:
    // 判断是否为需要的数据区域
    BOOL isDataSectionNeeded(const WCHAR* pModuleName);

    // 创建小转储文件
    BOOL createMiniDump(PEXCEPTION_POINTERS pep, LPCTSTR strFileName);

    // 回调函数
    static LONG CALLBACK unhandledExceptionFilterEx(PEXCEPTION_POINTERS pException);
   private:
    // 要生成的小转储文件文件名称
    std::string exeDumpFilePath;
    std::string dumpRootPath;
    // 事件指针
    void* pEvent;
};
}  // namespace dump
}  // namespace ztaClient
}  // namespace koal
#endif
dumpFileManager.cpp
#include "dumpFileManager.h"
#include "io.h"
#include <DbgHelp.h>
#include <time.h>

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

namespace koal {
namespace ztaClient {
namespace dump {

BOOL CALLBACK miniDumpCallback(PVOID pParam, const PMINIDUMP_CALLBACK_INPUT pInput, PMINIDUMP_CALLBACK_OUTPUT pOutput) {
    if (pInput == 0 || pOutput == 0) {
        return FALSE;
    }
    switch (pInput->CallbackType) {
        case ModuleCallback:
            if (pOutput->ModuleWriteFlags & ModuleWriteDataSeg) {
                if (!(DumpFileManager::Instance()->isDataSectionNeeded(pInput->Module.FullPath))) {
                    pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg);
                }
            }
        case IncludeModuleCallback:
        case IncludeThreadCallback:
        case ThreadCallback:
        case ThreadExCallback:
            return TRUE;
        default:
            break;
    }

    return FALSE;
}

LONG CALLBACK DumpFileManager::unhandledExceptionFilterEx(PEXCEPTION_POINTERS pException) {
    if (NULL == pException) {
        return EXCEPTION_CONTINUE_SEARCH;
    }

    int ret = access(Instance()->dumpRootPath.c_str(), 0);
    if (-1 == ret) {
        CreateDirectory(LPCSTR(Instance()->dumpRootPath.c_str()), NULL);
    }

    if (Instance()->createMiniDump(pException, Instance()->exeDumpFilePath.c_str())) {
        MessageBox(NULL, LPCSTR("程序出错,创建小转储文件成功"), LPCSTR("致命错误"), MB_OK | MB_ICONINFORMATION);

    } else {
        MessageBox(NULL, LPCSTR("程序出错,创建小转储文件成功"), LPCSTR("致命错误"), MB_OK | MB_ICONINFORMATION);
    }
    if (Instance()->pEvent) {
        Instance()->installProcessQuit();
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

DumpFileManager::DumpFileManager() { pEvent = NULL; }

DumpFileManager::~DumpFileManager() {}

BOOL DumpFileManager::isDataSectionNeeded(const WCHAR* pModuleName) {
    WCHAR szFileName[_MAX_FNAME] = L"";

    if (NULL == pModuleName) {
        return FALSE;
    }
    _wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);
    if (_wcsicmp(szFileName, L"ntdll") == 0) {
        return TRUE;
    }

    return FALSE;
}

BOOL DumpFileManager::createMiniDump(PEXCEPTION_POINTERS pep, LPCTSTR strFileName) {
    MINIDUMP_EXCEPTION_INFORMATION mdei;
    MINIDUMP_CALLBACK_INFORMATION mci;
    HANDLE hFile = NULL;

    if (NULL == pep || NULL == strFileName) {
        return FALSE;
    }
    hFile = CreateFile(strFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) {
        mdei.ThreadId = GetCurrentThreadId();
        mdei.ExceptionPointers = pep;
        mdei.ClientPointers = FALSE;

        mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)miniDumpCallback;

        mci.CallbackParam = NULL;

        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci);

        CloseHandle(hFile);
        return TRUE;
    }

    return FALSE;
}

void DumpFileManager::installDumpCollect(void* pInstallEvent) {
    if (pInstallEvent) {  // 注册了事件
        pEvent = pInstallEvent;
    }

    SetUnhandledExceptionFilter(unhandledExceptionFilterEx);
}

void* DumpFileManager::setDumpFilePath(const char* rootPath, const char* pAppName) {
    if (NULL == rootPath || NULL == pAppName) {
        return FALSE;
    }

    char TIME[64] = {0};
    char DAY[64] = {0};

    SYSTEMTIME sys;
    GetLocalTime(&sys);
    sprintf(TIME, "%02d_%02d_%02d", sys.wHour, sys.wMinute, sys.wSecond);

    time_t curTm;
    time(&curTm);
    struct tm pTm = *localtime(&curTm);
    sprintf(DAY, "%04d%02d%02d", pTm.tm_year + 1900, pTm.tm_mon + 1, pTm.tm_mday);

    char exeName[256] = {0};
    sprintf(exeName, "%s.mini_%s_%s.dmp", pAppName, DAY, TIME);

    std::string dumpFilePath = rootPath;
    std::string fileName = exeName;
    dumpRootPath = dumpFilePath;
    exeDumpFilePath = dumpFilePath + "\\" + fileName;

    return (void*)this;
}
//程序崩溃后需要做的事情,自定义,不需要可以不加
void DumpFileManager::installProcessQuit() {
    //koal::ztaClient::safeQuit* reg = (koal::ztaClient::safeQuit*)pEvent;
    //reg->sendMsg();
}

}  // namespace dump
}  // namespace ztaClient
}  // namespace koal
SetUnhandledExceptionFilter函数说明

简单使用SetUnhandledExceptionFilter()函数让程序优雅崩溃
最后网上查了一番,发现SetUnhandledExceptionFilter这个函数解决了一切。

设置异常捕获函数:
当异常没有处理的时候,系统就会调用SetUnhandledExceptionFilter所设置异常处理函数。例如一些程序
在出错的时候,会向用户报告说程序那出错就是利用这个.例如QQ..异常处理中的一部分
当发生异常时,比如内存访问违例时,CPU硬件会发现此问题,并产生一个异常(你可以把它理解为中断)
然后CPU会把代码流程切换到异常处理服务例程。操作系统异常处理服务例程会查看当前进程是否处于调试状态
如果是,则通知调试器发生了异常,如果不是则操作系统会查看当前线程是否安装了的异常帧链(FS[0])  
如果安装了SEH(try.... catch....),则调用SEH,并根据返回结果决定是否全局展开或局部展开。  
如果异常链中所有的SEH都没有处理此异常,而且此进程还处于调试状态,则操作系统会再次通知调试器发生异常  
(二次异常)。如果还没人处理,则调用操作系统的默认异常处理代码UnhandledExceptionHandler  
不过操作系统允许你Hook这个函数,就是通过SetUnhandledExceptionFilter函数来设置。  
大部分异常通过此种方法都能捕获,不过栈溢出、覆盖的有可能捕获不到。

总结了下搜到的资料,这个函数的返回值有三种情况:

EXCEPTION_EXECUTE_HANDLER equ 1 表示我已经处理了异常,可以优雅地结束了  
EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束  
EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行 

方法使用

在需要需用的cpp里,直接调用就行

代码如下(示例):

main.cpp
#include "dumpFileManager.h"
#define KOAL_ZTA_DUMP koal::ztaClient::dump::DumpFileManager

int main(int argc, char *argv[]){
   //dump文件生成的路径
   std::string dumpPath = "D:\\test";//根据自己需求改(注意windows下目录为\\双斜杠)
   // 崩溃记录
    ((KOAL_ZTA_DUMP *)KOAL_ZTA_DUMP::Instance()->setDumpFilePath(dumpPath.c_str(), "test.exe"))->installDumpCollect(/*(void *)reg 不需要注册事件可以不传入回调函数的地址,函数默参数为NULL*/);
    //休眠五秒,五秒后程序崩溃。
    Sleep(5000);
    //这段代码可以使程序崩溃,作为测试用
    _asm int 3;
}
技术说明

知识点:

  • windows的api函数
  • 回调函数
  • 指针函数

开发环境

  • vs2010

编码格式

  • GB2312
    (记得在vscode中或者记事本中设置代码的编码格式为GB2312,用UTF-8的话,弹窗中文会乱码)

如果编译报错,可以试着修改一下vs的配置。我这边代码是没有问题的。


总结

例如:以上就是今天要讲的内容,本文仅仅简单介绍了windows下dump的生成,其中用到了大量的windows的api函数。可以多研究研究。

  • 10
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Windows程序崩溃时,操作系统会生成一个崩溃转储(Dump文件,用于帮助开发人员诊断和调试程序故障。这个Dump文件记录了程序崩溃时的内存状态,包括堆栈信息、寄存器状态、变量值等关键数据。 生成Dump文件的方法有多种,例如: 1. 使用Windows上自带的任务管理器。打开任务管理器,在“进程”选项卡中找到崩溃程序进程,右键点击选择“创建转储文件”即可生成Dump文件。 2. 使用Windows上自带的Dr.Watson工具(仅适用于旧版本)。Dr.Watson是一种活动监视工具,它会在程序崩溃时自动记录信息,生成.DMP文件。可以在Windows注册表中启用Dr.Watson功能。 3. 使用Windows Debugging Tools。这是一套由微软提供的调试工具,其中包括了生成Dump文件的命令行工具Dumpchk、Msdia.dll等。使用这些工具可以对Dump文件进行详细的调试和分析。 一旦生成Dump文件,开发人员可以使用各种调试工具来分析这个文件,以寻找程序崩溃的原因。比如,可以使用Visual Studio自带的调试器或WinDbg调试器来打开Dump文件,并逐步调试程序,查看导致崩溃的代码行。 Dump文件对于故障排除非常有用,可以帮助开发人员定位和解决程序中的错误。通过分析Dump文件,我们可以获得崩溃时的内存状态,从而找到导致崩溃的具体代码,修复问题,提升软件的稳定性和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值