_try_except原理

调用_except_handle3这个异常处理函数,这里并不是每个编译器的异常处理函数都是相同的,然后存入结构体,将esp的值赋给fs:[0],再就是提升堆栈的操作

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

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

可以看到只有一个异常处理函数

那么这里编译器是如何做到只用一个异常处理函数的呢?编译器把原来_EXCEPTION_REGISTRATION_RECORD结构进行了拓展,添加了三个成员

struct _EXCEPTION_REGISTRATION{struct _EXCEPTION_REGISTRATION *prev;void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);struct scopetable_entry *scopetable;int trylevel;int _ebp;};

新堆栈结构如下

scopetable

struct scopetable_entry
{DWORD previousTryLevel  //上一个try{}结构编号 PDWRD        lpfnFilter         //过滤函数的起始地址PDWRD        lpfnHandler    //异常处理程序的地址
}

查看地址可以发现有三个结构体

存储着的正式异常函数的开始地址和结束地址

第一个值previousTryLevel是上一个try结构的编号,这里如果在最外层就是-1,如果在第二层就是0,如果在第三层就是1,以此类推

trylevel

该成员表示代码运行到了哪个try结构里面,进入一个try则加1,try结构执行完成之后则减1

_except_handler3

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

2.执行_except_handler3函数

<1> 根据trylevel 选择scopetable数组

<2> 调用scopetable数组中对应的lpfnFilter函数

1.EXCEPTION_EXECUTE_HANDLER(1) 执行except代码

2.EXCEPTION_CONTINUE_SEARCH(0) 寻找下一个

3.EXCEPTION_CONTINUE_EXECUTION(-1) 重新执行

<3> 如果lpfnFilter函数返回0 向上遍历 直到previousTryLevel=-1

假设有两个异常点

首先找到trylevel为0

然后找到异常过滤表达式为1

然后遍历数组的lpfnFilter

如果返回值为1则调用异常处理函数,如果为0则该异常函数不处理,如果为-1则继续从原异常点向下执行

假设在B这个地方出异常,得到trylevel为2

那么这里就回去遍历lpfnFilter为2的地方

假设这里返回值为0,则继续查找,注意这个地方是向上查找,首先判断当前previousTryLevel的值是否为-1,如果为-1就停止查找(-1代表已经是最外层)try结构,然后再向上找,假设这里返回值仍然为0,判断previousTryLevel的值为-1,就停止查找,没有找到响应的异常处理函数

_try_finally原理

无论try结构体中是什么代码,都会执行finally里面的代码

// SEH6.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>VOID ExceptionTest()
{__try{return;printf("Other code");}__finally{printf("Must run this code");}
}int main(int argc, char* argv[])
{ExceptionTest();getchar();return 0;
}

局部展开

try里面没有异常,而是returncontinuebreak等语句时,就不会走_except_handle3这个函数,而是调用_local_unwind2进行展开

然后调用[ebx + esi*4 + 8]

跟进去就到了finally语句块的地方

我们探究一下实现的原理,这里本来应该是lpfnFilter参数,指向异常处理过滤的代码的地址,但是这里是0。只要这个地方的地址为0就是finally语句块

__global_unwind2函数最终会调用一个RtlUnwind函数,该函数内容比较杂乱,其大体流程如下

全局展开

// SEH6.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>VOID ExceptionTest()
{__try{__try{__try{*(int*)0 = 1;}__finally{printf("Must run this code : A");}}__finally{printf("Must run this code : B");}}__except(1){printf("Here is Exception_functions");}
}int main(int argc, char* argv[])
{ExceptionTest();getchar();return 0;
}

全局展开就是一层一层的向上找异常处理函数,finally模块还是照常执行

未处理异常

入口程序的最后一道防线

这里调用mainCRTStartup(),然后调用入口程序

相当于这里才是一个进程开始执行的地方

这里有一个call调用,跟进去看看

发现有修改fs:[0]的操作,这里就相当于编译器为我们注册了一个异常处理函数

这里到kernel32.dll里面的BaseProcessStart里面看一下,这里有一个注册SEH异常处理函数的操作

线程启动的最后一道防线

// SEH7.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>DWORD WINAPI ThreadProc(LPVOID lpParam)
{int i = 1;return 0;
}int main(int argc, char* argv[])
{CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);getchar();return 0;

可以发现线程也是从kernel32.dll开始的

然后跟进调用

可以发现还是注册了一个异常处理函数

还是去IDA里面看BaseThreadStart函数,发现也注册了一个SEH异常的函数

UnhandledExceptionFilter

相当于编译器为我们生成了一段伪代码

__try
{}
__except(UnhandledExceptionFilter(GetExceptionInformation())
{//终止线程//终止进程
}

只有程序被调试时,才会存在未处理异常

UnhandledExceptionFilter的执行流程:

1) 通过NtQueryInformationProcess查询当前进程是否正在被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH,此时会进入第二轮分发 2) 如果没有被调试: 查询是否通过SetUnhandledExceptionFilter注册处理函数 如果有就调用 如果没有通过SetUnhandledExceptionFilter注册处理函数 弹出窗口 让用户选择终止程序还是启动即时调试器 如果用户没有启用即时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLER

SetUnhandledExceptionFilter

如果没有通过SetUnhandledExceptionFilter注册异常处理函数,则程序崩溃

