1. 程式人生 > >windows客戶端開發--讓你的客戶端崩潰之前生成dump檔案

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

function writes the necessary crash dump information to a file without saving the whole process space. This crash dump information file is called a minidump. This technical article provides info about how to write and use a minidump.

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檔案 原始碼 要保持版本一直,否則就無法準確定位了。