xml地图|网站地图|网站标签 [设为首页] [加入收藏]

智能硬件

当前位置:美高梅游戏网站 > 智能硬件 > 9.结构化异常处理演示

9.结构化异常处理演示

来源:http://www.gd-chuangmei.com 作者:美高梅游戏网站 时间:2019-11-07 06:30

近期一直被一个问题所困扰,就是写出来的程序老是出现无故崩溃,有的地方自己知道可能有问题,但是有的地方又根本没办法知道有什么问题。更苦逼的事情是,我们的程序是需要7x24服务客户,虽然不需要实时精准零差错,但是总不能出现断线丢失数据状态。故刚好通过处理该问题,找到了一些解决方案,怎么捕获访问非法内存地址或者0除以一个数。从而就遇到了这个结构化异常处理,今就简单做个介绍认识下,方便大家遇到相关问题后,首先知道问题原因,再就是如何解决。废话不多说,下面进入正题。

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>

#define  VAR_WATCH()  printf("nDividen=%d,nDivisor=%d, nResult=%d.n",nDividen,nDivisor,nResult)


int main()
{
    int nDividen = 22, nDivisor = 0, nResult = 100;
    __try{
        printf("Before div in __try block:");

        VAR_WATCH();
        nResult = nDividen / nDivisor;
        printf("After div in __try block");
        VAR_WATCH();

    }
    __except (printf("in __except block:"),VAR_WATCH(),
        GetExceptionCode()==EXCEPTION_INT_DIVIDE_BY_ZERO?
        (nDivisor=1,
            printf("Divide Zero exception detected:"),VAR_WATCH(),
            EXCEPTION_CONTINUE_EXECUTION):
        EXCEPTION_CONTINUE_SEARCH)
    {
        printf("In handle block.n");
    }



    return getchar();
}

//except后面的括号里面是一个过滤表达式,这里是一个三目运算符
//如果异常代码是除0的话,就会把除数改为1!然后再返回一个EXCEPTION_CONTINUE_EXECUTION继续执行!

什么是结构化异常处理

结构化异常处理(structured exception handling,下文简称:SEH),是作为一种系统机制引入到操作系统中的,本身与语言无关。在我们自己的程序中使用SEH可以让我们集中精力开发关键功能,而把程序中所可能出现的异常进行统一的处理,使程序显得更加简洁且增加可读性。

美高梅棋牌,使用SHE,并不意味着可以完全忽略代码中可能出现的错误,但是我们可以将软件工作流程和软件异常情况处理进行分开,先集中精力干重要且紧急的活,再来处理这个可能会遇到各种的错误的重要不紧急的问题(不紧急,但绝对重要)

当在程序中使用SEH时,就变成编译器相关的。其所造成的负担主要由编译程序来承担,例如编译程序会产生一些表(table)来支持SEH的数据结构,还会提供回调函数。

注:
不要混淆SHE和C++ 异常处理。C++ 异常处理再形式上表现为使用关键字catch美高梅游戏网站,throw,这个SHE的形式不一样,再windows Visual C++中,是通过编译器和操作系统的SHE进行实现的。

在所有 Win32 操作系统提供的机制中,使用最广泛的未公开的机制恐怕就要数SHE了。一提到SHE,可能就会令人想起 *__try__finally* 和 *__except* 之类的词儿。SHE实际上包含两方面的功能:终止处理(termination handing)异常处理(exception handing)

编译器为了支持try......except......;会产生额外的指令,

终止处理

终止处理程序确保不管一个代码块(被保护代码)是如何退出的,另外一个代码块(终止处理程序)总是能被调用和执行,其语法如下:

__try
{
    //Guarded body
    //...
}
__finally
{
    //Terimnation handler
    //...
}

**__try __finally** 关键字标记了终止处理程序的两个部分。操作系统和编译器的协同工作保障了不管保护代码部分是如何退出的(无论是正常退出、还是异常退出)终止程序都会被调用,即**__美高梅游戏官网娱乐,finally**代码块都能执行。

00C117A3  push        0FFFFFFFEh            //特殊的信息是记录在特殊的板块里面的
00C117A5  push        0C17F20h  
00C117AA  push        offset _except_handler4 (0C11D00h) 

try块的正常退出与非正常退出

