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

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

不知道昨天有木有小伙伴尝试写一下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,"我处理了异常\r\n",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.想了解底层自己查询一下,不多做讲解.

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

依赖注入[7]: .NET Core DI框架[服务注册]

包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象。服务注册就是创建出现相应的Servi...

19340
来自专栏积累沉淀

Python快速学习第八天

本文内容全部出自《Python基础教程》第二版 10.1 模块 现在你已经知道如何创建和执行自己的程序(或脚本)了,也学会了怎么用import从外部模...

41660
来自专栏西二旗一哥

iOS - Dissecting objc_msgSend on ARM64

我们的朋友 ldp 又出现了。这回它读取了 x12 中指针,这个指针指向了要查找的bucket。每个bucket包含了一个选择器和一个 IMP 。 x9 现在包...

14140
来自专栏coding...

swift3.0 基础练习-实现99乘法表

8930
来自专栏我是攻城师

深入理解Java类加载器机制

Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们...

44320
来自专栏CaiRui

Bash Shell 小试牛刀

一、终端打印 [root@cai ~]# echo welcome to bash! welcome to bash! [cairui@cai ~]$ echo...

19560
来自专栏运维小白

linux基础(day26)

9.1正则介绍_grep(上) 正则介绍 正则就是一串有规律的字符串 掌握好正则对编写shell脚本帮助交大 各种编程语言中都有正则,原理是一样的 grep/e...

267100
来自专栏Janti

JVM活学活用——类加载机制

类的实例化过程 ---- 有父类的情况 1. 加载父类静态     1.1 为静态属性分配存储空间并赋初始值     1.2 执行静态初始化块和静态初始化...

42780
来自专栏dodott的专栏

angularJs中筛选功能-angular.filter-1

通过bower:通过在你的终端执行:$ bower install angular-filte

17140
来自专栏大内老A

ASP.NET Web API路由系统:路由系统的几个核心类型

虽然ASP.NET Web API框架采用与ASP.NET MVC框架类似的管道式设计,但是ASP.NET Web API管道的核心部分(定义在程序集Syste...

337100

扫码关注云+社区

领取腾讯云代金券