//1.挂入链表相当于这部分
//fs[0]-> Exception_asm{mov eax, fs:[0]mov temp,eaxlea ecx,Exceptionmov fs:[0],ecx}//为SEH成员赋值Exception.Next = (_EXCEPTION*)temp;Exception.Handler = (DWORD)&MyEexception_handler;//下面是2,3
EXCEPTION_DISPOSITION _cdecl MyEexception_handler
(struct _EXCEPTION_RECORD *ExceptionRecord, //异常结构体PVOID EstablisherFrame,                      //SEH结构体地址struct _CONTEXT *ContextRecord,               //存储异常发生时的各种寄存器的值 栈位置等PVOID DispatcherContext
)
{if (ExceptionRecord->ExceptionCode == 0xC0000094)     //2.异常过滤{ContextRecord->Eip = ContextRecord->Eip + 2;           //3.异常处理ContextRecord->Ecx = 100;return ExceptionContinueExecution;}return ExceptionContinueSearch;
}

编译器支持的SEH,SEH是windows平台下特有的换在别的平台下它是不支持的。

//这里的代码底层实现就类似上面的代码。
_try                        //1.挂入链表{}_except(过滤表达式)    //2.异常过滤{异常处理程序     //3.异常处理程序}异常过滤表达式常量值
1) EXCEPTION_EXECUTE_HANDLER (1)    执行except代码
2) EXCEPTION_CONTINUE_SEARCH (0)    寻找下一个异常处理函数
3) EXCEPTION_CONTINUE_EXECUTION (-1)    返回出错位置重新执行

过滤表达式3种方式

  1. 直接写常量值
  2. 表达式
  3. 调用函数

表达式写法