try块可能会因为returngoto,异常等非自然退出,也可能会因为成功执行而自然退出。但不论try块是如何退出的,finally块的内容都会被执行。

int Func1()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        //正常执行
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __finally{
        //结束处理
        cout << "finally nTemp = " << nTemp << endl;
    }
    return nTemp;
}

int Func2()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        //非正常执行
        return 0;
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __finally{
        //结束处理
        cout << "finally nTemp = " << nTemp << endl;
    }
    return nTemp;
}

结果如下:

Func1
nTemp = 22  //正常执行赋值
finally nTemp = 22  //结束处理块执行

Func2
finally nTemp = 0   //结束处理块执行

以上实例可以看出,通过使用终止处理程序可以防止过早执行return语句,当return语句视图退出try块的时候,编译器会让finally代码块再它之前执行。对于在多线程编程中通过信号量访问变量时,出现异常情况,能顺利是否信号量,这样线程就不会一直占用一个信号量。当finally代码块执行完后,函数就返回了。

为了让整个机制运行起来,编译器必须生成一些额外代码,而系统也必须执行一些额外工作,所以应该在写代码的时候避免再try代码块中使用return语句,因为对应用程序性能有影响,对于简单demo问题不大,对于要长时间不间断运行的程序还是悠着点好,下文会提到一个关键字**__leave**关键字,它可以帮助我们发现有局部展开开销的代码。

一条好的经验法则:不要再终止处理程序中包含让try块提前退出的语句,这意味着从try块和finally块中移除return,continue,break,goto等语句,把这些语句放在终止处理程序以外。这样做的好处就是不用去捕获哪些try块中的提前退出,从而时编译器生成的代码量最小,提高程序的运行效率和代码可读性。

把特殊的异常处理结构通过压栈的方式在栈上形成一个动态的结构体,然后再赋给FS:[00000000h],就相当于在fs:[00000000h]上注册了一个异常处理结构(2'03"),注册了之后,等会发生异常,就会找到异常处理结构;
一除0,就飞到CPU内核去了,开始跳到除0错误那边,然后分发异常,分发异常的时候给调试器第一轮,visual studio会收到这个通知,但是对于除0的第一轮,visual studio不会处理,它会报告不处理,它不处理之后,内核就会继续分发,把这些异常信息copy到用户栈上,然后到用户态来找,来找这些异常分发函数;

####finally块的清理功能及对程序结构的影响

在编码的过程中需要加入需要检测,检测功能是否成功执行,若成功的话执行这个,不成功的话需要作一些额外的清理工作,例如释放内存,关闭句柄等。如果检测不是很多的话,倒没什么影响;但若又许多检测,且软件中的逻辑关系比较复杂时,往往需要化很大精力来实现繁琐的检测判断。结果就会使程序看起来结构比较复杂,大大降低程序的可读性,而且程序的体积也不断增大。

对应这个问题我是深有体会,过去在写通过COM调用WordVBA的时候,需要层层获取对象、判断对象是否获取成功、执行相关操作、再释放对象,一个流程下来,本来一两行的VBA代码,C++ 写出来就要好几十行(这还得看操作的是几个什么对象)。

下面就来一个方法让大家看看,为什么有些人喜欢脚本语言而不喜欢C++的原因吧。

为了更有逻辑,更有层次地操作 OfficeMicrosoft 把应用(Application)按逻辑功能划分为如下的树形结构

Application(WORD 为例,只列出一部分)
  Documents(所有的文档)
        Document(一个文档)
            ......
  Templates(所有模板)
        Template(一个模板)
            ......
  Windows(所有窗口)
        Window
        Selection
        View
        .....
  Selection(编辑对象)
        Font
        Style
        Range
        ......
  ......

只有了解了逻辑层次,我们才能正确的操纵 Office。举例来讲,如果给出一个VBA语句是:

Application.ActiveDocument.SaveAs "c:abc.doc"

那么,我们就知道了,这个操作的过程是:

  1. 第一步,取得Application
  2. 第二步,从Application中取得ActiveDocument
  3. 第三步,调用 Document 的函数 SaveAs,参数是一个字符串型的文件名。

这只是一个最简单的的VBA代码了。来个稍微复杂点的如下,在选中处,插入一个书签:

 ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="iceman"

此处流程如下:

  1. 获取Application
  2. 获取ActiveDocument
  3. 获取Selection
  4. 获取Range
  5. 获取Bookmarks
  6. 调用方法Add

获取每个对象的时候都需要判断,还需要给出错误处理,对象释放等。在此就给出伪码吧,全写出来篇幅有点长

#define RELEASE_OBJ(obj) if(obj != NULL) 
                        obj->Realse();

BOOL InsertBookmarInWord(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    hr = GetApplcaiton(..., &pDispApplication);
    if (!(SUCCEEDED(hr) || pDispApplication == NULL))
        return FALSE;

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        RELEASE_OBJ(pDispApplication);
        return FALSE;
    }

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        RELEASE_OBJ(pDispApplication);
        return FALSE;
    }

    hr = GetSelection(..., &pDispSelection);
    if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        return FALSE;
    }

    hr = GetRange(..., &pDispRange);
    if (!(SUCCEEDED(hr) || pDispRange == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        return FALSE;
    }

    hr = GetBookmarks(..., &pDispBookmarks);
    if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        return FALSE;
    }

    hr = AddBookmark(...., bookname);
    if (!SUCCEEDED(hr)){
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
        return FALSE;
    }
    ret = TRUE;
    return ret;

