博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
白话windows之四 异常处理机制(VEH、SEH、TopLevelEH...)
阅读量:5010 次
发布时间:2019-06-12

本文共 5324 字,大约阅读时间需要 17 分钟。

原文地址:

今天是我侄子的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_SEARCHEXCEPTION_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_SEARCHEXCEPTION_EXECUTE_HANDLEREXCEPTION_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_SEARCHEXCEPTION_EXECUTE_HANDLEREXCEPTION_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的做法是会弹出一个对话框:
名称:  sd.jpg 查看次数: 0 文件大小:  33.7 KB
这个时候还有一次修复异常并让程序继续运行的机会,就是点击“调试”按钮。其他按钮都很导致异常进程被终止。
终于写完了,为了完成该文,特意写了个小软件。里面包含一个异常产生程序(ExcepHandler)和一个简单调试器(MyDbg)源码在此:
.
(调试器参考了超然兄的代码,特此感谢)

转载于:https://www.cnblogs.com/HouZhiHouJueBlogs/articles/3936513.html

你可能感兴趣的文章
动态绑定treeview的方法
查看>>
jvm参数
查看>>
3-1 案例环境初始化
查看>>
读《构建之法》第四章和十七章有感
查看>>
01背包
查看>>
开发一个12306网站要多少钱?技术分析12306合格还是不合格
查看>>
Selenium 入门到精通系列:六
查看>>
HTTP与TCP的区别和联系
查看>>
android 实现2张图片层叠效果
查看>>
我个人所有的独立博客wordpress都被挂马
查看>>
html5——动画案例(时钟)
查看>>
调用Android系统“应用程序信息(Application Info)”界面
查看>>
ios中用drawRect方法绘图的时候设置颜色
查看>>
数据库中的外键和主键理解
查看>>
个人博客03
查看>>
Expression<Func<T,TResult>>和Func<T,TResult>
查看>>
文件缓存
查看>>
关于C语言中return的一些总结
查看>>
Codeforces Round #278 (Div. 2)
查看>>
51. N-Queens
查看>>