Dump文件的生成和使用

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接: https://blog.csdn.net/lk142500/article/details/80563552

1 簡介

第一次遇到程序崩潰的問題,以前爲單位開發了一個插件程序,在本機運行沒有出現問題,但把生成的可執行文件拷貝到服務器上一運行程序,剛進入插件代碼,插件服務就崩潰了,當時被這個問題整的很慘,在同事的幫助下了解到,對於程序崩潰,最快的解決方式是生成dump文件,經過生成dump文件使用調試工具進行調試,還原程序崩潰時的狀態,可以起到快速定位排查問題的做用。Dump文件是進程的內存鏡像。能夠把程序的執行狀態經過調試器保存到dump文件中。Dump文件是用來給驅動程序編寫人員調試驅動程序用的,這種文件必須用專用工具軟件打開,好比使用WinDbg、VS打開。由於第一次遇到此類問題,徹底沒有頭緒,但同事很快經過dump文件很快定位到空指針問題,秉承着遇到的問題在遇到第二次不能再是問題的原則,對dump文件的含義、生成、做用、分析、定位排查的過程進行說明,算是對遇到的程序崩潰的問題總結。
本文檔適用於開發人員。php

2 Dump文件的含義和做用

2.1 Dump文件的類型

Windows下Dump文件分爲兩大類,內核模式Dump和用戶模式Dump。內核模式Dump是操做系統建立的崩潰轉儲,最經典的就是系統藍屏,這時候會自動建立內核模式的Dump。用戶模式Dump進一步能夠分爲完整Dump(Full Dump)和迷你Dump(Minidump)。完整Dump包含了某個進程完整的地址空間數據,以及許多用於調試的信息,而Minidump則有許多類型,根據須要能夠包含不一樣的信息,有的可能只包含某個線程和部分模塊的信息。在程序開發過程當中出現的應用崩潰屬於用戶模式Dump。所以,要弄清楚這種Dump文件的組成、生成方式、做用。html

2.2 Dump文件的做用

Dump文件是進程的內存鏡像,能夠把程序的執行狀態經過調試器保存到dump文件中。主要是用來在系統中出現異常或者崩潰的時候來生成dump文件,而後用調試器進行調試,這樣就能夠把生產環境中的dmp文件拷貝到本身的開發機上,調試就能夠找到程序出錯的位置。
在C++編程實踐中,一般都會遇到內存訪問無效、無效對象、堆棧溢出、空指針調用等常見的C/C++問題,而這些問題最後常會致使:系統崩潰。爲解決崩潰問題經常使用的手段一個就是生成dump文件進行代碼調試,另一個就是使用遠程調試remote debugger進行調試。但remote debugger在要求程序源代碼和可執行文件在同一個局域網內,對環境的要求較高。所以對於程序崩潰較好的解決方式即是生成dump文件進行解析,快速定位到程序崩潰位置,對問題進行排查。在本次插件崩潰的過程當中,程序崩潰的兩行代碼以下:程序員

NETSDKPLUGIN_TRACE("- CHikNetDevice::SetSipConfig Starts");
	std::string ServerIp;
	int ServerPort;
	std::string UserName;
	std::string Password;
	int enabledAutoLogin;
	std::string localNo;
	int loginCycle;
	DWORD errCode;
	char szLan[128] = {0};
	if (!GetCallParam(*ParamNode, enabledAutoLogin, ServerIp, ServerPort, localNo,loginCycle, Msg))
	{
		NETSDKPLUGIN_ERROR("The parameter passed to config the sip configuration is invalid in CHikNetDevice::SetSipCOnfig");
		return DEV_ERR_FAILED;
	}
    //%s對應的是char*,若傳入了std::string,程序在此崩潰。調用std::string對象的c_str()能夠生成對應的const char*
    NETSDKPLUGIN_DEBUG("- enabledAutoLogin: %d, ServerIp: %s, ServerPort: %d, UserName: %s,Password:*******,localNo: %s, loginCycle: %d",
        enabledAutoLogin, ServerIp, ServerPort, UserName.c_str(), localNo.c_str(), loginCycle);

在程序運行的過程當中,在插件打印了"- CHikNetDevice::SetSipConfig Starts"以後,程序崩潰,這能夠經過日誌打印出來,在最下面NETSDKPLUGIN_DEBUG函數中,對應%s,應爲C風格字符串指針,而傳入的倒是C++ std::string類型的對象,致使了程序崩潰。
以後的崩潰代碼以下:數據庫

ISpVoice *pVoice = NULL;  
	if (FAILED(::CoInitialize(NULL)))  
		return FALSE;  
	HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
	if (SUCCEEDED(hr) && (NULL != pVoice))  
	{  
		CComPtr <ISpStream> cpWavStream;  
		CComPtr <ISpStreamFormat> cpOldStream;  
		CSpStreamFormat originalFmt;
		hr = pVoice->GetOutputStream(&cpOldStream);
        //在沒有聲卡的狀況下pVoice->GetOutputStream()中cpOldStream會生成空指針
        //以前的代碼並有對hr和cpOldStream進行非空判斷,致使了程序在此處發生崩潰,由於生成了空指針
        if (FAILED(hr) || NULL == cpOldStream)
        {
            TALKCLIENTPLUGIN_ERROR("- GetOutputStream failed, lastError:[%d][%d]", hr, GetLastError());
            return FALSE;
        }
		originalFmt.AssignFormat(cpOldStream);
		char SaveName[30];
		// 基於當前系統的當前日期/時間
		time_t now = time(0);
		tm *t = localtime(&now);
		sprintf_s(SaveName, "%d-%d-%d %d-%d-%d.wav", t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); 
		strcpy_s(SzFileName, strlen(SaveName)+1, SaveName);
		hr = SPBindToFile(SaveName, SPFM_CREATE_ALWAYS, &cpWavStream, &originalFmt.FormatId(), originalFmt.WaveFormatExPtr());

上述代碼片斷藍色劃線處originalFmt.AssignFormat(cpOldStream)的函數體編程

HRESULT AssignFormat(ISpStreamFormat * pStream)
{
    ::CoTaskMemFree(m_pCoMemWaveFormatEx);
    m_pCoMemWaveFormatEx = NULL;
    HRESULT hr = pStream->GetFormat(&m_guidFormatId, CoMemWaveFormatEx);
    if (SUCCEEDED(hr) && m_pCoMemWaveFormatEx)
    {
        if (m_pCoMemWaveFormatEx->wFormatTag == WAVE_FORMAT_PCM)
        {
            m_pCoMemWaveFormatEx->cbSize = 0; // Always set ze to zero for WAVE_FORMAT_PCM.
        }
        if (m_pCoMemWaveFormatEx->nAvgBytesPerSec == 0 ||
            m_pCoMemWaveFormatEx->nBlockAlign == 0 ||
            m_pCoMemWaveFormatEx->nChannels == 0)
        {
            Clear();
            hr = E_INVALIDARG;
        }
    }
    return hr;
}

用了pStream->GetFormat()函數,而在以前,若聲卡被禁用或者在遠程桌面未設置下圖,則pStream爲空指針,而在使用以前並無進行cpOldStream非空判斷,所以程序崩潰,致使程序出現了崩潰。
這裏寫圖片描述
而星辰在定位這個問題時在main函數中插入了dump文件生成的控制代碼,很快便定位到了該空指針異常。windows

2.3 Dump文件的生成

程序在運行時,不免會有一些異常狀況發生,特別是在條件不允許去掛調試器的時候,如何快速的定位錯誤的方法就顯得很重要。
都是一種很重要的定位錯誤的方法,出得好的日誌能夠方便程序員快速的定位問題所在。但日誌有時也顯不足:
日誌有時只能定位大致錯誤範圍,卻沒法確認問題所在,好比程序抓到一個未知的異常。
沒有機會來出日誌,或者能出日誌的時候已經沒法得到和錯誤相關的信息,好比程序崩潰的時候。
日誌明顯不足的時候,把進程中相關數據DUMP下來分析就是一個比較實用方便的方法。不少應用都會提供這類功能,以便在程序出現問題時能夠把相關的數據發給開發者,方便開發者分析問題。相似Office這樣的應用都會有這個功能,當應用崩潰時會彈出對話框,提示是否發送錯誤相關的數據。
因爲Dump文件可以保存程序內部的內存、堆棧、句柄、線程等程序運行相關的信息,很是具備重要性,所以瞭解如何生成Dump文件也是避免茫然無措,不知如何下手場景的途徑之一。數組

2.3.1 經過使用任務管理器生成

該方式能夠生成.DMP文件,經過打開任務管理器,找到插件服務對應的進程,右擊,選擇建立轉儲文件:
這裏寫圖片描述
.DMP文件的存放位置以下圖所示:
這裏寫圖片描述
生成的轉儲文件能夠經過VS打開,可是正常運行的程序生成.DMP文件並無什麼大的做用。上述的方法要求在程序崩潰時並不直接退出時纔可使用,通常場景下,程序崩潰比較粗暴,所以可使用下述的方式建立Dump文件服務器

2.3.2 經過編程自動生成

當程序遇到未處理異常(主要指非指針形成)致使程序崩潰死,若是在異常發生以前調用了SetUnhandledExceptionFilter()函數,異常交給函數處理。MSDN中描述爲:
Issuing SetUnhandledExceptionFilter replaces the existing top-level exception filter for all existing and all future threads in the calling process.
於是,在程序開始處增長SetUnhandledExceptionFilter()函數,並在函數中利用適當的方法生成Dump文件,便可實現須要的功能。
在編程過程當中,能夠預期的異常都經過結構化異常(try/catch)進行了處理。此時,若是發生了未預期的異常,這些異常處理代碼沒法處理,則轉由Windows提供的默認異常處理器來進行處理,這個特殊的異常處理函數爲UnhandledExceptionFilter。該函數會顯示一個消息框,提示發生了未處理的異常,同時,讓用戶選擇結束或調試該進程。也就是以下界面:
所以,爲了更友好的處理未預期的異常(主要是建立內存轉儲),能夠覆蓋默認的異常處理操做。這是經過函數SetUnhandledExceptionFilter完成的,函數原型以下:markdown

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
        _In_ LPTOP_LEVEL_EXCEPTION_FILTER  lpTopLevelExceptionFilter

lpTopLevelExceptionFilter即異常處理函數指針,若是設置爲NULL,則默認使用UnhandledExceptionFilter。所以咱們能夠對照lpTopLevelExceptionFilter自定義一個異常處理函數。咱們須要建立內存轉儲。這經過函數MiniDumpWriteDump來實現。
下述代碼是一個經過MiniDumpWriteDump函數來實現轉儲文件建立架構

LONG WINAPI MyUnhandledExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo )
{
    HANDLE hFile = CreateFile("mini.dmp", GENERIC_READ|GENERIC_WRITE,
        FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if( hFile == INVALID_HANDLE_VALUE )
        return EXCEPTION_EXECUTE_HANDLER;

    MINIDUMP_EXCEPTION_INFORMATION mdei;
    mdei.ThreadId = GetCurrentThreadId();
    mdei.ExceptionPointers = ExceptionInfo;
    mdei.ClientPointers = NULL;
    MINIDUMP_CALLBACK_INFORMATION mci;  
    mci.CallbackRoutine     = NULL;  
    mci.CallbackParam       = 0;  

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci);  

    CloseHandle(hFile);

    AfxMessageBox("已成功建立崩潰轉儲!");

     return EXCEPTION_EXECUTE_HANDLER;
}