这只是伪码,虽然也可以通过goto减少代码行,但是goto用得不好就出错了,下面程序中稍不留神就goto到不该取得地方了。

BOOL InsertBookmarInWord2(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    hr = GetApplcaiton(..., &pDispApplication);
    if (!(SUCCEEDED(hr) || pDispApplication == NULL))
        goto exit6;

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        goto exit5;
    }

    hr = GetActiveDocument(..., &pDispDocument);
    if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
        goto exit4;
    }

    hr = GetSelection(..., &pDispSelection);
    if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
        goto exit4;
    }

    hr = GetRange(..., &pDispRange);
    if (!(SUCCEEDED(hr) || pDispRange == NULL)){
        goto exit3;
    }

    hr = GetBookmarks(..., &pDispBookmarks);
    if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
        got exit2;
    }

    hr = AddBookmark(...., bookname);
    if (!SUCCEEDED(hr)){
        goto exit1;
    }

    ret = TRUE;
exit1:
    RELEASE_OBJ(pDispApplication);
exit2:
    RELEASE_OBJ(pDispDocument);
exit3:
    RELEASE_OBJ(pDispSelection);
exit4:
    RELEASE_OBJ(pDispRange);
exit5:
    RELEASE_OBJ(pDispBookmarks);
exit6:
    return ret;

此处还是通过SEH的终止处理程序来重新该方法,这样是不是更清晰明了。

BOOL InsertBookmarInWord3(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    __try{
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            return FALSE;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            return FALSE;
        }

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL)){
            return FALSE;
        }

        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL)){
            return FALSE;
        }

        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL)){
            return FALSE;
        }

        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL)){
            return FALSE;
        }

        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr)){
            return FALSE;
        }

        ret = TRUE;
    }
    __finally{
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
    }
    return ret;

这几个函数的功能是一样的。可以看到在InsertBookmarInWord中的清理函数(RELEASE_OBJ)到处都是,而InsertBookmarInWord3中的清理函数则全部集中在finally块,如果在阅读代码时只需看try块的内容即可了解程序流程。这两个函数本身都很小,可以细细体会下这两个函数的区别。

