今天是我侄子的4岁生日,特此更新一篇,祝我小侄子在幼儿园多泡MM
我们都知道SEH异常处理机制,那VEH、TopLevelEH呢?他们执行的先后顺序是怎样的呢?当这些机制都不使用的情况下,会发生什么情况呢?异常处理器是怎么工作的?如果你对此感兴趣,那我们就一起来扒开异常处理机制的面纱吧
术语 :
SEH : 结构化异常处理
VEH : 向量化异常处理
TopLevelEH :顶层异常处理
EXCEPTION_EXECUTE_HANDLER :该异常被处理。从异常处下一条 指令继续执行
EXCEPTION_CONTINUE_SEARCH :不能处理该异常,让别人处理它吧
EXCEPTION_CONTINUE_EXECUTION :该异常被忽略。从异常处处继续执行
//调试器返回值:
DBG_CONTINUE : 等同于EXCEPTION_CONTINUE_EXECUTION
DBG_EXCEPTION_NOT_HANDLED :等同于EXCEPTION_CONTINUE_SEARCH
想想对我这等语文是体育老师教出来的童鞋来说,想把这个主题将透彻,还真是有点难度的~,我们还是按异常处理器执行顺序来吧,废话不多说,开始。。。
异常处理器其实包含
内核异常处理 和
R3异常处理 ,
内核异常处理 比较简单,我也对它没兴趣,所以这里就把它给
忽略 了。我们只讲R3程序产生异常时,异常处理器是怎么工作的。
异常处理器处理顺序流程 :
1. 交给调试器 (进程必须被调试)
2. 执行VEH
3. 执行SEH
4. TopLevelEH (进程被调试时不会被执行)
5. 交给调试器 (上面的异常处理都说处理不了,就再次交给调试器)
6. 调用异常端口通知csrss.exe
大致分上面几步把,下面咱就详细讨论一下各个步骤都干了哪些细活
1. 第一次交给调试器
如果该出现异常的程序正在被调试,则该异常首先交给调试器处理(通过DebugPort)。
调试器拿到这个异常后,需要判断是否要处理该异常,如果处理该异常返回
DBG_CONTINUE ,否则返回
DBG_EXCEPTION_NOT_HANDLED
while(!bExit)
{
DWORD dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
DEBUG_EVENT debugEvent;
WaitForDebugEvent(&debugEvent, INFINITE);
switch ( debugEvent.dwDebugEventCode )
{
case EXCEPTION_DEBUG_EVENT:
{
EXCEPTION_DEBUG_INFO* pExcpInfo = &debugEvent.u.Exception;
if ( MessageBox(0,_T("处理该异常?"), _T("我是调试器"),MB_YESNO)==IDYES )
{
dwContinueStatus = DBG_CONTINUE;
//...
}
}
break;
//...
}
ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, dwContinueStatus);
}
2. 执行VEH
这里就不讲Veh的概念了,有兴趣的去Google一下。
如果
没有被调试 ,或者
调试器返回DBG_EXCEPTION_NOT_HANDLED ,则就会检查是否存在VEH。如果存在VEH,则把异常交给他们处理。
VEH是个链表 ,可以存在多个Veh。
每个VEH按顺序被调用 。
一个VEH可以返回连个值:
EXCEPTION_CONTINUE_SEARCH 、
EXCEPTION_CONTINUE_EXECUTION 。返回
EXCEPTION_EXECUTE_HANDLER 是
无效 的,等同于EXCEPTION_CONTINUE_SEARCH。
当一个Veh返回
EXCEPTION_CONTINUE_SEARCH ,则把异常交给下一个VEH处理。
如果返回
EXCEPTION_CONTINUE_EXECUTION ,认为已经被处理,退出异常处理器
在异常指令处继续执行 。
从执行顺序来看,
VEH是在SEH之前执行 的,并且
不依赖某一线程 ,本进程中
任何 线程出现
异常都可以被VEH处理 ,所以在有些时候是很有用处的。
怎么添加一个VEH呢?
LONG NTAPI FirstVectExcepHandler( PEXCEPTION_POINTERS pExcepInfo )
{
if( ... )
{
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
//参数1=1表示插入Veh链的头部,=0表示插入到VEH链的尾部
AddVectoredExceptionHandler( 1, &FirstVectExcepHandler );
3. 执行SEH
SEH应该是大家都比较熟悉的了。当所有的VEH都不处理该异常,该异常就会让SEH处理。
SEH是基于线程栈的异常处理机制,所以它只能处理自己线程的异常。
先看一个示例代码:
LONG FirstSEHer( PEXCEPTION_POINTERS pExcepInfo )
{
TCHAR* pTitle = _T("第一个SEH处理器");
_tprintf( _T("[EH.Exe] [SEH][1] in \n") );
LONG nRet = ShowSelectMessageBox(pTitle);
_tprintf( _T("[EH.Exe] [SEH][1] out \n") );
return nRet;
}
LONG SecondSEHer( PEXCEPTION_POINTERS pExcepInfo )
{
TCHAR* pTitle = _T("第二个SEH处理器");
_tprintf( _T("[EH.Exe] [SEH][2] in \n") );
LONG nRet = ShowSelectMessageBox(pTitle);
_tprintf( _T("[EH.Exe] [SEH][2] out \n") );;
return nRet;
}
void ExcepFunction()
{
__try
{
__try
{
__try
{
_tprintf( _T("[EH.Exe] *[CALL] int 3\n") );
__asm int 3;
}
__finally
{
printf( "[EH.Exe] *[SEH][0] finally call...\n" );
}
}
__except( FirstSEHer(GetExceptionInformation()) )
{
_tprintf( _T("[EH.Exe] [SEH][1] 被俺处理了~(只有返回EXCEPTION_EXECUTE_HANDLER才会走到这里)\n"));
}
}
__except( SecondSEHer(GetExceptionInformation()) )
{
_tprintf( _T("[EH.Exe] [SEH][2] 被俺处理了(只有返回EXCEPTION_EXECUTE_HANDLER才会走到这里)\n"));
}
}
ExcepFunction 函数有三个SEH,但是有两个Headler。当__asm int 3;被执行时就会被SEH捕获。捕获后,首先交给FirstSEHer处理,如果
FirstSEHer 返回
EXCEPTION_CONTINUE_SEARCH 则才会交给
SecondSEHer 处理。
FirstSEHer 可以返回三个值:
EXCEPTION_CONTINUE_SEARCH 、
EXCEPTION_EXECUTE_HANDLER 、
EXCEPTION_CONTINUE_EXECUTION 。
当返回
EXCEPTION_CONTINUE_SEARCH ,执行上一层SEH,这里执行
SecondSEHer
返回
EXCEPTION_EXECUTE_HANDLER 时则表示异常被处理,会先把内部的__finally块执行完,再跳到自身的__except块中执行。
返回
EXCEPTION_CONTINUE_EXECUTION 时表示该异常被忽略,会
再次执行__asm int 3处指令 。如果该条汇编不被修正成其他指令(如nop),则会再次产生一个异常。
另外,如果想在 try catch的C++异常中捕获系统异常,必须让C++支持SEH异常处理。设置方法: Vc-〉项目属性-->配置属性-->c/C++-->代码生成-->启用C++异常,选中"是,但有SEH异常(/EHa)"。
4. TopLevelEH
顶层异常处理,这个其实是
利用SEH实现的 。在最顶层的SEH中,可以注册一个顶层异常处理器。虽然他是基于SEH实现的,但是它可以处理
所有线程 抛出的异常。
当SEH都处理不了该异常,在最顶层的SEH中就会检查是否注册了顶层异常处理,如果注册了,则执行顶层异常处理。
注意:如果该进程正在调试状态,顶层异常处理会被忽略,不会被执行 。
顶层异常处理函数也可以返回三个值:
EXCEPTION_CONTINUE_SEARCH 、
EXCEPTION_EXECUTE_HANDLER 、
EXCEPTION_CONTINUE_EXECUTION 。
返回
EXCEPTION_CONTINUE_EXECUTION 时,和SEH一样。
返回
EXCEPTION_EXECUTE_HANDLER 时,则直接
杀死该进程 。
返回
EXCEPTION_CONTINUE_SEARCH 时,会查注册表,检查是否存在
实时调试器。 注册表路径:KLM\software\microsoft\windows nt\currentvsrsion\aedebug。如果Auto==1,Debugger!=NULL则根据Debugger中指示的参数启动实时调试器,让调试器处理该异常 。
(如果不存在顶层异常且进程没被调试,也会检查并启动实时调试器)
注册方法:
LONG NTAPI TopLevelExcepFilter( PEXCEPTION_POINTERS pExcepInfo )
{
TCHAR* pTitle = _T("*顶级* 异常处理器");
_tprintf( _T("[EH.Exe] [TOP] in \n") );
LONG nRet = ShowSelectMessageBox(pTitle);
_tprintf( _T("[EH.Exe] [TOP] out \n") );;
return nRet;
}
//注册
SetUnhandledExceptionFilter( &TopLevelExcepFilter );
顶层异常处理通常用来生成程序Dump文件。供开发人员分析。
5. 再次交给调试器
如果上述的异常处理机制都没有处理该异常,则调试器会再次接收该异常。
调试器这个时候返回
DBG_CONTINUE ,则和第一次相同。
返回
DBG_EXCEPTION_NOT_HANDLED ,则直接
杀死该进程 。
6. 调用异常端口通知csrss.exe
当上面提到的都没有处理该异常,则调用
ExceptionPort 通知csrss.exe。csrss.exe的做法是会弹出一个对话框:
这个时候还有一次修复异常并让程序继续运行的机会,就是点击“调试”按钮。其他按钮都很导致异常进程被终止。
终于写完了,为了完成该文,特意写了个小软件。里面包含一个异常产生程序(ExcepHandler) 和一个简单调试器(MyDbg) 。源码在此:
TestException.rar
(调试器参考了超然兄的代码,特此感谢)
欢迎各位拍砖~
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: