《Visual C++异常处理机制原理与应用(二)—— C/C++结构化异常处理之try-finally终止处理的使用与原理(下)》

在上一篇文章中,我们其实只分析了终止型异常处理程序中正常的执行流程,这种情况的出现其实需要作如下假设:

  • __try块中的代码执行过程中不会引发异常
  • 这部分代码不会试图提前离开__try块的作用范围(如包含goto、break、continue、return等会导致执行流越出__try块部分的指令)

然而俗话说,“理想很丰满,现实太骨感”,在编程中,不得不考虑到各种各样的情况。当被保护的代码块中出现异常或尝试提前跳出时,按照终止型异常处理程序设立的初衷,都应该让__finally块中的代码得以执行。仔细分析,二者虽然都需要__finally块中的代码有机会执行,但实现原理却不尽相同:

  • 当被保护代码块中引发异常时,OS捕获到该异常并传递到用户层,系统的SEH机制发挥作用,开始遍历FS:[0]指向的EXCEPTION_REGISTRATION结构,找到其中的异常处理函数入口并逐一执行,直到某个异常处理函数报告已处理该异常。

    • 在此情况下,VC++的终止型异常处理程序为了保证即便在__try保护的代码块中出现异常也依然能执行__finally块中的代码,就必须利用系统的SEH机制,在进入__try块前在SEH上注册一个新节点。(这个步骤在上一篇中已经分析过了)

    • 既然在进入__try块时向SEH链上加入了新的节点,那么在终止型异常处理程序结束后,必然也需要将之前向SEH链加入的节点摘掉。否则如果后续代码执行出现异常,系统SEH机制调用到的仍然是之前的处理函数。

    在上一篇文章中,我们其实故意忽略掉了该异常处理段结束后,对SEH链相应节点的摘链操作。这部分对应的反汇编代码如下:

       26:  return 0;
    00E52527 33 C0                xor         eax,eax  
        27: }
    00E52529 52                   push        edx  
    00E5252A 8B CD                mov         ecx,ebp  
    00E5252C 50                   push        eax  
    00E5252D 8D 15 5C 25 E5 00    lea         edx,ds:[0E5255Ch]  
    00E52533 E8 A2 ED FF FF       call        @_RTC_CheckStackVars@8 (0E512DAh)  
    00E52538 58                   pop         eax  
    00E52539 5A                   pop         edx  
    00E5253A 8B 4D F0             mov         ecx,dword ptr [ebp-10h]  
    00E5253D 64 89 0D 00 00 00 00 mov         dword ptr fs:[0],ecx  
    00E52544 59                   pop         ecx  
    00E52545 5F                   pop         edi  
    00E52546 5E                   pop         esi  
    00E52547 5B                   pop         ebx  
    00E52548 81 C4 F0 00 00 00    add         esp,0F0h  
    00E5254E 3B EC                cmp         ebp,esp  
    00E52550 E8 18 EC FF FF       call        __RTC_CheckEsp (0E5116Dh)  
    00E52555 8B E5                mov         esp,ebp  
    00E52557 5D                   pop         ebp  
    00E52558 C3                   ret  

    这段代码主要进行了以下4项工作:

    1. 清空eax寄存器,返回0
    2. 根据函数入口处在栈上设置的Cookie探针值,检查该值是否被修改(防止栈溢出攻击覆盖掉其下方的返回地址)
    3. 对之前加入SEH链的新节点执行摘链操作:从[ebp-0x10]处取得后继SEH节点的地址并赋给FS:[0]
    4. 校验堆栈平衡,确保esp的值在执行函数执行过程中保持了平衡(利用ebp作为参照)
  • 当被保护代码块包含有试图提前跳出__try块的指令时,由于并没有异常发生,此时只能依靠编译器自己检测到这些指令,并在执行它们前让__finally块中的代码有机会被执行。

下面就来分析一下__try块中含有试图提早退出的代码。

DWORD funcTest02()
{
    DWORD dwTemp = 3;
    __try 
    {
        dwTemp = 5;
        cout << "before return:" << dwTemp << endl;
        return dwTemp;
        cout << "after return:" << dwTemp << endl;
    }
    __finally
    {
        if (AbnormalTermination())
        {
            cout << "__try块中执行时提前退出了" << endl;
        }
        else
        {
            cout << "执行流程自然转到了__finally块中" << endl;
        }
        cout << "enter finally:" << dwTemp << endl;
        dwTemp = 10;
        cout << "before exit finally:" << dwTemp << endl;
    }
    cout << "after finally:" << dwTemp << endl;
}

该函数的执行结果如下:

这里写图片描述

其反汇编代码如下:

   28: 
    29: DWORD funcTest02()
    30: {
00E525D0 55                   push        ebp  
00E525D1 8B EC                mov         ebp,esp  
00E525D3 6A FE                push        0FFFFFFFEh  
00E525D5 68 E8 AF E5 00       push        0E5AFE8h  
00E525DA 68 D0 2C E5 00       push        offset _except_handler4 (0E52CD0h)  
00E525DF 64 A1 00 00 00 00    mov         eax,dword ptr fs:[00000000h]  
00E525E5 50                   push        eax  
00E525E6 81 C4 14 FF FF FF    add         esp,0FFFFFF14h  
00E525EC 53                   push        ebx  
00E525ED 56                   push        esi  
00E525EE 57                   push        edi  
00E525EF 8D BD 04 FF FF FF    lea         edi,[ebp-0FCh]  
00E525F5 B9 39 00 00 00       mov         ecx,39h  
00E525FA B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00E525FF F3 AB                rep stos    dword ptr es:[edi]  
00E52601 A1 00 C0 E5 00       mov         eax,dword ptr [__security_cookie (0E5C000h)]  
00E52606 31 45 F8             xor         dword ptr [ebp-8],eax  
00E52609 33 C5                xor         eax,ebp  
00E5260B 50                   push        eax  
00E5260C 8D 45 F0             lea         eax,[ebp-10h]  
00E5260F 64 A3 00 00 00 00    mov         dword ptr fs:[00000000h],eax  //在SEH链头注册新节点
    31:     DWORD dwTemp = 3;
00E52615 C7 45 E0 03 00 00 00 mov         dword ptr [dwTemp],3  
    32:     __try 
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<下面将状态标志置为0了!<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
00E5261C C7 45 FC 00 00 00 00 mov         dword ptr [ebp-4],0
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<下面将提前退出标记置为true!<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
00E52623 C7 85 08 FF FF FF 01 00 00 00 mov         dword ptr [ebp-0F8h],1
    33:     {
    34:         dwTemp = 5;
00E5262D C7 45 E0 05 00 00 00 mov         dword ptr [dwTemp],5  
    35:         cout << "before return:" << dwTemp << endl;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值