int main()
{
00C117A0  push        ebp  
00C117A1  mov         ebp,esp  
00C117A3  push        0FFFFFFFEh            //特殊的信息是记录在特殊的板块里面的!
00C117A5  push        0C17F20h  
00C117AA  push        offset _except_handler4 (0C11D00h)  //这里是vc运行时默认处理的函数
00C117AF  mov         eax,dword ptr fs:[00000000h]  
00C117B5  push        eax  
00C117B6  add         esp,0FFFFFF04h  
00C117BC  push        ebx  
00C117BD  push        esi  
00C117BE  push        edi  
00C117BF  lea         edi,[ebp-10Ch]  
00C117C5  mov         ecx,3Dh  
00C117CA  mov         eax,0CCCCCCCCh  
00C117CF  rep stos    dword ptr es:[edi]  
00C117D1  mov         eax,dword ptr [__security_cookie (0C19004h)]  
00C117D6  xor         dword ptr [ebp-8],eax  
00C117D9  xor         eax,ebp  
00C117DB  push        eax  
00C117DC  lea         eax,[ebp-10h]  
00C117DF  mov         dword ptr fs:[00000000h],eax  
00C117E5  mov         dword ptr [ebp-18h],esp  
    int nDividen = 22, nDivisor = 0, nResult = 100;
00C117E8  mov         dword ptr [nDividen],16h  
00C117EF  mov         dword ptr [nDivisor],0  
00C117F6  mov         dword ptr [nResult],64h  
    __try{
00C117FD  mov         dword ptr [ebp-4],0  
        printf("Before div in __try block:");
00C11804  push        offset string "Before div in __try block:" (0C16B30h)  
00C11809  call        _printf (0C1131Bh)  
00C1180E  add         esp,4  

        VAR_WATCH();
00C11811  mov         eax,dword ptr [nResult]  
00C11814  push        eax  
00C11815  mov         ecx,dword ptr [nDivisor]  
00C11818  push        ecx  
00C11819  mov         edx,dword ptr [nDividen]  
00C1181C  push        edx  
00C1181D  push        offset string "nDividen=%d,nDivisor=%d, nResult"... (0C16B50h)  
00C11822  call        _printf (0C1131Bh)  
00C11827  add         esp,10h  
00C1182A  mov         ecx,dword ptr [nDivisor]  
        nResult = nDividen / nDivisor;
00C1182D  mov         eax,dword ptr [nDividen]  
00C11830  cdq  
00C11831  idiv        eax,ecx  
00C11833  mov         dword ptr [nResult],eax  
        printf("After div in __try block");
00C11836  push        offset string "After div in __try block" (0C16B80h)  
00C1183B  call        _printf (0C1131Bh)  
00C11840  add         esp,4  
        VAR_WATCH();
00C11843  mov         eax,dword ptr [nResult]  
00C11846  push        eax  
00C11847  mov         ecx,dword ptr [nDivisor]  
00C1184A  push        ecx  
00C1184B  mov         edx,dword ptr [nDividen]  
00C1184E  push        edx  
00C1184F  push        offset string "nDividen=%d,nDivisor=%d, nResult"... (0C16B50h)  
00C11854  call        _printf (0C1131Bh)  
00C11859  add         esp,10h  

    }
00C1185C  mov         dword ptr [ebp-4],0FFFFFFFEh  
00C11863  jmp         $LN8+17h (0C11908h)  
    __except (printf("in __except block:"),VAR_WATCH(),
00C11868  mov         eax,dword ptr [ebp-14h]  
00C1186B  mov         ecx,dword ptr [eax]  
00C1186D  mov         edx,dword ptr [ecx]  
00C1186F  mov         dword ptr [ebp-104h],edx  
00C11875  push        offset string "in __except block:" (0C16BA0h)  
00C1187A  call        _printf (0C1131Bh)  
00C1187F  add         esp,4  
00C11882  mov         eax,dword ptr [nResult]  
00C11885  push        eax  
00C11886  mov         ecx,dword ptr [nDivisor]  
00C11889  push        ecx  
00C1188A  mov         edx,dword ptr [nDividen]  
00C1188D  push        edx  
00C1188E  push        offset string "nDividen=%d,nDivisor=%d, nResult"... (0C16B50h)  
00C11893  call        _printf (0C1131Bh)  
00C11898  add         esp,10h  
00C1189B  cmp         dword ptr [ebp-104h],0C0000094h  
00C118A5  jne         main+140h (0C118E0h)  
00C118A7  mov         dword ptr [nDivisor],1  
00C118AE  push        offset string "Divide Zero exception detected:" (0C16BB8h)  
00C118B3  call        _printf (0C1131Bh)  
    __except (printf("in __except block:"),VAR_WATCH(),
00C118B8  add         esp,4  
00C118BB  mov         eax,dword ptr [nResult]  
00C118BE  push        eax  
00C118BF  mov         ecx,dword ptr [nDivisor]  
00C118C2  push        ecx  
00C118C3  mov         edx,dword ptr [nDividen]  
00C118C6  push        edx  
00C118C7  push        offset string "nDividen=%d,nDivisor=%d, nResult"... (0C16B50h)  
00C118CC  call        _printf (0C1131Bh)  
00C118D1  add         esp,10h  
00C118D4  mov         dword ptr [ebp-10Ch],0FFFFFFFFh  
00C118DE  jmp         main+14Ah (0C118EAh)  
00C118E0  mov         dword ptr [ebp-10Ch],0  
00C118EA  mov         eax,dword ptr [ebp-10Ch]  
$LN15:
00C118F0  ret  
$LN8:
00C118F1  mov         esp,dword ptr [ebp-18h]  
        GetExceptionCode()==EXCEPTION_INT_DIVIDE_BY_ZERO?
        (nDivisor=1,
            printf("Divide Zero exception detected:"),VAR_WATCH(),
            EXCEPTION_CONTINUE_EXECUTION):
        EXCEPTION_CONTINUE_SEARCH)                                          //except后面的括号里面是一个过滤表达式,这里是一个三目运算符
    {                                                                       //如果异常代码是除0的话,就会把除数改为1!然后再返回一个EXCEPTION_CONTINUE_EXECUTION继续执行!
        printf("In handle block.n");
00C118F4  push        offset string "In handle block.n" (0C16BE0h)  
00C118F9  call        _printf (0C1131Bh)  
00C118FE  add         esp,4  

    }
00C11901  mov         dword ptr [ebp-4],0FFFFFFFEh  
    }



    return getchar();
00C11908  mov         esi,esp  
00C1190A  call        dword ptr [__imp__getchar (0C1A164h)]  
00C11910  cmp         esi,esp  
00C11912  call        __RTC_CheckEsp (0C11113h)  

关键字 __leave

try块中使用**__leave关键字会使程序跳转到try块的结尾,从而自然的进入finally块。
对于上例中的InsertBookmarInWord3try块中的return完全可以用
__leave** 来替换。两者的区别是用return会引起try过早退出系统会进行局部展开而增加系统开销,若使用**__leave**就会自然退出try块,开销就小的多。

BOOL InsertBookmarInWord4(const string& bookname)
{
    BOOL ret = FALSE;
    IDispatch* pDispApplication = NULL;
    IDispatch* pDispDocument = NULL;
    IDispatch* pDispSelection = NULL;
    IDispatch* pDispRange = NULL;
    IDispatch* pDispBookmarks = NULL;
    HRESULT hr = S_FALSE;

    __try{
        hr = GetApplcaiton(..., &pDispApplication);
        if (!(SUCCEEDED(hr) || pDispApplication == NULL))
            __leave;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL))
            __leave;

        hr = GetActiveDocument(..., &pDispDocument);
        if (!(SUCCEEDED(hr) || pDispDocument == NULL))
            __leave;

        hr = GetSelection(..., &pDispSelection);
        if (!(SUCCEEDED(hr) || pDispSelection == NULL))
            __leave;

        hr = GetRange(..., &pDispRange);
        if (!(SUCCEEDED(hr) || pDispRange == NULL))
            __leave;

        hr = GetBookmarks(..., &pDispBookmarks);
        if (!(SUCCEEDED(hr) || pDispBookmarks == NULL))
            __leave;

        hr = AddBookmark(...., bookname);
        if (!SUCCEEDED(hr))
            __leave;

        ret = TRUE;
    }
    __finally{
        RELEASE_OBJ(pDispApplication);
        RELEASE_OBJ(pDispDocument);
        RELEASE_OBJ(pDispSelection);
        RELEASE_OBJ(pDispRange);
        RELEASE_OBJ(pDispBookmarks);
    }
    return ret;
}

