• 第24章 SEH结构化异常处理_异常处理及软件异常


    24.1  程序的结构

    (1)try/except框架

    __try{
    
       //被保护的代码块
           ……
    }
    
    __except(except fileter/*异常过滤程序*/){
        //异常处理程序
    }

    (2)说明

      ①当__try块中的代码发生异常时,__except()中的过滤程序就被调用。

      ②过滤程序可以是一个简单的表达式或一个函数(返回值应为EXCEPTION_CONTINUE_SEARCH、EXCEPT_CONTINUE_EXECUTE或EXCEPT_EXECUTE_HANDLER之一)

      ③过滤表达式中可以调用GetExceptionCode和GetExceptionInformation函数取得正在处理的异常信息。但这两个函数不能在异常处理程序中使用。

      ④与try/finally不同,try/except中可以使用return、goto、continue和break,它们并不会导致局部展开。

    24.2 异常过滤程序

    (1)返回值

    标识

    说明

    EXCEPTION_EXECUTE_HANDLER

    1

    执行except花括号内代码,同时执行全局展开。最后程序从except花括号后面的第一句代码继续运行。

    EXCEPTION_CONTINUE_SEARCH

    0

    向外层查找带except的try块,并调用对应的异常过滤程序。

    EXCEPTION_CONTINUE_EXECUTE

    -1

    重新执行导致异常的那条CPU指令本身。

    (2)全局展开——异常过滤程序返回EXCEPTION_EXECUTE_HANDLER是会执行全局展开

      ①当某个__try块中的代码触发了异常时(也可能是__try块中调用的函数中引发异常),操作系统会从最靠近引发异常代码的地方开始从下层往上层查找__except块(这里的层是指__try块的嵌套层),对于找到的每一个__except块,会先计算它的异常过滤器,如果过滤器返回EXCEPTION_CONTINUE_SEARCH,则说明此__except块不处理此类异常,需要继续往上层查找,如果某过滤器返回EXCEPTION_EXECUTE_HANDLER则说明此__except块可以处理此类异常,即找到了异常的处理代码,此时停止查找,但是在执行该__except块中的异常处理代码之前,要先进行全局展开。

      ②全局展开的过程与查找__except块的过程类似,只不过这次是查找从底层向上查找__finally块,查找过程中遇到的每一个__finally块中的代码都被执行,直到查找到前面说的处理异常的__except块那一层停止,这时全局展开完成,然后执行__except块中的异常处理代码。

      ③执行完异常处理代码之后,指令流从__except块后的第一条指令开始。从这里也可以看出全局展开也是为了保证__finally语义的正确性,因为指令流从引发异常代码转到到__except异常处理代码时也导致了指令流从__try块嵌套层中所有与__finally对应的__try块中流出,由前面的__finally语义说明可知,此时必须要执行全局展开过程以包成__finally语义的正确性。

    (3)停止全局展开——将return置于finally块中可阻止全局展开。【未定义行为,VC2013直接报错了!】

    (4)慎用EXCEPTION_CONTINUE_EXECUTION

      ①尝试修复错误,出现失败的实例分析

      *pchBuffer = TEXT("J");//C/C++语句
    
      //编译后的产生的机器指令
      MOV EAX,DWORD PTR[pchBuffer]
      MOV WORD PTR[EAX], 'J'  //导致异常的指令。当异常过滤程序捕获该异常后,修正
                              //pchBuffer,让其指向一个正确的地址。并让系统重新
                              //执行第二要CPU指令。问题在于寄存器不可能自动更新
                              //以反映变量pchBuffer的更新,于是该异常又致另一个导
                              //异常,程序陷入了死循环

      ②虚拟内存结合SEH可实现按需调拨存储器,有时能写出运行速度快和高效的应用程序(见第15章的《如何预订大块地址空间和为地址空间稀疏调拨存储器》

      ③系统为线程栈建了一个SEH框。当线程试图访问栈中尚未调拨存储器的区域时,会引发一个异常。系统内部的异常过滤程序会捕获到该异常并在内部调用VirtualAlloc为线程栈调拨更多的存储,并且返回EXCEPTION_CONTINUE_EXECUTION让原先抛出异常的指令重新执行下去。

    【SEHAndMemory】演示虚拟内存的按需调拨

    #include <windows.h>
    #include <tchar.h>
    #include <locale.h>
    
    #define PAGELIMIT 80
    LPBYTE lpNxtPage;
    DWORD dwPages = 0;
    DWORD dwPageSize;//页面大小,一般为4KB
    
    INT PageFualtExceptionFilter(DWORD dwCode){
        LPVOID lpvResult;
    
        //不是非法访问内存
        if (dwCode !=EXCEPTION_ACCESS_VIOLATION){
            return EXCEPTION_EXECUTE_HANDLER;//执行except块的异常处理程序代码
        }
    
        //当超过指定的页面数时
        if (dwPages >=PAGELIMIT){
            return EXCEPTION_EXECUTE_HANDLER;//执行except块的异常处理程序代码
        }
    
        //非法访问内存,则为预订的空间提交下一页物理存储器
        lpvResult = VirtualAlloc((LPVOID)lpNxtPage, dwPageSize, MEM_COMMIT, PAGE_READWRITE);
        if (lpvResult == NULL){
            return EXCEPTION_EXECUTE_HANDLER;//执行except块的异常处理程序代码
        }
    
        //提交成功
        dwPages++;
        lpNxtPage += dwPageSize;
    
        _tprintf(_T("第%d页提交成功!
    "), dwPages);
        return EXCEPTION_CONTINUE_EXECUTION; //重新执行触发异常的那条CPU指令
    }
    
    int main(){
        _tsetlocale(LC_ALL, _T("chs"));
    
        LPVOID lpvBase;LPTSTR lpPtr;BOOL bSuccess;
        SYSTEM_INFO sSysInfo;
        GetSystemInfo(&sSysInfo);
        dwPageSize = sSysInfo.dwPageSize;
    
        _tprintf(_T("CPU页面大小为%dKB.
    "), sSysInfo.dwPageSize / 1024);
    
        //预订存储器
        lpvBase = VirtualAlloc(NULL, PAGELIMIT*dwPageSize, MEM_RESERVE, PAGE_NOACCESS);
    
        lpPtr = (LPTSTR)(lpNxtPage = (LPBYTE)lpvBase);
        for (DWORD i = 0; i < PAGELIMIT*dwPageSize/sizeof(TCHAR);i++){
            __try{
                lpPtr[i] = _T('a');//写入一个字节的数据
            }
            __except (PageFualtExceptionFilter(GetExceptionCode())){
                _tprintf(_T("异常被处理
    "));
                //ExitProcess(GetLastError());
            }
        }
    
        bSuccess = VirtualFree(lpvBase, 0, MEM_RELEASE);
        _tprintf(_T("释放操作%s.
    "), bSuccess ? _T("成功") : _T("失败"));
        _tsystem(_T("PAUSE"));
        return 0;
    }

    24.3 GetExceptionCode

    (1)GetExceptionCode是个内联函数,其代码直接嵌入到被调用的地方(注意与函数调用的区别),它的返回值表明刚刚发生的异常的类型(定义在WinBase.h中,如EXCEPTION_ACCESS_VIOLATION)

    (2)该函数只能在异常过滤程序里(即__except之后的小括号内)或者异常处理程序的代码里调用(__except块后面的花括号内),但不能在异常过滤函数中使用。

    //合法代码

    __try{

         y = 0;

         x = / y;

    }

    __except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?

           EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){ //在__except块的小括内使用,合法

         switch (GetExceptionCode()){ //__except块的花括中使用,合法!

          ......

         }

    }

    //非法代码

    LONG MyFilter(void){}

    {

         //在异常过滤函数中使用GetExceptionCode,不合法!

         return ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?

             EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);

    }

    __try{

         y = 0;

         x = 4 / y;

    }

    __except(MyFilter()){ //可改成将GetExceptionCode作为参数传给MyFilter的形式。

        //处理异常

    }

    (3)异常错误代码的规则

    31-30

    29

    28

    27-16

    15-0

    内容

    严重性

    Microsoft/

    Customer

    保留位

    设备代码

    异常代码

    含义

    0=Success

    1=Informational

    2=Warning

    3=error

    0=Mircosoft所定义的代码

    1=Customer所定义的代码

    一直为0

    前256个值为Micorsoft所保留。(如FACILITY_NULL(0)表示该异常可以在系统任何设备出现,并不只发生在一些特定的设备上)

    由Microsoft/

    Customer所定义的代码

    24.4 GetExceptionInformation

    (1)GetExceptionInformation可获取异常发生时,系统向发生异常的线程栈中压入的EXCEPTION_RECORD、CONTEXT和EXCEPTION_POINTERS结构中的异常信息或CPU有关的信息

    (2)这个函数只能在异常过滤程序中调用(即__except块的小括号),因为EXCEPT_RECORD、CONTEXT和EXCEPTION_POINTER数据结构只有在系统计算异常过滤程序时才有效。一旦控制流被转移到其他地方,这些栈上的数据结构会被销毁。但我们可以自己保存他们,以备后用。

    //保存栈中异常信息的方法
    void FuncSkunk(){
        //声明一些可以保存异常信息的结构体,须在try块外面声明
        EXCEPTION_RECORD SavedExceptRec;
        CONTEXT          SavedContext;
     
        __try{
     
        }
        __except ( //注意逗号表达式,取最后一个表达式为整个表达式的值。
            SavedExceptRec = *(GetExceptionInformation())->ExceptionRecord,
            SavedContext = *(GetExceptionInformation())->ContextRecord,
            EXCEPTION_EXECUTE_HANDLER){
            //异常处理
        }
    }

    (3)EXCEPTION_RECORD结构体——刚发生的异常的详细信息

    字段

    说明

    DWORD ExceptionCode

    异常代码,就是GetExceptionCode函数的返回值

    DWORD ExceptionFlags

    异常标志

    0—表示继续的异常;EXCEPTION_NONCONTINUABLE—不可继续的异常,如果程序试图在一个不可继续的异常之后继续执行,会引发EXCEPTION_NONCONTINUABLE_EXCEPTION异常。

    PEXCEPTION_RECORD pExceptionRecord

    指向另一个未处理异常的EXCEPTION_RECORD结构。(即嵌套异常发生时,异常会形成异常链)

    PVOID ExceptionAddress

    导致异常的CPU指令的地址

    DWORD NumberParameters

    ExceptionInformation数组里元素的个数。对绝大部分的异常来说,这个值为0。

    ULONG_PTR ExceptionInformation

    [EXCEPTION_MAXIMUM_PARAMETERS]

    描述异常的附加参数数组,对绝大部分的异常来说,这个数组元素都未定义。

    24.5 软件异常——RaiseException函数

    参数

    说明

    DWORD dwExceptionCode

    要抛出异常的标识符,可参考《异常错误代码规则》来编写

    DWORD dwExceptionFlags

    必须下列两者之一

    0:

    EXCEPTION_NONCONTINUABLE:异常不可继续,即不能再异常过滤程序中返回EXCEPTION_CONTINUE_EXECUTE,否则重新执行那条导致错误的CPU指令会继续抛出一个新的EXCEPTION_NONCONTINUABLE_EXCEPTION异常。

    DWORD nNumberOfArguments

    用来传递有关抛出异常的附加信息。一般不需要。可将nNumberOfArgument设为0。pArguments设为NULL。

    Const ULONG_PTR* pArguments

    返回值

    void

     【RaiseException程序】——演示自己抛出的软件异常

    #include <tchar.h>
    #include <windows.h>
    
    DWORD FilterFunction(){
        _tprintf(_T("1")); //第1句被输出的语句
        return EXCEPTION_EXECUTE_HANDLER;
    }
    
    int main(){
        __try{
            __try{
                RaiseException(1, 0, 0, NULL);
            }
            __finally{
                _tprintf(_T("2")); //第2句被输出的语句
            }
        }
        __except (FilterFunction()){
            _tprintf(_T("3
    ")); //第3句被输出的语句
        }
        _tsystem(_T("PAUSE"));
    return 0; }
  • 相关阅读:
    Java虚拟机一览表
    Java程序员的10道XML面试题
    bzoj 1644: [Usaco2007 Oct]Obstacle Course 障碍训练课【spfa】
    bzoj 1703: [Usaco2007 Mar]Ranking the Cows 奶牛排名【bitset+Floyd传递闭包】
    bzoj 1664: [Usaco2006 Open]County Fair Events 参加节日庆祝【dp+树状数组】
    bzoj 2100: [Usaco2010 Dec]Apple Delivery【spfa】
    bzoj 2015: [Usaco2010 Feb]Chocolate Giving【spfa】
    bzoj 1741: [Usaco2005 nov]Asteroids 穿越小行星群【最大点覆盖】
    bzoj 1645: [Usaco2007 Open]City Horizon 城市地平线【线段树+hash】
    bzoj 2060: [Usaco2010 Nov]Visiting Cows 拜访奶牛【树形dp】
  • 原文地址:https://www.cnblogs.com/5iedu/p/5245299.html
Copyright © 2020-2023  润新知