C++ 记录Windows程序崩溃时的dumpfile

【原理】
      windows程序当遇到异常,没有try-catch或者try-catch也无法捕获到的异常时,程序就会自动退出,如果这时候没有dump文件的话,我们是没有得到任何程序退出的信息。在windows程序异常退出之前,会预先调用一个在程序中注册的异常处理回调函数(默认是没有设置),只要我们在这个回调函数中调用MiniDumpWriteDump函数就可以产生我们想要的dump文件。


【实现】

1.调用SetUnhandledExceptionFilter注册一个自定义的异常处理回调函数
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

2.CreateFile创建dump文件,调用MiniDumpWriteDump函数往dump文件写异常信息


【实现】

#pragma  once 

#include <windows.h>

class CMiniDumper
{
public:
    static HRESULT  CreateInstance();
    static HRESULT  ReleaseInstance();

public:
    LONG WriteMiniDump(_EXCEPTION_POINTERS *pExceptionInfo);

private:
    void SetMiniDumpFileName(void);
    BOOL GetImpersonationToken(HANDLE* phToken);
    BOOL EnablePrivilege(LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld);
    BOOL RestorePrivilege(HANDLE hToken, TOKEN_PRIVILEGES* ptpOld);

private:
	CMiniDumper();
	virtual ~CMiniDumper(void);

private:
    TCHAR	m_szMiniDumpPath[MAX_PATH];
    TCHAR	m_szAppPath[MAX_PATH];
};
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <tchar.h>
#include <dbghelp.h>
#include "miniDump.h"

#ifdef UNICODE
    #define _tcssprintf wsprintf
    #define tcsplitpath _wsplitpath
#else
    #define _tcssprintf sprintf
    #define tcsplitpath _splitpath
#endif


//-----------------------------------------------------------------------------
// GLOBALs
//-----------------------------------------------------------------------------
CMiniDumper       *gs_pMiniDumper      = NULL;
LPCRITICAL_SECTION gs_pCriticalSection = NULL;

//-----------------------------------------------------------------------------
// APIs
//-----------------------------------------------------------------------------
// Based on dbghelp.h
typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess,
										DWORD dwPid,
										HANDLE hFile,
										MINIDUMP_TYPE DumpType,
										CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
										CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
										CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);

BOOL IsDataSectionNeeded(const WCHAR* pModuleName)
{
	if (pModuleName == 0)
	{
		return FALSE;
	}

	WCHAR szFileName[_MAX_FNAME] = {0};
	_wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);

	if (_wcsicmp(szFileName, L"ntdll") == 0)
		return TRUE;

	return FALSE;
}

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

	return FALSE;
}

//-----------------------------------------------------------------------------
// Name: unhandledExceptionHandler()
// Desc: Call-back filter function for unhandled exceptions
//-----------------------------------------------------------------------------
LONG WINAPI UnhandledExceptionHandler(_EXCEPTION_POINTERS *pExceptionInfo)
{
    if (NULL == gs_pMiniDumper)
        return EXCEPTION_CONTINUE_SEARCH;

    return gs_pMiniDumper->WriteMiniDump(pExceptionInfo);
}

// 此函数一旦成功调用,之后对 SetUnhandledExceptionFilter 的调用将无效  
void DisableSetUnhandledExceptionFilter()
{
    HMODULE     hModule = LoadLibrary(L"kernel32.dll");
    void* pAddr = (void*)GetProcAddress(hModule, "SetUnhandledExceptionFilter");
    if (pAddr)
    {
        unsigned char code[16] = { 0 };
        int			  size     = 0;

        code[size++] = 0x33;
        code[size++] = 0xC0;
        code[size++] = 0xC2;
        code[size++] = 0x04;
        code[size++] = 0x00;

        DWORD dwOldFlag  = 0;
        DWORD dwTempFlag = 0;

        VirtualProtect(pAddr, size, PAGE_READWRITE, &dwOldFlag);
        WriteProcessMemory(GetCurrentProcess(), pAddr, code, size, NULL);
        VirtualProtect(pAddr, size, dwOldFlag, &dwTempFlag);
    }
    FreeLibrary(hModule);
}

//-----------------------------------------------------------------------------
// Name: CreateInstance()
// Desc: Instanse gs_pMiniDumper
//-----------------------------------------------------------------------------
HRESULT CMiniDumper::CreateInstance()
{
    if (NULL == gs_pMiniDumper)
    {
        gs_pMiniDumper = new CMiniDumper();
    }
    if (NULL == gs_pCriticalSection)
    {
        gs_pCriticalSection = new CRITICAL_SECTION;
        InitializeCriticalSection(gs_pCriticalSection);
    }
   
    return(S_OK);
}