下面用WinDbg来看一看从内核态到用户态:

异常处理程序

软件异常是我们都不愿意看到的,但是错误还是时常有,比如CPU捕获类似非法内存访问和除0这样的问题,一旦侦查到这种错误,就抛出相关异常,操作系统会给我们应用程序一个查看异常类型的机会,并且运行程序自己处理这个异常。异常处理程序结构代码如下

  __try {
      // Guarded body
    }
    __except ( exception filter ) {
      // exception handler
    }

注意关键字**__except**,任何try块,后面必须更一个finally代码块或者except代码块,但是try后又不能同时有finallyexcept块,也不能同时有多个finnalyexcept块,但是可以相互嵌套使用

美高梅游戏网站 1

异常处理基本流程

int Func3()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        nTemp = 22;
        cout << "nTemp = " << nTemp << endl;
    }
    __except (EXCEPTION_EXECUTE_HANDLER){
        cout << "except nTemp = " << nTemp << endl;
    }
    return nTemp;
}

int Func4()
{
    cout << __FUNCTION__ << endl;
    int nTemp = 0;
    __try{
        nTemp = 22/nTemp;
        cout << "nTemp = " << nTemp << endl;
    }
    __except (EXCEPTION_EXECUTE_HANDLER){
        cout << "except nTemp = " << nTemp << endl;
    }
    return nTemp;
}

