3 打印崩溃信息以及输出崩溃Dump文件

3.1 缘由

本来不想写这一小结的,感觉有点跳跃性,因为对WINSDK不熟悉会导致这一小节看起来有些云雾. 包括我本人,对这一块也是畏畏缩缩的查了半天MSDN.但是由于第2节中提到了打印堆栈信息,就想提一提这个事情.而且现在除了在开发环境中可以方便的Debug,我们的程序却主要应用于客户端环境,那里可没有VS,也没有各种用于调试的工具.那我们只在程序崩溃或者异常时候收集其信息,然后将该信息拿到开发环境下再进行处理.那本节的必要性,不言而喻.

3.2 源码及说明

如下代码实现了捕获异常时堆栈信息以及dump文件.<文件:FADE\Fade\Stack\ExceptionCollector.h>

/********************************
author:FakeCpp
date:2017/07/14
filename:ExceptionCollector.h
describe:
********************************/
#ifndef _FAKECPP_EXCEPTIONCOLLECTOR_
#define _FAKECPP_EXCEPTIONCOLLECTOR_

#include <windows.h>
#include "dbghelp.h"
#pragma comment(lib,"dbghelp.lib")
interface IExceptionHandler
{
	virtual void OnBegin(){}
	virtual void OnExceptionStr(const char* ExcptionStr){}
	virtual void OnError(const char* ErrStr){}
	virtual void OnEnd(){}
	virtual void OnDumpFile(LPCTSTR DumpFilePath){}

