//1.挂入链表相当于这部分
//fs[0]-> Exception
_asm
{
mov eax, fs:[0]
mov temp,eax
lea ecx,Exception
mov 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种方式
- 直接写常量值
- 表达式
- 调用函数
表达式写法
_try
{
_asm
{
xor edx,edx
xor ecx,ecx
mov eax,10
idiv 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,edx
xor ecx,ecx
mov eax,10
idiv 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 ebp
mov ebp,esp
push 0FFh
push offset string "i386\\chkesp.c"+0FFFFFFD4h (00422020)
push offset __except_handler3 (00401224)
mov eax,fs:[00000000]
push eax
mov dword ptr fs:[0],esp
add esp,0B8h
push ebx
push esi
push edi
mov dword ptr [ebp-18h],esp
lea edi,[ebp-58h]
mov ecx,10h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
_try
mov dword ptr [ebp-4],0
{
_try
mov dword ptr [ebp-4],1
{}
mov dword ptr [ebp-4],0
jmp $L16979+0Ah (0040107c)
_except(1)
mov eax,1
ret
mov esp,dword ptr [ebp-18h]
{}
mov dword ptr [ebp-4],0
}
mov dword ptr [ebp-4],0FFFFFFFFh
jmp $L16975+0Ah (00401095)
_except(1)
mov eax,1
ret
mov esp,dword ptr [ebp-18h]
{}
mov dword ptr [ebp-4],0FFFFFFFFh
_try
mov dword ptr [ebp-4],2
{}
mov dword ptr [ebp-4],0FFFFFFFFh
jmp $L16983+0Ah (004010b5)
_except(1)
mov eax,1
ret
mov esp,dword ptr [ebp-18h]
{}
mov dword ptr [ebp-4],0FFFFFFFFh
mov ecx,dword ptr [ebp-10h]
mov dword ptr fs:[0],ecx
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
不管其内部嵌套或反复使用多少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执行过程
- CPU检测到异常 查中断表执行处理函数CommonDispatchException -> KiDispatchException ->KiUserExceptionDispatcher -> RtIDispatchException -> VEH ->SEH
- 执行_except_handler3函数
<1>根据 trylevel 选择 scopetable 数组
<2>调用 scopetable 数组中对应的 IpfnFilter 函数
- EXCEPTION_EXECUTE_HANDLER (1) 执行except代码
- EXCEPTION_CONTINUE_SEARCH (0) 寻找下一个
- 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 全局展开
调用局部展开
- 触发异常,_except_handler3接管
- _except_handler3就会找异常处理函数
- 找自己和它的下一个发现scopetable.IpfnFilter=0,处理不了
- 最后那一个_except(1)后可以处理了,但它会先执行_global_unwind2
- _global_unwind2 会从触发异常的那个try开始,调用局部展开