结构化异常处理(SEH)是Windows操作系统提供的强大异常处理功能。而Visual C++中的__try{}/__finally{}和__try{}/__except{}结构本质上是对Windows提供的SEH的封装。

一、分类

  • Per-Thread类型SEH(也称为线程异常处理),用来监视某线程代码是否发生异常。
  • Final类型SEH(也称为进程异常处理、筛选器或顶层异常处理),用于监视整个进程中所有线程是否发生异常。在整个进程中,该类型异常处理过程只有一个,可通过SetUnhandledExceptionFilter设置。

二、SEH相关的数据结构和API

2.1、相关结构

1) 线程信息块TIB(Thread Information Block或TEB)

typedef struct _NT_TIB {
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //异常的链表 PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib; union {
PVOID FiberData;
DWORD Version;
}; PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;
} NT_TIB;

Fs:[0]总是指向当前线程的TIB,其中0偏移的指向线程的异常链表,即ExceptionList是指向异常处理链表(EXCEPTION_REGISTRATION结构)的一个指针。

(2)EXCEPTION_REGISTRATION结构

typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Prev; //指向前一个EXCEPTION_REGISTRATION的指针
PEXCEPTION_ROUTINE Handler; //当前异常处理回调函数的地址
} EXCEPTION_REGISTRATION_RECORD;

2.2、相关API

(1)SetErrorMode:控制错误模式,如是否出现错误对话框

(2)SetUnhandledExceptionFilter:设定顶层异常处理回调函数

(3)RaiseException:用于引发异常,可指定自己的异常代码及相关信息

(4)GetThreadContext/SetThreadContext:获取或设置线程环境

(5)RtlUnwind:栈展开操作

三、线程异常处理

局部的,仅仅监视进程中某特定线程是否发生异常。

3.1、线程异常处理特点

  ①Windows系统为每个线程单独提供了一种异常处理的方法,当一个线程出现错误时,操作系统调用用户定义的一系列回调函数,在这些回调函数中,可以进行修复错误或其它的一些操作,最后的返回值告系统系统下一步的动作(如继续搜索异常处理程序或终止程序等)。

  ②SEH是基于线程的,使用SEH可以为每个线程设置不同的异常处理程序(回调函数)而且可以为每个线程设置多个异常处理程序。

  ③ 由于SEH使用了与硬件平台相关的数据指针,所以不同硬件平台使用SHE的方法有所不同。

3.2、回调函数原型

EXCEPTION_DISPOSITION __cdecl _except_handler(
struct _EXCEPTION_RECORD *ExceptionRecord,//指向包含异常信息的EXCEPTION_RECORD结构
void* EstablisherFrame,//指向该异常相关的EXCEPTION_REGISTRATION结构
struct _CONTEXT *ContextRecord,//指向线程环境CONTEXT结构的指针
void* DispatcherContext){ //该域暂无意义 …… //4种返回值及含义
//1.ExceptionContinueExecution(0):回调函数处理了异常,可以从异常发生的指令处重新执行。
//2.ExceptionContinueSearch(1):回调函数不能处理该异常,需要要SEH链中的其他回调函数处理。
//3.ExceptionNestedException(2):回调函数在执行中又发生了新的异常,即发生了嵌套异常
//4.ExceptionCollidedUnwind(3):发生了嵌套的展开操作 return …
}

3.3、注册异常处理回调

线程异常处理的注册

push _exception_handler //异常回调函数_exception_handler的地址,即handler
push fs:[0] //保存前一个异常回调函数的地址,即prev
mov fs:[0],esp //安装新的EXCEPTION_REGISTRATION结构(两个成员:prev,handler)。
//此时栈顶分别是prev和handler,为新的EXCEPTION_REGISTRATION结
//构,mov fs:[0],esp,就可以让fs:[0]指向该指构。

3.4、异常回调函数的调用过程

①线程信息块(TIB),永远放在fs段选择器指定的数据段的0偏移处,即fs:[0]的地方就是TIB结构。对不同的线程fs寄存器的内容有所有不同,但fs:[0]都是指向当前线程的TIB结构体,所以fs:[0]是一个EXCEPTION_REGISTRATION结构体的指针。

②当异常发生时,系统从fs:[0]指向的内存地址处取出ExceptionList字段,然后从ExceptionList字段指向的EXCEPTION_REGISTRATION结构中取出handler字段,并根据其中的地址去调用异常处理程序(回调函数)。

3.5、SEH链及异常的传递

通知调试器→SEH链→顶层异常处理→系统默认处理

(1)系统查看产生异常的进程是否被正在被调试,如果正在被调试,那么向调试器发送EXCEPTION_DEBUG_EVENT事件。

(2)如果进程没有没有被调试或者调试器不去处理这个异常,那么系统检查异常所处的线程并在这个线程环境中查看fs:[0]来确定是否安装SEH异常处理回调函数,如果有则调用它。

(3)回调函数尝试处理这个异常,如果可以正确处理的话,则修正错误并将返回值设置为ExceptionContinueExecution,这时系统将结束整个查找过程。

(4)如果回调函数返回ExceptionContinueSearch,相当于告诉系统它无法处理这个异常,系统将根据SEH链中的prev字段得到前一个回调函数地址并重复步骤3,直至链中的某个回调函数返回ExceptionContinueExection为止,查找结束。

(5)如果到了SEH链的尾部却没有一个回调函数愿意处理这个异常,那么系统会再被检查进程是否正在被调试,如果被调试的话,则再一次通知调试器。

(6)如果调试器还是不去处理这个异常或进程没有被调试,那么系统检查有没有Final型的异常处理回调函数,如果有,就去调用它,当这个回调函数返回时,系统会根据这个函数的返回值做相应的动作。

(7)如果没有安装Final型回调函数,系统直接调用默认的异常处理程序终止进程,但在终止之前,系统再次调用发生异常的线程中的所有异常处理过程,目的是让线程异常处理过程获得最后清理未释放资源的机会,其后程序终止。

 四、异常处理的堆栈展开(Stack Unwind)

(1)什么是展开操作

  ①当发生异常时,系统遍历EXCEPTION_REGISTRATION结构链表,从链表头,直到它找到一个处理这个异常的处理程序。一旦找到,系统就再次重新遍历这个链表,直到处理这个异常的节点为止(即返回ExceptionContinueExecution节点)。在这第二次遍历中,系统将再次调用每个异常处理函数。关键的区别是,在第二次调用中,异常标志被设置为2。这个值被定义 为EH_UNWINDING

  ②注意展开操作是发生在出现异常时,这个异常回调函数拒绝处理的时候(即返回ExceptionContinueSearch)。这里系统会从链表开始遍历(因异常的嵌套,可理解为由内层向外层),所以各异常回调函数会第1次被依次调用,直到找到同意处理的节点。然后,再重新从链表头开始(即由内向外)第2次调用以前那些曾经不处理异常的节点,直到同意处理的那个异常的节点为止。

  ③当一个异常处理程序拒绝处理某个异常时,它实际上也就无权决定流程最终将从何处恢复。只有处理某个异常的异常处理程序才能决定待所有异常处理代码执行完毕之后流程最终将从何处恢复。即,当异常已经被处理完毕,并且所有前面的异常帧都已经被展开之后,流程从处理异常的那个回调函数决定的地方开始继续执行。

  ④展开操作完成后,同意处理异常的回调函数也必须负责把Fs:[0]恢复到处理这个异常的EXCEPTION_REGISTRATION上,即展开操作导致堆栈上处理异常的帧以下的堆栈区域上的所有内容都被移除了,这个异常处理也就成了SEH链表的第1个节点。

(2)为什么要进行堆栈展开

  ①第1个原因是告知回调函数将被卸掉,以让被卸载的回调函数有机会进行一些清理未释放资源的机会。因为一个函数发生异常时,执行流程通常不会从这个函数正常退出。所以可以导致资源未被正确释放(如C++类的对象析构函数没被调用等)。

  ②第2个原因是如果不进行堆栈展开,可能会引发未知的错误。(见《软件加密技术内幕》第132-134页)。

(3)如何展开:RtlUnwind(lpLastStackFrame,lpCodelabel,lpExceptionRecord,dwRet);

  ①lpLastStackFrame:当遍历到这个帧时就停止展开异常帧。为NULL时表示展开所有回调函数。

  ②lpCodeLabel:指向该函数返回的位置。如果指定为NULL,表示函数使用正常的方式返回。

  ③lpExceptionRecord:指定一个EXCPETION_RECORD结构。这个结构将在展开操作的时候被传给每一个被调用的回调函数,一般建议使用NULL来让系统自动生成代表展开操作的EXCEPTION_RECORD结构。

  ④dwRet一般不被使用。

五、编译器层面对系统SEH机制的封装

5.1 扩展的EXCEPTION_REGISTRATION级相关结构:VC_EXCEPTION_REGISTRATION

(1)VC_EXCEPTION_REGISTRATION结构

 struct VC_EXCEPTION_REGISTRATION
{
VC_EXCEPTION_REGISTRATION* prev; //前一个结构体的指针
FARPROC handler; //永远指向_exception_handler4回调函数
scopetable_entry* scopetable;//指向scpoetable数组的指针
int _index; //有的书上也叫tryLevel。scopetable项的当前项
DWORD _ebp; //当前ebp值,用于访问各成员
}

(2)scopetable_entry结构体

struct scopetable_entry
{
DWORD prev_entryindex;//指向前一个scopetable_entry在scopetable中的索引
FARPROC lpfnFilter; //对应于__except块后小括号内的过滤函数;__finally时为NULL
FARPROC lpfnHandler;//__exception或__finally后花括号{}内的代码地址。
}

(3)VC异常帧堆栈布局及VC默认的异常处理

5.2 数据结构组织

(1)每个函数只注册一个VC_EXCEPTION_REGISTRATION结构(也叫异常帧,如图中的有5个Frame,即有5个函数调用)。可见,该SEH异常链从链表头部到链表尾共有5节点,分对应于5个异常处理帧。但需注意的是,通过VC安装的节点为VC_EXCEPTION_REGISTRATION结构,图中有3个,对应的回调函数为VCSHE!_exception_handler4(0x00E0178),而系统安装的是EXCEPTION_REGISTRATION结构的帧,位于链表尾部最后的两个节点,对应的回调函数分别为ntdll!_exception_handler4

(2)VC为每个函数内的所有__try块建立一个scopetable表,其中每个__try块对应于scopetable中的一项。(用scopetable_entry结构体来表示这个__try项,结构里分别用lpfnFilter和lpfnHandler来表示__except/__finally的过滤函数和处理函数,其中_finally没有过滤函数,只有异常处理函数)。

(3)若有__try块嵌套,则在scopetable_entry结构里的prev_entryindex或指明,多层嵌套形成单向链表。

(4)对于VC的异常处理,其每个异常帧的回调处理函数都统一设为_except_handler4。每进入一个try块里,编译器会将VC_EXCEPTION_REGISTRATION中tryLevel赋值为相应的值。一旦该try块异常发生,系统会先从VC_EXCEPTION_REGISTRATION的handler域中找到_exception_handler4函数(C运行时库函数),然后根据当前tryLevel的值找到scopetable表中这个__try块相应的过滤函数和处理函数对异常进行相应的处理。

(5)与_except块不同,_finally块的lpfnFilter为NULL,即没有过滤函数

5.3 _exception_handler4函数的执行流程

(1)异常发生时,根据index找到scopetable项,并调用lfpnFilter。如果过滤函数lpfnFilter返回EXCEPTION_EXECUTE_HANDLER,则执行全局展开之后调用lpfnHandler函数。如果过滤函数lpfnFilter返回EXCEPTION_CONTINUE_EXECUTION,则_except_handler4简单地返回EXCEPTION_CONTINUE_EXECUTION,交由系统恢复线程的执行

(2)如果lpfnFilter返回EXCEPTION_CONTINUE_SEARCH时,此时_except_handler4查看previndex是否是0xFFFFFFFE,若是则_except_handler4返回ExceptionContinueSearch让系统继续遍历外层SEH链或由系统直接处理。否则_except_handler4根据previndex找到相应的过滤函数,根据其返回值重复上面的动作。直到异常被处理或previndex为0xFFFFFFFE为止。

