zoukankan      html  css  js  c++  java
  • 异常处理第三讲,SEH(结构化异常处理),异常展开问题

               异常处理第三讲,SEH(结构化异常处理),异常展开问题


    作者:IBinary
    出处:http://www.cnblogs.com/iBinary/
    版权所有,欢迎保留原文链接进行转载:)

    不知道昨天有木有小伙伴尝试写一下SEH异常处理的代码.如果没写过,请回去写( :) 不写也没关系 ( ̄┰ ̄*))

    那么说下昨天的异常处理的问题

    一丶昨天代码问题所在

    请看下昨天的代码

    // SEHecpt.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <WINDOWS.H>
    #include <STDLIB.H>
    #include <WINNT.H>
    
    void fun2();
    
    EXCEPTION_DISPOSITION __cdecl HANDLER1(
                                           struct _EXCEPTION_RECORD *ExceptionRecord,
                                           void * EstablisherFrame,
                                           struct _CONTEXT *ContextRecord,
                                           void * DispatcherContext)
    {
    
        MessageBox(NULL,"我处理了异常
    ",NULL,NULL);
        return ExceptionContinueSearch;
    }                   
    void fun1()
    {
        __asm
        {
           
            push offset HANDLER1
            push fs:[0]
            mov fs:[0],esp
        }
    
        fun2();
         char *p = NULL;
         *p = 1;
        __asm
        {
            pop fs:[0]
            add esp,4
            ret
        }
    }
    void fun2()
    {
        char *p =NULL;
        *p = 1;
    }
    
    int main(int argc, char* argv[])
    {
            
        fun1();    
        system("pause");
    
    }

    上图代码则是我们昨天的代码,我们编译,链接,并且运行一下.

    第一次:

    当我们点击异常确定

    程序会显示退出,因为我们的返回这设置的是继续搜索,也就是我不处理了,交给上一层处理,而上一层是操作系统

    我们点击关闭程序

    这个时候,我们的回调又被操作系统掉了一次,第二次来的时候的标志是2,具体的可以通过输出参数查看.

    最后点击确定我们的程序才退出了.

    那么我们不觉着奇怪吗,为什么操作系统会第二次调用了一次我们的回调函数?

    原因是操作系统正在进行异常展开,调用我们的回调是告诉我们,该处理的处理.

    二丶什么是异常展开

    上面我们说了异常展开,也把我们的代码贴出来了.那么现在思考一个问题

    当 fun1函数调用fun2函数 

    的时候,fun2函数也注册一个SEH筛选器异常,(注册相当于往链表头插入)

    例如下面的代码

    void fun1()
    {
        __asm
        {
           
            push offset HANDLER1
            push fs:[0]
            mov fs:[0],esp
        }
    
        fun2();
         char *p = NULL;
         *p = 1;
        __asm
        {
            pop fs:[0]
            add esp,4
            ret
        }
    }
    void fun2()
    {
        __asm
        {
          push offset HANDLER2  //注册回调函数
          push fs:[0]           //压入旧的链表指针
          mov fs:[0],esp       //新的位置变成当前的SEH
        }
        char *p =NULL;
        *p = 1;
        //取消注册,和上面一样,不写了,为了节省空间
    }

    那么我们知道,现在的链表头是Fun2,也就是 Fun2链表中的next位置指向了Fun1的位置,回调函数也是fun2的

    那么我们现在想想,如果fun2出现了异常,而fun2的回调函数是处理不了这个异常的,那么会交给fun1去处理

    这个没问题吧,但是你想,fun2交给fun1处理的时候,取消注册是不可能在执行了.

    也就是说,现在的fun2 是链表头,并没有断开连接,或者卸载这个函数,那么如果这个时候fun1出现了问题怎么办?

    操作系统当出现异常的时候,会依次遍历这个链表,此时的Fun2已经是无效的了,我们并不能让它去调用.而是应该把异常的链表的首地址,重置为当前的fun1所在的位置.

    看下图:

    那么这种操作,就叫做异常展开,简单来说就是 fun1 调用fun2 fun2出现了异常,自己的异常链表来不及卸载,此时只能交给fun1去处理,那么现在我们应该把链表的位置重置为fun1的异常链表,fun2的不在需要了.否则操作系统调用的时候则是调用了一个错误的地址.

    说到释放的时候我们上面说了,操作系统会根据错误标志2,来接着调用一次我们的异常回调函数,这就是因为在操作系统帮我们卸载这个异常链表,但是会依次的调用一次我们的回调函数,通知我们,该释放资源的释放资源,该处理的处理,给我们一次释放资源的机会.

     

     三丶异常处理的顺序

    异常处理处理发生的时候,会有顺序的

    1.系统首先发送给调试器 调试器优先级最高

    2.如果没有调试器,系统会继续查找线程相关的异常处理,

    3.每个线程相关的异常处理例程,可以处理或者不处理这个异常,如果不处理,并且安装了多个线程相关的处理例程,可交给连起来的其它例程处理

    4.不处理这个异常,在判断程序是否在调试状态,如果在就接着给调试器

    5.如果没有的话,或者不处理,那么操作系统就会调用筛选器异常

    6.如果没有,那么系统会调用默认的异常处理,也就是崩溃的的界面

    7.在终结之前,对其展开操作,然后依次调用设置的SEH链表中的回调函数,给予一次最后清理的机会.

    四丶主动引发异常

    我们说过throw这个语句会抛出一个异常,其实底层调用的也是API

    void RaiseException(DWORD dwExeptionCode,
             DWORD   dwExceptionFlages
              DWORD nNumberOfArguments,
              Const DWORD * lpArguments

    前两个分别是退出代码,和错误标志,这个在筛选器异常已经讲过了

    最后两个参数是用户自定义的.throw这个语法就是调用的这个API

    五丶自动展开操作

    我们说过,异常展开的时候,我们自己也可以去做,也可以交给操作系统做,而操作系统做的时候也是调用的API

    RtIUnwind  具体可以查询下MSDN.想了解底层自己查询一下,不多做讲解.

    关于可处理异常,以及异常的第二个参数的应用,明天讲解,怕一下 讲解太多

     

     作者:IBinary
    出处:http://www.cnblogs.com/iBinary/
    版权所有,欢迎保留原文链接进行转载:)

  • 相关阅读:
    js分享插件
    json格式
    事物TransactionScope
    CheckBox全选、取消全选
    JQuery中的prop和attr
    [转]javascript之数组操作
    pcntl_fork()函数说明
    从库因为sql错误导致主从同步被中断的问题解决
    查看进程的命令ps
    给mysql创建用户
  • 原文地址:https://www.cnblogs.com/iBinary/p/7599713.html
Copyright © 2011-2022 走看看