测试代码如下,我自己构造一个异常处理函数callback并用SetUnhandledExceptionFilter注册,构造一个除0异常,当没有被调试的时候就会调用callback处理异常,然后继续正常运行,如果被调试则不会修复异常,因为这是最后一道防线,就会直接退出,起到反调试的效果

// SEH7.cpp : Defines the entry point for the console application.
//#include "stdafx.h"
#include <windows.h>long _stdcall callback(_EXCEPTION_POINTERS* excp)
{excp->ContextRecord->Ecx = 1;return EXCEPTION_CONTINUE_EXECUTION;
}int main(int argc, char* argv[])
{SetUnhandledExceptionFilter(callback);_asm{xor edx,edxxor ecx,ecxmov eax,0x10idiv ecx}printf("Run again!");getchar();return 0;
}

直接启动可以正常运行

使用od打开则直接退出

KiUserExceptionDispatcher

只有当前程序处于调试的时候才可能产生未处理异常

1) 调用RtlDispatchException  查找并执行异常处理函数2) 如果RtlDispatchException返回真,调用ZwContinue再次进入0环,但线程再次返回3环时,会从修正后的位置开始执行。3) 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发
(参见KiUserExceptionDispatcher代码)

SEH异常之编译器原理探究相关推荐

  1. 浏览器工作原理探究详解

    浏览器工作原理探究 标签: 浏览器工作原理 / web性能优化 引言 最近对web的性能优化比较感兴趣,而前端代码主要在浏览器工作的.如果对浏览器的工作原理了解清楚,可以为web性能优化提供方向以及理 ...

  2. Source Map的原理探究

    摘要: Source Map很神奇,它的原理挺复杂的- 原文:source map 的原理探究 地址:https://www.cnblogs.com/Wayou/p/understanding_fro ...

  3. KVM 虚拟化原理探究--启动过程及各部分虚拟化原理

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  4. C++异常 和 SEH异常 的一点小结

    [如何区分异常] "C++异常" 就是 try{}catch(...){} "SEH异常" 就是 __try{} __except(-1/0/1){} 目前微软 ...

  5. Leveldb学习笔记:leveldb的使用与原理探究

    文章目录 前言 一.leveldb是什么? 二.leveldb的使用 1.打开一个数据库 2.Status 3.关闭一个数据库 4.数据库读写 5.原子更新 6.同步写 7.并发 8.迭代器 9.Sn ...

  6. java lock的原理,Java中Lock原理探究

    在对于lock锁的使用上,很多人只是掌握了最基础的方法,但是对实现的过程不是很清楚.这里我们对lock锁功能的实现进行分析,以ReentrantLock为例,分析它的锁类型,并对相关的调用方法进行展示 ...

  7. vmware nat模式原理探究,实现虚拟机跨网段管理

    vmware nat模式原理探究: 理解nat模式,我们能更加了解主机与虚拟机之间如何通信,以及虚拟机如何实现上网. 以及便于我们分析虚拟机与主机无法通信和无法上外网的问题. 下面通过实战:虚拟网络拓 ...

  8. 并发队列-无界非阻塞队列 ConcurrentLinkedQueue 原理探究

    并发队列-无界非阻塞队列 ConcurrentLinkedQueue 原理探究 http://www.importnew.com/25668.html 一. 前言 常用的并发队列有阻塞队列和非阻塞队列 ...

  9. KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听

    书读百变,其义自见! 将KVO形式以代码实现呈现,通俗易懂,更容易掌握 :GitHub   -链接如果失效请自动搜索:https://github.com/henusjj/KVO_base 代码中有详 ...

最新文章

  1. Leangoo看板Jenkins配置指南
  2. 纪念小柴昌俊 | 中微子天体物理学的诞生
  3. cvpr2019/cvpr2018/cvpr2017(Papers/Codes/Project/Paper reading)
  4. 元素位置互换之移位算法
  5. linux mysql优化_Linux上跑MySQL优化技巧
  6. 计算机专业考注册测绘师经验,注册测绘师考试攻略
  7. Autodesk 3DMax2023下载安装教程
  8. 仅三行代码的按键扫描程序,绝对够经典(秒杀郭天祥讲解的按键扫描)
  9. Python 自动发送邮件
  10. python 实现复数计算器
  11. 斯坦福NLP名课带学详解 | CS224n 第15讲 - NLP文本生成任务(NLP通关指南·完结)
  12. 关于js 中 try catch用法
  13. 【MATLAB】求复合函数
  14. 全球及中国表面保护胶带行业研究及十四五规划分析报告
  15. mongodb中文件导入报错error inserting documents解决方法
  16. 强制删除桌面多余或恶意IE图标方法 :强制删除桌面多余或恶意IE图标方法:
  17. 豆瓣2018年度电影榜单
  18. siri 苹果语音控制功能
  19. 如何用matlab绘制180°根轨迹、参数根轨迹
  20. 八门大神可以修改服务器游戏吗,八门神器怎么修改游戏 修改游戏完美教程

热门文章

  1. javaWeb-filter(过滤器二)
  2. 岛屿的最大面积(dfs)
  3. 无法调用独显,nvidia-setting打开失败
  4. linux eclipse cuda,CUDA Linux eclipse intel Mosix
  5. 20221219今天的世界发生了什么
  6. 友立公司编著的教程中有关“场顺序”选择的相关说明
  7. Chrome 已称王,IE 今何在?
  8. 信息系统项目管理师第四版知识摘编:第2章 信息技术发展
  9. php ues导入,php中同时使用多个use导入命名空间时的问题
  10. ubuntu安装google app engine环境