_try{_asm{xor edx,edxxor ecx,ecxmov eax,10idiv ecx   //EDX = EAX /ECX}}//如果异常码为0xC0000094返回1否则返回0_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){printf("如果出现异常 此处处理\n");}

调用函数写法

//参数根据需要来写,可以不要参数
int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo)
{pExceptionInfo->ContextRecord->Ecx = 1;//异常处理return EXCEPTION_CONTINUE_EXECUTION;//返回出错位置重新执行
}int main()
{_try{_asm{xor edx,edxxor ecx,ecxmov eax,10idiv ecx //EDX = EAX /ECX}}//GetExceptionInformation获取异常结构指针_except(ExceptFilter(GetExceptionInformation())){printf("如果出现异常 此处处理\n");}getchar();
}

这是没用异常的函数汇编代码的生成

这是用了异常的函数汇编代码生成

异常处理函数:_except_handler3(不同的编译器这个也是不一样的)

可以看到用了异常函数的多了很多代码,新增的代码其实就是让 fs[0] 指向新增的异常处理链。

如果我们自己写SEH,用一个异常处理函数就要手动挂入一个SEH链表,两个就挂入两次,以此类推…,但用编译器的就不需要。

_try_except嵌套重复

每个使用_try _except的函数,不管其内部嵌套或反复使用多少try_except,都只注册一遍,即只将一个_EXCEPTION_REGISTRATION_RECORD挂入当前线程的异常链表中(对于递归函数,每一次调用都会创建一个 _EXCEPTION_REGISTRATION_RECORD,并挂入线程的异常链表中)。

我这用了3个_try _except

_try{_try{}_except(1){}}_except(1){}_try{}_except(1){}

只要挂一次SEH链就会修改一次 FS[0]

  push        ebpmov         ebp,esppush        0FFhpush        offset string "i386\\chkesp.c"+0FFFFFFD4h (00422020)push        offset __except_handler3 (00401224)mov         eax,fs:[00000000]push        eaxmov         dword ptr fs:[0],espadd         esp,0B8hpush        ebxpush        esipush        edimov         dword ptr [ebp-18h],esplea         edi,[ebp-58h]mov         ecx,10hmov         eax,0CCCCCCCChrep stos    dword ptr [edi]_trymov         dword ptr [ebp-4],0{_trymov         dword ptr [ebp-4],1{}mov         dword ptr [ebp-4],0jmp         $L16979+0Ah (0040107c)_except(1)mov         eax,1retmov         esp,dword ptr [ebp-18h]{}mov         dword ptr [ebp-4],0}mov         dword ptr [ebp-4],0FFFFFFFFhjmp         $L16975+0Ah (00401095)_except(1)mov         eax,1retmov         esp,dword ptr [ebp-18h]{}mov         dword ptr [ebp-4],0FFFFFFFFh_trymov         dword ptr [ebp-4],2{}mov         dword ptr [ebp-4],0FFFFFFFFhjmp         $L16983+0Ah (004010b5)_except(1)mov         eax,1retmov         esp,dword ptr [ebp-18h]{}mov         dword ptr [ebp-4],0FFFFFFFFhmov         ecx,dword ptr [ebp-10h]mov         dword ptr fs:[0],ecxpop         edipop         esipop         ebxmov         esp,ebppop         ebpret

不管其内部嵌套或反复使用多少try_except,都只挂一次,指向的异常处理函数也是确定的。

3个异常处理函数就应该有3个链,它只挂了一个,这时怎么实现的呢?

拓展SEH链结构

struct _EXCEPTION_REGISTRATION
{ struct _EXCEPTION_REGISTRATION* prev;//下一个异常链void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_ REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);//指向异常函数struct scopetable_entry scopetable;int trylevel;   //当前代码执行在那个try里int _ebp;
};

名字有变不过叫什么名字无所谓了,第一和第二个成员没变,后面加了3个成员后面再讲…

无论怎么拓展,有两个成员必须要有,一个是链表节点一个是异常处理函数。

_EXCEPTION_REGISTRATION .scopetable成员分析

struct scopetable_entry
{DWORD previousTryLevel;    //上一个try{}结构编号PDWORD lpfnFilter;        //指向过滤函数的起始地址,_except()括号内那个PDWORD lphnHandle;       //异常处理程序的地址
};
int ExceptionFilter()
{return EXCEPTION_CONTINUE_EXECUTION;
}void a()
{_try{//异常点A}_except(EXCEPTION_EXECUTE_HANDLER){printf("异常处理函数1\n");}_try{//异常点B_try{//异常点C   }_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){printf("异常处理函数C \n");}}_except(ExceptionFilter()){printf("异常处理函数2\n");}}int main()
{a();
}


我这写了3个异常就会有3块,scopetable_entry结构的第2第3我们知道了,第一个成员是什么?

previousTryLevel第一和第二的值都是 -1 ,第三个是 1
第一个它没有上一个try它是-1,第二个它是最外层它也是-1,第三个它的上一个是 1 。(结合下面的下标理解)

scopetable[0].previousTryLevel =-1;
scopetable[0].IpfnFilter=过滤函数1;
scopetable[0].lfnHandler =异常处理函数1;scopetable[1].previousTryLevel =-1;
scopetable[1].IpfnFilter=过滤函数2;
scopetable[1].IpfnHandler=异常处理函数2;scopetable[2].previousTryLevel = 1;
scopetable[2].IpfnFilter=过滤函数3;
scopetable[2].lpfnHandler=异常处理函数3;

struct scopetable;这个结构已经将所有你所有的过滤函数指针,异常处理函数指针,全部放进数组里了, 所以无论它使用多少try_except,都只需要注册一次。

_EXCEPTION_REGISTRATION .trylevel

int ExceptionFilter()
{return 1;
}void a()
{_try{_try{}_except(EXCEPTION_EXECUTE_HANDLER){printf("异常处理函数\n");}}_except(EXCEPTION_EXECUTE_HANDLER){printf("异常处理函数\n");}_try{_try{}_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){printf("异常处理函数\n");}}_except(ExceptionFilter()){printf("异常处理函数\n");}}


这个 -1 就是当前trylevel的值

struct scopetable_entry虽然记录了异常信息但异常触发了,它不知道查谁所以需要trylevel来记录当前的代码执行在哪个try里。

当它要进入try时第一件事就是将trylevel修改为对应的scopetable[ ]索引值。

退出时又将 trylevel 改为 - 1。

_except_handler3执行过程

  1. CPU检测到异常 查中断表执行处理函数CommonDispatchException -> KiDispatchException ->KiUserExceptionDispatcher -> RtIDispatchException -> VEH ->SEH
  2. 执行_except_handler3函数

<1>根据 trylevel 选择 scopetable 数组
<2>调用 scopetable 数组中对应的 IpfnFilter 函数

  1. EXCEPTION_EXECUTE_HANDLER (1) 执行except代码
  2. EXCEPTION_CONTINUE_SEARCH (0) 寻找下一个
  3. EXCEPTION_CONTINUE_EXECUTION (-1) 重新执行触发异常的代码

<3>如果IpfnFilter 函数返回1,调用lpfnHandler指向的异常处理函数
<3>如果IpfnFilter 函数返回0,寻址下一个异常处理函数
<3>如果IpfnFilter 函数返回-1,重新执行触发异常的代码

_try_finally

_try
{//可能出现错误的代码
}
_finally
{//一定会执行的代码
}


_finally里的代码就算你直接将他return或者触发异常,它一样会执行,下面看看它是然后实现的。

scopetable[0].previousTryLevel =-1;
scopetable[0].IpfnFilter=0;
scopetable[0].lfnHandler =0x401179;

通过IpfnFilter来判断如果它的值为0,就是 _try_finally{};

它进入try时马上就掉用了 _local_unwind2 这个函数,_local_unwind2 这个函数就叫局部展开。


调用了lfnHandler 指向的函数。

全局展开_global_unwind2

void a()
{_try{_try{_try{*(int*)0 = 10;}_finally{printf("一定会执行的代码A\n");}}_finally{printf("一定会执行的代码B\n");}}  _except(1){printf("异常处理函数\n");}}

记得在_except(1)那下断,往下跟就能看到

_global_unwind2 全局展开


调用局部展开

  1. 触发异常,_except_handler3接管
  2. _except_handler3就会找异常处理函数
  3. 找自己和它的下一个发现scopetable.IpfnFilter=0,处理不了
  4. 最后那一个_except(1)后可以处理了,但它会先执行_global_unwind2
  5. _global_unwind2 会从触发异常的那个try开始,调用局部展开

6.编译器拓展SEH相关推荐

  1. 编译器扩展SEH(2)

    文章目录 问题 自动挂入的异常处理函数 __try __except嵌套,重复 原始的`_EXCEPTION_REGISTRATION_RECORD`结构体 编译器拓展的_EXCEPTION_REGI ...

  2. Windows异常学习笔记(四)—— 编译器扩展SEH

    Windows异常学习笔记(四)-- 编译器扩展SEH 要点回顾 编译器支持的SEH 过滤表达式 实验一:理解_try_except 实验二:_try_except 嵌套 拓展SEH结构体 scope ...

  3. Unity编译器拓展学习总结(二):各个平台菜单栏拓展

    Unity编译器拓展学习总结(二):各个平台菜单栏拓展 前情参考:Unity编译器拓展学习总结(一):常用工具API 文章目录 Unity编译器拓展学习总结(二):各个平台菜单栏拓展 前言 菜单栏拓展 ...

  4. Unity编译器拓展学习总结(一):常用工具API

    Unity编译器拓展学习总结(一):常用工具API 文章目录 Unity编译器拓展学习总结(一):常用工具API 前言 一.常用工具API 1.PrefabUtility 2.AssetDatabas ...

  5. 编译器扩展SEH(1)

    文章目录 编译器支持的SEH 过滤表达式规则: 过滤表达式的3种情况 直接写常量值 表达式 调用函数 编译器支持的SEH __try{ -------------------------------- ...

  6. 深入解析结构化异常处理(SEH) - by Matt Pietrek

    目录 1.浅析SEH 2.移向更深处 3.编译器层面的SEH 4.扩展的异常处理帧 5.ShowSEHFrames程序 6.展开 7.未处理异常 8.进入地狱 9.结论 ​尽管以前写过一篇SEH相关的 ...

  7. c语言程序设计 滴水视频,编程达人滴水中级班视频教程

    Java视频教程详情描述: <编程达人滴水中级班视频教程>编程达人来了,N部视频教程让你成为真正的编程达人,融会贯通将是本套视频最大的目的. Java视频教程目录: ├─APC机制 │  ...

  8. Windows系统程序设计之结构化异常处理

    标 题: [原创]Windows系统程序设计之结构化异常处理 作 者: 北极星2003 时 间: 2006-09-20,20:21:28 链 接: http://bbs.pediy.com/showt ...

  9. Structured Exception Handling

    http://msdn.microsoft.com/zh-cn/magazine/cc301172.aspx Matt Pietrek 著   董岩 译 原文:http://www.microsoft ...

最新文章

  1. android 图片作为背景图片,Android设置网络图片为手机背景图片的方法
  2. JavaScript基础04【逻辑、复制、关系、相等运算符、Unicode编码表】
  3. 零基础学Python(第十七章 模块import)
  4. dubbo启动时检查服务
  5. VB快速查找大型文件中包含的字符串
  6. 计算机中那些事儿(七):近期拆计算机小感
  7. 「Mac新手必备」自定义 Mac 上的控制中心和菜单栏
  8. Java程序员最厉害的是什么,「解密」谁是世界上最好的java程序员?
  9. java方面的文献综述怎么写_java论文参考文献
  10. 计算机人工智能领域英文文献,人工智能英文参考文献精选(3)
  11. Python遥感开发之GDAL读写遥感影像
  12. 计算机网络 如何算 子网号,计算机网络的划分以及主机号子网号的计算方法
  13. CentOS7系统安装参考
  14. 人工智能Java SDK:图片分类(支持imagenet数据集分类)
  15. 计算机摄影比赛,计算机系摄影大赛圆满结束
  16. 使用ST-LINK下载STM32程序
  17. 计算机美化标题教案,计算机基础教案标题.doc
  18. 阿里,昨天被主管告知3.25了,感觉自己好失败...
  19. Storm Event Imagery Dataset 数据简介
  20. 如何成为一名合格的运筹优化算法工程师?

热门文章

  1. 成功解决OpenCV Error: Assertion failed (ssize.width 0 ssize.height 0) in cv::resize, file C:\proj
  2. Java:Java编程实现导出二维码
  3. ML之LoRSGD:基于LoR(逻辑回归)、SGD梯度下降算法对乳腺癌肿瘤(10+1)进行二分类预测(良/恶性)
  4. 3D数学 ---- 矩阵和线性变换
  5. C语言实现常用数据结构——堆
  6. android 中的常用组件
  7. 【转载】OI生涯结束……在逸夫楼那些的日子里
  8. bzoj1791: [Ioi2008]Island 岛屿 单调队列优化dp
  9. 【NOI2013】向量内积
  10. Linux 下如何安装软件?