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 小结
是不是好简单,是不是没有想的那么复杂.加上有了后面的包装,收集崩溃信息,简直是不要太容易!