windows下的异常处理机制
SEH(通过 __try __except)
// 02_SEH异常处理.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
// 编写异常过滤函数用于绝对返回哪个值
DWORD ExceptionFilter(DWORD ExceptionCode, PEXCEPTION_POINTERS ExceptionPointers)
{
printf("开始处理异常\n");
if (ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
// 修复除零异常,然后程序继续执行
// 修复的原理就是产生异常时的寄存器我们都能获取到,
// 然后将产生错误的位置,修复以下,比如这个异常的指令是
// idiv eax,dword ptr [ebp-2Ch]
int* p = (int*)(ExceptionPointers->ContextRecord->Ebp -0x2c);
*p = 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
else if (ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
// 内存访问异常,我无法处理,写入日志之后,直接不回去执行
return EXCEPTION_EXECUTE_HANDLER;
}
else// 异常被修复,返回继续执行
{
//其他的异常,我都处理不了,让别人处理吧
return EXCEPTION_CONTINUE_SEARCH;
}
}
void Fun()
{
//被保护的代码
__try
{
//int a = 10,b =0;
//printf("我要产生异常了\n");
//int c = a / b;
//printf("异常被我处理了\n");
int* p = (int*)0x32432434;
printf("我要产生异常了\n");
*p = 100;
printf("异常被我处理了\n");
}
//1 从except的后面继续执行 EXCEPTION_EXECUTE_HANDLER
//0 处理不了 EXCEPTION_CONTINUE_SEARCH
//-1 从异常处继续执行 EXCEPTION_CONTINUE_EXECUTION
//GetExceptionCode()能够获取到是什么异常
//GetExceptionInformation() 是能够获取到异常的信息 最主要的是寄存器信息
__except (ExceptionFilter(GetExceptionCode(), GetExceptionInformation()))
{
int a = 0;
a++;
printf("嘿嘿 掠过一个异常\n");
}
}
int main()
{
__try
{
Fun();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
printf("还是让爸爸来处理吧\n");
}
system("pause");
}
UEH
#include <iostream>
#include <Windows.h>
// 编写异常过滤函数用于绝对返回哪个值
DWORD ExceptionFilter(DWORD ExceptionCode, PEXCEPTION_POINTERS ExceptionPointers)
{
printf("开始处理异常\n");
//if (ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
//{
// 修复除零异常,然后程序继续执行
// 修复的原理就是产生异常时的寄存器我们都能获取到,
// 然后将产生错误的位置,修复以下,比如这个异常的指令是
// idiv eax,dword ptr [ebp-2Ch]
// int* p = (int*)(ExceptionPointers->ContextRecord->Ebp - 0x2c);
// *p = 1;
// return EXCEPTION_CONTINUE_EXECUTION;
//}
if (ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
// 内存访问异常,我无法处理,写入日志之后,直接不回去执行
return EXCEPTION_EXECUTE_HANDLER;
}
else// 异常被修复,返回继续执行
{
//其他的异常,我都处理不了,让别人处理吧
return EXCEPTION_CONTINUE_SEARCH;
}
}
void Fun()
{
//被保护的代码
__try
{
int a = 10,b =0;
printf("我要产生异常了\n");
int c = a / b;
printf("异常被我处理了\n");
//int* p = (int*)0x32432434;
//printf("我要产生异常了\n");
//*p = 100;
//printf("异常被我处理了\n");
}
//1 从except的后面继续执行 EXCEPTION_EXECUTE_HANDLER
//0 处理不了 EXCEPTION_CONTINUE_SEARCH
//-1 从异常处继续执行 EXCEPTION_CONTINUE_EXECUTION
//GetExceptionCode()能够获取到是什么异常
//GetExceptionInformation() 是能够获取到异常的信息 最主要的是寄存器信息
__except (ExceptionFilter(GetExceptionCode(), GetExceptionInformation()))
{
int a = 0;
a++;
printf("嘿嘿 掠过一个异常\n");
}
}
// 自己定义的顶层异常处理程序
LONG WINAPI TopLevelExceptionHandler(_EXCEPTION_POINTERS* ExceptionInfo)
{
printf("UEH处理\n");
// 修正被除数
int* p = (int*)(ExceptionInfo->ContextRecord->Ebp - 0x2c);
*p = 1;
// 修正除数后重新执行产生异常的指令
return EXCEPTION_CONTINUE_EXECUTION;
// 处理不了
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
SetUnhandledExceptionFilter(TopLevelExceptionHandler);
__try
{
Fun();
}
__except (EXCEPTION_CONTINUE_SEARCH)
{
printf("还是让爸爸来处理吧\n");
}
system("pause");
}
VEH(向量化异常处理) AddVectoredExceptionHandler
在程序一开始的地方注册一个回调函数,在程序的任意位置产生异常,都会调用回调函数,这种异常处理是全局的,注册后全进程有效。
VCH(主要做收尾工作)AddVectoredContinueHandler
SEH原理解析
FS:[0]在用户层,永远是一个线程的TEB结构
什么是TEB(Thread Environment Block)线程环境块。
是一个结构体,里面存储了很多线程相关的信息。
TEB的第一个字段是 _NT_TIB _NT_TIB的第一个字段是 ExceptionList。ExceptionList是一个指针
FS:[0] 是TEB的地址 TEB的第一个字段就是 ExceptionList指针。
所以FS:[0] 是异常链的指针。
SEH异常处理是怎么处理的。
通过FS:[0]找到 异常链,遍历异常链中的 Handler 挨个调用一次。
遍历SHE 处理函数
// 05_遍历SEH处理函数.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
// 遍历SEH异常处理链
void EnumSEH()
{
// 获取头节点
PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr;
__asm push fs : [0]
__asm pop ExceptionList
// 遍历 ExceptionList 中的所有函数
while (ExceptionList != (PEXCEPTION_REGISTRATION_RECORD)-1)
{
printf("Address: %p\n", ExceptionList->Handler);
ExceptionList = ExceptionList->Next;
}
}
void Fun()
{
//被保护的代码
__try
{
//遍历SEH
EnumSEH();
}
__except (1)
{
printf("嘿嘿 掠过一个异常\n");
}
}
int main()
{
__try
{
Fun();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("还是让爸爸来处理吧\n");
}
}
自己挂载SEH
// 06_自己挂载SEH.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
// 自定义的 SEH 处理函数
EXCEPTION_DISPOSITION NTAPI ExceptionRoutine(
_Inout_ struct _EXCEPTION_RECORD* ExceptionRecord,
_In_ PVOID EstablisherFrame,
_Inout_ struct _CONTEXT* ContextRecord,
_In_ PVOID DispatcherContext)
{
printf("进入到我安装的异常处理器中\n");
ContextRecord->Ecx = 1;
return ExceptionContinueExecution;
}
int main()
{
// 保存原始的处理函数,方便还原
PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr;
//备份头节点
__asm push fs : [0]
__asm pop ExceptionList
// 安装自己的异常处理函数
__asm push ExceptionRoutine //在栈上放置异常处理函数地址
__asm push fs : [0] //在栈上放置头节点地址
//
//ESP 头节点地址,头节点此时就变为了下一个结点
// 异常处理函数地址
__asm mov fs : [0] , esp
// 主动触发一个异常
int number = 10;
number /= 0;
// 恢复以前的异常处理函数
__asm add esp, 0x08
__asm mov eax, ExceptionList
__asm mov fs : [0] , eax
return 0;
}
总结:
当异常交由用户处理时,按照一下顺序调用异常处理方式:VEH---->SHE—>UEH—>VCH
UEH 和 VCH 他们是后面出现的异常处理机制,他们比SEH使用起来更为简单,他们只需要在程序一开始的,注册一个回调函数,然后在程序的任何一个位置产生异常,都会调用回调函数,进行异常的处理。