5.4 小结:异常处理流程及全局展开

参考:

https://www.cnblogs.com/5iedu/p/5205428.html

https://www.cnblogs.com/5iedu/p/5223846.html

结构化异常SEH处理机制详细介绍(一)的更多相关文章

  1. 结构化异常SEH处理机制详细介绍(二)

    本文将全面阐述__try,__except,__finally,__leave异常模型机制,它也即是Windows系列操作系统平台上提供的SEH模型.SEH实际包含两个主要功能:结束处理(termin ...

  2. [C++]深入解析结构化异常处理(SEH)

    http://www.cppblog.com/weiym/archive/2015/02/27/209884.html 尽管以前写过一篇SEH相关的文章<关于SEH的简单总结>, 但那真的 ...

  3. Windows 结构化异常

    结构化异常不能用于需要调用对象析构函数的函数中 __try{ } __except(){ } __try{ } __finally{ }

  4. Session机制详细介绍

    Session机制详细介绍  

  5. vb.net结构化异常处理和“邪用”

    vb.net中的错误处理包括两种:非结构化异常处理技术和结构化异常处理.非结构化异常处理技术在vb 6.0中使用的比较普遍,即通过Err对象和ON Error.Go To.Resume等语句来实现.这 ...

  6. SQLite中的WAL机制详细介绍-与回滚日志原理

    一.什么是WAL? WAL的全称是Write Ahead Logging,它是很多数据库中用于实现原子事务的一种机制,SQLite在3.7.0版本引入了该特性. 二.WAL如何工作? 在引入WAL机制 ...

  7. hibernate缓存机制详细介绍

    hibernate的缓存机制,包括一级缓存(session级别).二级缓存(sessionFactory级别). 一:hibernate的 N+1问题 list()获得对象: 如果通过list()方法 ...

  8. Windows结构化异常

    不错的总结: http://hi.baidu.com/wangxvfeng101/item/518f6efdab4e5616ff35820e http://www.vckbase.com/index. ...

  9. 深入解析结构化异常处理(SEH)

    jpg 改 rar

随机推荐

  1. jquery 如何获取select 选中项的下一个选项的值

    <select> <option value="1" selected="selected">a</option> < ...

  2. git学习笔记 ---管理修改

    现在,假定你已经完全掌握了暂存区的概念.下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件. 你会问,什么是修改?比如你新增了一行,这就是一个修改 ...

  3. go ---fmt

    //main package main import ( "fmt" "os" ) type Data struct { } func (self Data) ...

  4. ABP 基于DDD的.NET开发框架 学习(七)继承不同的service直接调用api的区别

    1.IApplicationService->IBaseService->具体IXXXService 具体XXXService->BaseService,具体IXXXService ...

  5. OO第三单元(地铁,JML)单元总结

    OO第三单元(地铁,JML)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉并了解JML来是我们具有规格化编程架构的思想.这个单元的主题一开始并不明了,从第一次作业的路径到第二次 ...

  6. iOS - WebRTC 自编译(音视频即时通讯开源库)

    什么是WebRTC? WebRTC,名称源自网页实时通信(Web Real-Time Communication)的缩写,简而言之它是一个支持网页浏览器进行实时语音对话或视频对话的技术.是谷歌2010 ...

  7. 如何通过webpack和node来实现多个静态页面html,多个入口,能打包能热加载开发环境调试

    demo已经传到了github,地址:https://github.com/13476075014/04.node-vue-project/tree/master/03.singlewebpack: ...

  8. cloudera manager(CDH)实践

    cloudera manager 可以简化 Hadoop 的安装配置过程,自动在集群节点上安 装 hadoop 相关组件,创建用户,并管理各个组件服务.本手册以 cloudera manager 的 ...

  9. SQL SERVER-日期按时区转换

    SELECT SWITCHOFFSET('2019-07-19 08:35:06.637','+08:00')

  10. Unable to guess the mime type as no guessers are available 2 9

    做了一个上传图片的功能,在本地上传通过,服务器报 bug Unable to guess the mime type as no guessers are available(Did you enab ...