在程序崩溃时,需要对异常的情况进行捕获,并捕获到的堆栈信息保持下来。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 <string>
#include <memory>
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<CCreateDump*>    m_sptrInstance;
};

/
创建dmp的.cpp文件
#include <Windows.h>
#include "CListenDump.h"
#include <DbgHelp.h>
#pragma comment(lib,  "dbghelp.lib")

std::shared_ptr<CCreateDump*>  CCreateDump::m_sptrInstance = make_shared<CCreateDump*>();
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<CCreateDump*>(new CCreateDump);
	}
	return *m_sptrInstance;
}



//主函数cpp文件如下:
// CreateDump.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include "CListenDump.h"
#include<windows.h>
#include<stdio.h>
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路径

 pdb路径如红笔所示

 (2)设置源代码路径:

 

 

(3)点击运行

 调试结果如下:

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