首页
社区
课程
招聘
[旧帖] [求助]SEH学习问题 0.00雪花
发表于: 2008-5-26 22:35 9365

[旧帖] [求助]SEH学习问题 0.00雪花

2008-5-26 22:35
9365
在学习http://bbs.pediy.com/showthread.php?threadid=14042的时候遇到一个问题。在这篇文章的Moving In a Little Deeper的时候,文中列举的例子是自己安装的一个Exception Handler和一个_try _exception结构。也同时是在这个部分,文中引出了UnWinding的话题。
    而我自己也做过一个实验,就是用这篇文章的第一部分中的例子,在堆栈上构造了一个ExceptionHandler链,其实现的结构跟文中的图4(http://www.live-share.com/files/323817/exceptionfig04.gif.html)一样。其输出结果:(http://www.live-share.com/files/323818/output.gif.html)。结果显示在没有_try _exception结构的时候,操作系统并没有在找到异常处理程序的时候,重新遍历整个Exception Hanlder链。
    所以我现在的问题是:这个Unwinding到底是系统SEH还是编译器实现的SEH?如果是操作系统的机制,那么为什么上面的例子会给出无法解释的结果(是不是程序中有什么错误的地方?)。如果是编译器实现SEH,那么能不能帮忙解答一下Unwinding在第二次遍历之前做过什么工作(如何决定ExceptionCode ExceptionFlags的值,如何知道当前Exception Handler是处理该异常的回调方程?(知道不是处理该例程的回调方程是因为返回了ExceptionContinueSearch))。

     文章中的例子和我自己测试用的例子源程序列于下面:
文章中引入Unwinding例子:http://www.live-share.com/files/323824/myseh2.rar.html
我自己测试用例子:http://www.live-share.com/files/323826/SEHChain.rar.html

谢谢解答!

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (27)
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
当然是操作系统的……不明白你什么问题,哪里无法解释了
2008-5-26 23:05
0
雪    币: 202
活跃值: (57)
能力值: ( LV9,RANK:370 )
在线值:
发帖
回帖
粉丝
3
首先,谢谢回答。

如果是操作系统的机制,我测试的例子中,EXCEPTION_DISPOSITION链表并没有第二次执行,只执行了一遍(也就是少了Unwinding机制中的第二次遍历过程,这个就是我不明白的地方。详细的你可以看看我测试用的那个例子的代码)。(文中的例子(或者说是Unwinding机制)是,系统找到处理异常后的程序,还要再一次遍历 EXCEPTION_DISPOSITION链表)
2008-5-27 12:27
0
雪    币: 208
活跃值: (10)
能力值: ( 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
2008-5-27 13:43
0
雪    币: 202
活跃值: (57)
能力值: ( 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!

并没有第二次的遍历过程。
2008-5-27 14:06
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
最后一个Handler返回0试试

p.s. 我只有VS8 Express,没有windows.h,又不想下SDK,你知道怎么可以编译么?
2008-5-27 16:24
0
雪    币: 202
活跃值: (57)
能力值: ( LV9,RANK:370 )
在线值:
发帖
回帖
粉丝
7
返回0的结果跟返回ExceptionContinueExecution结果一样。
VS8我不知道能不能编译,我用的是VC6。
2008-5-27 19:53
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
8
你看的文章不是都有讲了

这是Compiler弄的
跟写程式的人无关
2008-5-27 20:10
0
雪    币: 202
活跃值: (57)
能力值: ( 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机制出现了。看来这个机制的确是操作系统的机制?
现在又有问题了,这个机制到底是如何启动的?
2008-5-27 20:11
0
雪    币: 202
活跃值: (57)
能力值: ( LV9,RANK:370 )
在线值:
发帖
回帖
粉丝
10
不对啊,这虽然是Compiler实现的,但是还是操作系统的一种机制。9楼我的测试结果显示操作系统默认的异常处理Handler也是启用了这个机制的——给异常处理程序第二次执行的机会。

这个机制到底是如何实现的?
2008-5-27 20:15
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
恩,看来我错了,大致用od分析了一下myseh2
发现是编译器产生的那个exception hanlder里面调用了RtlUnwind
所以要发生unwind的话还是得使用try{}except{}
2008-5-27 20:23
0
雪    币: 202
活跃值: (57)
能力值: ( LV9,RANK:370 )
在线值:
发帖
回帖
粉丝
12
看来要启用Unwinding的话还得使用一些手段,继续学习中……
2008-5-27 20:28
0
雪    币: 208
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
是的,Unwinding不是操作系统自发触发的,而是通过RtlUnwind函数触发的
只有编译器在异常处理之前知道该handler是不是合适的处理程序(通过类型匹配),然后在调用真正的handler之前进行unwind
2008-5-27 20:31
0
雪    币: 2067
活跃值: (82)
能力值: ( 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 高
//==============================
2008-5-27 20:41
0
雪    币: 202
活跃值: (57)
能力值: ( LV9,RANK:370 )
在线值:
发帖
回帖
粉丝
15
编译器不可能知道一个Handler是不是合适的处理程序,比如我在一个Handler中判断,是不是发生了XXX错误,是的话就处理不是的话就不处理,这样编译器还怎么能预先知道这个Handler是不是合适的处理程序?我想还是有其他的机制在里面起作用……
2008-5-27 20:43
0
雪    币: 202
活跃值: (57)
能力值: ( LV9,RANK:370 )
在线值:
发帖
回帖
粉丝
16
谢谢!我得慢慢消化消化。
2008-5-27 21:03
0
雪    币: 2067
活跃值: (82)
能力值: ( 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++ 收到, 善后. 结束.
2008-5-27 21:35
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
18
你好像比较想知道何时会 Unwind
即然 Unwind 的发生是位于 C++ 的 try except 包装内

只要你不处理例外就好了
由你的例子得知 C0000005 会. 至于其它的例外会不会, 就等你研究了.
2008-5-27 21:59
0
雪    币: 202
活跃值: (57)
能力值: ( LV9,RANK:370 )
在线值:
发帖
回帖
粉丝
19
谢谢解答!其实我想知道的是如何自己产生Unwind,即在ExceptionHandler3中做些处理,使得当找到这个异常处理程序的时候,再次遍历列表。也就是模拟系统默认的那个处理机制。

现在我在看编译器(VC++)的异常处理机制的实现(_exception_handler3和frame-exception),大致有点模糊的概念了,假如我没有理解错误的话(myseh2.cpp中的Unwinding由_exception_handler3产生),而我测试用的那个例子中Unwinding则是由
在BaseProcessStart中安装的系统默认ExceptionHandler产生,不知道是不是这样?

我想我还得多跟几个例子才能一点一点搞明白。
2008-5-27 23:13
0
雪    币: 2067
活跃值: (82)
能力值: ( 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

以上供你参考
2008-5-28 04:44
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
21
你用二支不同年代的 exe 在埋头比对当做研究
可能会陷入泥沼里. 毕竟 C++ 的处理方式可能有所变动.
三思啊...毕竟这东西跟 Compiler 的作法有关.

上面的流程了解之后, 你再去看那篇文章应该有很多地方都明白了.
那篇文章在讲到 第二次调用 的部份...我持保留的态度.
可能是翻译文的关系吧.
2008-5-28 05:04
0
雪    币: 205
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
.............................
2008-5-28 05:09
0
雪    币: 202
活跃值: (57)
能力值: ( 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;
}
2008-5-28 23:02
0
雪    币: 202
活跃值: (57)
能力值: ( 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
}
2008-5-28 23:41
0
雪    币: 2067
活跃值: (82)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
25
各种Compiler的包装我倒是没什么研究
OS的部份用Debugger进去对照一些前辈写的PCode应该更能加深印像.

相信现在你比在1楼时更得心力了.
那你的问题解决了吗?
2008-5-29 02:38
0
游客
登录 | 注册 方可回帖
返回
//