转自https://www.cnblogs.com/yangchaobj/archive/2013/02/23/2923479.html
标准异常都在头文件
异常 描述
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 当使用了无效的参数时,会抛出该异常。
std::length_error 当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
std::overflow_error 当发生数学上溢时,会抛出该异常。
std::range_error 当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error 当发生数学下溢时,会抛出该异常。
#include
#include
using namespace std;
struct MyException : public exception
{
const char * what () const throw ()
{
return "C++ Exception";
}
};
int main()
{
try
{
throw MyException();
}
catch(MyException& e)
{
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
}
catch(std::exception& e)
{
//其他的错误
}
}
//project -> Properties -> C/C++ -> Code Generation --> Enable C++ Exceptions
//选择 Yes with SEH Exceptions (/EHa) 这样的话C++的try catch 也可以捕获到空指针,内存越界,0除异常
//默认是选择Yes (/EHsc)
#include
#include
using namespace std;
/**********************************
//project -> Properties -> C/C++ -> Code Generation --> Enable C++ Exceptions
//选择 Yes with SEH Exceptions (/EHa) 这样的话C++的try catch 也可以捕获到空指针,内存越界,0除异常
//默认是选择Yes (/EHsc)
**********************************/
void TestIntType()
{
try
{
throw 1;
}
catch(...)
{
cout<< "在 try block 中, 准备抛出一个异常." << endl;
}
}
void TestDoubleType()
{
try
{
throw 0.5;
}
catch(...)
{
cout<< "在 try block 中, 准备抛出一个异常." << endl;
}
}
void TestEmptyPointType()
{
try
{
int* p = NULL;
*p = 3;
}
catch(...)
{
cout<< "非法地址操作异常" << endl;
}
}
void TestDivZeroType()
{
try
{
int b = 0;
int a = 3/b;
}
catch(...)
{
cout<< "0除异常" << endl;
}
}
void TestMemoryOutType()
{
int * a = new int[4];
try
{
for (int i = 0; i<245; i++)
{
a++;
}
*a = 3;
}
catch(...)
{
cout<< "内存越界异常" << endl;
}
}
int main(int argc, char* argv[])
{
TestEmptyPointType();
//TestDivZeroType();
TestMemoryOutType();
return 1;
}
问题:有些异常可以捕获,生成 dump,有些则不能。
要解决这个问题,必须对 windows 和 C++ 的异常机制有一个了解。
平时我们写程序都这样捕获异常:
1 try 2 { 3 fire_access_violation(); 4 } 5 catch (exception& ex) 6 { 7 // ...... 8 } 9 catch (...) 10 { 11 // ...... 12 }
第3行的函数会触发 ACCESS_VIOLATION (0xC0000005) 异常,第5行用来catch 普通的 C++异常,第9行用来捕获其他异常,ACCESS_VIOLATION 就属于这种异常。
但运行后发现,第9行的 catch (...) 什么也抓不到。要让它起作用,还需要调整默认的编译参数,找到 C++ -> Code Generation -> Enable C++ Exceptions 选项,设为 Yes with SEH Exceptions (/EHa),即通过SEH机制,让C++的 catch (...) 能够捕获所有的异常。
这一步做下来,我们已经可以捕获try 块上的所有异常了。但虽然捕获了异常,我们还无法知道异常类型。也没有前文所说的 ContextRecord 信息。要更精确的得到这些信息,还需要多做一步:把 SEH 映射成C++异常。
C 提供了一个函数 _set_se_translator
_se_translator_function _set_se_translator( _se_translator_function seTransFunction ); typedef void (*_se_translator_function)(unsigned int, struct _EXCEPTION_POINTERS* );
这个函数要尽量早的注册到系统,所以在线程一开始的时候,就要调用这个函数。
它给本线程设置一个异常转换函数,当有 SEH 异常发生时,会调到用户注册的 _se_translator_function,传入 exception code 和 EXCEPTION_POINTERS,这时我们需要自己生成一个结构体,把这些非常有用的信息存在结构体里。还是那句话:如果能确保异常处理还在本堆栈上,并且异常信息还没有出栈,则存一个指针即可,否则要把 EXCEPTION_POINTERS 的整个结构复制过来,在遇到嵌套异常时,他的成员 ExceptionRecord 有可能是一个链表,如何保存这个链表还是件比较麻烦的事儿,所以能在原地处理异常时最好!
转换函数看起来可以是这样:
1 struct StructuredException : public exception 2 { 3 PEXCEPTION_POINTERS exp_ptr; 4 5 StructuredException() : exception() {} 6 StructuredException(const char* text) : exception(text) {} 7 }; 8 9 void trans_func(unsigned int code, EXCEPTION_POINTERS* p) 10 { 11 cout << "Exception Record: " << p->ExceptionRecord << endl; 12 StructuredException se; 13 se.exp_ptr = p; 14 throw se; 15 }
StructuredException 从 C++ 异常 exception 继承。只要安装了 tranlator 的线程内发生了结构化异常,windows 就会在该线程上调用次函数。
最后一句即第14行,抛出StructuredException,即抛出一个C++异常。通过 catch (exception& ex) 就可以捕获它。当然为了区别处理结构化异常,也可以这也写 catch (StructuredException& se) {...},毕竟结构化异常的出现时超出预期的。
到了这一步,可以保证每个线程都在我们的保护之下。但还有一些线程不是我们自己写的,可能是一些库自己创建的,这些线程有可能没有try块的保护,如果这些线程发生了异常,还是会造成程序悄悄退出,没有 dump。windows 也提供了方法来抓住这种漏网之鱼。
Windows 提供了 UnhandledExceptionFilter 来处理那些没被 catch 住的异常。
1 LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter( 2 __in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter 3 ); 4 5 LONG WINAPI UnhandledExceptionFilter( 6 __in struct _EXCEPTION_POINTERS* ExceptionInfo 7 ); 8 9 LONG WINAPI unhandled_exception_filter(__in struct _EXCEPTION_POINTERS *ExceptionInfo) 10 { 11 // do something 12 return EXCEPTION_CONTINUE_SEARCH; // EXCEPTION_EXECUTE_HANDLER 13 }
整个进程设置一个 filter 就可以拦住进程内任何线程抛出的漏网的异常。如果你用 debugger 调试 filter 代码,会发现它从来不会被调到,因为异常已经被 debugger 拦下来了,于是就不是“漏网之鱼”,所以这个 filter 不会被调到。
可以看到 filter 的参数里也有 EXCEPTION_POINTERS,所以在这个 filter 函数里我们可以调用 MiniDumpWriteDump 生成我们需要的 dump 文件。filter被调到的线程就是发生异常的线程。
做到这一步,进程内几乎所有的异常都会被我们捕获到并通过 mini dump 文件把现场保存下来以供事后分析。
为什么说是“几乎所有”呢,还是有一些特殊情况下的异常无法被捕获到:
当遇到 Heap Corrupt 异常时(两次 delete 同一块内存地址就会出现这种异常),windows有两种处理策略:杀掉进程或者不杀。在64位的 windows server 下,当堆发生错误时,系统会直接杀死进程,不会有全局展开,异常捕获函数什么都不活不到,这种异常代码是:0xc0000374,这时需要外挂 windbg 或 cdb 来帮助我们 dump。
如果 mini dump 的参数设的过于“求全”,既要保存堆栈信息又要保持堆信息,系统很可能没有足够的硬盘空间来保持 dump 文件,这样 mini dump 也会失败。
但无论如何,只要进程非正常退出,总会在系统事件中记上一笔,我们在 Event Viewer 里就可以看到。
总结:
通过 /Eha 编译参数,我们可以既捕获C++异常,又捕获结构化异常。
通过 _set_se_tranlator 可以把结构化异常转换成C++异常,并保存异常发生的现场(需要 /Eha 参数)。
通过 SetUnhandledExceptionFilter,可以捕获没有 try-catch/__try __except 保护的线程上抛出的异常。
通过 MiniDumpWriteDump 加上我们保存的异常现场信息,我们可以在生成 mini dump 时把 进程id、线程id、发生异常的时间、ContextRecord 都写进 mini dump(前三项通过文件名的形式),极大的方便后期调试。
通过以上努力,可以保证绝大多数异常都会被捕获并保存下现场信息。