随笔 - 505  文章 - 1034  trackbacks - 0
<2008年4月>
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910


子曾经曰过:编程无他,唯手熟尔!

常用链接

留言簿(94)

随笔分类(649)

随笔档案(505)

相册

BCB

Crytek

  • crymod
  • Crytek's Offical Modding Portal

Game Industry

OGRE

other

Programmers

Qt

WOW Stuff

搜索

  •  

积分与排名

  • 积分 - 893947
  • 排名 - 14

最新随笔

最新评论

阅读排行榜

评论排行榜

好不容易复现的宕机bug,结果不产生dump文件,让俺情何以堪啊!坑爹嘛!
大部分情况还好,就是弹出这种“Runtime Error”框的时候,不产生dump文件,解决它!!!


VS2005中SetUnhandledExceptionFilter函数应用


很多软件通过设置自己的异常捕获函数,捕获未处理的异常,生成报告或者日志(例如生成mini-dump文件),达到Release版本下追踪Bug的目的。但是,到了VS2005(即VC8),Microsoft对CRT(C运行时库)的一些与安全相关的代码做了些改动,典型的,例如增加了对缓冲溢出的检查。新CRT版本在出现错误时强制把异常抛给默认的调试器(如果没有配置的话,默认是Dr.Watson),而不再通知应用程序设置的异常捕获函数,这种行为主要在以下三种情况出现。

(1)       调用abort函数,并且设置了_CALL_REPORTFAULT选项(这个选项在Release版本是默认设置的)。

(2)       启用了运行时安全检查选项,并且在软件运行时检查出安全性错误,例如出现缓存溢出。(安全检查选项 /GS 默认也是打开的)

(3)       遇到_invalid_parameter错误,而应用程序又没有主动调用

_set_invalid_parameter_handler设置错误捕获函数。

所以结论是,使用VS2005(VC8)编译的程序,许多错误都不能在SetUnhandledExceptionFilter捕获到。这是CRT相对于前面版本的一个比较大的改变,但是很遗憾,Microsoft却没有在相应的文档明确指出。

解决方法

       之所以应用程序捕获不到那些异常,原因是因为新版本的CRT实现在异常处理中强制删除所有应用程序先前设置的捕获函数,如下所示:

 /* Make sure any filter already in place is deleted. */

 SetUnhandledExceptionFilter(NULL);

 UnhandledExceptionFilter(&ExceptionPointers);

解决方法是拦截CRT调用SetUnhandledExceptionFilter函数,使之无效。在X86平台下,可以使用以下代码。

#ifndef _M_IX86

       #error "The following code only works for x86!"

#endif

 

void DisableSetUnhandledExceptionFilter()

{

    void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),

                                                         "SetUnhandledExceptionFilter");

    if (addr)

    {

              unsigned char code[16];

              int size = 0;

              code[size++] = 0x33;

              code[size++] = 0xC0;

              code[size++] = 0xC2;

              code[size++] = 0x04;

              code[size++] = 0x00;

 

               DWORD dwOldFlag, dwTempFlag;

              VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);

              WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);

              VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);

       }

}

在设置自己的异常处理函数后,调用DisableSetUnhandledExceptionFilter禁止CRT设置即可。

其它讨论

       上面通过设置api hook,解决了在VS2005上的异常捕获问题,这种虽然不是那么“干净”的解决方案,确是目前唯一简单有效的方式。

       虽然也可以通过_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT), signal(SIGABRT, ...), 和_set_invalid_parameter_handler(...) 解决(1)(3),但是对于(2),设置api hook是唯一的方式。


http://blog.csdn.net/WinGeek/article/details/3942995
[Windows编程] 如何捕捉程序异常/crash 并生成 dump 文件

前面介绍如如何用WinDBG 生成crash dump 《WinDBG 技巧:如何生成Dump 文件(.dump 命令) 》,但是用户机器上通常不安装WinDBG, 而且多数用户也不知道怎么使用WinDBG。 所以最好是自己程序里面能够捕捉exception/crash,并且生成crash dump,然后通过网络传回到自己服务器。

 

捕捉exception 可以用API 函数 SetUnhandledExceptionFilter 。生成crash dump 可以用DbgHelp.dll 里面的MiniDumpWriteDump 函数。

 

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter( __in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter );

 

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 );

 

 

