windows客戶端開發--讓你的客戶端崩潰之前生成dump檔案
debug時候我們可以很快速、精確的定位問題所在。
但是對於release版本,我們往往無能為力。
尤其面對一群難纏的客戶,情況就會更加糟糕。
而且對於release版本來說,crash的時候日誌系統往往起不到任何作用。而且,我們也不可能捕獲所有的異常,更何況,客戶端崩潰的原因都是我們捕獲不了的異常。
這就需要dump檔案了。
dump檔案是C++程式發生異常時,儲存當時程式執行狀態的檔案,是除錯異常程式重要的方法,所以程式崩潰時,除了日誌檔案,dump檔案便成了我們查詢錯誤的最後一根救命的稻草。
Not all bugs can be found prior to release, which means not all bugs that throw exceptions can be found before release. Fortunately, Microsoft has included in the Platform SDK a function to help developers collect information on exceptions that are discovered by users. The MiniDumpWriteDump
SetUnhandledExceptionFilter
呼叫SetUnhandledExceptionFilter註冊一個自定義的異常處理回撥函式,也就是在回撥函式中進行寫minidump檔案。
MiniDumpWriteDump
這個函式就是用來寫dump了:
#include <dbghelp.h>
#include <shellapi.h>
#include <shlobj.h>
int GenerateDump(EXCEPTION_POINTERS* pExceptionPointers)
{
BOOL bMiniDumpSuccessful;
WCHAR szPath[MAX_PATH];
WCHAR szFileName[MAX_PATH];
WCHAR* szAppName = L"AppName" ;
WCHAR* szVersion = L"v1.0";
DWORD dwBufferSize = MAX_PATH;
HANDLE hDumpFile;
SYSTEMTIME stLocalTime;
MINIDUMP_EXCEPTION_INFORMATION ExpParam;
GetLocalTime( &stLocalTime );
GetTempPath( dwBufferSize, szPath );
StringCchPrintf( szFileName, MAX_PATH, L"%s%s", szPath, szAppName );
CreateDirectory( szFileName, NULL );
StringCchPrintf( szFileName, MAX_PATH, L"%s%s\\%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
szPath, szAppName, szVersion,
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
GetCurrentProcessId(), GetCurrentThreadId());
hDumpFile = CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
ExpParam.ThreadId = GetCurrentThreadId();
ExpParam.ExceptionPointers = pExceptionPointers;
ExpParam.ClientPointers = TRUE;
bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);
return EXCEPTION_EXECUTE_HANDLER;
}
void SomeFunction()
{
__try
{
int *pBadPtr = NULL;
*pBadPtr = 0;
}
__except(GenerateDump(GetExceptionInformation()))
{
}
}
最後,獻上完整的程式碼:
新建一個nimidumo.h:
#pragma once
#include <windows.h>
#include <DbgHelp.h>
#include <stdlib.h>
#pragma comment(lib, "dbghelp.lib")
#ifndef _M_IX86
#error "The following code only works for x86!"
#endif
inline BOOL IsDataSectionNeeded(const WCHAR* pModuleName)
{
if (pModuleName == 0)
{
return FALSE;
}
WCHAR szFileName[_MAX_FNAME] = L"";
_wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);
if (_wcsicmp(szFileName, L"ntdll") == 0)
return TRUE;
return FALSE;
}
inline BOOL CALLBACK 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);
case IncludeModuleCallback:
case IncludeThreadCallback:
case ThreadCallback:
case ThreadExCallback:
return TRUE;
default:;
}
return FALSE;
}
inline void CreateMiniDump(PEXCEPTION_POINTERS pep, LPCTSTR strFileName)
{
HANDLE hFile = CreateFile(strFileName, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE))
{
MINIDUMP_EXCEPTION_INFORMATION mdei;
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = pep;
mdei.ClientPointers = NULL;
MINIDUMP_CALLBACK_INFORMATION mci;
mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
mci.CallbackParam = 0;
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpNormal, (pep != 0) ? &mdei : 0, NULL, &mci);
CloseHandle(hFile);
}
}
LONG __stdcall MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
{
CreateMiniDump(pExceptionInfo, L"core.dmp");
MessageBox(0, L"Error", L"error", MB_OK);
/*printf("Error address %x/n", pExceptionInfo->ExceptionRecord->ExceptionAddress);
printf("CPU register:/n");
printf("eax %x ebx %x ecx %x edx %x/n", pExceptionInfo->ContextRecord->Eax,
pExceptionInfo->ContextRecord->Ebx, pExceptionInfo->ContextRecord->Ecx,
pExceptionInfo->ContextRecord->Edx);*/
return EXCEPTION_EXECUTE_HANDLER;
}
// 此函式一旦成功呼叫,之後對 SetUnhandledExceptionFilter 的呼叫將無效
void DisableSetUnhandledExceptionFilter()
{
void* addr = (void*)GetProcAddress(LoadLibrary(L"kernel32.dll"),
"SetUnhandledExceptionFilter");
if (addr)
{
unsigned char code[16];
int size = 0;
code[size++] = 0x33;
code[size++] = 0xC0;
code[size++] = 0xC2;
code[size++] = 0x04;
code[size++] = 0x00;
DWORD dwOldFlag, dwTempFlag;
VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
}
}
void InitMinDump()
{
//註冊異常處理函式
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
//使SetUnhandledExceptionFilter
DisableSetUnhandledExceptionFilter();
}
#pragma once
在我們的main.cc中使用:
#include <iostream>
#include "minidump.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char * lpCmdLine, int nCmdShow)
{
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
_asm int 3 //只是為了讓程式崩潰
return 0;
}
這樣,在程式崩潰的時候,我們就會得到一個core.dump檔案。
接下來也是重點,如何根據dump檔案定位程式碼:
在VS中 檔案->開啟->檔案
然後就是進行除錯:
點選 使用僅限本機進行除錯
這裡需要注意的是:
dump檔案 pdb檔案 原始碼 要保持版本一直,否則就無法準確定位了。