//-----------------------------------------------------------------------------
// Name: ReleaseInstance()
// Desc: Release gs_pMiniDumper
//-----------------------------------------------------------------------------
HRESULT  CMiniDumper::ReleaseInstance()
{
    if (NULL != gs_pMiniDumper)
    {
        delete gs_pMiniDumper;
        gs_pMiniDumper = NULL;
    }
    if (NULL != gs_pCriticalSection)
    {
        DeleteCriticalSection(gs_pCriticalSection);
        gs_pCriticalSection = NULL;
    }

    return(S_OK);
}

//-----------------------------------------------------------------------------
// Name: CMiniDumper()
// Desc: Constructor
//-----------------------------------------------------------------------------
CMiniDumper::CMiniDumper()
{
	// 使应用程序能够取代每个进程和线程的顶级异常处理程序	
	::SetUnhandledExceptionFilter(UnhandledExceptionHandler);
	DisableSetUnhandledExceptionFilter();
}

//-----------------------------------------------------------------------------
// Name: ~CMiniDumper()
// Desc: Destructor
//-----------------------------------------------------------------------------
CMiniDumper::~CMiniDumper(void)
{

}

//-----------------------------------------------------------------------------
// Name: setMiniDumpFileName()
// Desc: 
//-----------------------------------------------------------------------------
void CMiniDumper::SetMiniDumpFileName(void)
{
	time_t currentTime;
	time(¤tTime);

	_tcssprintf(m_szMiniDumpPath, _T("%s.%ld.dmp"), m_szAppPath, currentTime);
}

//-----------------------------------------------------------------------------
// Name: getImpersonationToken()
// Desc: The method acts as a potential workaround for the fact that the 
//       current thread may not have a token assigned to it, and if not, the 
//       process token is received.
//-----------------------------------------------------------------------------
BOOL CMiniDumper::GetImpersonationToken(HANDLE* phToken)
{
	*phToken = NULL;
	if (!OpenThreadToken(GetCurrentThread(), 
						 TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
						 TRUE,
						 phToken))
	{
		if (GetLastError() == ERROR_NO_TOKEN)
		{
			// No impersonation token for the current thread is available. 
			// Let's go for the process token instead.
			if (!OpenProcessToken(GetCurrentProcess(),
								  TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
								  phToken))
				return FALSE;
		}
		else
			return FALSE;
	}

	return TRUE;
}

//-----------------------------------------------------------------------------
// Name: enablePrivilege()
// Desc: Since a MiniDump contains a lot of meta-data about the OS and 
//       application state at the time of the dump, it is a rather privileged 
//       operation. This means we need to set the SeDebugPrivilege to be able 
//       to call MiniDumpWriteDump.
//-----------------------------------------------------------------------------
BOOL CMiniDumper::EnablePrivilege(LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld)
{
	BOOL				bOk = FALSE;
	TOKEN_PRIVILEGES	tp;

	tp.PrivilegeCount = 1;
	tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	bOk = LookupPrivilegeValue(0, pszPriv, &tp.Privileges[0].Luid);
	if (bOk)
	{
		DWORD cbOld = sizeof(*ptpOld);
		bOk = AdjustTokenPrivileges(hToken, FALSE, &tp, cbOld, ptpOld, &cbOld);
	}

	return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
}

//-----------------------------------------------------------------------------
// Name: restorePrivilege()
// Desc: 
//-----------------------------------------------------------------------------
BOOL CMiniDumper::RestorePrivilege(HANDLE hToken, TOKEN_PRIVILEGES* ptpOld)
{
	BOOL bOk = AdjustTokenPrivileges(hToken, FALSE, ptpOld, 0, NULL, NULL);
	return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
}