结果如下:

Func3
nTemp = 22  //正常执行

Func4
except nTemp = 0 //捕获异常,

Func3try块只是一个简单操作,故不会导致异常,所以except块中代码不会被执行,Func4try块视图用22除0,导致CPU捕获这个事件,并抛出,系统定位到except块,对该异常进行处理,该处有个异常过滤表达式,系统中有三该定义(定义在Windows的Excpt.h中):

1. EXCEPTION_EXECUTE_HANDLER:
    我知道这个异常了,我已经写了代码来处理它,让这些代码执行吧,程序跳转到except块中执行并退出
2. EXCEPTION_CONTINUE_SERCH
    继续上层搜索处理except代码块,并调用对应的异常过滤程序
3. EXCEPTION_CONTINUE_EXECUTION
    返回到出现异常的地方重新执行那条CPU指令本身

面是两种基本的使用方法:

  • 方式一:直接使用过滤器的三个返回值之一
__try {
   ……
}
__except ( EXCEPTION_EXECUTE_HANDLER ) {
   ……
}
  • 方式二:自定义过滤器
__try {
   ……
}
__except ( MyFilter( GetExceptionCode() ) )
{
   ……
}

LONG MyFilter ( DWORD dwExceptionCode )
{
  if ( dwExceptionCode == EXCEPTION_ACCESS_VIOLATION )
    return EXCEPTION_EXECUTE_HANDLER ;
  else
    return EXCEPTION_CONTINUE_SEARCH ;
}

image.png

.NET4.0中捕获SEH异常

在.NET 4.0之后,CLR将会区别出一些异常(都是SEH异常),将这些异常标识为破坏性异常(Corrupted State Exception)。针对这些异常,CLR的catch块不会捕捉这些异常,一下代码也没有办法捕捉到这些异常。

try{
    //....
}
catch(Exception ex)
{
    Console.WriteLine(ex.ToString());
}

因为并不是所有人都需要捕获这个异常,如果你的程序是在4.0下面编译并运行,而你又想在.NET程序里捕捉到SEH异常的话,有两个方案可以尝试:

  • 在托管程序的.config文件里,启用legacyCorruptedStateExceptionsPolicy这个属性,即简化的.config文件类似下面的文件:
App.Config

<?xml version="1.0"?>
<configuration>
 <startup>
   <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
 </startup>
    <runtime>
      <legacyCorruptedStateExceptionsPolicy enabled="true" />
    </runtime>
</configuration>

这个设置告诉CLR 4.0,整个.NET程序都要使用老的异常捕捉机制。

  • 在需要捕捉破坏性异常的函数外面加一个HandleProcessCorruptedStateExceptions属性,这个属性只控制一个函数,对托管程序的其他函数没有影响,例如:
[HandleProcessCorruptedStateExceptions]
try{
    //....
}
catch(Exception ex)
{
    Console.WriteLine(ex.ToString());
}

美高梅游戏网站 2

image.png

美高梅游戏网站 3

image.png

