VC异常处理

SEH定义
  SEH("Structured Exception Handling"),即结构化异常处理.是(windows)操作系统提供给程序设计者的强有力的处理程序错误或异常的武器。   在VISUAL C++中你或许已经熟悉了_try{} _finally{} 和_try{} _except {} 结构,这些并不是编译程序本身所固有的,本质上只不嵌詗indows内在提供的结构化异常处理的包装,不用这些高级语言编译器所提供的包装 ,照样可以利用系统提供的强大seh处理功能,在后面你将可以看到,用系统本身提供seh结构和规则以及ASM语言,我们将对SEH的机制以及实现有一个彻底的了解.。
发生异常时系统的处理顺序
  1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器的存在吗?   2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.   3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程,可交由链起来的其他例程处理.   4.如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.   5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异常处理例程的话,系统转向对它的调用。   6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框,你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统就调用ExitProcess终结程序。   7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会。


SetUnhandledExceptionFilter可以设置在WER弹出之前的最后一次异常处理的机会。所以只要设置好我们的异常处理函数就可以捕获到Unhandled Exception。

[Code]
SetUnhandledExceptionFilter(CleanToolExceptionFun);
LONG WINAPI CleanToolExceptionFun(struct _EXCEPTION_POINTERS* ExceptionInfo)
{

}
[/Code]

在这个函数里你可以做以下几件事:
1·获得异常的ExceptionRecord和Context
2·获得异常编号
3·获得异常的模块
4·获得堆栈的信息
5·程序继续执行还是退出

1和2都好办,直接通过传递进来的参数就可以获得:
[Code]
ExceptionInfo->ExceptionRecord->ExceptionAddress;
[/Code]

3通常的做法是,通过ExceptionRecord获得异常的内存地址,调用VirtualQuery获得Handle,再通过GetModuleFileName获得出现异常的文件路径。
[code]
MEMORY_BASIC_INFORMATION mbi = {0};
if (FALSE == ::VirtualQuery( addr, &mbi, sizeof(mbi) ) ) return;
UINT_PTR h_module = (UINT_PTR)mbi.AllocationBase;
::GetModuleFileNameW((HMODULE)h_module, sz_module, len);
return;
[/code]

4的做法是通过StackWalk64函数便利堆栈。
[code]
StackWalk64(IMAGE_FILE_MACHINE_I386,hCurrentProcess,hCurrentThread,&sStackFrame,pContext,0,0,0,0)
[/code]

5是通过函数的返回值来确定的。

在实现的过程遇到几个好玩的问题:
1·如果exe调用dll,dll中要使用AFX_MANAGE_STATE(AfxGetStaticModuleState())之后,dll的资源才能被访问到;但是如果dll调用exe,则必须使用AFX_MANAGE_STATE(AfxGetAppModuleState())。

2·StackWalk64的问题:在Release版本下,Stackwalk64获得的堆栈信息不完整,具体表现为:如果异常是由MFC的模块抛出的,那么获得的堆栈将缺少栈最上方我们自己的模块的信息。比如A->B->MFC,则获得的栈为(至顶向下):MFC->A。但是在Debug的版本下不存在这个问题。为此我还特意用OD调试了Release版本,发现堆栈是完整的,那么只能认为是StackWalk的问题了。

3·在便利异常的模块的节表的时候,Release版本下程序偶尔会莫名其妙地退出。

4·Release编译的优化问题:如果我直接写int i = 5/0,编译时会直接报错。如果我写成:
int i = 0;
AfxMessageBox(L"%d", 5 /i),编译就可以通过。汗……



-------------------------------------------------------------------------------------------------------------------------

LONG CDataManager::UnHandleException(_EXCEPTION_POINTERS* pei)
{
    tstring path = GetModulPath();
    tstring appname = _T("EATM");
    CreateDirectory(_T("D:\\exception"), NULL);

    tstring exceptionFile = _T("D:\\exception\\eatm.log");
    tstring date = FormatDate();
    tstring datetime = FormatDateTime(-1, _T("-"), _T("-"));
    tstring dumpFile = _T("D:\\exception\\") + appname + datetime + _T(".dmp");

    CIniFile inifile(exceptionFile);
    tstring msg;
    msg.format(_T("UnHandleException: %s"), dumpFile.c_str());
    inifile.WriteStr(date, appname + datetime, msg);


    HANDLE lhDumpFile = CreateFile(dumpFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL , NULL);

    MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;
    loExceptionInfo.ExceptionPointers = pei;
    loExceptionInfo.ThreadId = GetCurrentThreadId();
    loExceptionInfo.ClientPointers = TRUE;

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), lhDumpFile,
                      MiniDumpNormal,
                      &loExceptionInfo, NULL, NULL);

    CloseHandle(lhDumpFile);

    return EXCEPTION_EXECUTE_HANDLER;//EXCEPTION_CONTINUE_SEARCH;
}




你可能感兴趣的:(C/C++)