代码示例:

 

 

  1. #include <dbghelp.h>  
  2. #include <shellapi.h>  
  3. #include <shlobj.h>  
  4.   
  5.   
  6. // 自定义的exectpion filter  
  7. LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pExceptionPointers)  
  8. {  
  9.   
  10.     SetErrorMode( SEM_NOGPFAULTERRORBOX );  
  11.   
  12.     //收集信息  
  13.      CStringW strBuild;  
  14.     strBuild.Format(L"Build: %s %s", __DATE__, __TIME__);  
  15.     CStringW strError;  
  16.     HMODULE hModule;  
  17.     WCHAR szModuleName[MAX_PATH] = L"";  
  18.     GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)pExceptionPointers->ExceptionRecord->ExceptionAddress, &hModule);  
  19.     GetModuleFileName(hModule, szModuleName, ARRAYSIZE(szModuleName));  
  20.     strError.AppenedFormat(L"%s %d , %d ,%d.", szModuleName,pExceptionPointers->ExceptionRecord->ExceptionCode, pExceptionPointers->ExceptionRecord->ExceptionFlags, pExceptionPointers->ExceptionRecord->ExceptionAddress);  
  21.   
  22.     //生成 mini crash dump  
  23.     BOOL bMiniDumpSuccessful;  
  24.     WCHAR szPath[MAX_PATH];   
  25.     WCHAR szFileName[MAX_PATH];   
  26.     WCHAR* szAppName = L"AppName";  
  27.     WCHAR* szVersion = L"v1.0";  
  28.     DWORD dwBufferSize = MAX_PATH;  
  29.     HANDLE hDumpFile;  
  30.     SYSTEMTIME stLocalTime;  
  31.     MINIDUMP_EXCEPTION_INFORMATION ExpParam;  
  32.     GetLocalTime( &stLocalTime );  
  33.     GetTempPath( dwBufferSize, szPath );  
  34.     StringCchPrintf( szFileName, MAX_PATH, L"%s%s", szPath, szAppName );  
  35.     CreateDirectory( szFileName, NULL );  
  36.     StringCchPrintf( szFileName, MAX_PATH, L"%s%s//%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",   
  37.                szPath, szAppName, szVersion,   
  38.                stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,   
  39.                stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,   
  40.                GetCurrentProcessId(), GetCurrentThreadId());  
  41.     hDumpFile = CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE,   
  42.                 FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);  
  43.   
  44.     MINIDUMP_USER_STREAM UserStream[2];  
  45.     MINIDUMP_USER_STREAM_INFORMATION UserInfo;  
  46.     UserInfo.UserStreamCount = 1;  
  47.     UserInfo.UserStreamArray = UserStream;  
  48.     UserStream[0].Type = CommentStreamW;  
  49.     UserStream[0].BufferSize = strBuild.GetLength()*sizeof(WCHAR);  
  50.     UserStream[0].Buffer = strBuild.GetBuffer();  
  51.     UserStream[1].Type = CommentStreamW;  
  52.     UserStream[1].BufferSize = strError.GetLength()*sizeof(WCHAR);  
  53.     UserStream[1].Buffer = strError.GetBuffer();  
  54.   
  55.     ExpParam.ThreadId = GetCurrentThreadId();  
  56.     ExpParam.ExceptionPointers = pExceptionPointers;  
  57.     ExpParam.ClientPointers = TRUE;  
  58.       
  59.     MINIDUMP_TYPE MiniDumpWithDataSegs = MiniDumpNormal   
  60.             | MiniDumpWithHandleData   
  61.             | MiniDumpWithUnloadedModules   
  62.             | MiniDumpWithIndirectlyReferencedMemory   
  63.             | MiniDumpScanMemory   
  64.             | MiniDumpWithProcessThreadData   
  65.             | MiniDumpWithThreadInfo;  
  66.     bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),   
  67.                     hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);  
  68.   // 上传mini dump 到自己服务器(略)  
  69.   ...  
  70.   
  71.   return EXCEPTION_CONTINUE_SEARCH; //或者 EXCEPTION_EXECUTE_HANDLER 关闭程序  
  72. }  
  73.    
  74. int _tmain()  
  75. {  
  76.   // 设置 execption filter  
  77.   SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);  
  78.   ....  
  79.   return 0;  
  80. }  


http://blog.csdn.net/mergerly/article/details/6662797
 VC++ Runtime Error 异常捕获之不挂的程序

  VC++ Runtime Error,  对不少朋友来说, 这是一个十分讨厌的错误提示,  您可能不知道如何着手调试: 产生这个错误的原因是什么? 确实只有知道了产生这个错误的直接原因, 才能去调试这个错误.

     刚碰到这个错误的时候, 是发生在视频解码的时候,  由于解码一直在工作状态,  所以我也不知道如何去调试, 当出现这个错误之后, 我们大多数时候就忽略了, 想从其他地方解决, 提高稳定性, 甚至怀疑解码器的稳定性;  后来, 我接触解码库之后,  开始调试这样的错误, 刚开始这样的错误并不容易重现, 往往要几个小时,  当这个错误重现之后,  程序还是在运行的, 只是其中的某一个线程中断了执行,  其中的这个线程弹出了 "VC++ Runtime Error" 这样的对话框, 如果你点击它, 则整个应用程序会直接退出. 为了调试, 我就不能点击这个对话框, 而是使用VC2005附件到进程, 然后再直接中断进程, 这个时候, 会有一个线程中断点就在对话框的消息循环中, 仔细查看堆栈, 发现了一个函数: msvcrt.dll!_abort() ,  到这里是时候查看MSDN了:

     

      函数名: abort

  功 能: 异常终止一个进程

  用 法: void abort(void);

      In a single or multithreaded Windows-based application, abort calls the Windows MessageBox function to create a message box to display the message with anOK button. When the user clicks OK, the program aborts immediately. 

    

    我们的程序就是基于WINDOS窗口的多线程应用程序,  调用了abort就会弹出对话框, 在release版本中, 就是一个确认对话框, 点击后程序就提示出错并退出.

    在正常的程序里, 我们是不会调用abort的, 除非是遇到了严重的, 不能恢复的错误.  那么到底这个abort是怎么被调用的呢, 我们自己写的代码显然是没有这个函数, 再仔细查看堆栈,  发现是在一个C语言版本的开源库中.  我们的程序是需要7*24小时运行的, 出现了解码异常应该要被我们忽略, 而不是应用程序崩溃. 开源的跨平台解码库是C语言写的,  在出现了严重错误时, 就直接abort这也是可以理解的, 不过, 这样的程序在我们的代码中显然要避免.  大哥, 现在都是什么年代了, 很多程序都是需要一直跑的, 我只好改的库的源代码来重新编译程序才能解决这个问题了, 该怎么改了, 如果去分析解码的逻辑, 我们没有专业的人才.  我想就干脆从abort函数这里入手, 直接返回成功值, 但是这样对解码逻辑影响更大, 可能导致更大的错误,  我想到了操作系统的异常机制, 由于我们是在WINDOWS平台上工作, 所以可以利用WINDOWS结构化异常,  我们可取消abort调用, 在这里我们使用代码产生一个结构化异常(SEH), 结构化异常分为硬件异常和软件异常, CPU可以检查到内存非法访问和除零错误等异常, 那么我们就将abort替换成除零语句, 比如 int i = 10/0;

     当程序执行到这里的时候,  CPU会捕捉这个异常, 并提示用户, 我们可以在调用解码函数的地方, 增加SEH捕捉代码, 来捕捉这个错误, 那么程序就能忽略这个错误并继续执行了. 后来的事实也证明了这个错误的忽略对程序并没有什么明显的影响.  怎么写这个捕捉代码呢, 操作系统支持的SEH捕捉代码块为 __try - __finally 块和 __try - __except 块, 而__try - __finnaly块就可以实现我们的功能.  写到这里, 可能有朋友要说了, 我们平时见的最多的是try-catch语句,  那么我要解释一下了, try-catch 是C++异常的处理方式,  而__try-__finnaly是操作系统SEH异常处理方式.  在C++语言的try-catch并不能捕捉操作系统结构化异常(比如CPU异常, 内存访问冲突, 除零错误等). C++异常只能捕获软件异常, 通常是调用throw而产生的异常, 比如MFC异常中常见的CException.  

    SEH异常和C++异常有本质的区别, SEH是操作系统提供的异常处理技术, 在任何支持该操作系统的编程语言中, 都可以使用, 而C++异常处理只能在编写C++代码时使用。然而, 应当知道WINDOWS的VC++编译器是使用操作系统结构化异常来实现C++异常的.  也就是说, C++的try块在VC++下编译时, 会变成__try块,  C++的catch块会变成SEH的 __except块: catch测试则变成SEH异常过滤器, catch中的代码则变为__except中的代码. 事实上, C++的throw块, 在编译的时候也会变成SEH的RaiseException函数调用, 由c++异常变为SEH异常.

    __finnally的好处在于, 有时更详细的异常信息对我们没有更大帮助, 我们只需要捕获到异常并忽略它。上面提到C++异常在VC++里被转换成SEH异常, 那么在VC下使用try-catch是否能捕获硬件异常呢? 比如我们常见的 0x0000000C 不可读或写.  VC++编译器已经提供了支持:

      try {;}  catch(...){;}   这样的语句就能够捕获所有异常:包括CPU异常, 以及C++异常;  不过需要注意的是, 在VC6.0中, 是默认支持的. 但是在VC2005中,  是默认不捕获CPU异常的. 区别在于一个C++编译选项/Eha , 只有这个选项打开才能用上面的try-catch()捕捉SEH异常.

 

