使用SetUnhandledExceptionFilter()调试windows平台下的崩溃弹窗问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012372584/article/details/87965913

昨天看了同事的程序,发现解决崩溃弹窗问题的方法很不错,上网搜了一下,也基本都是这个方法,以前确实没怎么关注过这个问题,测试了一下,把出现问题的源代码准确的定位了出来。

1、同事对其进行了封装,封装类如下:

#pragma once

#include <windows.h>
#include <imagehlp.h>
#include <stdlib.h>

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

#define _DUMP_FILE_PATH (_T("dump\\"))

class DumpFileManager
{
private:
	//要生成的小转储文件文件名称
	CString m_strDumpFile;
private:
	//判断是否为需要的数据区域
	BOOL IsDataSectionNeeded(const WCHAR* pModuleName);
	//发生错误时的回调函数
	static BOOL CALLBACK MiniDumpCallback(PVOID pParam,const PMINIDUMP_CALLBACK_INPUT pInput,PMINIDUMP_CALLBACK_OUTPUT pOutput);
	//创建小转储文件
	BOOL CreateMiniDump(EXCEPTION_POINTERS* pep, LPCTSTR strFileName);
	//钩子函数
	static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
	//钩子函数
	BOOL PreventSetUnhandledExceptionFilter();
	//回调函数
	static LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException);
public:
	//指定小转储文件保存路径
	BOOL DeclarDumpFile(CString strAppPath);
	//注册异常处理回调函数
	void RunCrashHandler();
};
#include "StdAfx.h"
#include "DumpFileManager.h"

DumpFileManager *g_dump_manager = NULL;

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 CALLBACK DumpFileManager::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(!(g_dump_manager->IsDataSectionNeeded(pInput->Module.FullPath))) 
		   {
             pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg); 
		   }
	   }
    case IncludeModuleCallback:
    case IncludeThreadCallback:
    case ThreadCallback:
    case ThreadExCallback:
       return TRUE;
    default:
		break;
    }

    return FALSE;
}

BOOL DumpFileManager::CreateMiniDump(EXCEPTION_POINTERS* pep, LPCTSTR strFileName)
{
	MINIDUMP_CALLBACK_INFORMATION mci;
	MINIDUMP_EXCEPTION_INFORMATION mdei;
    HANDLE hFile = NULL;
	
	if(NULL == pep || NULL == strFileName)
	{
		return FALSE;
	}
	hFile = CreateFile(strFileName,GENERIC_WRITE, 0, 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       = 0;
		MINIDUMP_TYPE mdt       = (MINIDUMP_TYPE)0x0000ffff;

		MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci);
		CloseHandle(hFile); 
		return TRUE;
	}

	return FALSE;
}

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI DumpFileManager::MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
   return NULL;
}

BOOL DumpFileManager::PreventSetUnhandledExceptionFilter()
{
	DWORD dwOrgEntryAddr = 0;
    HMODULE hKernel32 = NULL;
	void *pOrgEntry = NULL;
	unsigned char newJump[100] = {0};
	void *pNewFunc = NULL;
	DWORD dwNewEntryAddr = 0;
	DWORD dwRelativeAddr = 0;
	SIZE_T bytesWritten = 0;
	
    if (NULL == (hKernel32 = LoadLibrary(_T("kernel32.dll"))))
	{
       return FALSE;
	}
   
    if(NULL == (pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter")))
	{
		return FALSE;
	}
    dwOrgEntryAddr = (DWORD) pOrgEntry;
    dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far

    pNewFunc = &MyDummySetUnhandledExceptionFilter;
    dwNewEntryAddr = (DWORD) pNewFunc;
    dwRelativeAddr = dwNewEntryAddr -  dwOrgEntryAddr;

    newJump[0] = 0xE9;  // JMP absolute
    memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));

    return WriteProcessMemory(GetCurrentProcess(),pOrgEntry,newJump,sizeof(pNewFunc) + 1,&bytesWritten);
}

LONG WINAPI DumpFileManager::UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException)
{
	BOOL bRetVal = FALSE;

	if(NULL == pException || NULL == g_dump_manager)
	{
		return EXCEPTION_CONTINUE_SEARCH;
	}

	if(g_dump_manager->CreateMiniDump(pException,g_dump_manager->m_strDumpFile))
	{
		FatalAppExit(-1,_T("程序错误,小转储文件创建成功"));
	}
	else
	{
		FatalAppExit(-1,_T("程序错误,小转储文件创建失败"));
	}

    return EXCEPTION_CONTINUE_SEARCH;

}

//运行异常处理
void DumpFileManager::RunCrashHandler()
{
	g_dump_manager = this;
    SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);
    PreventSetUnhandledExceptionFilter();
}

BOOL DumpFileManager::DeclarDumpFile(CString strAppPath)
{
    SYSTEMTIME syt;
	CString strDumpFileName = _T("");
	int xFlag = 0;
	CString strNeedDir = _T("");

	if(strAppPath.GetLength() <= 0)
	{
		return FALSE;
	}
    GetLocalTime(&syt);
	strDumpFileName.Format(_T("%s%s"),strAppPath,_DUMP_FILE_PATH);
	do
	{
		xFlag = strDumpFileName.Find(_T("\\"),xFlag+1);
		if(xFlag > 3)
		{
			strNeedDir = strDumpFileName.Left(xFlag);
			if(PathIsDirectory(strNeedDir))
			{
				continue ;
			}
			if(!(CreateDirectory(strNeedDir,0)))
			{
				break;
			}
		}
	}while(xFlag >= 0);

	xFlag = syt.wHour*60 + syt.wMinute;
	m_strDumpFile.Format(_T("%s%04d-%02d-%02d_%d.dmp"),strDumpFileName,syt.wYear,syt.wMonth,syt.wDay,xFlag);

	return TRUE;
}

2、使用方法:在程序开始运行的时候设置dump文件生成目录,并注册异常回调函数:

int npos = 0;
	CString strPath = _T("");
	TCHAR strTemp[MAX_PATH] = {0};
	TCHAR strAppPath[MAX_PATH] = {0};
	
	if(GetModuleFileName(NULL,strTemp, MAX_PATH) > 0)
	{
		strPath = strTemp;
		if((npos = strPath.ReverseFind('\\')) > 0)
		{
			_tcscpy_s(strAppPath,strPath.Left(npos+1));
		}
	}
	m_DumpFileManager.DeclarDumpFile(strAppPath);
	m_DumpFileManager.RunCrashHandler();

3、在release下进行配置debug选项:

C/C++选项---常规---调试信息格式---(设置程序数据库/Zi);

连接器选项---调试---生成调试信息---(设置为是);

C/C++选项---优化---禁用。

4、调试时最好 *.exe 、*.pdb、*.dump、dbghelp.dll放在同一目录下,*.exe 、*.pdb要是同次编译生成的。双击*.dump进行调试,选择“使用仅限本机进行调试”,进行问题定位。

猜你喜欢

转载自blog.csdn.net/u012372584/article/details/87965913