下面有更使用的文章
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版的程式來說,很多代碼是經過編譯器優化過的,是以定位的時候可能會有所偏差,大家可以考慮設定選項去掉代碼優化。
其他可以調試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使用者态程式高效排錯,熊力著,電子工業出版社