文章目录
异常和中断
- 中断一般都是由外部硬件引发的。
- 而异常一般都是由软件触发的(例如 int3,除0中断,访问空指针的指令)
- IDT表中包含了所有中断处理函数和异常处理函数,当CPU产生异常或中断的时候, 会根据异常编号或中断编号读取IDT表中的处理函数的地址, 然后调用这个函数来处理异常或中断. 这些函数是由操作系统所填充的.
- 一般分为三类
- 错误类
- 陷阱类
- 终止类
Windows提供的异常处理机制
SEH
终结处理器:
__try
… __finally
- 终结处理器 : 无论try块的代码以何种方式离开, 都会保证finally块的代码被执行.
- 如果是以
return
,break
,continue
,goto
方式离开的, 统称为非正常离开方式, 编译器会产生额外的代码来保证finally块的执行. 因此不建议使用这些方式离开try块. - 如果希望中途离开try块, 可以使用
__leave
来离开, 这种方式不会产生额外的代码
int main()
{
printf("进入main函数\n");
__try {
printf("进入__try块\n");
//__leave;
}
__finally {
printf("进入__Finally块\n");
}
printf("离开main函数");
}
异常处理器:
__try
…except
- except有一个异常过滤表达式, 这个表达式的值决定了产生异常之后程序的执行位置.
- 值为1 , 执行except块的代码 :
EXCEPTION_EXCUTE_HANDLER
- 值为0, 搜索上一层的except块:
EXCEPTION_CONTINUE_SEARCH
- 值为-1, 继续执行产生异常的那条指令:
EXCEPTION_CONTINUE_EXECUTION
- 值为1 , 执行except块的代码 :
- 可以通过
GetExceptionInformation
来获取异常的信息, 这个函数会返回一个EXCEPTION_POINTERS
结构体的指针
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord; // 异常记录,包含了异常的类型,触发异常的汇编指令的地址
PCONTEXT ContextRecord; // 产生异常时的线程上下文
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
#include "pch.h"
#include <windows.h>
#include <stdio.h>
DWORD seh_filter()
{
printf("SEH异常过滤函数seh_filter被调用\n");
return EXCEPTION_CONTINUE_EXECUTION; //继续执行产生异常的指令
}
int main()
{
printf("进入main函数\n");
// 1. 执行except块
__try{
*(int*)0 = 0;// 触发异常
}
/*执行异常处理块(except块)*/
__except (EXCEPTION_EXECUTE_HANDLER) //执行except块
{
printf("触发了一个异常\n");
}
// 2. 重新执行产生异常的代码
//__try {
// *(int*)0 = 0;// 触发异常
//}
// /*先调用函数,然后使用函数的返回值来决定执行方向*/
//__except (seh_filter()) { //返回值为EXCEPTION_CONTINUE_EXECUTION,既重复执行产生异常的代码段,
//并不会执行excpet代码块
// printf("触发了一个异常\n");
//}
// 3. 搜索上一层
__try {
__try {
*(int*)0 = 0;// 触发异常
}
/*搜索上一层try...except块*/
__except (EXCEPTION_CONTINUE_SEARCH) {
//搜索上一层异常处理
}
}
/*执行异常处理块*/
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("main函数的except块被执行\n");
}
printf("离开main函数\n");
}
- 异常信息结构体中的线程上下文
#include "pch.h"
#include <windows.h>
#include <stdio.h>
int g_nNum;
void test_fun()
{
MessageBox(0, 0, 0, 0);
//需要退出进程
ExitProcess(0);
}
DWORD seh_filter(EXCEPTION_POINTERS* exp)
{
printf("SEH异常过滤函数seh_filter被调用\n");
//异常代码和产生异常的地址
printf("异常代码:%08X , 异常地址:%08X\n",
exp->ExceptionRecord->ExceptionCode,
exp->ExceptionRecord->ExceptionAddress);
//线程上下文
printf("EIP=%08X EAX=%08X ECX=%08X\n",
exp->ContextRecord->Eip,
exp->ContextRecord->Eax,
exp->ContextRecord->Ecx);
// 修改线程上下文, 修改的其实是内存中的一个记录
// 异常处理完毕之后,会执行iret指令, 这条指令会
// 将内存中的线程上下文恢复到硬件的寄存器中.
// 因此改掉了此处的eax的值,就改掉了硬件中的寄存器.
exp->ContextRecord->Eax = (DWORD)&g_nNum;
// 在异常过滤函数中,修改eip,到时候返回到触发异常的
// 指令时, 实际是由eip的值来决定大的, eip指向了哪里
// 异常出来完毕之后就会回到哪里
exp->ContextRecord->Eip = (DWORD)&test_fun;
//返回继续执行异常的代码块
return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
printf("进入main函数\n");
// 1. 执行except块
__try{
*(int*)0 = 0;// 触发异常
}
/*执行异常处理块(except块)*/
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("触发了一个异常\n");
}
// 2. 重新执行产生异常的代码
__try {
//*(int*)0 = 0;// 触发异常
_asm xor eax, eax;
_asm mov [eax], 0;
}
/*先调用函数,然后使用函数的返回值来决定执行方向*/
__except (seh_filter(GetExceptionInformation())) {
printf("触发了一个异常\n");
}
// 3. 搜索上一层
__try {
__try {
*(int*)0 = 0;// 触发异常
}
/*搜索上一层try...except块*/
__except (EXCEPTION_CONTINUE_SEARCH) {
}
}
/*执行异常处理块*/
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("main函数的except块被执行\n");
}
printf("离开main函数\n");
}
VEH
- 通过
AddVectoredExceptionHandler
函数来注册异常处理函数. - 产生异常时, 系统会遍历VEH链表来调用异常处理函数. 异常函数必须返回两种值, 来决定异常处理的下一步的执行位置.
- 继续执行(-1) :
EXCEPTION_CONTINUE_EXECUTION
- 继续搜索(0) :
EXCEPTION_CONTINUE_SEARCH
- 系统就会找到VEH链表中的下一个处理函数.当VEH链没有下一个的时候,系统才会去找
SEH
- 系统就会找到VEH链表中的下一个处理函数.当VEH链没有下一个的时候,系统才会去找
- 继续执行(-1) :
#include "pch.h"
#include <windows.h>
#include <stdio.h>
LONG WINAPI veh_handler(EXCEPTION_POINTERS* exp)
{
printf("veh_handler(继续执行)\n");
return EXCEPTION_CONTINUE_EXECUTION;
}
LONG WINAPI veh_handler2(EXCEPTION_POINTERS* exp)
{
printf("veh_handler(继续搜索)\n");
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
//先往头部插入veh_handler2这个异常处理函数
AddVectoredExceptionHandler(TRUE, veh_handler2);
//再往头部插入veh_handler这个异常处理函数
AddVectoredExceptionHandler(TRUE, veh_handler);
//最终顺序就是veh_handler->veh_handler2
//因为veh_handler的返回值是EXCEPTION_CONTINUE_EXECUTION
//所以压根不会执行veh_handler2
*(int*)0 = 0;
}
UEH
- 顶级异常处理函数 : 当VEH,SEH的异常处理函数都返回
搜索上一个
的话, 才会找到UEH. - 在64位系统下, 程序处于调试状态, UEH的过滤函数不会被调用.但是在运行态会被调用.在32为系统下没有影响.
#include "pch.h"
#include <windows.h>
#include <stdio.h>
LONG WINAPI ueh_filter(EXCEPTION_POINTERS* exp)
{
printf("UEH过滤函数被调用\n");
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
// 设置UEH
// 顶级异常处理器
SetUnhandledExceptionFilter(ueh_filter);//注册UEH过滤函数
*(int*)0 = 0;
printf("main\n");
}
VCH
- 注册和VEH是一样:,但是注册的API不一样, 它使用API来注册
AddVectoredContinueHandler
。 - 不是一个正经的异常处理器.
异常处理机制的优先级
VEH -> SEH -> UEH
异常处理机制的处理过程
如果UEH都无法处理的话就会提交给系统,让系统处理。
SEH的原理
- 异常过滤函数和异常处理函数的区别:
- SEH的异常处理函数是由系统直接调用,由编译器生成并注册。
- SEH的异常过滤函数其实是由编译器生成的代码所调用,异常过滤函数就是__except(异常过滤函数)。
流程:- 系统调用SEH的异常处理函数(由编译器生成) , 异常处理函数再调用异常过滤函数.
- 如何直接注册一个SEH的异常处理函数(而不是异常过滤函数)
- SEH的异常处理函数保存在哪里? 怎么找到?
- 这条链表的首地址就保存在
TEB
结构中. - 保存的位置只能是栈空间. 使用一个链表来保存, 每个节点的结构:
- 这条链表的首地址就保存在
typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; // 指向下一个SEH结构体的指针 PEXCEPTION_ROUTINE Handler; // SEH的异常处理函数 } EXCEPTION_REGISTRATION_RECORD;
- SEH的异常处理函数保存在哪里? 怎么找到?
TEB
- 线程环境块(Thread Environment Block)
- 保存了和一个线程相关的大量的信息.
- Windows将
TEB
结构的变量保存在内存中的一个位置上. 这个地址是无法直接获取到的. 但是通过FS
段寄存器可以找到这个结构体.FS
段寄存器的段基地址正是这个结构体的首地址. - SEH异常链最后一个节点的地址是
-1
;
遍历SEH异常链
EXCEPTION_REGISTRATION_RECORD* head = NULL;
//1. 得到头节点地址
_asm {
push dword ptr fs : [0];
pop dword ptr head;
}
printf("节点地址 | 处理函数 \n");
while (head !=
(EXCEPTION_REGISTRATION_RECORD*)-1)
{
printf("%08X | %08X\n",
head,
head->Handler);
head = head->Next;
}
手工安装SEH节点
//SEH处理函数的格式
EXCEPTION_DISPOSITION NTAPI seh_handler(
_Inout_ struct _EXCEPTION_RECORD *ExceptionRecord,
_In_ PVOID EstablisherFrame,
_Inout_ struct _CONTEXT *ContextRecord,
_In_ PVOID DispatcherContext
)
{
printf("seh_handler\n");
return ExceptionContinueExecution;
}
int main{
// 纯汇编:
_asm
{
push seh_handler; // Handler
push dword ptr fs : [0]; // Next
//这个时候esp指向NEXT
//esp+4指向异常处理函数地址,所以将esp赋值给fs:[0]
//头节点就成为我们创建的异常处理函数了。
mov fs : [0], esp;
}
*(int*)0 = 0;
// 卸载seh节点
_asm
{
pop dword ptr fs : [0];
add esp, 4;
}
}
C++内联汇编版本:
// 安装一个处理函数
EXCEPTION_REGISTRATION_RECORD seh_node = { 0 };
seh_node.Handler = seh_handler;
printf("seh_handler : %08X\n", seh_handler);
// seh_node.Handler; = fs:[0];
_asm
{
push dword ptr fs : [0];
pop dword ptr[seh_node.Next];
}
// fs:[0] = &seh_node;
_asm
{
lea eax, seh_node;
mov fs : [0], eax;
}
找出程序的SEH处理函数
- 编译器生成的
__try...__except
代码是默认使用except_handler4
来作为异常处理函数. 异常过滤表达式, except块的代码都是被except_handler4
函数所间接调用的.