異常處理與MiniDump詳解

異常處理與MiniDump詳解(4) MiniDump

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

討論新聞組及文件

一、   綜述

總算講到MiniDump了。

Dump有多有用我都無法盡數,基本上屬於定位錯誤修復BUG的倚天劍。(日誌可以算是屠龍刀)這些都是對於那些不是必出的BUG,放在外面運行的時候出現的BUG而言的,那些能夠通過簡單調試就能發現的BUG,一般都不足爲懼。

 

二、   基本應用

MiniDump之所以叫MiniDump,自然是有其Mini之處。。。(廢話),呵呵,MS提供了一個API函數,MiniDumpWriteDump,(在Dbghelp.h中聲明,需要導入DbgHelp.lib使用)所以我纔將其稱爲MiniDump,其實Dump也能表達同樣的意思。。。。

MiniDump最簡單的應用在於程序崩潰的時候,將崩潰時那一刻的信息寫進一個文件,以方便以後查找錯誤。使用方法說簡單就簡單,說難也難。

1.             怎麼感知到程序的崩潰?

Window提供了較爲方便的方法去感知到程序的幾種崩潰情況。

在《Breakpad在進程中完成dump的流程描述》一文中,我描述了一下Breakpad獲取到程序崩潰的方法,事實上,這也是典型的Windows下感知程序崩潰的方法,那篇文章是剛開始工作的時候,完成公司自己的ExceptionHandle庫的時候寫的工作筆記,現在看起來也還是有一定的參考價值。

Windows下感知程序崩潰(其實就是運行時的嚴重錯誤)的方法有3個核心的函數,分別如下:

SetUnhandledExceptionFilter(HandleException)確定出現沒有控制的異常發生時調用的函數爲HandleException.

_set_invalid_parameter_handler(HandleInvalidParameter)確定出現無效參數調用發生時調用的函數爲HandleInvalidParameter.

_set_purecall_handler(HandlePureVirtualCall)確定純虛函數調用發生時調用的函數爲HandlePureVirtualCall.

3個函數的使用方法一致,都是在發生自己關心的(見上面的描述)異常時,調用參數傳進來回調函數,Windows會將崩潰信息通過參數傳入回調函數,這時候就是進行Dump的絕佳時機。詳細的信息可以查閱MSDN,我這裏就不復制資料了,那樣有copy文檔之嫌,這裏以SetUnhandledExceptionFilter爲例,演示實際與MiniDumpWriteDump配合使用的情況。像這些比較複雜的API,MSDN中連個Example都沒有,說實話,當時掌握花了一點時間。

ExceptionExample:

#include <windows.h>

#include <Dbghelp.h>

using namespace std;

 

#pragma auto_inline (off)

#pragma commentlib"DbgHelp" )

 

// 爲了程序的簡潔和集中關注關心的東西,按示例程序的慣例忽略錯誤檢查,實際使用時請注意

LONG WINAPI MyUnhandledExceptionFilter(

struct _EXCEPTION_POINTERSExceptionInfo

    )

{

    HANDLE lhDumpFile = CreateFile(_T("DumpFile.dmp"), GENERIC_WRITE, 0, NULLCREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);

 

    MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;

    loExceptionInfo.ExceptionPointers = ExceptionInfo;

    loExceptionInfo.ThreadId = GetCurrentThreadId();

    loExceptionInfo.ClientPointers = TRUE;

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFileMiniDumpNormal, &loExceptionInfoNULLNULL);

 

    CloseHandle(lhDumpFile);

 

    return EXCEPTION_EXECUTE_HANDLER;

}

 

 

void Fun2()

{

    int *p = NULL;

    *p = 0;

}

 

void Fun()

{

    Fun2();

}

 

int main()

{

    SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

 

    Fun();

 

    return 1;

}

 

API的調用僅僅作爲釋放,查看下MSDN就知道使用方法了,

#pragma auto_inline (off)

#pragma commentlib"DbgHelp" )

兩句講一下,第一句是取消掉自動內聯效果,這樣才能達到更好的演示效果,不然,Fun,Fun2這種簡單的函數會被自動內聯,那麼也就沒有堆棧,不好看到實際中Dump的作用。效果與VS2005編譯選項的,C/C++->優化->內聯函數展開->only _inline一樣。