	static void SetDumpFileDir(const char* DumpDir);
	static void StartCollector(IExceptionHandler* pEH);
};
struct CExceptionCollector
{
	static void SetDumpFileDir(const char* DumpDir)
	{
		memset(m_chDumpDir,0,MAX_PATH);
		if (DumpDir)
			strncpy_s(m_chDumpDir,DumpDir,sizeof(m_chDumpDir));
	}
	static void StartCollector(IExceptionHandler* pEH)
	{
		m_pExceptionHandler = pEH;
		m_PreviousFilter = ::SetUnhandledExceptionFilter(_UnhandledExceptionFilter);
	}
private:
	static LONG WINAPI _UnhandledExceptionFilter(CONST PEXCEPTION_POINTERS pExceptionInfo)
	{
		//获取dump文件路径
		char DumpFilePath[MAX_PATH] = {0};
		char MoudulePath[MAX_PATH] = {0};
		if (*m_chDumpDir == 0 || !::PathIsDirectory(m_chDumpDir))
		{
			::GetModuleFileName(NULL,MoudulePath,MAX_PATH);
			char* pLocal1 = strrchr(MoudulePath,'/');
			char* pLocal2 = strrchr(MoudulePath,'\\');
			pLocal1 = pLocal1>pLocal2?pLocal1:pLocal2;
			*(++pLocal1) = 0;
		}
		else
		{
			strncpy_s(MoudulePath,m_chDumpDir,sizeof(MoudulePath));
		}

		COleDateTime CurrTime = COleDateTime::GetCurrentTime();
		char DumpFileName[MAX_PATH] = {0};
		sprintf_s(DumpFileName,"%04d%02d%02d_%02d%02d%02d.dmp",CurrTime.GetYear(),CurrTime.GetMonth(),
			CurrTime.GetDay(),CurrTime.GetHour(),CurrTime.GetMinute(),CurrTime.GetMinute());
		sprintf_s(DumpFilePath,"%s%s",MoudulePath,DumpFileName);
		//创建并且写入dump
		HANDLE hDumpFile = ::CreateFile(DumpFilePath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
		if (hDumpFile&&hDumpFile!=INVALID_HANDLE_VALUE)
		{
			MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
			dumpInfo.ExceptionPointers = pExceptionInfo;
			dumpInfo.ThreadId = GetCurrentThreadId();
			dumpInfo.ClientPointers = TRUE;
			MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);  
			CloseHandle(hDumpFile);
			m_pExceptionHandler->OnDumpFile(DumpFilePath);
		}

		char szContext[MAX_PATH] = {0};
		m_pExceptionHandler->OnBegin();
		//输出Except文本信息
		HANDLE hProcess = GetCurrentProcess();
		if (SymInitialize(GetCurrentProcess(),NULL,TRUE))//初始化进程符号句柄
		{
			DWORD dw = SymGetOptions();
			dw &= ~SYMOPT_UNDNAME;
			dw |= SYMOPT_LOAD_LINES;
			SymSetOptions(dw);

			STACKFRAME64 stackFrame;
			memset(&stackFrame, 0, sizeof(stackFrame));
			stackFrame.AddrPC.Mode = AddrModeFlat;
			stackFrame.AddrFrame.Mode = AddrModeFlat;
			stackFrame.AddrStack.Mode = AddrModeFlat;
			stackFrame.AddrReturn.Mode = AddrModeFlat;
			stackFrame.AddrBStore.Mode = AddrModeFlat;

			DWORD dwMachType;
			LPCONTEXT pContext = pExceptionInfo->ContextRecord;
#if defined(_M_IX86)
			dwMachType                   = IMAGE_FILE_MACHINE_I386;
			// program counter, stack pointer, and frame pointer
			stackFrame.AddrPC.Offset     = pContext->Eip;
			stackFrame.AddrStack.Offset  = pContext->Esp;
			stackFrame.AddrFrame.Offset  = pContext->Ebp;
#elif defined(_M_AMD64)
			// only program counter
			dwMachType                   = IMAGE_FILE_MACHINE_AMD64;
			stackFrame.AddrPC.Offset     = pContext->Rip;
#elif defined(_M_MRX000)
			// only program counter
			dwMachType                   = IMAGE_FILE_MACHINE_R4000;
			stackFrame.AddrPC.Offset     = pContext->Fir;
#elif defined(_M_ALPHA)
			// only program counter
			dwMachType                   = IMAGE_FILE_MACHINE_ALPHA;
			stackFrame.AddrPC.Offset     = (unsigned long) pContext->Fir;
#elif defined(_M_PPC)
			// only program counter
			dwMachType                   = IMAGE_FILE_MACHINE_POWERPC;
			stackFrame.AddrPC.Offset     = pContext->Iar;
#elif defined(_M_IA64)
			// only program counter
			dwMachType                   = IMAGE_FILE_MACHINE_IA64;
			stackFrame.AddrPC.Offset     = pContext->StIIP;
#elif defined(_M_ALPHA64)
			// only program counter
			dwMachType                   = IMAGE_FILE_MACHINE_ALPHA64;
			stackFrame.AddrPC.Offset     = pContext->Fir;
#else
#error("Unknown Target Machine");
#endif
			while (true)
			{
				memset(szContext,0,MAX_PATH);
				if (!::StackWalk64(dwMachType, hProcess, NULL, &stackFrame, (LPVOID)pContext,
					NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
				{
					break;
				}
				if (stackFrame.AddrStack.Offset == 0)
					break;
				ULONG64 buffer[(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)];
				PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
				pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
				pSymbol->MaxNameLen = MAX_SYM_NAME;

				DWORD64 dwDisplacement64 = 0;
				if (SymFromAddr(hProcess, stackFrame.AddrPC.Offset, &dwDisplacement64, pSymbol))
				{
					sprintf_s(szContext,MAX_PATH, TEXT("%p\t%p\t%hs+0x%X"), 
						(LPCVOID)stackFrame.AddrPC.Offset, (LPCVOID)stackFrame.AddrFrame.Offset, 
						pSymbol->Name, dwDisplacement64);
					m_pExceptionHandler->OnExceptionStr(szContext);

					//FileName And Line
					IMAGEHLP_LINE64 Line;
					Line.SizeOfStruct=sizeof(IMAGEHLP_LINE64);
					DWORD dwDisplacement=0;
					if (SymGetLineFromAddr64(hProcess,stackFrame.AddrPC.Offset,&dwDisplacement,&Line))
					{
						sprintf_s(szContext,MAX_PATH, TEXT("<%hs:%d+0x%X>"),
							Line.FileName,Line.LineNumber,dwDisplacement);
						m_pExceptionHandler->OnExceptionStr(szContext);
					}
					else
					{
						sprintf_s(szContext,MAX_PATH,TEXT("SymGetLineFromAddr64 Err:%d"),GetLastError());
						m_pExceptionHandler->OnError(szContext);
					}
				}
				else
				{
					sprintf_s(szContext,MAX_PATH,TEXT("SymFromAddr Err: %d"),GetLastError());
					m_pExceptionHandler->OnError(szContext);
				}
			}
		}
		if (!SymCleanup(hProcess))
		{
			memset(szContext,0,MAX_PATH);
			sprintf_s(szContext,MAX_PATH,TEXT("SymCleanup Err: %d"),GetLastError());
			m_pExceptionHandler->OnError(szContext);
		}
		m_pExceptionHandler->OnEnd();
		if (m_PreviousFilter != NULL)
			return m_PreviousFilter(pExceptionInfo);
		else
			return EXCEPTION_CONTINUE_SEARCH;
	}
private:
	static LPTOP_LEVEL_EXCEPTION_FILTER m_PreviousFilter;
	static char m_chDumpDir[MAX_PATH];
	static IExceptionHandler* m_pExceptionHandler;
};
__declspec(selectany) char CExceptionCollector::m_chDumpDir[MAX_PATH]={0};
__declspec(selectany) IExceptionHandler* CExceptionCollector::m_pExceptionHandler = NULL;
__declspec(selectany) LPTOP_LEVEL_EXCEPTION_FILTER CExceptionCollector::m_PreviousFilter = NULL;

void IExceptionHandler::SetDumpFileDir( const char* DumpDir ){
	CExceptionCollector::SetDumpFileDir(DumpDir);
}
void IExceptionHandler::StartCollector( IExceptionHandler* pEH ){
	CExceptionCollector::StartCollector(pEH);
}
#endif

这段代码通过IExceptionHandler来提供接口,其中OnBegin, OnExceptionStr, OnError, OnEnd这四个接口是用来打印崩溃异常时候的堆栈信息.OnDumpFile是完成dump文件导出的时间通知.

 

这段代码大致的流程:

1.使用SetUnhandledExceptionFilter替换掉默认的异常处理函数(_UnhandledExceptionFilter)

2._UnhandledExceptionFilter,通过传来的PEXCEPTION_POINTERS生成dump文件

3.初始化当前进程的符号信息(SymInitialize),以及初始化堆栈帧数据(STACKFRAME64)

4.根据PEXCEPTION_POINTERS获取的LPCONTEXT,和堆栈帧数据来获取堆栈记录(StackWalk64)

5.最后通过获取的堆栈记录信息来获取符号信息(SymFromAddr)

6.清理(SymCleanup)

有兴趣的朋友可以去跟一下AtlDumpStack的源码,跟这里的流程差不多.

3.3 应用

该段代码可以很方便的让你的程序拥有收集异常信息的功能.包含了相应的头文件之后,在程序开始的地方,比如MFC对话框框架程序中的InitInstance()中加入如下一段代码:

BOOL CTrick1App::InitInstance()
{
	static struct MyExceptionHandler:IExceptionHandler
	{
		MyExceptionHandler(){
			IExceptionHandler::SetDumpFileDir(NULL);
			IExceptionHandler::StartCollector(this);
		}
		void OnBegin()
		{
			printf("Begin\r\n");
		}
		void OnExceptionStr(const char* ExcptionStr)
		{
			printf("%s\r\n",ExcptionStr);
		}
		void OnError(const char* ErrStr)
		{
			printf("Error:%s\r\n",ErrStr);
		}
		void OnEnd()
		{
			printf("End\r\n");
		}
		void OnDumpFile(LPCTSTR DumpFilePath)
		{
			printf("%s\r\n",DumpFilePath);
		}
	} ME;
//其他代码
}

其中, MyException是一个Nested Class,想到了什么吗?

这里我们的手法和AtlDumpStack中基本上是一样的,都是提供了一些接口用来获取信息.那么你也可以模仿第二节中对AtlDumpStack包装的方法来包装这里.下一小节,我们就来说说包装.

3.4 包装

下面是对上述接口的一个包装函数,你可以修改其中的代码来满足你的需求.<代码:Fade\Stack\DumpCrashInfo.h>

/********************************
author:FakeCpp
date:2017/07/14
filename:DumpCrashInfo.h
describe:
********************************/
#ifndef _FAKECPP_DUMPCRASHINFO_
#define _FAKECPP_DUMPCRASHINFO_

#include "ExceptionCollector.h"
void DumpCrashInfo()
{
	static struct MyExceptionHandler:IExceptionHandler
	{
		MyExceptionHandler(){
			IExceptionHandler::SetDumpFileDir(NULL);
			IExceptionHandler::StartCollector(this);
		}
		void OnBegin()
		{
			printf("Begin\r\n");
		}
		void OnExceptionStr(const char* ExcptionStr)
		{
			printf("%s\r\n",ExcptionStr);
		}
		void OnError(const char* ErrStr)
		{
			printf("Error:%s\r\n",ErrStr);
		}
		void OnEnd()
		{
			printf("End\r\n");
		}
		void OnDumpFile(LPCTSTR DumpFilePath)
		{
			printf("%s\r\n",DumpFilePath);
		}
	} ME;
}
#endif

参见如上代码,现在我们只需要在程序开始的地方调一下DumpCrashInfo即可

3.5 小结

是不是好简单,是不是没有想的那么复杂.加上有了后面的包装,收集崩溃信息,简直是不要太容易!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值