文章目录

  • 问题
  • 自动挂入的异常处理函数
    • __try __except嵌套,重复
    • 原始的`_EXCEPTION_REGISTRATION_RECORD`结构体
    • 编译器拓展的_EXCEPTION_REGISTRATION_RECORD结构体
    • scopetable_entry结构体
    • scopetable_entry举例子:
    • 第一个:
    • 第二个:
    • 第三个:
    • 第四个:
    • _except_handler4执行过程
    • 解释:
    • trylevel

问题

我们在制作SEH链的时候,每增加一个,就得进行一次如下代码

__asm{mov eax,FS:[0]
mov temp,eax
lea ecx,myException
mov FS:[0],ecx                       -------------->修改FS:[0]挂入链表
}

那么当编译进行自行生成时,会不会也是每增加一个try,except,然后就压入一个_EXCEPTION_REGISTRATION_RECORD结构体呢?接下来我们来看看如下代码会压入多少_EXCEPTION_REGISTRATION_RECORD结构体

#include<Windows.h>
#include<iostream>int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo) {pExceptionInfo->ContextRecord->Ecx = 1;return EXCEPTION_CONTINUE_EXECUTION;
}void  TestException() {__try { __try {}__except (EXCEPTION_EXECUTE_HANDLER) {printf("异常处理函数Y");}}__except (EXCEPTION_EXECUTE_HANDLER) {printf("异常处理函数X");}__try {__try {   }__except (GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {printf("异常处理函数Y");}}__except (ExceptFilter(GetExceptionInformation())) {printf("异常处理函数Z");}}int main() {TestException();
}

反汇编代码如下:



看了一下,就只有一个_EXCEPTION_REGISTRATION_RECORD结构体,只不过这个结构体跟我们手动挂的原始_EXCEPTION_REGISTRATION_RECORD结构体不太一样,接下来我们来解释一下。

自动挂入的异常处理函数

_except_handler4(编译器固定,那么异常处理函数名也就固定;随着编译器改变而改变)

__try __except嵌套,重复

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

原始的_EXCEPTION_REGISTRATION_RECORD结构体

typedef struct _EXCEPTION_REGISTRATION_RECORD{struct _EXCEPTION_REGISTRATION_RECORD *Next;
PEXCEOTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;

然而如果编译器的_EXCEPTION_REGISTRATION_RECORD结构体是这样的话,那么压入一个_EXCEPTION_REGISTRATION_RECORD结构体就能办到__try __except重复嵌套的话,那是根本不可能的,所以需要自行拓展

编译器拓展的_EXCEPTION_REGISTRATION_RECORD结构体

typedef struct _EXCEPTION_REGISTRATION_RECORD{struct _EXCEPTION_REGISTRATION_RECORD *Next;
void (*void)(PEXCEPTION_RECORD,PEXCEPTION_REGISTRATION,
PCONTEXT,PEXCEPTION_RECORD);
struct scopetable_entry *scopetable;
int trylevel;
int _ebp;
}

这里trylevel暂时等于-2,scopetable地址等于 0x1990F0h,接下来看看这两值有何作用

scopetable_entry结构体

scopetable_entry成员分析struct   scopetable_entry{DWORD   previousTryLevel        //上一个try{}结构编号
PDWRD   IpfnFilter              //过滤函数的起始地址(小括号里面的常量或者表达式或者函数)
PDWRD  IpfnHandler          //异常处理程序的地址
}

scopetable[0].previousTryLevel=-2
scopetable[0]. IpfnFilter=过滤函数1
scopetable[0]. IpfnHandler=异常函数1

scopetable[1].previousTryLevel=0
scopetable[1]. IpfnFilter=过滤函数2
scopetable[1]. IpfnHandler=异常函数2

scopetable[2].previousTryLevel=-1
scopetable[2]. IpfnFilter=过滤函数3
scopetable[2]. IpfnHandler=异常函数3

scopetable[3].previousTryLevel=2
scopetable[3]. IpfnFilter=过滤函数4
scopetable[3]. IpfnHandler=异常函数4

scopetable_entry举例子:


随便找出一个来举个例子:

第一个:


上一个try结构编号是-2,过滤函数的起始地址是0x19532F,异常处理程序的地址:0x195335

它把过滤函数当成一个代码来执行,然后执行完后直接返回相应的返回结果。

紧接着,进入了异常处理程序代码

第二个:


上一个try结构编号是0,过滤函数的起始地址是0x195309,异常处理程序的地址:0x19530f

它把过滤函数当成一个代码来执行,然后执行完后直接返回相应的返回结果。

紧接着,进入了异常处理程序代码

第三个:


上一个try结构编号是-1,过滤函数的起始地址是0x1953b9,异常处理程序的地址:0x1953c6

过滤函数中有一个函数,进行跳转之后,再次进行返回,然后执行完后直接返回相应的返回结果。

紧接着,进入了异常处理程序代码

第四个:


上一个try结构编号是2(编号为2,即上一个try位于第三个try),过滤函数的起始地址是0x195363,异常处理程序的地址:0x195399

还是把过滤函数中的当成几段代码,执行完之后再返回,最后进行返回值判断,根据返回值判断下一步如何进行

紧接着,进入了异常处理程序代码

_except_handler4执行过程

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

解释:

CPU发现异常,根据异常类型来查找中断表,查中断表找到具体的异常处理函数,执行处理函数之后,处理函数会调用CommonDispatchException,CommonDispatchException把出异常的那些相关信息存储起来(哪里出异常,什么类型的异常等),然后调用KiDispatchException(异常分发处理函数),这个函数会判断这个异常是0环异常还是3环异常,如果是3环异常,那么会修正EIP,把Trap_Frame指向KiUserExceptionDispatcher,然后当线程回到三环时,会从KiUserExceptionDispatcher开始执行,这个函数第一件事就是查RtlDispatchException,来查找异常处理函数在哪里?先找VEH,然后再找SEH(fs:[0]))

当如果我们自己抛出的异常,执行流程除了前三步(CPU检测异常 查中断表执行处理函数 CommonDispatchException),后面的一模一样

2.执行_except_handler4函数

  1. 根据trylevel选择scopetable数组
  2. 调用scopetable数组中对应的IpfnFilter函数
    EXCEPTION_EXECUTE_HANDLER(1)执行except代码
    EXCEPTION_CONTINUE_SEARCH(0)寻找下一个
    EXCEPTION_CONTINUE_EXECUTION(-1)重新执行
  3. 如果IpfnFilter函数返回0,向上遍历,直到previousTryLevel=-1

trylevel

trylevel表示代码正在执行在第几个try中(它是时刻正在变化的,进入一个try或者走出一个try, trylevel都会被改变)


trylevel初始化为-1,此时的话也就代表未处于try中

当后面开始进入try时

进入第一个try,trylevel 值被赋为0;进入第二个try,trylevel 值被赋为1;退出第二个try,trylevel 值被赋为0


进入第三个try,trylevel 值被赋为2;进入第四个try,trylevel 值被赋为3;退出第四个try,trylevel 值被赋为2


当退出所有的try时,trylevel值变为-1

这样的话trylevel结合scopetable数组就可以准确找到出错位置。

注意:
如果出异常后,就不会往下执行了,然后异常就被CPU捕获了,通过各种判断,最终转到_except_handler4,然后

  1. 根据trylevel选择scopetable数组
  2. 调用scopetable数组中对应的IpfnFilter函数
    1.EXCEPTION_EXECUTE_HANDLER(1)执行except代码
    2.EXCEPTION_CONTINUE_SEARCH(0)寻找下一个
    3.EXCEPTION_CONTINUE_EXECUTION(-1)重新执行

编译器扩展SEH(2)相关推荐

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

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

  2. 编译器扩展SEH(1)

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

  3. PL/0语言编译器扩展 编译原理课程实践(1)

    转眼大学生活就要结束,编译原理课程学的东西很多都忘记了.当时我们编译原理课程实践是PL/0语言编译器扩展,在原有PL/0语言文法进行扩展.我写这次博文一是为了回忆以前学的知识,加深记忆:二是和大家分享 ...

  4. 编译器扩展-ScriptableWizard

    可以创建一个对话框,然后统一修改某些组件的值 public class Change :ScriptableWizard{public int 字段 = 100;/// <summary> ...

  5. 6.编译器拓展SEH

    //1.挂入链表相当于这部分 //fs[0]-> Exception_asm{mov eax, fs:[0]mov temp,eaxlea ecx,Exceptionmov fs:[0],ecx ...

  6. 编译器扩展-MenuItem

    MenuItem MenuItem属性允许你添加菜单项到主菜单和检视面板上下文菜单. 用法1:层级划分以及划线 [MenuItem("龙之介工具箱/起飞")] static voi ...

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

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

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

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

  9. 嵌入式C语言自我修养:从芯片、编译器到操作系统(附送书籍)

    关注+星标公众号,不错过精彩内容 来源 | 宅学部落 最近,阅读了王工(王利涛)赠送的一本由他编著的书籍<嵌入式C语言自我修养>,感觉写的挺不错.今天分享一下这本书籍<嵌入式C语言自 ...

最新文章

  1. 二十二、redis持久化之AOF
  2. 基于Matlab的标记分水岭分割算法(imreconstruct)
  3. linux用不用装固态硬盘驱动,固态硬盘要不要安装驱动?总算弄明白了
  4. html标签anchor,浏览器端-W3School-HTML:HTML DOM Anchor 对象
  5. mysql自动添加多条数据_用一条mysql语句插入多条数据
  6. AccessibilityService的具体应用场景
  7. launchpad不用图标_Launchpad Manager,一款非常方便的启动台图标管理工具
  8. 源码解析Servlet和HttpServlet
  9. Effective C# Item18:实现标准Dispose模式
  10. crossover卸载 linux,Ubuntu中卸载CrossOver困难?那是你没学会这个命令
  11. Vue 自定义弹出框组件(类似淘宝选择规格)
  12. 字体arial不支持样式regular的解决方法
  13. Excel排序、筛选
  14. laravel php的if判断,Thinkphp框架和Laravel框架的区别
  15. 【科研绘图】Adobe illustrator加Origin组合绘图
  16. IP网络摄像机安装注意事项
  17. 若依框架登录去除验证码
  18. Photon与Unity核心技术之角色更换武器
  19. linux运行直播软件,在Linux下可用Wine安装和运行虎牙直播、斗鱼直播
  20. 理财入门-读《小狗钱钱》有感

热门文章

  1. Python之pandas:对pandas中dataframe数据中的索引输出、修改、重命名等详细攻略
  2. Dataset之COCO数据集:COCO数据集的简介、下载、使用方法之详细攻略
  3. ML之Clustering之K-means:K-means算法简介、应用、经典案例之详细攻略
  4. DL之ANN/DNN: 人工神经网络ANN/DNN深度神经网络算法的简介、应用、经典案例之详细攻略
  5. 20 道 Spring Boot 面试题
  6. 机器学习-MNIST数据集-神经网络
  7. jQuery $.ajax传递数组的traditional参数传递必须true 对象的序列化
  8. 单调有界数列一定有极限
  9. Asp.Net 2.0中的客户端脚本
  10. STM32学习笔记(三) STM32的GPIO的深入学习