異常處理與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用戶態程序高效排錯,熊力著,電子工業出版社
前面的系列文章:
異常處理與MiniDump詳解(3) SEH(Structured Exception Handling)
異常處理與MiniDump詳解(2) 智能指針與C++異常