//-----------------------------------------------------------------------------
// Name: writeMiniDump()
// Desc: 
//-----------------------------------------------------------------------------
LONG CMiniDumper::WriteMiniDump(_EXCEPTION_POINTERS *pExceptionInfo)
{
	LONG	retval				= EXCEPTION_CONTINUE_SEARCH;
	HANDLE	hImpersonationToken = NULL;

	if (!GetImpersonationToken(&hImpersonationToken))
		return FALSE;

	// You have to find the right dbghelp.dll. 
	// Look next to the EXE first since the one in System32 might be old (Win2k)
	HMODULE hDll					= NULL;
	if (GetModuleFileName(NULL, m_szAppPath, _MAX_PATH))
	{
        wchar_t szDir[_MAX_DIR]         = { 0 };
        TCHAR	szDbgHelpPath[MAX_PATH] = { 0 };

        _wsplitpath(m_szAppPath, NULL, szDir, NULL, NULL);
        _tcscpy(szDbgHelpPath, szDir);		
        _tcscat(szDbgHelpPath, _T("DBGHELP.DLL"));

		hDll = ::LoadLibrary(szDbgHelpPath);
	}

	if (hDll == NULL)
	{
		// If we haven't found it yet - try one more time.
		hDll = ::LoadLibrary(_T("DBGHELP.DLL"));
	}

	if (hDll)
	{
		// Get the address of the MiniDumpWriteDump function, which writes 
		// user-mode mini-dump information to a specified file.
		MINIDUMPWRITEDUMP MiniDumpWriteDump = (MINIDUMPWRITEDUMP)::GetProcAddress(hDll, "MiniDumpWriteDump");
		if (MiniDumpWriteDump != NULL)
		{
			SetMiniDumpFileName();

			// Create the mini-dump file...
			HANDLE hFile = ::CreateFile(m_szMiniDumpPath,
										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 = pExceptionInfo;
				ExInfo.ClientPointers	 = NULL;

				MINIDUMP_CALLBACK_INFORMATION mci;
				mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
				mci.CallbackParam = 0;

				// We need the SeDebugPrivilege to be able to run MiniDumpWriteDump
				TOKEN_PRIVILEGES tp;
				BOOL bPrivilegeEnabled = EnablePrivilege(SE_DEBUG_NAME, hImpersonationToken, &tp);
				BOOL bOk;

				// DBGHELP.dll is not thread-safe, so we need to restrict access...
				EnterCriticalSection(gs_pCriticalSection);
				{
					// Write out the mini-dump data to the file...
					bOk = MiniDumpWriteDump(GetCurrentProcess(),
											GetCurrentProcessId(),
											hFile,
											MiniDumpNormal,
                                            (NULL == pExceptionInfo) ? (NULL) : (&ExInfo),
											NULL,
											&mci);
				}
				LeaveCriticalSection(gs_pCriticalSection);

				// Restore the privileges when done
				if (bPrivilegeEnabled)
					RestorePrivilege(hImpersonationToken, &tp);

				if (bOk)
				{
					retval = EXCEPTION_EXECUTE_HANDLER;					
				}				

				::CloseHandle(hFile);
			}			
		}		
	}

    FreeLibrary(hDll);
    if (NULL != pExceptionInfo)
    {
        TerminateProcess(GetCurrentProcess(), 0);
    }

    return retval;
}
测试

// Dumper.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include "miniDump.h"

extern CMiniDumper       *gs_pMiniDumper;

int _tmain(int argc, _TCHAR* argv[])
{
	CMiniDumper::CreateInstance();

	char *p = NULL;
	*p = 'b';

	/*__try
	{
	*p = 'b';
	}
	__except (gs_pMiniDumper->WriteMiniDump(GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION)
	{

	}*/
}

生成的dmp文件

sh


Windows程序崩溃,操作系统会生成一个崩溃转储(Dump)文件,用于帮助开发人员诊断和调试程序故障。这个Dump文件记录程序崩溃的内存状态,包括堆栈信息、寄存器状态、变量值等关键数据。 生成Dump文件的方法有多种,例如: 1. 使用Windows上自带的任务管理器。打开任务管理器,在“进程”选项卡中找到崩溃程序进程,右键点击选择“创建转储文件”即可生成Dump文件。 2. 使用Windows上自带的Dr.Watson工具(仅适用于旧版本)。Dr.Watson是一种活动监视工具,它会在程序崩溃自动记录信息,生成.DMP文件。可以在Windows注册表中启用Dr.Watson功能。 3. 使用Windows Debugging Tools。这是一套由微软提供的调试工具,其中包括了生成Dump文件的命令行工具Dumpchk、Msdia.dll等。使用这些工具可以对Dump文件进行详细的调试和分析。 一旦生成了Dump文件,开发人员可以使用各种调试工具来分析这个文件,以寻找程序崩溃的原因。比如,可以使用Visual Studio自带的调试器或WinDbg调试器来打开Dump文件,并逐步调试程序,查看导致崩溃的代码行。 Dump文件对于故障排除非常有用,可以帮助开发人员定位和解决程序中的错误。通过分析Dump文件,我们可以获得崩溃的内存状态,从而找到导致崩溃的具体代码,修复问题,提升软件的稳定性和可靠性。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郎涯技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值