本機調試代碼,出現異常時出現的彈窗即UnhandledExceptionFilter爲默認的異常處理器工做產生的,此時能夠點擊中斷或者繼續,而在對應的右下方能夠看到調用堆棧,對於咱們排查定位很是有幫助。
這裏寫圖片描述

2.3.3 修改註冊碼生成

修改註冊碼的方式,沒有使用過,但經過查詢網上的材料,總結以下:

2.3.3.1 打開註冊表

Win + R 輸入regedit打開註冊表

2.3.3.2 依次找到以下對應項

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\WindowsError Reporting\LocalDumps

2.3.3.3 添加項

在該欄目下添加項如圖所示:
這裏寫圖片描述
這樣能夠保證假若程序故障後自行退出,則此方法就難以應用。不過,咱們能夠在註冊表中添加以下信息已確保系統在程序崩潰後自行保存一個dump文件。

2.3.4 經過編程自動生成實踐

單位對minidump文件生成方式經過編程進行了封裝。頭文件定義以下:

enum CRASHAPI_DUMP_TYPE
{
    MiniDumpType = 0x00000000,/*MiniDumpNormal*/
    FullDumpType = 0x00009b67 /*Full*/
};
    /************************************************************************** * Function: CrashAPI_Init * Description: Init the Crash API lib.Call this function as early in the start-up process as possible. * Input: (null) * Output: (null) * Return: returns true on success. **************************************************************************/
    CRASH_EXTERN bool CRASH_API CrashAPI_Init();

    /************************************************************************** * Function: CrashAPI_Uninit * Description: uninstall the library * Input: (null) * Output: (null) * Return: none **************************************************************************/
    CRASH_EXTERN void CRASH_API CrashAPI_Uninit();

    /************************************************************************** * Function: CrashAPI_SetDumpPath * Description: set the minidump file path.the file will be generated in the current dictionary if you don't set it. * Input: dump_path the path of the * Output: (null) * Return: returns true on success. **************************************************************************/
    CRASH_EXTERN bool CRASH_API CrashAPI_SetDumpPath(char * dump_path);

    /************************************************************************** * Function: CrashAPI_SetDumpType * Description: set the minidump file type.the file will be MiniDumpNormal if you don't set it. * Input: dump_type of MINIDUMP_TYPE * Output: (null) * Return: returns true on success. **************************************************************************/
    CRASH_EXTERN bool CRASH_API CrashAPI_SetDumpType(CRASHAPI_DUMP_TYPE dump_type);

    /************************************************************************** * Function: CrashAPI_WriteMinidump * Description: writes a minidump immediately.it can be used to * capture the execution state independently of a crash. * Input: (null) * Output: (null) * Return: returns true on success. **************************************************************************/
    CRASH_EXTERN bool CRASH_API CrashAPI_WriteMinidump();

    /************************************************************************** * Function: CrashAPI_SetCallBack * Description: Set the call back function which will be called when the crash occurs. * Input: (null) * Output: (null) * Return: returns true on success. **************************************************************************/
    CRASH_EXTERN bool CRASH_API CrashAPI_SetCallBack(CrashCallback callback);

在插件程序的main函數中插入以下代碼行,便可在插件崩潰時自動生成dump文件。

#include "stdafx.h"
#include "Socket\CompleteSocket.h"
#include "DeviceManager.h"
#include <signal.h>
#include "CrashAPI.h"

#pragma comment(lib, "CompleteSocket_md.lib")
#pragma comment(lib, "CrashAPI.lib")
int _tmain(int argc, _TCHAR* argv[])
{CrashAPI_Init();
    CrashAPI_SetDumpType(FullDumpType);
hlog_init("DA");hlog_fini();
    CrashAPI_Uninit();return ret;

}

在程序的main函數中添加頭文件中對應的CrashAPI_Init、CrashAPI_Uninit,而且設置生成dump文件的類型。在代碼中設置生成的dump文件類型爲FullDumpType。_tmain()函數所在的模塊是所寫插件的調用層DeviceAccess。由於添加了」CrashAPI.h」頭文件,同時#pragma comment(lib," CrashAPI.lib ")表示連接CrashAPI.lib這個庫。 和在工程設置裏寫上鍊入CrashAPI.lib的效果同樣,不過這種方法寫的 程序別人在使用你的代碼的時候就不用再設置工程settings了。
想要使得這些崩潰的dump文件生成的代碼生效,還須要把CrashAPI.lib對應的DLL文件拷貝到DeviceAccess的Release目錄下,該目錄存放了可執行文件和依賴的DLL,發佈程序時把包含該CrashAPI.dll在內的Release同時發佈到服務器上。以服務的方式啓動便可。
這樣在程序崩潰時,程序自動生成dump文件。若dmp文件是exe在另外一臺機器上產生的,則咱們最好把exe,pdb,dmp放到同一文件夾下,必須保證pdb與出問題的exe是同一時間生成的,用VS打開dmp文件後還須要設置符號表文件路徑和源代碼路徑,必須保證.exe,pdb,dmp是同一時間產生的,則直接點擊調試便可直接進入程序中斷,這樣經過查看調用堆棧,便可快速定位問題。

3 如何使用Dump文件排查崩潰問題

3.1 程序架構

插件程序的架構以下,我負責開發的模塊對接設備,即經過調用SDK調用報警盒子和中心管理機進行呼叫、廣播、掛斷,這是經過代理實現的。DeviceAccess包含main函數,其主要是進行接收socket數據報,而後對數據報進行解析。HikTalkClientPlugin是開發的代理設備接口,該項目導出HikTalkClientPlugin.dll供DeviceAccess進行調用,包括鏈接、斷開鏈接、調用廣播呼叫等功能。集成到服務器上時,以服務的形式運行了DeviceAccess,由其接收socket數據報,並經過對接對講平臺進行設備功能的調用。

3.2 調試過程

3.2.1 編譯HikTalkClientPlugin

首先編譯運行HikTalkClientPlugin生成對應的HikTalkClientPlugin.dll和HikTalkClientPlugin.pdb。而後把生成的HikTalkClientPlugin.dll和HikTalkClientPlugin.pdb複製到DeviceAccess項目的Release的指定目錄下(由於DeviceAccess的可執行程序要調用HikTalkClientPlugin.dll嘛,因此確定要和DeviceAccess一塊兒發佈的)。

3.2.2 編譯DeviceAccess

以Release模式編譯並運行DeviceAccess項目,能夠在輸出目錄中生成可執行文件,截圖以下:
這裏寫圖片描述

3.2.3 服務方式啓動程序

壓縮成rar並拷貝到服務器上以服務的方式啓動,讓前段發出設備操做請求,安靜等待程序崩潰。和預期同樣,程序崩潰,生成了dump文件。如圖所示:
這裏寫圖片描述

3.2.4 拷貝.dmp文件到開發機

把在服務器端生成的.dmp文件拷貝到開發機DeviceInterfaceAgent.exe對應的位置,在該目錄須要有DeviceInterfaceAgent.pdb。pdb文件,是VS生成的用於調試的符號文件(program database),保存着調試的信息。在VS的工程屬性,C/C++,調試信息格式,設置/Zi,那麼VS就會在構建項目時建立PDB文件。須要保證源代碼、pdb文件、可執行文件是與服務器上相同的版本,這樣才能夠進行正常的調試。

3.2.5 VS打開.dmp文件

使用VS打開.dmp文件進行調試,會發現程序直接在程序崩潰處停了下來。此時,查看調用堆棧信息[若沒有,點擊Alt + 7便可出現]。經過查看調用堆棧便可快速定位。

3.2.6 經過調用堆棧定位排查問題

經過調用堆棧定位排查問題,能夠看到如2.2中第二個崩潰緣由是空指針異常,所以找到空指針出現的位置,並在經過函數爲指針賦值以後添加空指針判斷和操做成功的判斷。發現這是HikTalkClientPlugin插件的問題,所以修改HikTalkClientPlugin的源代碼,從新編譯生成DLL,並把HikTalkClientPlugin.dll和HikTalkClientPlugin.pdb文件拷貝到服務器上Release/hplugin/ HikTalkClientPlugin目錄內,由於DeviceInterfaceAgent並無修改源代碼,無需變更。從新啓動服務接收socket請求。
以一樣的方式定位註冊時插件崩潰的問題,修改HikNetSdkClientPlugin源代碼,從新編譯HikNetSdkClientPlugin,生成HikNetSdkClientPlugin.pdb和HikNetSdkClientPlugin.dll,把該兩個文件拷貝至服務器Release/hplugin/HikNetSdkClientPlugin內,從新啓動服務,插件再也不崩潰。問題獲得解決。

4 實踐過程

以VS爲例,在開發機上開發代碼時,若是程序崩潰而且崩潰時並非直接退出,那麼點擊中斷以後的界面即爲調試Dump文件的情景。所以,dump文件對於這種本地開發或許做用並不大,可是若是程序在服務器端崩潰,那麼此時生成的Dump文件並不是常重要,它能夠避免你在一個較大的項目代碼面前茫然無措。但下面的代碼片斷均爲本地機上的代碼,而且異常也是刻意爲之,只是爲了演示dmp文件的生成和調試。其中的代碼均爲在網上搜索到,僅用來演示使用。

4.1 DumpTest1

4.1.1 新建項目DumpTest1

添加頭文件CCreateDump.h,代碼片斷以下:

#pragma once
#include <string>

using namespace std;
class CCreateDump
{
public:
    CCreateDump();
    ~CCreateDump(void);
    static CCreateDump* Instance();
    static long __stdcall UnhandleExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo);
    //聲明Dump文件,異常時會自動生成。會自動加入.dmp文件名後綴
    void DeclarDumpFile(std::string dmpFileName = "");
private:
    static std::string    strDumpFile; 
    static CCreateDump*    __instance;
};

添加CcreateDump.cpp,代碼片斷以下:

#include <Windows.h>
#include "CCreateDump.h"
#include <DbgHelp.h>
#pragma comment(lib, "dbghelp.lib")

CCreateDump* CCreateDump::__instance = NULL;
std::string CCreateDump::strDumpFile = "";

CCreateDump::CCreateDump()
{
}

CCreateDump::~CCreateDump(void)
{

}

long  CCreateDump::UnhandleExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo)
{
    HANDLE hFile   =   CreateFile(strDumpFile.c_str(), 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   =   ExceptionInfo;
        ExInfo.ClientPointers   =   FALSE;
        // write the dump
        BOOL   bOK   =   MiniDumpWriteDump(GetCurrentProcess(),   GetCurrentProcessId(),   hFile,   MiniDumpNormal,  &ExInfo,   NULL,   NULL   );
        CloseHandle(hFile);
        if (!bOK)
        {
            DWORD dw = GetLastError();
            //寫dump文件出錯處理,異常交給windows處理
            return EXCEPTION_CONTINUE_SEARCH;
        }
        else
        {    //在異常處結束
            return EXCEPTION_EXECUTE_HANDLER;
        }
    }
    else
    {
        return EXCEPTION_CONTINUE_SEARCH;
    }
}

void CCreateDump::DeclarDumpFile(std::string dmpFileName)
{
    SYSTEMTIME syt;
    GetLocalTime(&syt);
    char c[MAX_PATH];
    sprintf_s(c,MAX_PATH,"[%04d-%02d-%02d %02d:%02d:%02d]",syt.wYear,syt.wMonth,syt.wDay,syt.wHour,syt.wMinute,syt.wSecond);
    strDumpFile = std::string(c);
    if (!dmpFileName.empty())
    {
        strDumpFile += dmpFileName;
    }
    strDumpFile += std::string(".dmp");
    SetUnhandledExceptionFilter(UnhandleExceptionFilter);
}

CCreateDump* CCreateDump::Instance()
{
    if (__instance == NULL)
    {
        __instance = new CCreateDump;
    }
    return __instance;
}

添加測試程序Test.cpp,代碼片斷以下:

#include <Windows.h>
#include "CCreateDump.h"

int main(void)
{
    CCreateDump::Instance()->DeclarDumpFile("dumpfile");
    int *p = NULL;
    *p =5;
    return 0;
}

能夠清楚的看到,在測試程序中使用了空指針,即對空指針解引用,並對其進行賦值操做,違法操做。

