使用SetUnhandledExceptionFilter轉儲程序崩潰時內存DMP .


關於程序崩潰時轉儲內存DMP,可以設置注冊表,使程序崩潰時自動轉儲內存DMP,見程序崩潰時利用注冊表自動轉儲內存DMP。本文要介紹的是使用SetUnhandledExceptionFilter函數在程序崩潰時取得程序內存DMP,並解決一些困擾人的問題。

 

  從名字上就可以看出SetUnhandledExceptionFilter的作用就是設置未捕獲異常函數,程序崩潰就是因為有些異常我們沒有捕獲,而當這些異常我們沒捕獲時,系統就會調用SetUnhandledExceptionFilter設置的函數,在此函數中可以進行一些操作,比如彈出對話框、打印語句等。關於SetUnhandledExceptionFilter更詳細的信息,參見MSDN,這里不作詳細介紹。

  見代碼:

 

  1. LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)  
  2. {  
  3.     cout << "Unhandled Exception!!!" << endl;  
  4.   
  5.     return EXCEPTION_EXECUTE_HANDLER;  
  6. }  
  7.   
  8.   
  9. void StartUnhandledExceptionFilter()  
  10. {  
  11.     ::SetUnhandledExceptionFilter(ExpFilter);  
  12. }  
  13.   
  14. int main()  
  15. {     
  16.     cout << "begin !" << endl;  
  17.   
  18.     StartUnhandledExceptionFilter();  
  19.       
  20.     int i = 0;  
  21.     i = i / i;  
  22.   
  23.     cout << "end !" << endl;  
  24.   
  25.     getch();  
  26.   
  27.     return 0;  
  28. }  
LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)
{
	cout << "Unhandled Exception!!!" << endl;

	return EXCEPTION_EXECUTE_HANDLER;
}


void StartUnhandledExceptionFilter()
{
	::SetUnhandledExceptionFilter(ExpFilter);
}

int main()
{	
	cout << "begin !" << endl;

	StartUnhandledExceptionFilter();
	
	int i = 0;
	i = i / i;

	cout << "end !" << endl;

	getch();

	return 0;
}


  運行結果:

 

  main函數的第6行“i = i / i;”語句,產生一個除數為0的異常,這個異常我們沒有捕獲(使用try、catch或__try、__except等),因此系統調用::SetUnhandledExceptionFilter設置的函數ExpFilter,此函數輸出一個語句,然后返回EXCEPTION_EXECUTE_HANDLER,表明異常處理完畢,程序可以退出。

  有了上面的經驗,於是我們可以在ExpFilter函數中進行一些操作,保存程序的DMP,然后結合PDB,我們就可以分析程序崩潰的原因了。

 

  1. LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)  
  2. {  
  3.     char szExec[256];  
  4.     sprintf(szExec, "ntsd -c \".dump /f c:\\123.dmp;q\" -p %d",   
  5.         ::GetCurrentProcessId());  
  6.   
  7.     WinExec(szExec, SW_SHOWNORMAL);  
  8.   
  9.     Sleep(1000);  
  10.   
  11.     return EXCEPTION_EXECUTE_HANDLER;  
  12. }  
LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)
{
	char szExec[256];
	sprintf(szExec, "ntsd -c \".dump /f c:\\123.dmp;q\" -p %d", 
		::GetCurrentProcessId());

	WinExec(szExec, SW_SHOWNORMAL);

	Sleep(1000);

	return EXCEPTION_EXECUTE_HANDLER;
}


  執行ntsd語句得到程序的DMP,保存在C盤根目錄123.dmp,注意WinExec執行了ntsd語句后,要Sleep一段時間,因為WinExec是異步的,執行ntsd時可能主程序已經退出了,導致ntsd找不到指定的程序,無法生成DMP。

 

  以上是用ntsd得到程序的DMP,還可以利用Dbghelp.dll提供的MiniDumpWriteDump函數取得程序的DMP,代碼如下:

 

  1. LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)  
  2. {  
  3.     HANDLE hFile = ::CreateFile(  
  4.         "c:\\123.dmp",   
  5.         GENERIC_WRITE,   
  6.         0,   
  7.         NULL,   
  8.         CREATE_ALWAYS,   
  9.         FILE_ATTRIBUTE_NORMAL,   
  10.         NULL);  
  11.     if(INVALID_HANDLE_VALUE != hFile)  
  12.     {  
  13.         MINIDUMP_EXCEPTION_INFORMATION einfo;  
  14.         einfo.ThreadId          = ::GetCurrentThreadId();  
  15.         einfo.ExceptionPointers = pExp;  
  16.         einfo.ClientPointers    = FALSE;  
  17.           
  18.         ::MiniDumpWriteDump(  
  19.             ::GetCurrentProcess(),   
  20.             ::GetCurrentProcessId(),   
  21.             hFile,   
  22.             MiniDumpWithFullMemory,   
  23.             &einfo,   
  24.             NULL,   
  25.             NULL);  
  26.         ::CloseHandle(hFile);  
  27.      }  
  28.   
  29.     return EXCEPTION_EXECUTE_HANDLER;  
  30. }  
LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)
{
	HANDLE hFile = ::CreateFile(
		"c:\\123.dmp", 
		GENERIC_WRITE, 
		0, 
		NULL, 
		CREATE_ALWAYS, 
		FILE_ATTRIBUTE_NORMAL, 
		NULL);
	if(INVALID_HANDLE_VALUE != hFile)
	{
		MINIDUMP_EXCEPTION_INFORMATION einfo;
		einfo.ThreadId			= ::GetCurrentThreadId();
		einfo.ExceptionPointers	= pExp;
		einfo.ClientPointers	= FALSE;
		
        ::MiniDumpWriteDump(
			::GetCurrentProcess(), 
			::GetCurrentProcessId(), 
			hFile, 
			MiniDumpWithFullMemory, 
			&einfo, 
			NULL, 
			NULL);
        ::CloseHandle(hFile);
     }

	return EXCEPTION_EXECUTE_HANDLER;
}


  使用MiniDumpWriteDump需要加頭文件Dbghelp.h和鏈接文件Dbghelp.lib。MiniDumpWriteDump中第四個參數可以設置取得DMP的類型,例子中是取得所有內存DMP。

 

  現在來試下用windbg打開DMP,看看程序的堆棧,看是否能找到導致程序崩潰的地方,以下例子使用執行ntsd語句版本的ExpFilter:

  OK,成功打開DMP!仔細看看程序的堆棧,好像不對。main函數第6行產生異常,但堆棧中沒有,卻直接跳到了ExpFilter函數中執行ntsd的的地方。這是因為main函數並非程序最開始執行的函數,鏈接器在鏈接可執行文件時,選擇了正確的C/C++運行庫運行函數,在此運行庫函數中才調用的main函數,查看堆棧,可以知道,此運行庫函數為mainCRTStartup,此函數的相關代碼(VC++6.0下為crtexe.c)如下:

 

  1. __try {  
  2.     ...  
  3. }  
  4. __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )  
  5. {  
  6.     _exit( GetExceptionCode() );  
  7. }  
__try {
	...
}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
	_exit( GetExceptionCode() );
}


  省略號的地方就是調用main的地方,可以看到,調用main的地方被__try、__except包起來了,產生異常時,首先會調用_XcptFilter,之后的第二步才到我們設置的函數,堆棧自然也就變了。

 

  查找__try、__except的資料可知,__except后面跟一個表達式,表達式的值與SetUnhandledExceptionFilter設置的未捕獲異常函數返回值的意義一樣,如果設置為EXCEPTION_CONTINUE_SEARCH,表示異常沒有被識別到,異常繼續往上層拋,至到SetUnhandledExceptionFilter設置的未捕獲異常函數。有了這些資料,我們就可以解決不能正確顯示堆棧的問題了:

 

  1. int MyXcptFilter()  
  2. {  
  3.     return EXCEPTION_CONTINUE_SEARCH;  
  4. }  
  5.   
  6. void StartUnhandledExceptionFilter()  
  7. {  
  8.     ::SetUnhandledExceptionFilter(ExpFilter);  
  9.   
  10.     void *_XcptFilter = (void*)GetProcAddress(  
  11.         LoadLibrary("msvcrt.dll"), "_XcptFilter");  
  12.     DWORD dwOldProtect;   
  13.     VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);   
  14.     *(char*)_XcptFilter = 0xe9;  
  15.     *(unsigned int*)((char*)_XcptFilter + 1) =   
  16.         (unsigned int)MyXcptFilter - ((unsigned int)_XcptFilter + 5);   
  17.     VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect);   
  18. }  