第二句是標誌導入DbgHelp庫,以使用MiniDumpWriteMiniDump API。與VS2005編譯選項的鏈接器->輸入->附加依賴項中添加dbgHelp.lib效果一樣。

 

實際運行程序,(不能在VS中調試運行,不然異常控制權總是會被VS掌握,那麼,總是沒有辦法讓MyUnhandledExceptionFilter獲得控制權,詳細的描述見參考2),可以獲得一個名叫DumpFile.dmp的文件,此文件就是我們折騰了半天所謂的dump文件了。其他兩個函數與MiniDumpWriteMiniDump的配合使用方式也類似,就不多說了。

 

2.      Dump文件的使用

Dump文件的在Windows下的使用非常簡單,但是就是因爲太過於簡單,所以網上的描述也是非常簡單,想起來,那時候折騰出Dump文件時非常興奮,解決發現拿dump文件沒有辦法,網上簡單的描述用VS打開調試的方法總是沒有頭緒。。。。呵呵

正確的使用方法是,將崩潰程序的dmp, pdb,exe文件都放在同一個目錄下,然後雙擊運行dmp,(或者用VS打開),然後就會出現一個名爲dumpfile的解決方案並且包含一個dumpfile的工程,此時右鍵點擊此工程,選擇調試->啓動新實例(或者啓動並進入單步調試新實例)都行,此時程序會自動的調到源碼中崩潰的那一行,並且在call stack中有完整的堆棧信息,並且臨時變量的值也可以通過VS顯示出來,還有模塊信息,線程信息,

在上例中,堆棧信息是:

>     Exception.exe!Fun2()  行36       C++

      Exception.exe!main()  行50 C++

      Exception.exe!__tmainCRTStartup()  行597 + 0x17 字節       C

      kernel32.dll!7c817077()       

      [下面的框架可能不正確和/或缺失,沒有爲 kernel32.dll 加載符號]   

      ntdll.dll!7c93005d()  

 

然後,寄存器的值爲:

EAX = 00000000 EBX = 00000000 ECX = 0000B623

EDX = 7C92E514 ESI = 00000001 EDI = 00403384

EIP = 00401072 ESP = 0013FF7C EBP = 0013FFC0

EFL = 00010246

 

在normal模式下,dump文件速度較快,但是沒有內存信息,你甚至可以通過調整MiniDumpWriteMiniDump的參數來將運行時的整個內存都dump下來,這些都非常簡單,查看一下MSDN MiniDumpWriteMiniDump的信息即可。

有了這些信息,程序的錯誤定位(C++下一般是空指針的訪問比較多)已經是非常明朗的了,再配合日誌,一般的錯誤不難發現。這裏順帶說明一下,當運行的程序被改名或者糅合進其他地方後運行,用這樣的方式,一開始堆棧信息中是沒有完整的信息的,這時候可以在堆棧信息中,用右鍵菜單中的加載符號,選擇合適的文件pdb,這樣信息就出來了。。。。。(以前這個問題困擾了我們一天)

 

三、   高級應用

程序崩潰的問題解決了,問題是,有很多時候,很多程序是不允許隨便崩潰的,這樣,在程序崩潰後再去發現問題就有些晚了,那麼,有沒有程序不崩潰時也能發現問題的方法呢?前面描述的SEH就是一種讓程序不崩潰的方法,不過在那種方式下,按以前描述的方法,崩潰是不崩潰了,但是實際上,掩蓋了很多問題,對於問題的發現有些不利的地方。本文前面描述過了,MiniDump是一種快速發現問題的好方法,但是卻沒有辦法避免程序崩潰,那麼終極辦法是啥呢?我們的目的既然是程序不崩潰+快速發現問題,那麼終極辦法自然就是SEH+MiniDump了:)SEH和MiniDump都是Windows的特性,MS也的確提供了結合的方式。見下面的例子,呵呵,別太激動了。。。。這也是我們公司的服務器從內測時一天多次無任何通知,預告,警告的崩潰(總監甚至還曾因爲我的問題,半夜3點爬起來解決服務器崩潰問題)到現在服務器基本做到永不崩潰,即便出現問題了也有充足的時間從容的解決,然後在服務器中發通告,告訴文件服務器需要臨時維護。。。。呵呵,都依賴於此終極解決方案。。。。。

