//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,  AGE_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_cast(my_hdlr) - (reinterpret_cast(cpp_hdlr) + 5);
if(!saved_handler_instructions) { if(!ReadMemory(cpp_hdlr, cpp_handler_instructions, sizeof(cpp_handler_instructions))) return false; saved_handler_instructions = true; }
return WriteMemory(cpp_hdlr, &jmp_my_hdlr, sizeof(jmp_my_hdlr)); }
bool restore_cpp_handler() { if(!saved_handler_instructions) return false; else { void* loc = __CxxFrameHandler; return WriteMemory(loc, cpp_handler_instructions, sizeof(cpp_handler_instructions)); } } }
编译指令#pragma pack(push, 1)告诉编译器不要在jmp_instr结构中填充任何用于对齐的空间。没有这条指令,jmp_instr的大小将是8字节,而我们需要它是5字节。 现在重新回到异常处理这个主题上来。调用catch块时,它可能重新抛出异常或抛出新异常。前一种情况下,异常处理程序必须继续传播(propagate)当前异常;后一种情况下,它需要在继续之前销毁原来的异常。此时,处理程序要面对两个难题:“如何知道异常是源于catch块还是程序的其他部分”和“如何跟踪原来的异常”。我的解决方法是:在调用catch块之前,把当前异常保存在exception_storage对象中,并注册一个专用于catch块的异常处理程序——catch_block_protector。调用get_exception_storage()函数,就能得到exception_storage对象: exception_storage* p = get_exception_storage(); p->set(pexc, pexc_info);
注册 catch_block_protector; 调用catch块; //.... 这样,当catch块(重新)抛出异常时,程序将会执行catch_block_protector。如果是抛出了新异常,这个函数可以从exception_storage对象中分离出前一个异常并销毁它;如果是重新抛出原来的异常(可以通过ExceptionInformation数组的前两个元素知道是新异常还是旧异常,后一种情况下着两个元素都是0,参见下面的代码),就通过拷贝ExceptionInformation数组来继续传播它。下面的代码就是catch_block_protector()函数的实现。
//------------------------------------------------------------------- // 如果这个处理程序被调用了,可以断定是catch块(重新)抛出了异常。 // 异常处理程序(my_handler)在调用catch块之前注册了它。其任务是判断 // catch块抛出了新异常还是重新抛出了原来的异常,并采取相应的操作。 // 在前一种情况下,它需要销毁传递给catch块的前一个异常对象;在后一种 // 情况下,它必须找到原来的异常并将其保存到ExceptionRecord中供异常 // 处理程序使用。 //------------------------------------------------------------------- EXCEPTION_DISPOSITION catch_block_protector( _EXCEPTION_RECORD* ExceptionRecord, void* EstablisherFrame, struct _CONTEXT *ContextRecord, void* DispatcherContext ) throw () { EXCEPTION_REGISTRATION *pFrame; pFrame= reinterpret_cast<EXCEPTION_REGISTRATION*>(EstablisherFrame); if(!(ExceptionRecord->ExceptionFlags & (_EXCEPTION_UNWINDING | _EXCEPTION_EXIT_UNWIND))) { void *pcur_exc = 0, *pprev_exc = 0; const excpt_info *pexc_info = 0, *pprev_excinfo = 0; exception_storage* p = get_exception_storage(); pprev_exc = p->get_exception(); pprev_excinfo = p->get_exception_info(); p->set(0, 0); bool cpp_exc = ExceptionRecord->ExceptionCode == MS_CPP_EXC; get_exception(ExceptionRecord, &pcur_exc); get_excpt_info(ExceptionRecord, &pexc_info); if(cpp_exc && 0 == pcur_exc && 0 == pexc_info) //重新抛出 { ExceptionRecord->ExceptionInformation[1] = reinterpret_cast<DWORD>(pprev_exc); ExceptionRecord->ExceptionInformation[2] = reinterpret_cast<DWORD>(pprev_excinfo); } else { exception_helper::destroy(pprev_exc, pprev_excinfo); } } return ExceptionContinueSearch; }
下面是get_exception_storage()函数的一个实现: exception_storage* get_exception_storage() { static exception_storage es; return &es; }
在单线程程序中,这是一个完美的实现。但在多线程中,这就是个灾难了,想象一下多个线程访问它,并把异常对象保存在里面的情景吧。由于每个线程都有自己的堆栈和异常处理链,我们需要一个线程安全的get_exception_storage实现:每个线程都有自己单独的exception_storage,它在线程启动时被创建,并在结束时被销毁。Windows提供的线程局部存储(thread local storage,TLS)可以满足这个要求,它能让每个线程通过一个全局键值来访问为这个线程所私有的对象副本,这是通过TlsGetvalue()和TlsSetvalue这两个API来完成的。 Excptstorage.cpp中给出了get_exception_storage()函数的实现。它会被编译成动态链接库,因为我们可以籍此知道线程的创建和退出——系统在这两种情况下都会调用所有(当前进程加载的)dll的DllMain()函数,这让我们有机会创建特定于线程的数据,也就是exception_storage对象。 //excptstorage.cpp
#include "excptstorage.h" #include <windows.h>
namespace { DWORD dwstorage; }
namespace my_handler { __declspec(dllexport) exception_storage* get_exception_storage() throw () { void * p = TlsGetvalue(dwstorage); return reinterpret_cast <exception_storage*>(p); } }
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { using my_handler::exception_storage; exception_storage *p; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //主线程(第一个线程)不会收到DLL_THREAD_ATTACH通知,所以, //与其相关的操作也放在这了 dwstorage = TlsAlloc(); if (-1 == dwstorage) return FALSE; p = new exception_storage(); TlsSetvalue(dwstorage, p); break ; case DLL_THREAD_ATTACH: p = new exception_storage(); TlsSetvalue(dwstorage, p); break; case DLL_THREAD_DETACH: p = my_handler::get_exception_storage(); delete p; break ; case DLL_PROCESS_DETACH: p = my_handler::get_exception_storage(); delete p; break ; } return TRUE; }
结论
综上所述,异常处理是在操作系统的协助下,由C++编译器和运行时异常处理库共同完成的。
|