现在呢,cpu已经跳到内核的除0函数,内核进行分发,分发之后发现是用户态导致的异常,然后把异常信息复制到用户态栈,复制到用户态栈之后来找当前线程的异常处理链条,也就是fs:[0]链条,在找fs:[0]链条的时候找到了我们的异常处理器,即seh handler;(4'44'');在seh handler里面再执行我们的过滤表达式,过滤表达式呢,可以认为是一个特殊的函数,编译器会把他编译成一个特殊的函数,
在WinDbg中:

0:000> k
ChildEBP RetAddr  
00cff80c 00db215e seh__!main+0x8d [e:总复习总复习软件调试软件调试seh演示.cpp @ 18]
00cff820 00db1fc0 seh__!invoke_main+0x1e [f:ddvctoolscrtvcstartupsrcstartupexe_common.inl @ 64]
00cff878 00db1e5d seh__!__scrt_common_main_seh+0x150 [f:ddvctoolscrtvcstartupsrcstartupexe_common.inl @ 253]
00cff880 00db2178 seh__!__scrt_common_main+0xd [f:ddvctoolscrtvcstartupsrcstartupexe_common.inl @ 296]
00cff888 74978654 seh__!mainCRTStartup+0x8 [f:ddvctoolscrtvcstartupsrcstartupexe_main.cpp @ 17]
00cff89c 779e4a77 KERNEL32!BaseThreadInitThunk+0x24
00cff8e4 779e4a47 ntdll!__RtlUserThreadStart+0x2f
00cff8f4 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> r
eax=00000025 ebx=00b25000 ecx=00000000 edx=1013281c esi=00db104b edi=00cff7f4
eip=00db182d esp=00cff6f0 ebp=00cff80c iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
seh__!main+0x8d:
00db182d cc              int     3

自己做实验的:(先是ctrl+E,再是ctrl+O打开反汇编界面以及.cpp界面,在.cpp下断点(F9),再按F10单步走)

美高梅游戏网站 4

image.png

0:000> k                                //栈回溯!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ChildEBP RetAddr  
0057f7cc 008a215e seh___!main+0x91 [e:总复习总复习软件调试软件调试seh演示.cpp @ 18]
0057f7e0 008a1fc0 seh___!invoke_main+0x1e [f:ddvctoolscrtvcstartupsrcstartupexe_common.inl @ 64]
0057f838 008a1e5d seh___!__scrt_common_main_seh+0x150 [f:ddvctoolscrtvcstartupsrcstartupexe_common.inl @ 253]
0057f840 008a2178 seh___!__scrt_common_main+0xd [f:ddvctoolscrtvcstartupsrcstartupexe_common.inl @ 296]
0057f848 759a8654 seh___!mainCRTStartup+0x8 [f:ddvctoolscrtvcstartupsrcstartupexe_main.cpp @ 17]
WARNING: Stack unwind information not available. Following frames may be wrong.
0057f85c 77ea4a77 KERNEL32!BaseThreadInitThunk+0x24
0057f8a4 77ea4a47 ntdll!RtlGetAppContainerNamedObjectPath+0x137
0057f8b4 00000000 ntdll!RtlGetAppContainerNamedObjectPath+0x107
0:000> r                                        //查看寄存器!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
eax=00000016 ebx=002c2000 ecx=00000000 edx=00000000 esi=008a104b edi=0057f7b4
eip=008a1831 esp=0057f6b0 ebp=0057f7cc iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
seh___!main+0x91:
008a1831 f7f9            idiv    eax,ecx
0:000> dd 0057f6b0                                           //0057f6b0是上面中的esp的信息,可以查看内存以及栈中的信息!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
0057f6b0  1c208cec 008a104b 008a104b 002c2000
0057f6c0  ffffffff cccccccc c0000094 cccccccc
0057f6d0  cccccccc cccccccc cccccccc cccccccc
0057f6e0  cccccccc cccccccc cccccccc cccccccc
0057f6f0  cccccccc cccccccc cccccccc cccccccc
0057f700  cccccccc cccccccc cccccccc cccccccc
0057f710  cccccccc cccccccc cccccccc cccccccc
0057f720  cccccccc cccccccc cccccccc cccccccc

dd 0057f6b0这句话的意思是说从0057f6b0这个地址开始,以4个字节为一个单位开始查看内存,比如下面的1c208cec等等都是四个字节,

cccccccc是局部变量区域,由于栈是向低地址空间生长,
context结构有一个著名的0001007f标志,

这是著名的结构体Exception-record;

美高梅游戏网站 5

本文由美高梅游戏网站发布于智能硬件,转载请注明出处:9.结构化异常处理演示

关键词:

上一篇:Win10五月更新18362

下一篇:没有了