程序偶尔会出现:
Microsoft Visual C++ Runtime Library Runtime Error! 
Program: [APPPATH] Abnormal program termination
同时带有红色叉叉的对话框。

打开VC,附加对应的进程,在线程选项卡里查找类似MessageBox的字样,找到的那个就是弹出对话框的线程,右键,转到线程,查看调用堆栈,一般就能找到问题了

根据刚才的函数调用堆栈,可以发现,这个框框是由abort间接产生的,根据查看abort的源码,可以发现,产生结果是有选项_WRITE_ABORT_MSG和_CALL_REPORTFAULT这两个标志决定的,这两个标志一个是产生如上所述的对话框,一个是产生那个常见的错误报告对话框。在msdn里,和abort相关的还有个函数_set_abort_behavior,可通过此函数,改变abort的行为。

还有个常见的错误框与此类似,是pure virtual function 就是纯虚函数的调用,它的过程与abort相类似。

测试例子:

//SEH的异常处理过滤器
static LONG __stdcall MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExPtrs)
{
    AfxMessageBox("glllll");
    return EXCEPTION_EXECUTE_HANDLER;
}
void SignalHandler(int signal)
{
    AfxMessageBox("Application aborting...\n");
}
HANDLE handle;
DWORD WINAPI ThreadProc(LPVOID lp)
{
    // TODO: Add your control notification handler code here
    //int a = 0;
    //int b = 6 / a;
    char* pBuffer = (char*) (int)(rand());
    char szBuff[10];
    //StrCpy(pBuffer, "Hello, Crash!");
    //MessageBox(pBuffer);
    //strcpy(pBuffer, szBuff);
    //CloseHandle((HANDLE)handle);
    //abort();
    //throw bad_alloc("aaaaa");
    //terminate();
    RaiseException(0, 0, 0, NULL);
    AfxMessageBox("good");
    return 0;
}
char ppp;
static char pppp;
const char p5 = 'a';
const static char p6 = 'b';
void CCrashTestDlg::OnBnClickedButton1()
{
    _set_abort_behavior( 0, _WRITE_ABORT_MSG);
    _set_abort_behavior(0,  _CALL_REPORTFAULT);
    SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
    typedef void (*SignalHandlerPointer)(int);
    SignalHandlerPointer previousHandler;
    //previousHandler = signal(SIGABRT, SignalHandler);
    //MessageBox("ddd", NULL);
    //abort();
    DWORD dwID = 0;
    handle = CreateThread(NULL, 0, ThreadProc, handle, 0, &dwID);
    CloseHandle(handle);
    //WaitForSingleObject(handle, INFINITE);
  
}





posted on 2011-08-25 19:39 七星重剑 阅读(4482) 评论(1)  编辑 收藏 引用 所属分类: PL--c/c++IDE -- visual c++

FeedBack:
# re: 网游客户端弹出个“Runtime Error”不产生dump文件的解决办法[未登录] 2011-08-26 01:02 杨粼波
很好,我也正在为这个问题犯愁。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理