-----------------------------------------------------------------------------------------------------------------------------
让程序在崩溃时体面的退出之Unhandled Exception
转自 http://blog.csdn.net/starlee/article/details/6613424
程序是由代码编译出来的,而代码是由人写的。人非圣贤,孰能无过。所以由人写的代码有缺陷是很正常的。当然很多异常都在开发阶段被考虑到而添加了处理代码,或者用try/catch对可能出现异常的地方进行额外的照顾。可是,还是会有一些无法预料的异常(Unhandled Exception)在程序运行的时候出现。这些异常很多时候都会招致程序的崩溃。那么有没有什么方法可以让程序在崩溃的时候体面的退出呢?答案是肯定的。可以用Windows API中的SetUnhandledExceptionFilter来设置一个回调函数来处理这些无法预料的异常。
要想使用SetUnhandledExceptionFilter,必须#include <Windows.h>,这个函数的声明如下:
- LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
- __in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
- );
具体的参数和返回值的解释可以查找MSDN,有很详细的说明。下面就用具体的代码来说明怎样使用这个Windows API来使程序在崩溃的时候体面的退出。代码里面有详细的注释来帮助理解。
用VC创建一个名为Test的控制台程序,添加下面的代码。
- #include "stdafx.h"
-
- int _tmain(int argc, _TCHAR* argv[])
- {
-
-
- int i = 13;
- int j = 0;
- int k = i / j;
-
- return 0;
- }
编译上面的代码,到工程的Debug目录下找到编译好的Test.exe,双击运行,在Windows 7下就会出现下面的对话框告诉你程序无法运行,也就是崩溃了。
为了避免出现程序崩溃的情况,用前面所说的方法,把代码修改成下面这样。
- #include "stdafx.h"
- #include <Windows.h>
-
-
-
- LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
- {
-
-
-
-
-
- FatalAppExit(-1, _T("*** Unhandled Exception! ***"));
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
-
- int _tmain(int argc, _TCHAR* argv[])
- {
-
-
- SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);
-
-
-
- int i = 13;
- int j = 0;
- int k = i / j;
-
- return 0;
- }
编译上面的代码,到工程的Debug目录下找到编译好的Test.exe,双击运行,就会出现下面这样的对话框,点击OK按钮后,程序正常退出。
-----------------------------------------------------------------------------------------------------------------------------
让程序在崩溃时体面的退出之CallStack
转自 http://blog.csdn.net/starlee/article/details/6618849
在我的那篇《让程序在崩溃时体面的退出之Unhandled Exception》中提供了一个捕捉程序崩溃事件的方法,可以添加代码在程序崩溃的时候做出适当的处理。不过,只知道程序在什么时候崩溃,但是不知道为什么崩溃,这对于程序开发者来说没有任何意义。因为如果不知道程序崩溃的原因,就没法去找到代码中的缺陷,当然就没法去修改代码而避免程序的崩溃。
所有调试过代码的开发者都知道CallStack的重要性。如果在程序崩溃的时候得到CallStack,那么就能定位程序崩溃的具体位置,并最终找到解决方法。那么有没有什么方法在程序崩溃的时候得到CallStack呢?答案是肯定的。微软提供了一个DbgHelp.dll,里面包含了一系列的Windows API来供开发者调用。它是一个调试跟踪相关的模块,用于跟踪进程工作,在进程崩溃时收集程序产生异常时的堆栈信息,以供开发人员分析,从而很快找出使程序出现异常的原因。
下面用具体的例子代码来说明怎样使用DbgHelp.dll中的Windows API来得到CallStack。代码里面有详细的注释来帮助理解。
用VC创建一个名为Test的控制台程序,添加下面的代码。
-
-
- class CrashTest
- {
- public:
- void Test()
- {
- Crash();
- }
-
- private:
- void Crash()
- {
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
- };
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- CrashTest test;
- test.Test();
-
- return 0;
- }
编译上面的代码,到工程的Debug目录下找到编译好的Test.exe,双击运行,程序就会崩溃。从代码我们知道函数调用顺序是main() -> CrashTest::Test() -> CrashTest::Crash()。那么怎么在程序崩溃的时候得到CallStack呢?首先,先添加一些工具函数。
- #include <Windows.h>
- #include <DbgHelp.h>
- #include <iostream>
- #include <vector>
-
-
-
- #pragma comment(lib, "dbghelp.lib")
-
- using namespace std;
-
- const int MAX_ADDRESS_LENGTH = 32;
- const int MAX_NAME_LENGTH = 1024;
-
-
-
- struct CrashInfo
- {
- CHAR ErrorCode[MAX_ADDRESS_LENGTH];
- CHAR Address[MAX_ADDRESS_LENGTH];
- CHAR Flags[MAX_ADDRESS_LENGTH];
- };
-
-
-
- struct CallStackInfo
- {
- CHAR ModuleName[MAX_NAME_LENGTH];
- CHAR MethodName[MAX_NAME_LENGTH];
- CHAR FileName[MAX_NAME_LENGTH];
- CHAR LineNumber[MAX_NAME_LENGTH];
- };
-
-
-
- void SafeStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc)
- {
- if (nMaxDestSize <= 0) return;
- if (strlen(szSrc) < nMaxDestSize)
- {
- strcpy_s(szDest, nMaxDestSize, szSrc);
- }
- else
- {
- strncpy_s(szDest, nMaxDestSize, szSrc, nMaxDestSize);
- szDest[nMaxDestSize-1] = '\0';
- }
- }
-
-
-
- CrashInfo GetCrashInfo(const EXCEPTION_RECORD *pRecord)
- {
- CrashInfo crashinfo;
- SafeStrCpy(crashinfo.Address, MAX_ADDRESS_LENGTH, "N/A");
- SafeStrCpy(crashinfo.ErrorCode, MAX_ADDRESS_LENGTH, "N/A");
- SafeStrCpy(crashinfo.Flags, MAX_ADDRESS_LENGTH, "N/A");
-
- sprintf_s(crashinfo.Address, "%08X", pRecord->ExceptionAddress);
- sprintf_s(crashinfo.ErrorCode, "%08X", pRecord->ExceptionCode);
- sprintf_s(crashinfo.Flags, "%08X", pRecord->ExceptionFlags);
-
- return crashinfo;
- }
-
-
-
- vector<CallStackInfo> GetCallStack(const CONTEXT *pContext)
- {
- HANDLE hProcess = GetCurrentProcess();
-
- SymInitialize(hProcess, NULL, TRUE);
-
- vector<CallStackInfo> arrCallStackInfo;
-
- CONTEXT c = *pContext;
-
- STACKFRAME64 sf;
- memset(&sf, 0, sizeof(STACKFRAME64));
- DWORD dwImageType = IMAGE_FILE_MACHINE_I386;
-
-
-
- #ifdef _M_IX86
- sf.AddrPC.Offset = c.Eip;
- sf.AddrPC.Mode = AddrModeFlat;
- sf.AddrStack.Offset = c.Esp;
- sf.AddrStack.Mode = AddrModeFlat;
- sf.AddrFrame.Offset = c.Ebp;
- sf.AddrFrame.Mode = AddrModeFlat;
- #elif _M_X64
- dwImageType = IMAGE_FILE_MACHINE_AMD64;
- sf.AddrPC.Offset = c.Rip;
- sf.AddrPC.Mode = AddrModeFlat;
- sf.AddrFrame.Offset = c.Rsp;
- sf.AddrFrame.Mode = AddrModeFlat;
- sf.AddrStack.Offset = c.Rsp;
- sf.AddrStack.Mode = AddrModeFlat;
- #elif _M_IA64
- dwImageType = IMAGE_FILE_MACHINE_IA64;
- sf.AddrPC.Offset = c.StIIP;
- sf.AddrPC.Mode = AddrModeFlat;
- sf.AddrFrame.Offset = c.IntSp;
- sf.AddrFrame.Mode = AddrModeFlat;
- sf.AddrBStore.Offset = c.RsBSP;
- sf.AddrBStore.Mode = AddrModeFlat;
- sf.AddrStack.Offset = c.IntSp;
- sf.AddrStack.Mode = AddrModeFlat;
- #else
- #error "Platform not supported!"
- #endif
-
- HANDLE hThread = GetCurrentThread();
-
- while (true)
- {
-
-
-
- if (!StackWalk64(dwImageType, hProcess, hThread, &sf, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
- {
- break;
- }
-
- if (sf.AddrFrame.Offset == 0)
- {
- break;
- }
-
- CallStackInfo callstackinfo;
- SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, "N/A");
- SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, "N/A");
- SafeStrCpy(callstackinfo.ModuleName, MAX_NAME_LENGTH, "N/A");
- SafeStrCpy(callstackinfo.LineNumber, MAX_NAME_LENGTH, "N/A");
-
- BYTE symbolBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH];
- IMAGEHLP_SYMBOL64 *pSymbol = (IMAGEHLP_SYMBOL64*)symbolBuffer;
- memset(pSymbol, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAME_LENGTH);
-
- pSymbol->SizeOfStruct = sizeof(symbolBuffer);
- pSymbol->MaxNameLength = MAX_NAME_LENGTH;
-
- DWORD symDisplacement = 0;
-
-
-
- if (SymGetSymFromAddr64(hProcess, sf.AddrPC.Offset, NULL, pSymbol))
- {
- SafeStrCpy(callstackinfo.MethodName, MAX_NAME_LENGTH, pSymbol->Name);
- }
-
- IMAGEHLP_LINE64 lineInfo;
- memset(&lineInfo, 0, sizeof(IMAGEHLP_LINE64));
-
- lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
-
- DWORD dwLineDisplacement;
-
-
-
- if (SymGetLineFromAddr64(hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo))
- {
- SafeStrCpy(callstackinfo.FileName, MAX_NAME_LENGTH, lineInfo.FileName);
- sprintf_s(callstackinfo.LineNumber, "%d", lineInfo.LineNumber);
- }
-
- IMAGEHLP_MODULE64 moduleInfo;
- memset(&moduleInfo, 0, sizeof(IMAGEHLP_MODULE64));
-
- moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
-
-
-
- if (SymGetModuleInfo64(hProcess, sf.AddrPC.Offset, &moduleInfo))
- {
- SafeStrCpy(callstackinfo.ModuleName, MAX_NAME_LENGTH, moduleInfo.ModuleName);
- }
-
- arrCallStackInfo.push_back(callstackinfo);
- }
-
- SymCleanup(hProcess);
-
- return arrCallStackInfo;
- }
然后,就可以用《让程序在崩溃时体面的退出之Unhandled Exception》中提供的捕捉程序崩溃事件的方法添加一个回调函数,在这个函数里面调用上面的函数来得到程序崩溃时的CallStack。
-
-
- LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
- {
-
-
- #ifdef _M_IX86
- if (pException->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
- {
- static char TempStack[1024 * 128];
- __asm mov eax,offset TempStack[1024 * 128];
- __asm mov esp,eax;
- }
- #endif
-
- CrashInfo crashinfo = GetCrashInfo(pException->ExceptionRecord);
-
-
-
- cout << "ErrorCode: " << crashinfo.ErrorCode << endl;
- cout << "Address: " << crashinfo.Address << endl;
- cout << "Flags: " << crashinfo.Flags << endl;
-
- vector<CallStackInfo> arrCallStackInfo = GetCallStack(pException->ContextRecord);
-
-
-
- cout << "CallStack: " << endl;
- for (vector<CallStackInfo>::iterator i = arrCallStackInfo.begin(); i != arrCallStackInfo.end(); ++i)
- {
- CallStackInfo callstackinfo = (*i);
-
- cout << callstackinfo.MethodName << "() : [" << callstackinfo.ModuleName << "] (File: " << callstackinfo.FileName << " @Line " << callstackinfo.LineNumber << ")" << endl;
- }
-
-
-
- FatalAppExit(-1, _T("*** Unhandled Exception! ***"));
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
最后,在main函数的开头添加下面的代码。
-
-
- SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);
编译上面的代码,到工程的Debug目录下找到编译好的Test.exe,双击运行,程序在崩溃的时候就会输出CallStack。
-----------------------------------------------------------------------------------------------------------------------------
让程序在崩溃时体面的退出之Dump文件
转自 http://blog.csdn.net/starlee/article/details/6630816
在我的那篇《让程序在崩溃时体面的退出之CallStack》中提供了一个在程序崩溃时得到CallStack的方法。可是要想得到CallStack,必须有pdb文件的支持。但是一般情况下,发布出去的程序都是Release版本的,都不会附带pdb文件。那么我们怎么能在程序崩溃的时候找到出错的具体位置呢?这个时候就该Dump文件出场了!Dump文件是进程的内存镜像,可以把程序运行时的状态完整的保存下来。
要想在程序崩溃的时候创建Dump文件,就需要用到DbgHelp.dll中Windows API的MiniDumpWriteDump()函数。该函数声明如下:
- 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
- );
具体的参数和返回值的解释可以查找MSDN,有很详细的说明。下面依然用上一篇文章中的例子代码来说明怎么在程序崩溃的时候创建Dump文件。
-
-
- LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
- {
-
-
- FatalAppExit(-1, _T("*** Unhandled Exception! ***"));
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
-
-
-
- class CrashTest
- {
- public:
- void Test()
- {
- Crash();
- }
-
- private:
- void Crash()
- {
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
- };
-
- int _tmain(int argc, _TCHAR* argv[])
- {
-
-
- SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);
-
- CrashTest test;
- test.Test();
-
- return 0;
- }
在上面的程序崩溃的时候,会调用函数ApplicationCrashHandler()。创建Dump文件的代码就需要添加到该函数中。下面就是一个创建Dump文件的函数。
-
-
- void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)
- {
-
-
- HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-
-
-
- MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
- dumpInfo.ExceptionPointers = pException;
- dumpInfo.ThreadId = GetCurrentThreadId();
- dumpInfo.ClientPointers = TRUE;
-
-
-
- MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
-
- CloseHandle(hDumpFile);
- }
在函数ApplicationCrashHandler()用类似下面的代码来调用上面的函数,就可以在程序崩溃的时候创建Dump文件。
- CreateDumpFile(_T("C:\\Test.dmp"), pException);
下面简单说一下Dump文件的用法。将Dump文件拷贝到含有应用程序和对应的pdb文件的目录,在VS里面打开Dump文件(或者直接双击Dump文件),VS会自动创建一个Solution,直接调试运行,代码就会停到使程序崩溃的那一行上。就跟在VS里面调试代码一摸一样。(VS2008)
在VS2010里打开Dump文件,会显示一个Minidump File Summary,并且可以进行下面图中的操作。
-----------------------------------------------------------------------------------------------------------------------------
让程序在崩溃时体面的退出之SEH
转自 http://blog.csdn.net/starlee/article/details/6636723
SEH的全称是Structured Exception Handling,是Windows操作系统提供的一种异常处理方式。SEH是属于操作系统的特性,不为特定语言设计,从它的名字就能看出它是一种结构化的异常处理方式。SEH包括了2个部分:终止处理__try/__finally和异常处理__try/__except,下面分别进行介绍。
终止处理__try/__finally
__try/__finally可以保证无论try块内的代码执行结果如何,finally块内的代码总会被调用和执行。现在用下面的这个VC++中的控制台程序来说明。
- int _tmain(int argc, _TCHAR* argv[])
- {
- __try
- {
- MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
- __finally
- {
-
-
-
-
-
- MessageBox(NULL, _T("Message from '__finally' section"), _T("Test"), MB_OK);
- }
-
- MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);
-
- return 0;
- }
编译上面的代码。运行生成的EXE,会弹出下面的对话框。
点击OK按钮后,程序会崩溃。
在出现上面这个对话框的时候点击Cancel,将控制权返还给程序,那么下面的对话框就会弹出。
点击OK按钮后,程序正常退出。
由上面的例子可以看出,无论try块中的代码会不会出现异常,在程序终止的时候,finally块中的代码都会被调用和执行。所以一般情况下,finally块中的代码都是用来做一些清理工作和资源的释放。
异常处理__try/__except
__try/__except是用来捕捉异常的,只有当try块中的代码出现异常的时候,except块中的代码才会被调用和执行。它的语法是这样的:
- __try
- {
-
- }
- __except(expression)
- {
-
- }
它最大的一个好处就是可以完全控制异常进程。expression的值决定了异常被处理完后,进程该如何执行。下面依然用VC++中的控制台程序来说明。
- int _tmain(int argc, _TCHAR* argv[])
- {
- __try
- {
- MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
-
-
-
-
-
- MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);
- }
-
- MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);
-
- return 0;
- }
编译上面的代码。运行生成的EXE,会依次弹出下面的对话框。
可以看出,在异常处理代码被调用执行后(except块中的代码),程序继续可以继续运行,并正常退出,并没有崩溃!通过使用__try/__except可以捕捉到任何类型的异常,并保证程序不会崩溃!(想想这是多么的神奇,一个程序永远不会崩溃!)
下面解释一下except中表达式各个值的含义:
EXCEPTION_CONTINUE_SEARCH 异常没有被处理,继续向上抛出。如果更上层的代码没有异常捕捉机制,程序就会崩溃。
EXCEPTION_CONTINUE_EXECUTION 异常已经被处理,返回异常发生的地方继续执行。
EXCEPTION_EXECUTE_HANDLER 异常已经被处理,程序继续往后执行。
通过上面的例子可以知道,SEH的异常处理跟C++的异常处理不同,C++的try/catch不能控制异常进程,对于异常,要么处理,要么继续向上抛出。而SEH却能完全控制异常进程,处理完异常之后,还能决定进该进程如何执行。只要SEH运用得当,编写一个永不崩溃的应用程序成为可能。但是SEH有一个致命的弱点,那就是它是一种结构化的异常处理,所以不支持面向对象。下面用具体的VC++控制台程序来说明。
-
-
- class CrashTest
- {
- public:
- CrashTest() {}
- ~CrashTest() {}
-
- void Test()
- {
- Crash();
- }
-
- private:
- void Crash()
- {
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
- };
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- __try
- {
- CrashTest test;
- test.Test();
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
-
-
- }
-
- return 0;
- }
上面的代码不能通过编译,VC++编译器会给出这样的错误信息:error C2712: Cannot use __try in functions that require object unwinding。错误原因很简单,try块内使用了对象。
要想解决这个问题,可以把使用对象的逻辑放到一个函数里,然后在try里调用这个函数,来骗过编译器。把上面的代码修改成下面这样就可以通过编译。
- void Test()
- {
- CrashTest test;
- test.Test();
- }
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- __try
- {
- Test();
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
-
-
- }
-
- return 0;
- }
-----------------------------------------------------------------------------------------------------------------------------
让程序在崩溃时体面的退出之SEH+Dump文件
转自 http://blog.csdn.net/starlee/article/details/6649605
在我上篇文章《让程序在崩溃时体面的退出之SEH》中讲解了SEH中try/except可以捕捉异常,避免程序的崩溃,并且可以在处理完异常之后,还能决定进该进程如何执行。对于应用程序的使用者来说,并不知道异常的发生。但是对于软件的开发者来说,虽然避免了程序的崩溃,可是这样可以让程序崩溃的缺陷存在于代码中,就像一个定时炸弹,不知道什么时候会爆炸。要想修复这样的缺陷,首先要找到导致程序崩溃的那行代码。而我在我的那篇《让程序在崩溃时体面的退出之Dump文件》里面介绍了如何用Dump文件来定位使程序崩溃的代码。这里依然可以用同样的方法。下面就是创建Dump文件的函数。
-
-
- void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)
- {
-
-
- HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-
-
-
- MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
- dumpInfo.ExceptionPointers = pException;
- dumpInfo.ThreadId = GetCurrentThreadId();
- dumpInfo.ClientPointers = TRUE;
-
-
-
- MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
-
- CloseHandle(hDumpFile);
- }
从上面的代码中可以看出,要想创建Dump文件,必须得到一个指向EXCEPTION_POINTERS结构的指针。怎么在try/except块中得到这个指针呢?这个时候就需要用到Windows API中的GetExceptionInformation()。这个函数的返回值就是一个指向EXCEPTION_POINTERS结构的指针。下面是具体的代码。
-
-
- LONG CrashHandler(EXCEPTION_POINTERS *pException)
- {
-
-
-
-
-
- MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);
-
-
-
- CreateDumpFile(_T("C:\\Test.dmp"), pException);
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- __try
- {
- MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
-
-
- __except(CrashHandler(GetExceptionInformation()))
- {
-
-
- MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);
- }
-
- MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);
-
- return 0;
- }
编译上面的代码并运行,会依次弹出下面这些对话框,并在C盘创建一个Dump文件Test.dmp。
有了Dump文件,就可以轻松定位使程序崩溃的那行代码,具体方法可参考我的《让程序在崩溃时体面的退出之Dump文件》。
-----------------------------------------------------------------------------------------------------------------------------
让程序在崩溃时体面的退出之终极解决方案(SEH+Dump+Unhandled Exception Filter)
转自 http://blog.csdn.net/starlee/article/details/6655779;
http://blog.csdn.net/wjtxt/article/details/6975972
在我的上篇文章《让程序在崩溃时体面的退出之SEH+Dump文件》我介绍了怎样用SEH加上Dump文件来避免程序的崩溃并在程序崩溃时创建Dump文件来帮助定位出现异常的代码行。可是只有try/except块中try块中的代码出现异常才能被捕捉到,try块外面的代码出现异常,程序照样会崩溃。
下面用《让程序在崩溃时体面的退出之SEH+Dump文件》文中的代码为例子来说明。
-
-
- void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)
- {
-
-
- HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-
-
-
- MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
- dumpInfo.ExceptionPointers = pException;
- dumpInfo.ThreadId = GetCurrentThreadId();
- dumpInfo.ClientPointers = TRUE;
-
-
-
- MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
-
- CloseHandle(hDumpFile);
- }
-
-
-
- LONG CrashHandler(EXCEPTION_POINTERS *pException)
- {
-
-
-
-
-
- MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);
-
-
-
- CreateDumpFile(_T("C:\\Test.dmp"), pException);
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- __try
- {
- MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
-
-
- __except(CrashHandler(GetExceptionInformation()))
- {
-
-
- MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);
- }
-
- MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);
-
- return 0;
- }
编译上面的代码并运行,会依次弹出下面这些对话框,并在C盘创建一个Dump文件Test.dmp。
如果把上面代码中的main()函数改成下面的样子,运行编译后的程序依然会崩溃。
- int _tmain(int argc, _TCHAR* argv[])
- {
- __try
- {
- MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
-
-
- __except(CrashHandler(GetExceptionInformation()))
- {
-
-
- MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);
- }
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
-
- MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);
-
- return 0;
- }
这种情况在实际编程中是很有可能出现的,毕竟我们不可能事先预计到所有可能出现导致程序崩溃的情况,并把这些代码放到try/except块中。那么对于这些不可预知的异常该怎么办呢?这就要用到我那篇《让程序在崩溃时体面的退出之Unhandled Exception》中的方法:用Windows API中的SetUnhandledExceptionFilter设置一个回调函数来处理这些无法预料的异常。下面是在上面的例子代码上修改后的代码。其中函数CreateDumpFile没有任何变化。
-
-
- wstring GetPresentTime()
- {
- SYSTEMTIME time;
- GetLocalTime(&time);
-
- wchar_t wszTime[128];
- swprintf_s(wszTime, _T("%04d-%02d-%02d %02d-%02d-%02d-%03d"), time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);
-
- return wstring(wszTime);
- }
-
-
-
- LONG CrashHandler(EXCEPTION_POINTERS *pException)
- {
-
-
-
-
-
- MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);
-
-
-
- wstring strDumpFileName = _T("C:\\") + GetPresentTime() +_T(".dmp");
-
-
-
- CreateDumpFile(strDumpFileName.data(), pException);
-
- return EXCEPTION_EXECUTE_HANDLER;
- }
-
- int _tmain(int argc, _TCHAR* argv[])
- {
-
-
- SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)CrashHandler);
-
- __try
- {
- MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
- }
- __except(CrashHandler(GetExceptionInformation()))
- {
-
-
-
-
-
- MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);
- }
-
-
-
- int i = 13;
- int j = 0;
- int m = i / j;
-
- MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);
-
- return 0;
- }
编译上面的代码,运行生成的EXE文件,可以看到在弹出上面提到的那一系列的对话框后,程序正常退出,没有崩溃。同时,在C盘下生成了2个Dump文件,文件名指出了发生异常的时刻。
上面的代码仅仅是为了说明怎样配合使用SEH和SetUnhandledExceptionFilter,所以except后的表达式和SetUnhandledExceptionFilter中所设置的回调函数都使用了同一个函数CrashHandler。在实际的应用中可以根据不同的需求而使用不同的函数。这个函数的参数必须是一个指向EXCEPTION_POINTERS的指针,返回值必须是这3个中的一个:EXCEPTION_CONTINUE_SEARCH,EXCEPTION_CONTINUE_EXECUTION,EXCEPTION_EXECUTE_HANDLER。这3个值的具体含义可以查阅MSDN或者我的那篇《让程序在崩溃时体面的退出之SEH》。
实际情况下,是不应该用同一个回调函数的。因为在except表达式中的函数是处理try块中的代码异常的;而用SetUnhandledExceptionFilter设置的回调函数是用来处理代码中没有被捕捉到的异常的。对于未被捕捉到的异常,这个回调函数是不知道异常发生的地方的,虽然可以通过异常代码知道异常的类型,但是由于不知道是什么状况引起的异常,所以没法做出相应的异常处理。一般情况下,这个回调函数是应用程序崩溃前的最后一道防线,这个函数中的代码被执行完后,应用程序就会被终止。所以,大部分的应用程序,在这个函数里都是弹出一个发送错误报告的对话框,来告诉用户程序发生异常,需要终止,可以把错误报告(一般是包括Dump文件和一些必要的文本信息)发送到指定地方帮助开发者来修改代码缺陷,以提高软件质量。
使用上面的方法编写出的应用程序不会崩溃,并且在出现异常的时候会产生Dump文件。程序的使用者会获得非常良好的用户体验。如果再给应用程序添加上Log信息,配合上Dump文件,就可以很轻松的定位程序中的异常,帮助开发者快速的修复代码中的错误。
===================================================================================================================
补充一点:
1、 Dump文件放在哪里?
Dump文件不用非要放在你编译出来的位置,你完全可以建立一个新的文件夹来放它。但若不是存放在编译出来的位置,需要将编译生成的PDB文件拷贝到Dump文件目录,或是利用VS2005打开Dump文件后,设置PDB文件路径。
2、 如何恢复当时的现场?
可能你要问,怎么可能,这个dump文件可是用户发给我的,我不可能去用户家里调试吧?这个恢复现场可不是指的非要到那台机器上去,而是要把产生dump文件对应的二进制文件拿到。但是恢复现场需要所有的二进制文件都要对应,你一定要有导致用户崩溃的那些Exe和DLL。既然是你发布的程序,Exe文件当然你会有。所以这里只考虑DLL就行了。Dump文件中记录了所有DLL文件的版本号和时间戳,所以你一定可以同过某种途径拿到它。如果你能从用户那里拿到最好,如果不方便,用户不可能用的是我们平常不常用的操作系统,所以找个有对应系统的机器一般都会有。但是记住不仅是文件名称要一致,还要核对版本和时间戳,如果不同一样没有办法用。
3、 如果客户用了某个特殊的补丁怎么办?
其实这个问题也很好解决,只要它不阻碍阅读堆栈,就不用管它,调试Dump和运行程序不一样,缺少一两个DLL没有任何问题。
4、 如果真的需要怎么办?
符号文件现在主要是指PDB文件。
如果没有符号文件,那么调试的时候可能导致堆栈错误。
如果你丢失了这个发布版本中你编译出来的那些exe和DLL的PDB,那么这个损失是严重的,重新编译出来的版本是不能使用的。
我自己的DLL都有了,可是缺的是系统的DLL的对应PDB文件怎么办?微软在它的符号数据库上为我们提供了所有的PDB文件,还有部分非关键DLL。设置好后程序将自动下载需要的PDB及DLL文件。
5、 拿到需要的文件了,这些文件应该放在哪里?
符号数据库中的文件不用动,把其它的exe和DLL、PDB文件放在dump文件目录里就行了。
6、 我用的是VS2005,明明有源代码,为什么显示不了?
这个是dump调试的最头痛问题,代码可能已经改过了,即使你从SVN拿到当时的版本,时间戳也是错的,VS2005就是不让你显示代码。其实只要在Tools/Options,Debugging/General中去掉Require source files to exactly match the original version的复选就行了。
------------------------------------------------------------------------------------------------------------------------------
让程序在崩溃时体面的退出总结
转自 http://blog.csdn.net/starlee/article/details/6662011
终于把《让程序在崩溃时体面的退出》这个系列的6篇文章全部发表出来了。
这6篇文章分别是:
《让程序在崩溃时体面的退出之Unhandled Exception》
《让程序在崩溃时体面的退出之CallStack》
《让程序在崩溃时体面的退出之Dump文件》
《让程序在崩溃时体面的退出之SEH》
《让程序在崩溃时体面的退出之SEH+Dump文件》
《让程序在崩溃时体面的退出之终极解决方案(SEH+Dump+Unhandled Exception Filter)》
对这些东西的研究起始于项目中一个Prototype的开发。这个Prototype就是给我们的产品加上Log信息。既然是Log信息,那么在应用程序崩溃的时候也要记录发生了什么而导致程序崩溃,而且这个信息对于开发人员来说是非常重要的。为此,在网上搜索了大量的资料来看,英文的中文的,再加上MSDN和那本砖头书《Windows via C/C++》,总算是把Windows下软件开发中对致命异常的处理给搞清楚了。按照我以前的习惯,把学到的新知识从新梳理总结了一下,写成了这6篇系列文章。
现在文章发表完了,这篇文章只是一个总结,而不是终结。因为在学习和写文章的过程中,还有一些问题没有彻底搞明白,比如SEH中的EXCEPTION_CONTINUE_EXECUTION和SEH与C++中的EH的混合使用。
SEH中的EXCEPTION_CONTINUE_EXECUTION
不管是MSDN还是《Windows via C/C++》,对SEH中的EXCEPTION_CONTINUE_EXECUTION解释都一样:Exception is dismissed. Continue execution at the point where the exception occurred.。也就是说返回到出现异常的地方重新执行。而且《Windows via C/C++》还专门写了一个例子来说明。可是我按照书上的例子写出同样的代码,执行的结果却跟书上不一样:首先,代码执行结果并不是期望的那样;其次,出现异常的那行代码后面的代码并没有执行。在网上搜索了一通,得到的答案是:返回到出现异常的地方重新执行是返回到汇编指令的那个地方,而不是C++代码的那一行,如果这个指令用到了寄存器内的内容,那么由于寄存器内容没有被修改,所以执行结果依然是错的。但是这只是解释了第一问题,对于第二个问题依然没有答案。如果有时间的话,我会继续对这个问题进行研究。
SEH与C++中的EH的混合使用
SEH是结构化的,不支持面向对象。而C++中的try/catch又只能捕捉到预定义的异常。各有优缺点,要想捕捉到所有异常,就要2个都用。那么代码的可读性和可维护性就会很差。不过,有网友看到我的文章后告诉我了一篇文章的地址,那篇文章就是讲怎么样将SEH的异常转换成C++中的异常。这是一个非常好的方法。有空的话,我也会对这方面进行一下研究。
------------------------------------------------------------------------------------------------------------------------------
从另外一个进程dump去dump问题进程的一点讨论
MiniDumpWriteDump from another process
How do I get at the exception information when using MiniDumpWriteDump out-of-process?
loader lock deadlock in MiniDumpWriteDump