在程序崩溃时,需要对异常的情况进行捕获,并捕获到的堆栈信息保持下来。Windows操作系统提供了一个API函数可以在程序crash之前有机会处理这些异常,就是 SetUnhandleExceptionFilter函数。(C++也有一个类似函数set_terminate可以处理未被捕获的C++异常。)
一、函数介绍:
1、SetUnhandleExceptionFilter函数的定义如下:
SetUnhandledExceptionFilter(
_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
而LPTOP_LEVEL_EXCEPTION_FILTER的定义如下
typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
_In_ struct _EXCEPTION_POINTERS *ExceptionInfo
);
typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
所以定义一个lpTopLevelExceptionFilter函数去处理异常。而lpTopLevelExceptionFilter函数的参数类型是*PEXCEPTION_POINTERS。
2、另一个API函数是写生成dump文件的函数MiniDumpWriteDump,该函数定义如下:
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
);
第四个参数MINIDUMP_TYPE是转存奔溃信息的枚举类型,不同的枚举值保存奔溃信息范围不同,带来的影响就是dump文件的大小。MINIDUMP_TYPE的定义如下:
typedef enum _MINIDUMP_TYPE {
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
MiniDumpWithFullMemory = 0x00000002,
MiniDumpWithHandleData = 0x00000004,
MiniDumpFilterMemory = 0x00000008,
MiniDumpScanMemory = 0x00000010,
MiniDumpWithUnloadedModules = 0x00000020,
MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
MiniDumpFilterModulePaths = 0x00000080,
MiniDumpWithProcessThreadData = 0x00000100,
MiniDumpWithPrivateReadWriteMemory = 0x00000200,
MiniDumpWithoutOptionalData = 0x00000400,
MiniDumpWithFullMemoryInfo = 0x00000800,
MiniDumpWithThreadInfo = 0x00001000,
MiniDumpWithCodeSegs = 0x00002000,
MiniDumpWithoutAuxiliaryState = 0x00004000,
MiniDumpWithFullAuxiliaryState = 0x00008000,
MiniDumpWithPrivateWriteCopyMemory = 0x00010000,
MiniDumpIgnoreInaccessibleMemory = 0x00020000,
MiniDumpWithTokenInformation = 0x00040000,
MiniDumpWithModuleHeaders = 0x00080000,
MiniDumpFilterTriage = 0x00100000,
MiniDumpWithAvxXStateContext = 0x00200000,
MiniDumpWithIptTrace = 0x00400000,
MiniDumpValidTypeFlags = 0x007fffff,
} MINIDUMP_TYPE;
关于上面枚举值对应保存数据范围的介绍可参考MINIDUMP_TYPE详解_神知道下一秒会发生什么的博客-CSDN博客_minidump_type
下面介绍部分枚举值:
MiniDumpNormal
常选值,该类型仅包含捕获进程中所有线程的堆栈跟踪的必要信息。
MiniDumpWithDataSegs
包含来自所有加载模块中的可写数据段,此选项会使minidump文件显著增大,对每个模块的控制使用了MODULE_WRITE_FLAGS枚举类型的ModuleWriteDataSeg枚举值。如果我们希望查看全局变来那个值,但有不想使用MiniDumpWithFullMemory,可以使用此选项。
MiniDumpWithFullMemory
包含进程中所有可以访问的内存信息,原始内存信息包含在文件的末端,所以不用原始内存信息可以直接映射数据结构。但是该选项会造成minidump文件非常巨大。
使用该选项可以查看存储在栈上、堆上、模块数据段的所有数据。甚至还可以看到线程和进程环境块(Process Environment Block和Thread Environment Bolck, PEB和TEB)的数据。这些没有公开的数据结构可以给我们的调试提供无价的帮助。
MiniDumpWithHandleData
包含生成dump文件时活跃的系统句柄的高级别信息。
使用该选项minidump会包括故障时刻进程故障表里面的所有句柄。可以用WinDbg的!handle来显示这些信息。
MiniDumpFilterMemory
栈内存的内容会在保存到minidump前被过滤,除了重建栈跟踪所必须的指针,其他都会被用0数据覆盖。即调用栈可以被重建,但是所有局部变量和函数参数的值都是0。此选项只影响线程栈占用内存的内容。其他内存(比如堆)不受影响。如果使用了MiniDumpWithFullMemory,这个标志就不起作用了。
第五个参数定义如下:
typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
DWORD ThreadId;
PEXCEPTION_POINTERS ExceptionPointers;
BOOL ClientPointers;
} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
上述PEXCEPTION_POINTERS 为异常捕获指针。
二、demo如下:
//创建dmp文件头文件.h
#pragma once
#include
#include
using namespace std;
class CCreateDump
{
public:
CCreateDump();
~CCreateDump(void);
static CCreateDump* Instance();
static long __stdcall UnhandleExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo);
//声明Dump文件,异常时会自动生成。会自动加入.dmp文件名后缀
void DeclarDumpFile(std::string dmpFileName = "");
private:
static std::string m_strDumpFile;
static shared_ptr m_sptrInstance;
};
/
创建dmp的.cpp文件
#include
#include "CListenDump.h"
#include
#pragma comment(lib, "dbghelp.lib")
std::shared_ptr CCreateDump::m_sptrInstance = make_shared();
std::string CCreateDump::m_strDumpFile = "";
CCreateDump::CCreateDump()
{
}
CCreateDump::~CCreateDump(void)
{
}
long CCreateDump::UnhandleExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo)
{
HANDLE hFile = CreateFile(m_strDumpFile.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION ExInfo;
ExInfo.ThreadId = ::GetCurrentThreadId();
ExInfo.ExceptionPointers = ExceptionInfo;
ExInfo.ClientPointers = FALSE;
// write the dump
BOOL bOK = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL);
CloseHandle(hFile);
if (!bOK)
{
DWORD dw = GetLastError();
//写dump文件出错处理,异常交给windows处理
return EXCEPTION_CONTINUE_SEARCH;
}
else
{ //在异常处结束
return EXCEPTION_EXECUTE_HANDLER;
}
}
else
{
return EXCEPTION_CONTINUE_SEARCH;
}
}
void CCreateDump::DeclarDumpFile(std::string dmpFileName)
{
SYSTEMTIME syt;
GetLocalTime(&syt);
char szTime[MAX_PATH];
sprintf_s(szTime, MAX_PATH, "[%04d-%02d-%02dT%02d-%02d-%02d]", syt.wYear, syt.wMonth, syt.wDay, syt.wHour, syt.wMinute, syt.wSecond);
m_strDumpFile = dmpFileName + std::string(szTime);
m_strDumpFile += std::string(".dmp");
SetUnhandledExceptionFilter(UnhandleExceptionFilter);
}
CCreateDump* CCreateDump::Instance()
{
if (*m_sptrInstance == NULL)
{
m_sptrInstance = make_shared(new CCreateDump);
}
return *m_sptrInstance;
}
//主函数cpp文件如下:
// CreateDump.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include
#include "CListenDump.h"
#include
#include
int main()
{
char szPath[512] = { 0 };
//查找可执行文件路径,将dump文件写入可执行文件路径下
GetModuleFileName(NULL, szPath, sizeof(szPath) - 1);
printf("path:%s\n", szPath);
std::string strFilePathTmp = szPath;
std::string strDumPath = "";
if (strFilePathTmp.rfind(".exe") != string::npos)
{
int nIndex = strFilePathTmp.rfind("\\");
if (nIndex != -1)
{
strDumPath = strFilePathTmp.substr(0,nIndex+1);
}
}
strDumPath = strDumPath + "dumpfile";
CCreateDump::Instance()->DeclarDumpFile(strDumPath);
int nSub = 0;
int nValue = 10 / nSub;
std::cout << "Hello World!\n";
return 0;
}
上述产生dmp文件程序编写参考于Dump文件的生成和使用_不当初-CSDN博客_dump文件
三、调试dmp文件如下:
上述demo运行产生的dmp文件使用VS打开,本文工具使用VS2017,
(1)设置pdb路径
(2)设置源代码路径:
(3)点击运行
调试结果如下: