1. 程式人生 > >WinDbg除錯 崩潰程式

WinDbg除錯 崩潰程式

目的:學習和記錄WinDbg的一些使用。

宣告:文章裡面的很多東西都是取自網路上面,有出去的我會盡量註明出處。對於那些找不到地址的如果作者需要我可以後面新增。

1.安裝和下載

        WinDbg的下載地址,現在直接搜尋官網的時候感覺好難找到直接下載地址,都是一些什麼下載sdk什麼的,其實我就想安裝一個WinDbg而已,這裡給出兩個下載連結:

版本不是最新的,如果需要最新的可以自己在網上搜索一下,當然還有很多漢化版的(我個人喜好原版)。

下載好了直接安裝就行了。

1.1關於配置

         這個主要就是Symbol的問題了,網上一般都會推薦 設定環境變數例如:我的電腦>右鍵選單>屬性>高階選項卡>環境變數>系統變數>新建


變數名: _NT_SYMBOL_PATH
    變數值:SRV*F:\websymbols*
http://msdl.microsoft.com/download/symbols
"F:\websymbols"這個路徑可以自己設定。

如果不設定環境變數,那麼每次使用的時候記得去新增Symbol路徑就行了,Ctrl+S開啟windbg符號設定框(或者是File選單下面選擇Symbol File Path),開啟之後填入

有多個Symbol路徑中用";"處理。

2.dump檔案的獲得

        一種情況是,如果在測試人員那裡發現崩潰,我們就可以使用WinDbg去抓取dump檔案 。

1).看一下工作管理員,如果崩潰的程序還在就可以抓取。

2).開啟windbg,file--attach to a process,選擇崩潰的程序。

3).使用命令.dump /mf d:\testdemo.dmp 生成dmp檔案

具體的命令細節可以參考其他文件或者是網上搜索。

        第二種情況,如果是在客戶手裡,我們不太可能去安裝一個WinDbg去抓取dump檔案,所以我們需要程式能夠自動生成,這個網上也有很多原始碼。

注意:程式碼都依賴dbghelp.dll和對應的標頭檔案,這個可以在WinDbg的安裝路徑下面獲取到,需要把對應的dll檔案和你的執行程式放到一起,否則是不能收集到dmp的。

具體到專案的時候,可以修改下面的程式碼自動把dmp檔案通過網路傳送回來,或者是 有問題了叫使用者把對應的dmp檔案直接傳送給你也可以。

c版本

minidmp.h

#pragma once
#include <windows.h>
#include <stdlib.h>
#include <tchar.h>
#include <DbgHelp.h>

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

#ifdef UNICODE
#define TSprintf	wsprintf
#else
#define TSprintf	sprintf
#endif

// 啟動自動生成dump檔案的話,只需要在main函式開始處
// 呼叫該函式(EnableAutoDump)即可
void EnableAutoDump();
// 其它函式
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException);
void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException);

LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)  
{   
	TCHAR szDumpDir[MAX_PATH] = {0};
	TCHAR szDumpFile[MAX_PATH] = {0};
	TCHAR szMsg[MAX_PATH] = {0};
	SYSTEMTIME	stTime = {0};

	// 構建dump檔案路徑
	GetLocalTime(&stTime);
	::GetCurrentDirectory(MAX_PATH, szDumpDir);
	TSprintf(szDumpFile, _T("%s\\%04d%02d%02d_%02d%02d%02d.dmp"), szDumpDir, 
		stTime.wYear, stTime.wMonth, stTime.wDay,
		stTime.wHour, stTime.wMinute, stTime.wSecond);
	// 建立dump檔案
	CreateDumpFile(szDumpFile, pException);

	// 這裡彈出一個錯誤對話方塊並退出程式
	TSprintf(szMsg, _T("I'm so sorry, but the program crashed.\r\ndump file : %s"), szDumpFile);
	FatalAppExit(-1,  szMsg);  

	return EXCEPTION_EXECUTE_HANDLER;  
}  

void EnableAutoDump()
{
	SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);
}

inline void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)
{
	HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_READ | GENERIC_WRITE,
		0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if((hDumpFile != NULL) && (hDumpFile != INVALID_HANDLE_VALUE))
	{
		// Dump資訊
		MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
		dumpInfo.ExceptionPointers = pException;
		dumpInfo.ThreadId = GetCurrentThreadId();
		dumpInfo.ClientPointers = FALSE;

		// 寫入Dump檔案內容
		MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, 
			MiniDumpNormal, &dumpInfo, NULL, NULL);
		//MiniDumpWithFullMemoryInfo
		CloseHandle(hDumpFile);
	}
}

使用的時候在main裡面EnableAutoDump();一般是在其他執行之前呼叫。

c++版本

MiniDumper.h

/*
** Copyright (C) QPSOFT.COM All rights reserved.
*/

#ifndef INCLUDE_MINIDUMPER_H_
#define INCLUDE_MINIDUMPER_H_

#ifdef _MSC_VER

#include <string>

#include <windows.h>
#include <tchar.h>
#pragma comment(lib, "dbghelp.lib")

#include <dbghelp.h>
#pragma comment(lib, "user32.lib")

#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

namespace Dbg
{

typedef std::string StringA;
typedef std::wstring StringW;

#ifdef _UNICODE
typedef StringW String;
#else
typedef StringA String;
#endif // _UNICODE

typedef BOOL (WINAPI* PFNWRITEDUMP)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, PMINIDUMP_EXCEPTION_INFORMATION,
                                    PMINIDUMP_USER_STREAM_INFORMATION, PMINIDUMP_CALLBACK_INFORMATION);

class MiniDumper
{
protected:
    MiniDumper() { ::SetUnhandledExceptionFilter(&MiniDumper::TopLevelExceptionFilter); }
    ~MiniDumper() {};

private:
    MiniDumper(const MiniDumper&);
    const MiniDumper& operator=(const MiniDumper&);

public:
    static MiniDumper* Get()
    {
        static MiniDumper dumper;
        return &dumper;
    }

    void Init(const String& dumpFilePath = String(), const String& appVersion = _T("1.0.0.0"))
    {
        if (!dumpFilePath.empty() && ::PathIsDirectory(dumpFilePath.c_str()))
            m_DumpFilePath = dumpFilePath;
        else
        {
            TCHAR appPath[MAX_PATH];
            ::GetModuleFileName(NULL, appPath, MAX_PATH);
            ::PathRemoveFileSpec(appPath);
            m_DumpFilePath = appPath;
        }

        m_AppVersion = appVersion;
    }

    String GetDumpFile() const
    {
        SYSTEMTIME st;
        ::GetLocalTime(&st);
        TCHAR dt[11];
        wsprintf(dt, _T("%02d%02d%02d%02d%02d"), st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);

        TCHAR appPath[MAX_PATH];
        ::GetModuleFileName(NULL, appPath, MAX_PATH);
        ::PathRemoveExtension(appPath);

        String file(m_DumpFilePath);
        file += _T("\\");
        file += ::PathFindFileName(appPath);
        file += _T("_") + m_AppVersion + _T("_");
        file += dt;
        file += _T(".dmp");
        return file;
    }

private:
    static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS* ep)
    {
        LONG ret = EXCEPTION_CONTINUE_SEARCH;

        TCHAR dbgHelpDLL[MAX_PATH];
        ::GetModuleFileName(NULL, dbgHelpDLL, MAX_PATH);

        ::PathRemoveFileSpec(dbgHelpDLL);
        ::PathAppend(dbgHelpDLL, _T("dbghelp.dll"));

        HMODULE module = ::LoadLibrary(dbgHelpDLL);
        if (module == NULL)
		{
			::MessageBox(NULL, _T("提示"), _T("缺少dbghelp.dll"), MB_TOPMOST | MB_ICONSTOP);
            return ret;
		}

        PFNWRITEDUMP MiniDumpWriteDump = (PFNWRITEDUMP)::GetProcAddress(module, "MiniDumpWriteDump");
        if (MiniDumpWriteDump != NULL)
        {
            String dumpFile = MiniDumper::Get()->GetDumpFile();
            HANDLE file = ::CreateFile(dumpFile.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS,
                                       FILE_ATTRIBUTE_NORMAL, NULL);
            if (file == INVALID_HANDLE_VALUE)
                return ret;

            _MINIDUMP_EXCEPTION_INFORMATION mdei;
            mdei.ThreadId = ::GetCurrentThreadId();
            mdei.ExceptionPointers = ep;
            mdei.ClientPointers = NULL;

            MINIDUMP_TYPE mdt = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory |
                                                MiniDumpWithDataSegs |
                                                MiniDumpWithHandleData |
                                                MiniDumpWithFullMemoryInfo |
                                                MiniDumpWithThreadInfo /*|
                                                MiniDumpWithUnloadedModules*/);

            if (MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), file, mdt, &mdei, NULL, NULL))
            {
                ::MessageBox(NULL, dumpFile.c_str(), _T("Crash Report!"), MB_TOPMOST | MB_ICONSTOP);
                const String openDmp(_T("/select,") + dumpFile);
                ::ShellExecute(NULL, _T("open"), _T("explorer.exe"), openDmp.c_str(), NULL, SW_SHOWNORMAL);
                ret = EXCEPTION_EXECUTE_HANDLER;
            }

            CloseHandle(file);
        }

        return ret;
    }

private:
    String m_DumpFilePath;
    String m_AppVersion;
};

} // namespace qp

#endif // _MSC_VER

#endif // INCLUDE_MINIDUMPER_H_

呼叫方法 Dbg::MiniDumper::Get()->Init(); 也可以指定路徑和版本。

3.分析定位崩潰

我這裡說一下我自己的使用步驟。這裡假設程式為testdemo。

1.把testdemo.dmp檔案 和 testdemo.pdb testdemo.exe放在一個目錄(d:\test)

2.Windbg選擇 File->Open Crash Dump選擇你的dmp檔案

4.設定原始碼路徑ctrl+P 選擇 testdemo 工程路徑

5.在命令裡面輸入: !analyze –v  經過一段時間的等待就會出現 比較精確的崩潰位置

這裡有個前提就是你的程式碼沒有修改,還有對應的exe和客戶那裡的一樣的pdb未更新。所以我們在做專案的時候發一個版本需要把對應的程式碼備份或者是svn備註一下,否則你的程式碼不一致就會定位不到。

4.其他說明

        藉助Windbg可以幫助我們定位到崩潰的位置和其他的資訊,但是我們在做專案的時候可能程式碼量大,邏輯複雜,導致即便是定位到了崩潰點和觸發資訊,我們修改也是比較難的情況。這個時候就需要程式碼分析能力和程式碼的架構了,如果你程式碼分析能力強,架構清晰,是比較好修改了,如果程式碼結構混亂,這個時候修改很大情況會導致新的問題,並且修改也很難,      所以 前期的模組劃分,框架很重要。

當然最好是沒有bug或者是沒有不能重現的bug,可以多使用一些現有的成熟的框架,程式碼模組劃分一定要多花時間。