能力值:
( LV4,RANK:50 )
2 楼
当然是操作系统的……不明白你什么问题,哪里无法解释了
能力值:
( LV9,RANK:370 )
3 楼
首先,谢谢回答。
如果是操作系统的机制,我测试的例子中,EXCEPTION_DISPOSITION链表并没有第二次执行,只执行了一遍(也就是少了Unwinding机制中的第二次遍历过程 ,这个就是我不明白的地方。详细的你可以看看我测试用的那个例子的代码)。(文中的例子(或者说是Unwinding机制)是,系统找到处理异常后的程序,还要再一次遍历 EXCEPTION_DISPOSITION链表)
能力值:
( LV4,RANK:50 )
4 楼
这是你程序的输出:
Home Grown handler: Exception Code: C0000005 Exception Flags 0
Home Grown handler: Exception Code: C0000027 Exception Flags 2 EH_UNWINDING
Caught the exception in main()
第二行不就是第二遍么?此时flag为EH_UNWINDING
能力值:
( LV9,RANK:370 )
5 楼
这个是文中的程序输出的结果,他用了一个自己构造的Handler和一个_try _exception结构。
我测试用的代码是下面那个,是全部用的自己构造的Handlers。(其中前两个不处理异常,直到第三个才进行异常处理并返回ExceptionContinueExection。)
输出结果为:
Exception Handler 1 : Exception Code C0000005 Exception Flags 00000000
Exception Handler 2 : Exception Code C0000005 Exception Flags 00000000
Exception Handler 3 : Exception Code C0000005 Exception Flags 00000000
After writing!
并没有第二次的遍历过程。
能力值:
( LV4,RANK:50 )
6 楼
最后一个Handler返回0试试
p.s. 我只有VS8 Express,没有windows.h,又不想下SDK,你知道怎么可以编译么?
能力值:
( LV9,RANK:370 )
7 楼
返回0的结果跟返回ExceptionContinueExecution结果一样。
VS8我不知道能不能编译,我用的是VC6。
能力值:
( LV9,RANK:180 )
8 楼
你看的文章不是都有讲了
这是Compiler弄的
跟写程式的人无关
能力值:
( LV9,RANK:370 )
9 楼
当我将最后一个处理Handler的返回值改变为ExceptionContinueSearch(即改为不处理该异常)时,操作系统调用了默认的异常处理方案:(弹出个常见的内存不能写的对话框),此时输出结果变为:
Exception Handler 1 : Exception Code C0000005 Exception Flags 00000000
Exception Handler 2 : Exception Code C0000005 Exception Flags 00000000
Exception Handler 3 : Exception Code C0000005 Exception Flags 00000000
Exception Handler 1 : Exception Code C0000027 Exception Flags 00000002 EH_UNWIND
ING
Exception Handler 2 : Exception Code C0000027 Exception Flags 00000002 EH_UNWIN
DING
Exception Handler 3 : Exception Code C0000027 Exception Flags 00000002 EH_UNWIND
ING
UnWinding机制出现了。看来这个机制的确是操作系统的机制?
现在又有问题了,这个机制到底是如何启动的?
能力值:
( LV9,RANK:370 )
10 楼
不对啊,这虽然是Compiler实现的,但是还是操作系统的一种机制。9楼我的测试结果显示操作系统默认的异常处理Handler也是启用了这个机制的——给异常处理程序第二次执行的机会。
这个机制到底是如何实现的?
能力值:
( LV4,RANK:50 )
11 楼
恩,看来我错了,大致用od分析了一下myseh2
发现是编译器产生的那个exception hanlder里面调用了RtlUnwind
所以要发生unwind的话还是得使用try{}except{}
能力值:
( LV9,RANK:370 )
12 楼
看来要启用Unwinding的话还得使用一些手段,继续学习中……
能力值:
( LV4,RANK:50 )
13 楼
是的,Unwinding不是操作系统自发触发的,而是通过RtlUnwind函数触发的
只有编译器在异常处理之前知道该handler是不是合适的处理程序(通过类型匹配),然后在调用真正的handler之前进行unwind
能力值:
( LV9,RANK:180 )
14 楼
你说: 这虽然是Compiler实现的,但是还是操作系统的一种机制
也没错啦. 随人解释. (你安装 Exception Handler 时, VC 早装了一份自己的了)
你的 Handler 是在401000, 自己写的, 我就不列出了.
下面是 VC 的 Handler : (你可以先不看, 先看下面)
0040126B |. 83EC 08 sub esp,8
0040126E |. 53 push ebx
0040126F |. 56 push esi
00401270 |. 57 push edi
00401271 |. 55 push ebp
00401272 |. FC cld
00401273 |. 8B5D 0C mov ebx,[ebp+C]
00401276 |. 8B45 08 mov eax,[ebp+8] ; myseh2.<ModuleEntryPoint>
00401279 |. F740 04 0600000>test dword ptr [eax+4],6
00401280 |. 0F85 82000000 jnz 00401308
00401286 |. 8945 F8 mov [ebp-8],eax
00401289 |. 8B45 10 mov eax,[ebp+10]
0040128C |. 8945 FC mov [ebp-4],eax
0040128F |. 8D45 F8 lea eax,[ebp-8]
00401292 |. 8943 FC mov [ebx-4],eax
00401295 |. 8B73 0C mov esi,[ebx+C]
00401298 |. 8B7B 08 mov edi,[ebx+8] ; myseh2.00400000
0040129B |> 83FE FF /cmp esi,-1
0040129E |. 74 61 |je short 00401301
004012A0 |. 8D0C76 |lea ecx,[esi+esi*2]
004012A3 |. 837C8F 04 00 |cmp dword ptr [edi+ecx*4+4],0
004012A8 |. 74 45 |je short 004012EF
004012AA |. 56 |push esi
004012AB |. 55 |push ebp
004012AC |. 8D6B 10 |lea ebp,[ebx+10]
004012AF |. FF548F 04 |call [edi+ecx*4+4]
004012B3 |. 5D |pop ebp
004012B4 |. 5E |pop esi
004012B5 |. 8B5D 0C |mov ebx,[ebp+C]
004012B8 |. 0BC0 |or eax,eax
004012BA |. 74 33 |je short 004012EF
004012BC |. 78 3C |js short 004012FA
004012BE |. 8B7B 08 |mov edi,[ebx+8] ; myseh2.00400000
004012C1 |. 53 |push ebx
004012C2 |. E8 A9FEFFFF |call 00401170
004012C7 |. 83C4 04 |add esp,4
004012CA |. 8D6B 10 |lea ebp,[ebx+10]
004012CD |. 56 |push esi
004012CE |. 53 |push ebx
004012CF |. E8 DEFEFFFF |call 004011B2
004012D4 |. 83C4 08 |add esp,8
004012D7 |. 8D0C76 |lea ecx,[esi+esi*2]
004012DA |. 6A 01 |push 1
004012DC |. 8B448F 08 |mov eax,[edi+ecx*4+8]
004012E0 |. E8 61FFFFFF |call 00401246
004012E5 |. 8B048F |mov eax,[edi+ecx*4]
004012E8 |. 8943 0C |mov [ebx+C],eax
004012EB |. FF548F 08 |call [edi+ecx*4+8]
004012EF |> 8B7B 08 |mov edi,[ebx+8] ; myseh2.00400000
004012F2 |. 8D0C76 |lea ecx,[esi+esi*2]
004012F5 |. 8B348F |mov esi,[edi+ecx*4]
004012F8 |.^ EB A1 \jmp short 0040129B
004012FA |> B8 00000000 mov eax,0
004012FF |. EB 1C jmp short 0040131D
00401301 |> B8 01000000 mov eax,1
00401306 |. EB 15 jmp short 0040131D
00401308 |> 55 push ebp
00401309 |. 8D6B 10 lea ebp,[ebx+10]
0040130C |. 6A FF push -1
0040130E |. 53 push ebx
0040130F |. E8 9EFEFFFF call 004011B2
00401314 |. 83C4 08 add esp,8
00401317 |. 5D pop ebp
00401318 |. B8 01000000 mov eax,1
0040131D |> 5D pop ebp
0040131E |. 5F pop edi
0040131F |. 5E pop esi
00401320 |. 5B pop ebx
00401321 |. 8BE5 mov esp,ebp
00401323 |. 5D pop ebp
00401324 \. C3 retn 你看一下 004012C2 call 00401170
如下:
00401170 /$ 55 push ebp
00401171 |. 8BEC mov ebp,esp
00401173 |. 53 push ebx
00401174 |. 56 push esi
00401175 |. 57 push edi
00401176 |. 55 push ebp
00401177 |. 6A 00 push 0 ; /_eax_value = 0
00401179 |. 6A 00 push 0 ; |pExcptRec = NULL
0040117B |. 68 88114000 push 00401188 ; |ReturnAddr = myseh2.00401188
00401180 |. FF75 08 push dword ptr [ebp+8] ; |pRegistrationFrame = myseh2.<ModuleEntryPoint>
00401183 |. E8 D22C0000 call <jmp.&KERNEL32.RtlUnwind> ; \RtlUnwind
00401188 |. 5D pop ebp
00401189 |. 5F pop edi
0040118A |. 5E pop esi
0040118B |. 5B pop ebx
0040118C |. 8BE5 mov esp,ebp
0040118E |. 5D pop ebp
0040118F \. C3 retn
就又产生了一次例外, 这次为 C0000027 你不处理第一次的 C0000005 , 就算要退出程式, 也得让 VC 做个善后吧.
C0000027 对 OS 好像没特别定义.
//==============================
EH_UNWINDING 是什么意思呢?当异常回调被第二次调用时(带有 EH_UNWINDING 标志),
操作系统就给处理函数一次做所需清理的机会
.....
....
...
OS 给你一次机会, 也得你自己 Call 啊.....
...
....
.....
更一般地说,从异常中的 unwinding 使得位于处理帧的堆栈区域之下的所有的东西
都被移除,几乎相当于从未调用过那些函数。unwinding 的另一个效果就是链表中位
于处理异常的 EXCEPTION_REGISTRATION 之前的所有 EXCEPTION_REGISTRATIONs 都被
从链表中移除。这是有意义的,因为这些 EXCEPTION_REGISTRATION 一般都是在堆栈上
构建的。在异常被处理后,堆栈指针和堆栈帧指针在内存中的地址要比从链表中移除的
那些 EXCEPTION_REGISTRATIONs 高
//==============================
能力值:
( LV9,RANK:370 )
15 楼
编译器不可能知道一个Handler是不是合适的处理程序,比如我在一个Handler中判断,是不是发生了XXX错误,是的话就处理不是的话就不处理,这样编译器还怎么能预先知道这个Handler是不是合适的处理程序?我想还是有其他的机制在里面起作用……
能力值:
( LV9,RANK:370 )
16 楼
谢谢!我得慢慢消化消化。
能力值:
( LV9,RANK:180 )
17 楼
你 [0] <- 0 产生例外
Handler 1 显示 Exception Code C0000005, ExceptionContinueSearch
Handler 2 显示 Exception Code C0000005, ExceptionContinueSearch
Handler 3 显示 Exception Code C0000005, ExceptionContinueSearch 你 Handler 1~3 都是 ExceptionContinueSearch 代表你没处理这个例外
C++ 要结束程式了... call KERNEL32.RtlUnwind
Handler 1 收到 C0000027 , Handler 1 显示 Exception Code C0000027
Handler 2 收到 C0000027 , Handler 2 显示 Exception Code C0000027
Handler 3 收到 C0000027 , Handler 3 显示 Exception Code C0000027
最后 C++ 收到, 善后. 结束.
能力值:
( LV9,RANK:180 )
18 楼
你好像比较想知道何时会 Unwind
即然 Unwind 的发生是位于 C++ 的 try except 包装内
只要你不处理例外就好了
由你的例子得知 C0000005 会. 至于其它的例外会不会, 就等你研究了.
能力值:
( LV9,RANK:370 )
19 楼
谢谢解答!其实我想知道的是如何自己产生Unwind,即在ExceptionHandler3中做些处理,使得当找到这个异常处理程序的时候,再次遍历列表。也就是模拟系统默认的那个处理机制。
现在我在看编译器(VC++)的异常处理机制的实现(_exception_handler3和frame-exception),大致有点模糊的概念了,假如我没有理解错误的话(myseh2.cpp中的Unwinding由_exception_handler3产生),而我测试用的那个例子中Unwinding则是由
在BaseProcessStart中安装的系统默认ExceptionHandler产生,不知道是不是这样? 我想我还得多跟几个例子才能一点一点搞明白。
能力值:
( LV9,RANK:180 )
20 楼
===============================================================
底下是 MYSEH2.cpp - Matt Pietrek 1997
我分析了一下, 供你参考, 再决定你要自己产生 RtlUnwind 是不是合理
文章很长宽请自行贴到 UltraEdit 看. 不然无法对齐.
=============================================================== 在执行到 [0]<- 0 前, Exception_Handler 是如下的变化:
OEP 时 VC_EH - END
main()的try时 VC_EH - VC_EH - END
HomeGrownFrame()的try时 My_EH - VC_EH - VC_EH - END
所以有二个 VC_EH, 位址是一样的. 不用管他, 不影响分析,
下列我只写一个 VC_EH 来代表 mov [eax],0 发生下列的事 :
ntdll.KiUserExceptionDispatcher
/加进例外链 ntdll_EH - My_EH - VC_EH - END
|CALL My_EH : (Win2000 是写 CALL ECX)
| 你显示 Exception Code: C0000005
| return ExceptionContinueSearch
\还原例外链 My_EH - VC_EH - END
/加进例外链 ntdll_EH - My_EH - VC_EH - END
|CALL VC_EH :
| ....
| CALL RtlUnwind
| ....
| CALL _except ;显示 "Caught the exception in main()"
| ....
| ret ;
\ ** 为什么我没写还原例外链呢 ?
//--------------------------------------------------------------------
只有二层较看不出来, 我看过你的 SEHChain, 基本上不管有几层, 可大约写为:
i = 0
Next: 插入 ntdll.EH
Call EH[i+1]
还原 ntdll.EH
if ExceptionContinueSearch : i++ ; jmp Next
//--------------------------------------------------------------------
RtlUnwind() 会产生 C0000027 的例外, 亦即 例外中的例外 C0000005中的C0000027
同样的..过程跟上面 OS 处理例外时"大概"一样 :
/加进例外链 ntdll_EH2 - ntdll_EH - My_EH - VC_EH - END
| \_____这个是C0000005时插入的
|Call ntdll 自己的函数
\还原例外链 ntdll_EH - My_EH - VC_EH - END
例外链抽掉一个, 变成 My_EH - VC_EH - END
/加进例外链 ntdll_EH - My_EH - VC_EH - END
|Call My_EH :
| 你显示 Exception Code: C0000027
| return ExceptionContinueSearch
\还原例外链 My_EH - VC_EH - END
例外链抽掉一个, 变成 VC_EH - END
NtCoutinue
}
此时 RtlUnwind 结束了, 所有的 EH 也访问一遍了.
** 有没有注意到 RtlUnwind , NTDLL会抽掉例外链 (边访问边抽掉)
** 此例而言, KiUserExceptionDispatcher 只在发生 C0000005 时来到.
事实上是一去不回.
** VC_EH 呼叫 RtlUnwind 回来之后, 就会呼叫 _except (上一篇的 4012EB call [ECX*4+EI+08])
在_except里, 此例会执行 printf( "Caught the exception in main()\n" );
然后自行抽掉 main 建立的 VC_EH , 此时例外链 = END (事实上还有一个OEP时的VC_EH)
ret 时, 直接到 main 的上一层, 亦即回到 OEP 那段程式框 - 用 ExitProcess 结束此程式
注1 : 原码 *(PDWORD)0 = 0 我上面写 mov [eax], 0
注2 : 我写了多次的"此例", 表示我只分析 Matt Pietrek 1997 的 MYSEH2.exe
1996年的.exe 跟你2008年的 sehChain.exe , VC 的做法是否一样? 我就不知了.
请自行分析你自己的 sehChain.exe
以上供你参考
能力值:
( LV9,RANK:180 )
21 楼
你用二支不同年代的 exe 在埋头比对当做研究
可能会陷入泥沼里. 毕竟 C++ 的处理方式可能有所变动.
三思啊...毕竟这东西跟 Compiler 的作法有关.
上面的流程了解之后, 你再去看那篇文章应该有很多地方都明白了.
那篇文章在讲到 第二次调用 的部份...我持保留的态度.
可能是翻译文的关系吧.
能力值:
( LV2,RANK:10 )
22 楼
.............................
能力值:
( LV9,RANK:370 )
23 楼
谢谢解答。
我把你写的和我理解的总结一下(帮忙看看是不是有错误):
当异常发生的时候:
0、从用户态(User mode)转向系统核心态(Kernel mode),系统内核从中断描述符表中找到相应的处理程序,进行处理……………………。
1、系统核心态处理的结果是生成两个结构参数?(EXCEPTION_RECORD,CONTEXT)。这两个结构参数的地址被当作参数传递给ntdll.KiUserExceptionDispatcher。(所有的异常都是调用这个函数,无一例外)。KiUserExceptionDispatcher主要调用RtlExceptionDispatcher,如果用户异常处理函数把异常处理掉了,那么RtlExceptionDispatcher永远都不返回。(这个是不是就是Caugth Exception in Main时没有还原异常链表的原因?),如果RtlExceptionDispatcher有返回值,那么要么程序继续执行,要么引发另外一个异常,这个异常不可处理标志着程序必须结束。
KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )
{
DWORD retValue;
// Note: If the exception is handled, RtlDispatchException() never returns
if ( RtlDispatchException( pExceptRec, pContext ) )
retValue = NtContinue( pContext, 0 );
else
retValue = NtRaiseException( pExceptRec, pContext, 0 );
EXCEPTION_RECORD excptRec2;
excptRec2.ExceptionCode = retValue;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
RtlRaiseException( &excptRec2 );
}
1.1 RtlDispatchException的作用就是检查用户安装(registration)的异常处理链表(是否在线程的堆栈之内,是否比前一个EXCEPTION_HANDLER在堆栈中的位置要高?是否在堆栈中4字节对齐?),然后遍历这个异常链表搜索用户异常处理函数。RtlDispatchException并不直接调用用户异常处理回调函数而是调用RtlpExecuteHandlerForException函数来处理用户异常处理函数,并且根据这个RtlpExecuteHandlerException函数的处理返回值决定是继续搜索链表还是引发另外一个异常(如果用户异常处理函数中也发生了异常的话)即DISPOSITION_NESTED_EXCEPTION(异常中的异常)。(!!!!!如果RtlpExecuteHandlerForException直接处理了异常就不再返回,而是在异常发生的地方接着执行!!!!!)。
int RtlDispatchException( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )
{
DWORD stackUserBase;
DWORD stackUserTop;
PEXCEPTION_REGISTRATION pRegistrationFrame;
DWORD hLog;
// Get stack boundaries from FS:[4] and FS:[8]
RtlpGetStackLimits( &stackUserBase, &stackUserTop );
pRegistrationFrame = RtlpGetRegistrationHead();
while ( -1 != pRegistrationFrame )
{
PVOID justPastRegistrationFrame = &pRegistrationFrame + 8;
if ( stackUserBase > justPastRegistrationFrame )
{
pExcptRec->ExceptionFlags |= EH_STACK_INVALID;
return DISPOSITION_DISMISS; // 0
}
if ( stackUsertop < justPastRegistrationFrame )
{
pExcptRec->ExceptionFlags |= EH_STACK_INVALID;
return DISPOSITION_DISMISS; // 0
}
if ( pRegistrationFrame & 3 ) // Make sure stack is DWORD aligned
{
pExcptRec->ExceptionFlags |= EH_STACK_INVALID;
return DISPOSITION_DISMISS; // 0
}
if ( someProcessFlag )
{
// Doesn't seem to do a whole heck of a lot.
hLog = RtlpLogExceptionHandler( pExcptRec, pContext, 0,
pRegistrationFrame, 0x10 );
}
DWORD retValue, dispatcherContext;
retValue= RtlpExecuteHandlerForException(pExcptRec, pRegistrationFrame,
pContext, &dispatcherContext,
pRegistrationFrame->handler );
// Doesn't seem to do a whole heck of a lot.
if ( someProcessFlag )
RtlpLogLastExceptionDisposition( hLog, retValue );
if ( 0 == pRegistrationFrame )
{
pExcptRec->ExceptionFlags &= ~EH_NESTED_CALL; // Turn off flag
}
EXCEPTION_RECORD excptRec2;
DWORD yetAnotherValue = 0;
if ( DISPOSITION_DISMISS == retValue )
{
if ( pExcptRec->ExceptionFlags & EH_NONCONTINUABLE )
{
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.ExceptionNumber = STATUS_NONCONTINUABLE_EXCEPTION;
excptRec2.ExceptionFlags = EH_NONCONTINUABLE;
excptRec2.NumberParameters = 0
RtlRaiseException( &excptRec2 );
}
else
return DISPOSITION_CONTINUE_SEARCH;
}
else if ( DISPOSITION_CONTINUE_SEARCH == retValue )
{
}
else if ( DISPOSITION_NESTED_EXCEPTION == retValue )
{
pExcptRec->ExceptionFlags |= EH_EXIT_UNWIND;
if ( dispatcherContext > yetAnotherValue )
yetAnotherValue = dispatcherContext;
}
else // DISPOSITION_COLLIDED_UNWIND
{
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.ExceptionNumber = STATUS_INVALID_DISPOSITION;
excptRec2.ExceptionFlags = EH_NONCONTINUABLE;
excptRec2.NumberParameters = 0
RtlRaiseException( &excptRec2 );
}
pRegistrationFrame = pRegistrationFrame->prev; // Go to previous frame
}
return DISPOSITION_DISMISS;
}
1。1。1 RtlpExecuteHandlerForException有一个姐妹函数RtlpExecuteHandlerForUnwind。(也就是Unwind机制,就像sessiondiy说的这机制是操作系统的机制)这两个函数其实是同一个函数(ExecuteHandler)的不同封装:
_RtlpExecuteHandlerForException: // Handles exception (first time through)
MOV EDX,XXXXXXXX
JMP ExecuteHandler RtlpExecutehandlerForUnwind: // Handles unwind (second time through)
MOV EDX,XXXXXXXX
JMP ExecuteHandler
这个是meseh2.exe中的一段代码,似乎就是这个。
7C923753 BA D837927C mov edx, 7C9237D8
7C923758 EB 0D jmp short 7C923767
7C923767 53 push ebx
7C923768 56 push esi
7C923769 57 push edi
7C92376A 33C0 xor eax, eax
7C92376C 33DB xor ebx, ebx
7C92376E 33F6 xor esi, esi
7C923770 33FF xor edi, edi
7C923772 FF7424 20 push dword ptr [esp+20]
7C923776 FF7424 20 push dword ptr [esp+20]
7C92377A FF7424 20 push dword ptr [esp+20]
7C92377E FF7424 20 push dword ptr [esp+20]
7C923782 FF7424 20 push dword ptr [esp+20]
7C923786 E8 0E000000 call 7C923799
7C92378B 5F pop edi
7C92378C 5E pop esi
7C92378D 5B pop ebx
7C92378E C2 1400 retn 14
ExecuteHandler本身安装了一个异常处理函数(完完全全操作系统级别的最小安装),当用户异常处理函数又发生异常的时候,将返回DISPOSITION_NESTED_ EXCEPTION 或者 DISPOSITION_COLLIDED_UNWIND(这就是RtlpExecuteHandlerException返回值的源泉)。
7C923799 55 push ebp
7C92379A 8BEC mov ebp, esp
7C92379C FF75 0C push dword ptr [ebp+C]
7C92379F 52 push edx ;EDX指向ExecuteHandler的异常处理函数
7C9237A0 64:FF35 00000000 push dword ptr fs:[0] ;安装异常处理函数
7C9237A7 64:8925 00000000 mov dword ptr fs:[0], esp
7C9237AE FF75 14 push dword ptr [ebp+14]
7C9237B1 FF75 10 push dword ptr [ebp+10]
7C9237B4 FF75 0C push dword ptr [ebp+C]
7C9237B7 FF75 08 push dword ptr [ebp+8]
7C9237BA 8B4D 18 mov ecx, dword ptr [ebp+18]
7C9237BD FFD1 call ecx ;《加密解密第二版》中362页中的Call ECX?
7C9237BF 64:8B25 00000000 mov esp, dword ptr fs:[0] ;卸载ExecuteHandler的异常处理函数
7C9237C6 64:8F05 00000000 pop dword ptr fs:[0]
7C9237CD 8BE5 mov esp, ebp
7C9237CF 5D pop ebp
7C9237D0 C2 1400 retn 14
ExecuteHandler和他的异常处理函数列于下面:
int ExecuteHandler( PEXCEPTION_RECORD pExcptRec
PEXCEPTION_REGISTRATION pExcptReg
CONTEXT * pContext
PVOID pDispatcherContext,
FARPROC handler ) // Really a ptr to an _except_handler()
// Set up an EXCEPTION_REGISTRATION, where EDX points to the
// appropriate handler code shown below
PUSH EDX
PUSH FS:[0]
MOV FS:[0],ESP
// Invoke the exception callback function
EAX = handler( pExcptRec, pExcptReg, pContext, pDispatcherContext );
// Remove the minimal EXCEPTION_REGISTRATION frame
MOV ESP,DWORD PTR FS:[00000000]
POP DWORD PTR FS:[00000000]
return EAX;
}
Exception handler used for _RtlpExecuteHandlerForException:
{
// If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else
// assign pDispatcher context and return DISPOSITION_NESTED_EXCEPTION
return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT
? DISPOSITION_CONTINUE_SEARCH
: *pDispatcherContext = pRegistrationFrame->scopetable,
DISPOSITION_NESTED_EXCEPTION;
}
Exception handler used for _RtlpExecuteHandlerForUnwind:
{
// If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else
// assign pDispatcher context and return DISPOSITION_COLLIDED_UNWIND
return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT
? DISPOSITION_CONTINUE_SEARCH
: *pDispatcherContext = pRegistrationFrame->scopetable,
DISPOSITION_COLLIDED_UNWIND;
}
能力值:
( LV9,RANK:370 )
24 楼
上面的都是操作系统的机制,实际应用中编译器对SEH进行了封装,VC++对SEH的封装是引入了Exception-Frame,支持_try _except 嵌套结构。
3、在VC++中所有的_try _except都被封装在同一个异常处理回调函数中_exception_handler3函数(2中Call ECX 中ECX所指向的函数),我想这就是为什么
[QUOTE=sessiondiy;459243]
在执行到 [0]<- 0 前, Exception_Handler 是如下的变化:
OEP 时 VC_EH - END
main()的try时 VC_EH - VC_EH - END
HomeGrownFrame()的try时 My_EH - VC_EH - VC_EH - END
所以有二个 VC_EH, 位址是一样的. 不用管他, 不影响分析,
下列我只写一个 VC_EH 来代表
[/QUOTE]
中只有一个地址的原因,这是因为所有的_try _except都指向了 _exception_handler3。
3.1 编译器在操作系统最小的EXCEPTION_REGISTRATION后面有添加了几个变量构成了新的结构VC_EXCEPTION_REGISTRATION这个结构对操作系统是透明的,但是对VC++来说是用来实现Exception_Frame的关键所在。
// The basic, OS defined exception frame
struct EXCEPTION_REGISTRATION
{
EXCEPTION_REGISTRATION* prev;
FARPROC handler;
}; // Data structure(s) pointed to by Visual C++ extended exception frame
struct scopetable_entry
{
DWORD previousTryLevel;
FARPROC lpfnFilter;
FARPROC lpfnHandler;
};
// The extended exception frame used by Visual C++
struct VC_EXCEPTION_REGISTRATION : EXCEPTION_REGISTRATION
{
scopetable_entry * scopetable;
int trylevel;
int _ebp;
};
当发生异常的时候,_exceptio_handler3首先检查内层一场一场处理函数,如果不处理将寻找外层的异常处理函数。这个可以这样理解
_try
_try
Something;
_except
ExceptionHandler1();
end;
_except
ExceptionHandler2();
end;
当Something发生异常后,如果ExceptionHandler1()不处理,那么就该找ExceptionHandler2来处理。这就是_exception_handler3的大部分的工作内容,当前一场处理函数由VC_EXCEPTION_Handler中的tryLevel做索引在scopetable表中指出。如果当前trylevel异常处理函数不处理那么就由scopetable中的previousTryLevel指出下一个异常处理函数,也就是上层的异常处理函数。当前trylevel的数值是由编译器隐性给出的。
int __except_handler3(
struct _EXCEPTION_RECORD * pExceptionRecord,
struct EXCEPTION_REGISTRATION * pRegistrationFrame,
struct _CONTEXT *pContextRecord,
void * pDispatcherContext )
{
LONG filterFuncRet
LONG trylevel
EXCEPTION_POINTERS exceptPtrs
PSCOPETABLE pScopeTable
CLD // Clear the direction flag (make no assumptions!)
// if neither the EXCEPTION_UNWINDING nor EXCEPTION_EXIT_UNWIND bit
// is set... This is true the first time through the handler (the
// non-unwinding case)
if ( ! (pExceptionRecord->ExceptionFlags
& (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) )
{
// Build the EXCEPTION_POINTERS structure on the stack
exceptPtrs.ExceptionRecord = pExceptionRecord;
exceptPtrs.ContextRecord = pContextRecord;
// Put the pointer to the EXCEPTION_POINTERS 4 bytes below the
// establisher frame. See ASM code for GetExceptionInformation
*(PDWORD)((PBYTE)pRegistrationFrame - 4) = &exceptPtrs;
// Get initial "trylevel" value
trylevel = pRegistrationFrame->trylevel
// Get a pointer to the scopetable array
scopeTable = pRegistrationFrame->scopetable;
search_for_handler:
if ( pRegistrationFrame->trylevel != TRYLEVEL_NONE )
{
if ( pRegistrationFrame->scopetable[trylevel].lpfnFilter )
{
PUSH EBP // Save this frame EBP
// !!!Very Important!!! Switch to original EBP. This is
// what allows all locals in the frame to have the same
// value as before the exception occurred.
EBP = &pRegistrationFrame->_ebp
// Call the filter function
filterFuncRet = scopetable[trylevel].lpfnFilter();
POP EBP // Restore handler frame EBP
if ( filterFuncRet != EXCEPTION_CONTINUE_SEARCH )
{
if ( filterFuncRet < 0 ) // EXCEPTION_CONTINUE_EXECUTION
return ExceptionContinueExecution;
// If we get here, EXCEPTION_EXECUTE_HANDLER was specified
scopetable == pRegistrationFrame->scopetable
// Does the actual OS cleanup of registration frames
// Causes this function to recurse
__global_unwind2( pRegistrationFrame );
// Once we get here, everything is all cleaned up, except
// for the last frame, where we'll continue execution
EBP = &pRegistrationFrame->_ebp
__local_unwind2( pRegistrationFrame, trylevel );
// NLG == "non-local-goto" (setjmp/longjmp stuff)
__NLG_Notify( 1 ); // EAX == scopetable->lpfnHandler
// Set the current trylevel to whatever SCOPETABLE entry
// was being used when a handler was found
pRegistrationFrame->trylevel = scopetable->previousTryLevel;
// Call the _except {} block. Never returns.
pRegistrationFrame->scopetable[trylevel].lpfnHandler();
}
}
scopeTable = pRegistrationFrame->scopetable;
trylevel = scopeTable->previousTryLevel
goto search_for_handler;
}
else // trylevel == TRYLEVEL_NONE
{
retvalue == DISPOSITION_CONTINUE_SEARCH;
}
}
else // EXCEPTION_UNWINDING or EXCEPTION_EXIT_UNWIND flags are set
{
PUSH EBP // Save EBP
EBP = pRegistrationFrame->_ebp // Set EBP for __local_unwind2
__local_unwind2( pRegistrationFrame, TRYLEVEL_NONE )
POP EBP // Restore EBP
retvalue == DISPOSITION_CONTINUE_SEARCH;
}
} 3.2 _except_handler3的另外一个重要工作就是Unwind,当_except_handler3找到了处理该异常的异常处理函数,那么它将把该异常处理函数前面所有的异常处理函数都卸载掉,然后再执行该异常处理函数。(这就是我前面没有搞懂的Unwind),这个机制由调用_global_unwind2来实现。_global_unwind2只是对RtlUnwind的简单封装:
__global_unwind2(void * pRegistFrame)
{
_RtlUnwind( pRegistFrame,
&__ret_label,
0, 0 );
__ret_label:
}
RtlUnwind检查堆栈,建立新的EXCEPTION_REGISTRATION,然后再次遍历异常处理链,调用前面提到的RtlpExecuteHandlerForUnwind执行异常处理函数,调用RtlpUnlinkHandler移除不处理该异常的异常处理函数。
void _RtlUnwind( PEXCEPTION_REGISTRATION pRegistrationFrame,
PVOID returnAddr, // Not used! (At least on i386)
PEXCEPTION_RECORD pExcptRec,
DWORD _eax_value )
{
DWORD stackUserBase;
DWORD stackUserTop;
PEXCEPTION_RECORD pExcptRec;
EXCEPTION_RECORD exceptRec;
CONTEXT context;
// Get stack boundaries from FS:[4] and FS:[8]
RtlpGetStackLimits( &stackUserBase, &stackUserTop );
if ( 0 == pExcptRec ) // The normal case
{
pExcptRec = &excptRec;
pExcptRec->ExceptionFlags = 0;
pExcptRec->ExceptionCode = STATUS_UNWIND;
pExcptRec->ExceptionRecord = 0;
// Get return address off the stack
pExcptRec->ExceptionAddress = RtlpGetReturnAddress();
pExcptRec->ExceptionInformation[0] = 0;
}
if ( pRegistrationFrame )
pExcptRec->ExceptionFlags |= EXCEPTION_UNWINDING;
else
pExcptRec->ExceptionFlags|=(EXCEPTION_UNWINDING|EXCEPTION_EXIT_UNWIND);
context.ContextFlags =
(CONTEXT_i486 | CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS);
RtlpCaptureContext( &context );
context.Esp += 0x10;
context.Eax = _eax_value;
PEXCEPTION_REGISTRATION pExcptRegHead;
pExcptRegHead = RtlpGetRegistrationHead(); // Retrieve FS:[0]
// Begin traversing the list of EXCEPTION_REGISTRATION
while ( -1 != pExcptRegHead )
{
EXCEPTION_RECORD excptRec2;
if ( pExcptRegHead == pRegistrationFrame )
{
_NtContinue( &context, 0 );
}
else
{
// If there's an exception frame, but it's lower on the stack
// then the head of the exception list, something's wrong!
if ( pRegistrationFrame && (pRegistrationFrame <= pExcptRegHead) )
{
// Generate an exception to bail out
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
excptRec2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
_RtlRaiseException( &exceptRec2 );
}
}
PVOID pStack = pExcptRegHead + 8; // 8==sizeof(EXCEPTION_REGISTRATION)
if ( (stackUserBase <= pExcptRegHead ) // Make sure that
&& (stackUserTop >= pStack ) // pExcptRegHead is in
&& (0 == (pExcptRegHead & 3)) ) // range, and a multiple
{ // of 4 (i.e., sane)
DWORD pNewRegistHead;
DWORD retValue;
retValue = RtlpExecutehandlerForUnwind(
pExcptRec, pExcptRegHead, &context,
&pNewRegistHead, pExceptRegHead->handler );
if ( retValue != DISPOSITION_CONTINUE_SEARCH )
{
if ( retValue != DISPOSITION_COLLIDED_UNWIND )
{
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
excptRec2.ExceptionCode = STATUS_INVALID_DISPOSITION;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
RtlRaiseException( &excptRec2 );
}
else
pExcptRegHead = pNewRegistHead;
}
PEXCEPTION_REGISTRATION pCurrExcptReg = pExcptRegHead;
pExcptRegHead = pExcptRegHead->prev;
RtlpUnlinkHandler( pCurrExcptReg );
}
else // The stack looks goofy! Raise an exception to bail out
{
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
excptRec2.ExceptionCode = STATUS_BAD_STACK;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
RtlRaiseException( &excptRec2 );
}
}
// If we get here, we reached the end of the EXCEPTION_REGISTRATION list.
// This shouldn't happen normally.
if ( -1 == pRegistrationFrame )
NtContinue( &context, 0 );
else
NtRaiseException( pExcptRec, &context, 0 );
}
PEXCEPTION_REGISTRATION
RtlpGetRegistrationHead( void )
{
return FS:[0];
}
_RtlpUnlinkHandler( PEXCEPTION_REGISTRATION pRegistrationFrame )
{
FS:[0] = pRegistrationFrame->prev;
}
void _RtlpCaptureContext( CONTEXT * pContext )
{
pContext->Eax = 0;
pContext->Ecx = 0;
pContext->Edx = 0;
pContext->Ebx = 0;
pContext->Esi = 0;
pContext->Edi = 0;
pContext->SegCs = CS;
pContext->SegDs = DS;
pContext->SegEs = ES;
pContext->SegFs = FS;
pContext->SegGs = GS;
pContext->SegSs = SS;
pContext->EFlags = flags; // __asm{ PUSHFD / pop [xxxxxxxx] }
pContext->Eip = return address of the caller of the caller of this function
pContext->Ebp = EBP of the caller of the caller of this function
pContext->Esp = Context.Ebp + 8
}
能力值:
( LV9,RANK:180 )
25 楼
各种Compiler的包装我倒是没什么研究
OS的部份用Debugger进去对照一些前辈写的PCode应该更能加深印像.
相信现在你比在1楼时更得心力了.
那你的问题解决了吗?