天天看點

調試Release釋出版程式的Crash錯誤一、   綜述二、   基本應用三、   進階應用四、   參考資料

下面有更使用的文章

REF : http://blog.sina.com.cn/s/blog_48f93b530100g282.html

REF : http://vicchina.51.net/research/other/seh/minidumps/intro.htm

 當我們把自己的release版本程式釋出出去以後,一般都是在使用者的機器上運作。這種情況下,對于第四種方案,因為需要pdb檔案才能夠正确生成堆棧調用的函數行号及代碼行号,是以方案四隻适用于本地release版的調試,否則隻能生成不完整的堆棧資訊。對于前三種方案,其實隻需要使用者告知崩潰位址,然後在本地查找crash位址就可以了,但是定位crash的過程非常不友善,如果crash的情況比較多,前三種方案都不合适。而且,前三種方案均不能生成堆棧調用資訊,對于debug的作用有限。

    下面我們就來看一個更加完善的解決方案。

    方案五:SetUnhandledExceptionFilter + Minidump

    SetUnhandleExceptionFilter函數我們已經介紹過了,本方案的思路還是要利用我們自己的異常處理函數,來生成minidump檔案。

    1、Minidump概念

    minidump(小存儲器轉儲)可以了解為一個dump檔案,裡面記錄了能夠幫助調試crash的最小有用資訊。實際上,如果你在 系統屬性 -> 進階 -> 啟動和故障恢複 -> 設定 -> 寫入調試資訊 中選擇“小記憶體轉儲(64 KB)”的話,當系統意外停止時都會在C:\Windows\Minidump\路徑下生成一個.dmp字尾的檔案,這個檔案就是minidump檔案,隻不過這個是核心态的minidump。

   我們要生成的是使用者态的minidump,檔案中包含了程式運作的子產品資訊、線程資訊、堆棧調用資訊等。而且為了符合其mini的特性,dump檔案是壓縮過的。

    2、生成minidump檔案

    生成minidump檔案的API函數是MiniDumpWriteDump,該函數需要dbghelp.lib支援,其原型如下:

    BOOL WINAPI MiniDumpWriteDump(

      __in          HANDLE hProcess,

      __in          DWORD ProcessId,

      __in          HANDLE hFile,

      __in          MINIDUMP_TYPE DumpType,

      __in          PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,

      __in          PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,

      __in          PMINIDUMP_CALLBACK_INFORMATION CallbackParam

    );

    在我們的異常處理函數中加入以下代碼:

    HANDLE hFile = ::CreateFile( _T("E:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

     if( hFile != INVALID_HANDLE_VALUE)

     {

         MINIDUMP_EXCEPTION_INFORMATION einfo;

         einfo.ThreadId = ::GetCurrentThreadId();

         einfo.ExceptionPointers = pExInfo;

         einfo.ClientPointers = FALSE;

        ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpNormal, &einfo, NULL, NULL);

        ::CloseHandle(hFile);

     }

    其中,pExInfo變量為異常處理函數PEXCEPTION_POINTERS類型的參數。具體請參考MSDN。

    3、調試minidump

    調試dump檔案首先需要pdb檔案,是以我們build程式時需要設定 Debug Infomation Format 為 “Program Database(/Zi)”。其次,我們還要確定所用的dump檔案與源代碼、exe、pdb檔案版本是一緻的,這要求我們必須維護好程式版本資訊。

    調試minidump最友善的環境就是VS了,我們隻要将.dmp、.exe、.pdb檔案放在一個路徑下,保證源代碼檔案的路徑與編譯時的路徑一緻就可以了,剩下的就是VS幫我們完成。輕按兩下.dmp檔案或者在檔案打開工程中選擇“dump files”,加載dump檔案,然後按F5運作就能直接恢複crash時的現場了,你可以定位crash的代碼,可以檢視調用堆棧,可以檢視線程和子產品資訊...一切都跟你設定斷點調試一樣,太強大了!看個截圖吧。

調試Release釋出版程式的Crash錯誤一、   綜述二、   基本應用三、   進階應用四、   參考資料

    需要注意的是,對于release版的程式來說,很多代碼是經過編譯器優化過的,是以定位的時候可能會有所偏差,大家可以考慮設定選項去掉代碼優化。

    其他可以調試minidump的工具還有WinDbg等,大家可以查閱相關資料。

    本文主要參考了這篇文章:http://vicchina.51.net/research/other/seh/minidumps/intro.htm。

    下一篇,我們将給出一個調試release釋出程式的完美解決方案,适合使用者量較大的應用釋出程式的調試。

吃飯

來自:http://blog.csdn.net/vagrxie/article/details/4398721

核心在于設定:#pragma auto_inline (off)

異常處理與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 comment( lib, "DbgHelp" )

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

LONG WINAPI MyUnhandledExceptionFilter(

struct _EXCEPTION_POINTERS* ExceptionInfo

    )

{

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

    MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;

    loExceptionInfo.ExceptionPointers = ExceptionInfo;

    loExceptionInfo.ThreadId = GetCurrentThreadId();

    loExceptionInfo.ClientPointers = TRUE;

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFile, MiniDumpNormal, &loExceptionInfo, NULL, NULL);

    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 comment( lib, "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) SEH(Structured 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 comment( lib, "DbgHelp" )

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

LONG WINAPI MyUnhandledExceptionFilter(

struct _EXCEPTION_POINTERS* ExceptionInfo

    )

{

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

    MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;

    loExceptionInfo.ExceptionPointers = ExceptionInfo;

    loExceptionInfo.ThreadId = GetCurrentThreadId();

    loExceptionInfo.ClientPointers = TRUE;

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFile, MiniDumpNormal, &loExceptionInfo, NULL, NULL);

    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使用者态程式高效排錯,熊力著,電子工業出版社

繼續閱讀