SEH的用法和特性講解這裏不重複了,見前面的文章。《異常處理與MiniDump詳解(3) SEHStructured Exception Handling

要想利用MiniDumpWriteMiniDump,需要獲取的是MINIDUMP_EXCEPTION_INFORMATION結構的信息,這個結構中最重要的信息來源於PEXCEPTION_POINTERS的信息,這個信息在上述的例子中是在程序崩潰的時候,由Windows作爲參數傳入我們設定好的異常處理函數的,現在最主要的問題就是從哪裏獲取到這個異常信息了,通過MSDN,我們查到了GetExceptionInformation的函數,返回的就是這個信息,見MSDN:

LPEXCEPTION_POINTERS GetExceptionInformation(void);

不過,這裏MS給出了一個notice:

The Microsoft C/C++ Optimizing Compiler interprets this function as a keyword, and its use outside the appropriate exception-handling syntax generates a compiler error.

事實上,剛開始我使用的時候,哪個地方都試遍了,果然都是報編譯錯誤。因爲此函數使用方式如此奇怪,並且沒有example。。。。。最後在絕望中。。。看到了Platform Builder for Microsoft Windows CE 5.0的詞函數的說明,裏面有個說明,然後我吐血了。。。。

try

{

    // try block

}

except (FilterFunction(GetExceptionInformation())

{

    // exception handler block

}

 

原來是這樣使用的啊。。。。。。。。。。暈

HandleWithoutCrash例子:

#include <windows.h>

#include <Dbghelp.h>

using namespace std;

 

#pragma auto_inline (off)

#pragma commentlib"DbgHelp" )

 

// 爲了程序的簡潔和集中關注關心的東西,按示例程序的慣例忽略錯誤檢查,實際使用時請注意

LONG WINAPI MyUnhandledExceptionFilter(

struct _EXCEPTION_POINTERSExceptionInfo

    )

{

    HANDLE lhDumpFile = CreateFile(_T("DumpFile.dmp"), GENERIC_WRITE, 0, NULLCREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);

 

    MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;

    loExceptionInfo.ExceptionPointers = ExceptionInfo;

    loExceptionInfo.ThreadId = GetCurrentThreadId();

    loExceptionInfo.ClientPointers = TRUE;

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFileMiniDumpNormal, &loExceptionInfoNULLNULL);

 

    CloseHandle(lhDumpFile);

 

    return EXCEPTION_EXECUTE_HANDLER;

}

 

 

void Fun2()

{

    __try

    {

       static bool b = false;

      

       if(!b)

       {

           b = true;

           int *p = NULL;

           *p = 0;

        }

       else

       {

           MessageBox(NULL_T("Here"), _T(""), MB_OK);

       }

 

    }

    __except(MyUnhandledExceptionFilter(GetExceptionInformation()))

    {

    }

}

 

void Fun()

{

    Fun2();

}

 

int main()

{

    Fun();

    Fun();

 

    return 1;

}

 

這裏例子中,你可以調試程序了,因爲程序不會崩潰,這樣VS不會和你搶異常的控制。同時,看到dump文件的同時,也可以看到,程序實際上是繼續運行了下去,因爲MessageBox還是彈出來了。這。。。就是我們想要的。。。。。

我突然想到一首歌。。。。“I want to nobody but you...I want nobody but you.......”呵呵,目的達到了,驚豔嗎?

這裏有幾個要點,GetExceptionInformation()僅僅只能在__except的MS所謂的Filter中調用,其他地方會報編譯錯誤,其次,返回的值和一般的__except的意義是一樣的,要想程序運行,需要返回EXCEPTION_EXECUTE_HANDLER表示異常得到了控制。其他幾個值的含義見前篇的SEH

 

四、   參考資料

1.     MSDN—Visual Studio 2005 附帶版,Microsoft

2.     Windows用戶態程序高效排錯,熊力著,電子工業出版社

前面的系列文章:

異常處理與MiniDump詳解(3) SEHStructured Exception Handling

異常處理與MiniDump詳解(2) 智能指針與C++異常

異常處理與MiniDump詳解(1) C++異常

發佈了8 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章