zoukankan      html  css  js  c++  java
  • 使用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环境中编译,其他编译器略有不同,具体环境具体分析。

  • 相关阅读:
    财报就像一本故事书270页完整版本.pdf
    洛克菲勒留给儿子的38封信打包下载
    pip-20.2.3.tar.gz安装包下载
    python-3.8.6rc1-amd64.exe安装包下载
    apache-maven-3.6.3-bin.tar.gz 安装包下载
    中文拼音排序 element-ui的table web端实现
    vue中用axios下载后端的文档流(excel)
    git历史重写
    AMQP
    TODO
  • 原文地址:https://www.cnblogs.com/rainbowzc/p/3498667.html
Copyright © 2011-2022 走看看