基类对象,并且发生异常时它们中的一部分已经构造完成的话,就必须调用这些对象的析构函数。和普通函数一样,编译器也给构造函数生成了相关的数据来帮助完成这个任务。
展开堆栈时,异常处理程序调用的是用户定义的析构函数,这一点你必须注意,因为它也有可能抛出异常!C++标准规定堆栈展开过程中,析构函数不能抛出异常,否则系统将调用std::terminate。
实现
本节我们讨论其他三个有待详细解释的问题:
a)如何安装异常处理程序
b)catch块重新抛出异常或抛出新异常时应该如何处理
c)如何对所有线程提供异常处理支持
随同本文,有一个演示项目,查看其中的readme.txt文件可以得到一些编译方面的帮助①。
第一项任务是安装异常处理程序,也就是把VC++的处理程序替换掉。从前面的讨论中,我们已经清楚地知道__CxxFrameHandler函数是VC++所有异常处理工作的入口。编译器为每个函数都生成一段代码,它们在发生异常时被调用,把相应的funcinfo结构的指针交给__CxxFrameHandler。
install_my_handler()函数会改写__CxxFrameHandler的入口处的代码,让程序跳转到my_exc_handler()函数。不过,__CxxFrameHandler位于只读的内存页,对它的任何写操作都会导致访问违例,所以必须首先用VirtualProtectEx把该内存页的保护方式改成可读写,等改写完毕后,再改回只读。写入的数据是一个jmp_instr结构。
//install_my_handler.cpp #include <windows.h> #include "install_my_handler.h" //C++默认的异常处理程序 extern "C" EXCEPTION_DISPOSITION __CxxFrameHandler( struct _EXCEPTION_RECORD* ExceptionRecord, void* EstablisherFrame, struct _CONTEXT* ContextRecord, void* DispatcherContext ); namespace { char cpp_handler_instructions[5]; bool saved_handler_instructions = false; } namespace my_handler { //我的异常处理程序 EXCEPTION_DISPOSITION my_exc_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ) throw(); #pragma pack(push, 1) struct jmp_instr { unsigned char jmp; DWORD offset; }; #pragma pack(pop) bool WriteMemory(void* loc, void* buffer, int size) { HANDLE hProcess = GetCurrentProcess(); //把包含内存范围[loc,loc+size]的页面的保护方式改成可读写 DWORD old_protection; BOOL ret = VirtualProtectEx(hProcess, loc, size, PAGE_READWRITE, &old_protection); if(ret == FALSE) return false; ret = WriteProcessMemory(hProcess, loc, buffer, size, NULL); //恢复原来的保护方式 DWORD o2; VirtualProtectEx(hProcess, loc, size, old_protection, &o2); return (ret == TRUE); } bool ReadMemory(void* loc, void* buffer, DWORD size) { HANDLE hProcess = GetCurrentProcess(); DWORD bytes_read = 0; BOOL ret = ReadProcessMemory(hProcess, loc, buffer, size, &bytes_read); return (ret == TRUE && bytes_read == size); } bool install_my_handler() { void* my_hdlr = my_exc_handler; void* cpp_hdlr = __CxxFrameHandler; jmp_instr jmp_my_hdlr; jmp_my_hdlr.jmp = 0xE9; //从__CxxFrameHandler+5开始计算偏移,因为jmp指令长5字节 jmp_my_hdlr.offset = reinterpret_ca |