4.1.2 使用everything查找dbghelp.dll !

把該dbghelp.dll放置在該項目的目錄下,即右擊項目,打開資源管理器所在目錄。

4.1.3 生成dump文件

在VS中進行以下的配置:
這裏寫圖片描述
屬性–>連接器—>調試–>生成調試信息–>是。
屬性–>配置屬性–>常規–>字符集–>使用多字節字符集
點擊調試–>開始執行(不調試)–>查看運行結果
這裏寫圖片描述
注意:若是點擊了調試–>啓動調試,程序直接崩潰,但沒有退出,程序所呈現的界面即爲使用dmp文件調試bug的界面。能夠在項目所在目錄下看到dmp文件已經生成,以下圖所示:
這裏寫圖片描述

4.1.4 調試過程

4.1.4.1 拷貝dmp文件到exe、pdb文件所在目錄

找到程序的輸出目錄,在該目錄下能夠看到兩個文件生成,DumpTest1.exe, DumpTest1.pdb,把以前生成dmp文件拷貝到該目錄下。

4.1.4.2 使用VS打開dmp文件觀看運行界面

Microsoft Visual Studio給出的彈窗提示寫入位置爲0x00000000,而調用堆棧能夠指示出程序崩潰時的位置。經過這兩個位置能夠快速的幫助咱們定位出問題代碼。

4.2 DumpTest2

4.2.1 新建項目DumpTest2

添加代碼片斷minidump.h以下:

#pragma once
#include <windows.h>
#include <imagehlp.h>
#include <cstdlib>
#include <tchar.h>

#pragma comment(lib, "dbghelp.lib")
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;
}

//建立Dump文件
inline void CreateMiniDump(EXCEPTION_POINTERS* pep, LPCTSTR strFileName)
{
    HANDLE hFile = CreateFile(strFileName, GENERIC_WRITE, 0, 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     = FALSE;
        MINIDUMP_CALLBACK_INFORMATION mci;
        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); 
    }
}

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
    return NULL;
}
BOOL PreventSetUnhandledExceptionFilter()
{
    HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
    if (hKernel32 ==   NULL)
        return FALSE;
    void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");

    if(pOrgEntry == NULL)
        return FALSE;
    unsigned char newJump[ 100 ];
    DWORD dwOrgEntryAddr = (DWORD) pOrgEntry;
    dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far
    void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
    DWORD dwNewEntryAddr = (DWORD) pNewFunc;
    DWORD dwRelativeAddr = dwNewEntryAddr -  dwOrgEntryAddr;
    newJump[0] = 0xE9;  // JMP absolute
    memcpy(&newJump[ 1 ], &dwRelativeAddr, sizeof(pNewFunc));
    SIZE_T bytesWritten;
    BOOL bRet = WriteProcessMemory(GetCurrentProcess(),    pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten);
    return bRet;
}

LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException)
{
    TCHAR szMbsFile[MAX_PATH] = { 0 };
    ::GetModuleFileName(NULL, szMbsFile, MAX_PATH);
    TCHAR* pFind = _tcsrchr(szMbsFile, '\\');
    if(pFind)
    {
        *(pFind+1) = 0;
        _tcscat(szMbsFile, _T("CreateMiniDump.dmp"));
        CreateMiniDump(pException,szMbsFile);
    }
    // TODO: MiniDumpWriteDump
    FatalAppExit(-1,  _T("Fatal Error"));
    return EXCEPTION_CONTINUE_SEARCH;
}
//運行異常處理
void RunCrashHandler()
{
    SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);
    PreventSetUnhandledExceptionFilter();
}

添加測試代碼片斷,main.cpp,以下:

#include "minidump.h"
#include "cstdio"

class CrashTest
{
public:
    void Test()
    {
        Crash();
    }
private:
    void Crash()
    {
        strcpy(NULL,"adfadfg");
    }
};
int main(int argc, char* argv[])
{
    //設置異常處理函數
    RunCrashHandler();
    CrashTest test;
    test.Test();
    getchar();

    return 0;
}

4.2.2 使用everything查找dbghelp.dll

同上

4.2.3 生成dmp文件並使用vs2008調試dmp

這裏寫圖片描述
過程如上所示,所以再也不贅述。能夠經過VS2008的堆棧幀函數調用層次。


5 注意事項

5.1 pdb文件

程序數據庫 (.pdb) 文件(也稱爲符號文件)將你在類、方法和其餘代碼的源文件中建立的標識符映射到在項目的已編譯可執行文件中使用的標識符。 .pdb 文件還能夠將源代碼中的語句映射到可執行文件中的執行指令。 調試器使用此信息肯定兩個關鍵信息:顯示在 Visual Studio IDE 中的源文件和行號,以及可執行文件中在設置斷點時要中止的位置。 符號文件還包含源文件的原始位置以及(可選)源服務器的位置(可從中檢索源文件)。
在 Visual Studio IDE 中調試項目時,調試器須要知道查找代碼的 .pdb 和源文件的確切位置。 若是要在項目源代碼以外調試代碼(如項目調用的 Windows 或第三方代碼),則你必須指定 .pdb(也能夠是外部代碼的源文件)的位置,這些文件須要與可執行文件徹底匹配。pdb文件主要存儲了以下調試信息:
(1)public, private,和static函數地址。
(2)全局變量的名稱和地址。
(3)參數和局部變量的名稱及它們在棧中的偏移量。
(4)類型定義,包括class, structure,和 data definitions。

5.2 exe、dll和pdb一致性問題

調試時,系統會查找exe或者dll中指定位置的pdb文件,而且會跟蹤exe或者dll中pdb的校驗碼GUID來對現有的pdb文件進行版本校驗。這裏須要知道,即便源碼沒有作任何更改,該pdb文件對應的校驗碼也是不一樣的。詳細瞭解能夠參考引用。

6 C++崩潰常見問題

在編程實踐中,遭遇到了諸如內存無效訪問、無效對象、內存泄漏、堆棧溢出等不少C / C++ 程序員常見的問題,最後都是同一個結果:程序崩潰,爲解決崩潰問題,過程都是很是讓人難以忘懷的;
可謂吃一塹長一智,出現過幾回這樣的折騰後就尋思找出它們的原理和規律,把這些典型的編程錯誤一網打盡,通過系統性的分析和梳理,發現其內在機理大同小異,經過對錯誤表現和原理進行分類分析,把各類致使崩潰的錯誤進行歸類,詳細分類以下:
錯誤類型 具體表現 備註(案例)
聲明錯誤 變量未聲明 編譯時錯誤
初始化錯誤 未初始化或初始化錯誤 運行不正確
訪問錯誤 一、 數組索引訪問越界
二、 指針對象訪問越界
三、 訪問空指針對象
四、 訪問無效指針對象
五、 迭代器訪問越界
六、 空指針調用函數
內存泄漏 一、 內存未釋放
二、 內存局部釋放
參數錯誤 本地代理、空指針、強制轉換
堆棧溢出 調用堆棧溢出:
一、遞歸調用
二、循環調用
三、消息循環
四、大對象參數
五、大對象變量 參數、局部變量都在棧(Stack)上分配
轉換錯誤 有符號類型和無符號類型轉換
內存碎片 小內存塊重複分配釋放致使的內存碎片,最後出現內存不足 數據對齊,機器字整數倍分配
其它如內存分配失敗、建立對象失敗等都是容易理解和相對少見的錯誤,由於目前的系統大部分狀況下內存夠用;此外除0錯誤也是容易理解和防範;

7 引用

  1. https://www.cnblogs.com/zhoug2020/p/6025388.html
  2. https://blog.csdn.net/hustd10/article/details/52075265
  3. https://blog.csdn.net/itworld123/article/details/79061296

8 下載

https://download.csdn.net/download/lk142500/10545386

 

參考:Windows下dump文件生成與分析

出處:https://blog.csdn.net/lk142500/article/details/80563552

相關文章
相關標籤/搜索