int MyXcptFilter()
{
	return EXCEPTION_CONTINUE_SEARCH;
}

void StartUnhandledExceptionFilter()
{
	::SetUnhandledExceptionFilter(ExpFilter);

	void *_XcptFilter = (void*)GetProcAddress(
		LoadLibrary("msvcrt.dll"), "_XcptFilter");
	DWORD dwOldProtect; 
	VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); 
	*(char*)_XcptFilter = 0xe9;
	*(unsigned int*)((char*)_XcptFilter + 1) = 
		(unsigned int)MyXcptFilter - ((unsigned int)_XcptFilter + 5); 
	VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect); 
}


  修改msvcrt.dll中函數_XcpFilter,使調用_XcpFilter時直接跳轉到我們自己的函數MyXcptFilter去執行,此函數返回EXCEPTION_CONTINUE_SEARCH,因此__except后面表達式就為EXCEPTION_CONTINUE_SEARCH,表示此異常未能被識別,進而調用ExpFilter,堆棧也被保存下來了,結果如下:

 

  這下就OK了,我們就可以知道哪里產生異常了,查看Locals,可以看到i的值為0。

  注意:如果此時顯示的堆棧還不正確,可能是因為沒有加載kernel32.dll等文件的pdb,需要從微軟官網下載,將”srv*downstreamstore*http://msdl.microsoft.com/download/symbols“加入到Symbol File Path中,windbg即可自動從微軟官網下載相應版本的pdb文件。

  我們還可以這樣處理:

 

  1. void StartUnhandledExceptionFilter()  
  2. {  
  3.     ::SetUnhandledExceptionFilter(ExpFilter);  
  4.       
  5.     void *_XcptFilter = (void*)GetProcAddress(  
  6.         LoadLibrary("msvcrt.dll"), "_XcptFilter");  
  7.     DWORD dwOldProtect;   
  8.     VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);   
  9.     VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);   
  10.     *(char*)_XcptFilter       = 0x33;  
  11.     *((char*)_XcptFilter + 1) = 0xc0;  
  12.     *((char*)_XcptFilter + 2) = 0xc2;  
  13.     *((char*)_XcptFilter + 3) = 0x00;  
  14.     *((char*)_XcptFilter + 4) = 0x00;  
  15.     VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect);   
  16. }  
void StartUnhandledExceptionFilter()
{
	::SetUnhandledExceptionFilter(ExpFilter);
	
	void *_XcptFilter = (void*)GetProcAddress(
		LoadLibrary("msvcrt.dll"), "_XcptFilter");
	DWORD dwOldProtect; 
	VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); 
	VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); 
	*(char*)_XcptFilter       = 0x33;
	*((char*)_XcptFilter + 1) = 0xc0;
	*((char*)_XcptFilter + 2) = 0xc2;
	*((char*)_XcptFilter + 3) = 0x00;
	*((char*)_XcptFilter + 4) = 0x00;
	VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect); 
}


  同樣修改_XcptFilter,不過不是讓其跳轉,而是讓其直接返回EXCEPTION_CONTINUE_SEARCH的值0,也可以達到目的。

 

  如果不處理_XcptFilter,非主線程產生異常后的堆棧也不一樣,比如用_beginthread創建線程的堆棧就不對,而用CreateThread創建線程的堆棧卻是對的,至於原因,有興趣的可以試試,在非主線程中產生異常,然后分析DMP,一看就明白了。

  以上測試例子在VC++6.0環境中編譯,其他編譯器略有不同,具體環境具體分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM