异常(2) --- 编译器对于SEH异常的拓展

摘要:
我们之前只提到过编译器拓展SEH异常的,但是由于篇幅有限,并没有详细介绍过其是如何拓展的,现在,我们就来介绍一下,其编译器如何拓展SEH异常的。对于该函数,只要返回上面的三种值即可。4)_except_handler3函数分析对于拓展的SEH异常,其固定添加一个handler函数,而不是像我们之前可以自定义的,因为其要负责处理大量的数据结构。

Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

异常(2) --- 编译器对于SEH异常的拓展

异常(1)中,我们介绍了用户模拟异常与CPU异常的收集,以及内核层与用户层异常的处理,其中介绍过SEH异常。

我们之前只提到过编译器拓展SEH异常的,但是由于篇幅有限,并没有详细介绍过其是如何拓展的,现在,我们就来介绍一下,其编译器如何拓展SEH异常的。

1._try{} _excpet(){} 异常处理结构
2. _try{} _excpet(){} 反汇编分析 - 其如何将自己挂在链表上的
3._EXCEPTION_REGISTRATION 结构中的实现细节
4.局部展开 - _try{}_finally{}反汇编分析
5.全局展开 - try{}嵌套时找到最近一层excpt_handler

1._try{} _excpet(){} 异常处理结构

有过C++编程经验的人来说,这个肯定不陌生,我们看如下代码:

异常(2) --- 编译器对于SEH异常的拓展第1张

1)异常过滤表达式

异常过滤表达式存在三种:常量,等式,过滤函数。

1> 常量

如下,Windows提供三种定义,当然这只是最简单的,无法处理比较复杂的异常。

// Defined values for the exception filter expression
EXCEPTION_EXECUTE_HANDLER 1 // 走当前的异常处理程序
EXCEPTION_CONTINUE_SEARCH 0 // 搜索下一个异常处理程序
EXCEPTION_CONTINUE_EXECUTION (-1) // 到代码出错的位置继续执行

2> 等式

调用GetExceptionCode()函数来获取异常码,之后来判断异常码。

GetExceptionCode()==0xc0000095

其实其本质也是常量运算,当其表达式匹配时结果为1(EXCEPTION_EXECUTE_HANDLER ),当不匹配时0(EXCEPTION_CONTINUE_SEARCH ),其会寻找下一个SEH异常。

3> 调用异常处理函数

这里面也可以写函数,_except_handler,来自行对异常内容进行操作。

对于该函数,只要返回上面的三种值即可。

异常(2) --- 编译器对于SEH异常的拓展第2张

2)异常的各种宏介绍

在excpt.h中有很多宏,上面已经用到过两个 GetExceptionInformation() GetExceptionCode(),其定义如下:

#define GetExceptionCode _exception_code
#define exception_code _exception_code
#define GetExceptionInformation (struct _EXCEPTION_POINTERS*)_exception_info
#define exception_info (struct _EXCEPTION_POINTERS*)_exception_info

我们在VEH那一节中,添加的VEH异常例程就是_EXCEPTION_POINTERS*,很好理解。

2. _try{} _excpet(){} 反汇编分析 - 其如何将自己挂在链表上的

我们看其反汇编,无论其几层_try{}嵌套,其最终只挂入一次链表,其是如何实现的呢?

答案是:在挂入链表的结构上进行了拓展,之前的结构如下:

struct _EXCEPTION_REGISTRATION_RECORD {

struct_EXCEPTION_REGISTRATION_RECORD* Next; //0x0

enum _EXCEPTION_DISPOSITION (*Handler)(struct _EXCEPTION_RECORD* arg1, VOID* arg2, struct _CONTEXT* arg3, VOID* arg4);//0x4

};

而现在的结构如下

struct _EXCEPTION_REGISTRATION{

struct_EXCEPTION_REGISTRATION_RECORD* Next; //0x0

enum _EXCEPTION_DISPOSITION (*Handler)(struct _EXCEPTION_RECORD* arg1, VOID* arg2, struct _CONTEXT* arg3, VOID* arg4); //0x4

struct scopetable_entry * scopetable

int trylevel;

ine _ebp;

};

我们根据这个结构来查看反汇编代码:

可以发现,其编译器先处理SEH异常结构,再来提升堆栈。

另外,值得注意的是:Release版本会进行大量优化,但当出现_try{}_except(){},其不会对其进行优化,因为要保证堆栈结构。

异常(2) --- 编译器对于SEH异常的拓展第3张

要明白,其是拓展结构,并没有影响原来的结构,原来的结构在这里依然可以使用的,故其SEH拓展后的结构如下所示:

异常(2) --- 编译器对于SEH异常的拓展第4张

3._EXCEPTION_REGISTRATION 结构中的实现细节

我们之前介绍过,无论一个函数中有多少个Try,其只要一个_EXCEPTION_REGISTRATION结构体就好。

但是,我们肯定很好奇,其是如何实现的。下面,我们就来分析一下其是如何来实现的。

1)ScopeTable表结构

其是一串结构体数组,理解它的含义是理解SEH拓展的关键,结构体如下:

previousTryLevel - 上一个try的索引

lpfnFilter - except过滤表达式位置

lpfnHandler - except_handler执行函数

2)我们现在分析一层复杂的ScopeTable结构:

如下图,很明显,第一个previousTryLevel表示的是其存在上一层的嵌套,现在我们有一个问题,try0先执行还是try1先执行?

当然是try1先执行,然后沿着previousTryLevel找到try0的except,明白了这个逻辑再来看这张图就很好理解。

lpfnFilter指向其过滤表达式,lpfnHanler指向_except_handler,异常处理代码。

异常(2) --- 编译器对于SEH异常的拓展第5张

3)trylevel的含义

我们看其反汇编代码,当做的第一件事就是往trylevel中填写一个数字,我们在ScopeTable中看到其存在一个编号。

因此,很容易推断出 trylevel记录当前try所在的编号。

通过trylevel这个编号,进入表通过 (Scopetable+0x0c*trylevel) 计算,就很容易找到各个元素。

异常(2) --- 编译器对于SEH异常的拓展第6张

4)_except_handler3函数分析

对于拓展的SEH异常,其固定添加一个handler函数,而不是像我们之前可以自定义的,因为其要负责处理大量的数据结构。

其在xp操作系统下固定填写一个ntdll!_except_handler3函数,该函数就是负责完成上面那些逻辑的。

因为时间关系,暂时就不逆向该函数,但之后一定回头认认真真逆向一遍。

异常(2) --- 编译器对于SEH异常的拓展第7张

4.局部展开 - _try{}_finally{}反汇编分析

  我们还存在一种语句,_try{}_finally{}语句,这种语句存在一种特殊机制,即使你在try{}中执行return,finally代码也一定会执行。

  1)__try{}_finally反汇编分析:

    异常(2) --- 编译器对于SEH异常的拓展第8张

  2)__local_unwind2函数分析

    异常(2) --- 编译器对于SEH异常的拓展第9张

  3)_try{}_except函数的scopetable表

    我们查看该语句的地址表,其中lpexceptHandler的地址就是_try{}_finally{}的地址。

因此我们就可以推断函数的执行过程,如果lpfilter值为0,则lpexcepthandler为finally函数。

    异常(2) --- 编译器对于SEH异常的拓展第10张

  4)总结

    其编译器对其进行各种操作,将_finally语句打包成一个lpexcept_handler函数。

    为确保执行,当在_try{}中出现return语句,其会先调用一个局部展开函数,该函数会查表寻边遍历的_fianlly函数运行。

既然把finally{}作为一个函数,故编译器在编译它时末尾写上return语句。

    在_except函数后面肯定为retn,其作为一个函数调用,肯定不会正常执行,其如下图:

    异常(2) --- 编译器对于SEH异常的拓展第11张

5.全局展开 - try{}嵌套时找到最近一层excpt_handler

  前面我们提到过局部展开,当在try{}中遇到return时,其会调用局部展开,找到_finally函数执行,然后再返回。

  现在我们再考虑一种情况,当很多_try{}_finaly{}嵌套时,出现异常,其如何调用?

  如下图的实现机制-其本质就是调用全局展开。

   异常(2) --- 编译器对于SEH异常的拓展第12张

  当出现异常错误时,有_except_handler3接管,我们在分析中存在一个局部展开,一个全局展开,其全局展开。

  异常(2) --- 编译器对于SEH异常的拓展第13张

  全局展开机制有点复杂,现在先不分析,明确其作用就好。

免责声明:文章转载自《异常(2) --- 编译器对于SEH异常的拓展》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇qt designer启动后不显示界面问题的原因与解决办法Linux学习 : 总线-设备-驱动模型下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

基于GTID的主从架构异常处理流程

通常情况下我们主库的binlog只保留7天,如果从库故障超过7天以上的数据没有同步的话,那么主从架构就会异常,需要重新搭建主从架构。  本文就简单说明下如何通过mysqldump主库的数据恢复从库的主从架构 下面就以我们在线上业务中实际遇到的情况做个简单说明 本文就以以下集群为例: 主库: 192.168.38.249 从库: 192.168.38.230...

watch监听(数组或者对象)

handler:监听数组或对象的属性时用到的方法 deep:深度监听,为了发现对象内部值的变化,可以在选项参数中指定 deep:true 。注意监听数组的变动不需要这么做。 immediate: 在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调 tips: 只要bet中的属性发生变化(可被监测到的),便会执行handler函...

C++异常处理assert,throw,exit用法

常见的几个小细节问题。 assert应用:       在现实世界中,我们脑袋时刻都在判断对与错,对的事情我们会继续深入下去,而错的事情我们会马上停止,那么在编程开发中我们如何赋予程序这种判断事物对错的能力呢?其中一个方案就可以使用断言assert,我们最常用的地方就是在函数中检查形参的数据合法性。 1、函数所属头文件:         assert.h...

Dubbo的优雅下线原理分析

文/朱季谦 Dubbo如何实现优雅下线? 这个问题困扰了我一阵,既然有优雅下线这种说法,那么,是否有非优雅下线的说法呢? 这,还真有。 可以从linux进程关闭说起,其实,我们经常使用到杀进程的指令背后,就涉及到是否优雅下线的理念。 在日常开发当中,经常用到kill来关掉正在运行的进程,可能你曾看到过一些文章是不推荐使用kill -9 pid的指令来删除进...

Android 播放视频并获取指定时间的帧画面

最近做的项目要求既能播放视频(类似于视频播放器),又能每隔1s左右获取一帧视频画面,然后对图片进行处理,调查了一周,也被折磨了一周,总算找到了大致符合要求的方法。首先对调查过程中涉及到的方法进行简单介绍,再重点介绍最终所采用的方法,话不多说,进入正题。 一.MediaMetadataRetriever 播放视频并取得画面的一帧,大家最先想到应该都是这个,我...

Java IO 关闭流的方式

Java IO 关闭流的方式 分类 练习:将分割文件中的流关闭方式改为finally形式 练习:文件合并中的流关闭方式改为try()形式 传送门:这里更详细 分类 在try中关闭 弊端是如果文件不存在或者读取的时候有问题而抛出异常,那么就不会执行流的关闭语句,存在资源占用隐患 在finally中关闭 这是标准的关闭流的方式 1、首先把引用声...