9.结构化异常处理演示

近期一直被一个问题所困扰,就是写出来的程序老是出现无故崩溃,有的地方自己知道可能有问题,但是有的地方又根本没办法知道有什么问题。更苦逼的事情是,我们的程序是需要7×24服务客户,虽然不需要实时精准零差错,但是总不能出现断线丢失数据状态。故刚好通过处理该问题,找到了一些解决方案,怎么捕获访问非法内存地址或者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继续执行!

           异常处理第三讲,SEH(结构化异常处理),异常展开问题

作者:IBinary
出处:
版权所有,欢迎保留原文链接进行转载:)

不知道昨天有木有小伙伴尝试写一下SEH异常处理的代码.如果没写过,请回去写(
🙂 不写也没关系 ( ̄┰ ̄*))

那么说下昨天的异常处理的问题

 

            异常处理第二讲,结构化异常(微软未公开)

 转载请注明出处

讲解之前,请熟悉WinDbg的使用,工具使用的博客链接: 

 

一丶认识段寄存器FS的内容,以及作用

首先我们要先认识一下段寄存器FS的作用,和内容,

我们打开OD,随便附加一个32位程序,看下段寄存器内容是什么

 图片 1

现在先介绍一下段寄存器吧

段寄存器,保存的是系统信息的一个表.而FS则是存的下标,在OD中,这个都是固定的

32位系统中,没有分段的概念了. 在16位系统中,我们定位物理地址的时候

是段 * 16 + 偏移 = 20位地址 ,而32位,其实也是这样做的,也是段+偏移的形式

只不过32系统扩展了,直接就可以寻址了,所以 CS DS ES SS
等段寄存器的值都是0了

0 + 偏移,那么现在就可以把0省略了.

FS中下标中存的什么,我们可以看下

使用WinDbg看,因为OD是不能看的,你跳转过去是没有显示任何信息的

关于FS下标存储的是什么,我们可以去看雪的论坛去看下具体存放的什么.

看下帖子内容,请点击:

 

 

 

 

什么是结构化异常处理

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

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

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

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

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

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

一丶昨天代码问题所在

请看下昨天的代码

// SEHecpt.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <WINDOWS.H>
#include <STDLIB.H>
#include <WINNT.H>

void fun2();

EXCEPTION_DISPOSITION __cdecl HANDLER1(
                                       struct _EXCEPTION_RECORD *ExceptionRecord,
                                       void * EstablisherFrame,
                                       struct _CONTEXT *ContextRecord,
                                       void * DispatcherContext)
{

    MessageBox(NULL,"我处理了异常\r\n",NULL,NULL);
    return ExceptionContinueSearch;
}                   
void fun1()
{
    __asm
    {

        push offset HANDLER1
        push fs:[0]
        mov fs:[0],esp
    }

    fun2();
     char *p = NULL;
     *p = 1;
    __asm
    {
        pop fs:[0]
        add esp,4
        ret
    }
}
void fun2()
{
    char *p =NULL;
    *p = 1;
}

int main(int argc, char* argv[])
{

    fun1();    
    system("pause");

}

上图代码则是我们昨天的代码,我们编译,链接,并且运行一下.

第一次:

图片 2

当我们点击异常确定

图片 3

程序会显示退出,因为我们的返回这设置的是继续搜索,也就是我不处理了,交给上一层处理,而上一层是操作系统

我们点击关闭程序

图片 4

这个时候,我们的回调又被操作系统掉了一次,第二次来的时候的标志是2,具体的可以通过输出参数查看.

图片 5最后点击确定我们的程序才退出了.

那么我们不觉着奇怪吗,为什么操作系统会第二次调用了一次我们的回调函数?

原因是操作系统正在进行异常展开,调用我们的回调是告诉我们,该处理的处理.

style=”font-size: 14pt; background-color: #ff0000″>参考大牛总结自己: style=”background-color: #ff0000″>

  • style=”font-size: 14pt”>当一个线程出现错误时,操作系统给你一个机会被告知这个错误。说得更明白一些就是,当一个线程出现错误时,操作系统调用用户定义的一个回调函数。这个回调函数可以做它想做的一切。例如它可以修复错误,或者它也可以播放一段音乐。无论回调函数做什么,它最后都要返回一个值来告诉系统下一步做什么。(这不是十分准确,但就此刻来说非常接近。)

  • style=”font-size: 14pt”>当你的某一部分代码出错时,系统再回调你的其它代码,那么这个回调函数看起来是什么样子呢?换句话说,你想知道关于异常什么类型的信息呢?实际上这并不重要,因为Win32已经替你做了决定。异常的回调函数的样子如下:

    //TIB的 第一个DWORD是一个指向线程的EXCEPTION_REGISTARTION结构的指针。
    //在基于Intel处理器的Win32平台上,FS寄存器总是 指向当前的TIB。
    //因此在FS:[0]处你可以找到一个指向EXCEPTION_REGISTARTION结构的指针

    typedef struct _EXCEPTION_REGISTRATION_RECORD
    {
    PEXCEPTION_REGISTRATION_RECORD Next;

    PEXCEPTION_DISPOSITION Handler;

    } EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

enum EXCEPTION_DISPOSITION 
typedef enum _EXCEPTION_DISPOSITION
{
         ExceptionContinueExecution       = 0,
         ExceptionContinueSearch          = 1,
         ExceptionNestedException         = 2,
         ExceptionCollidedUnwind          = 3

} EXCEPTION_DISPOSITION;


//异常的回调函数的样子如下:
EXCEPTION_DISPOSITION __cdecl _except_handler( 
                                                struct _EXCEPTION_RECORD *ExceptionRecord,

                                                void * EstablisherFrame,

                                                struct _CONTEXT *ContextRecord,

                                                void * DispatcherContext
                                              );
  • style=”font-size: 14pt”>这个原型来自标准的Win32头文件EXCPT.H,乍看起来有些费解。但如果你仔细看,它并不是很难理解。首先,忽略掉返回值的类型(EXCEPTION_DISPOSITION)。你得到的基本信息就是它是一个叫作_except_handler并且带有四个参数的函数。

  • style=”font-size: 14pt”>这个函数的第一个参数是一个指向EXCEPTION_RECORD结构的指针。这个结构在WINNT.H中定义,如下所示:

    typedef struct _EXCEPTION_RECORD {

                                  DWORD ExceptionCode;
    
                                  DWORD ExceptionFlags;
    
                                  struct _EXCEPTION_RECORD *ExceptionRecord;
    
                                  PVOID ExceptionAddress;
    
                                  DWORD NumberParameters;
    
                                  DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
    
                            } EXCEPTION_RECORD;
    
  • 这个结构中的
    ExcepitonCode成员是赋予异常的代码。通过在WINNT.H中搜索以“STATUS_”开头的#define定义,你可以得到一个异常代码列
    表。例如所有人都非常熟悉的STATUS_ACCESS_VIOLATION的代码是0xC0000005。一个更全面的异常代码列表可以在
    Windows NT
    DDK的NTSTATUS.H中找到。此结构的第四个成员是异常发生的地址。其它成员暂时可以忽略。

  • style=”font-size: 14pt”>_except_handler函数的第二个参数是一个指向establisher帧结构的指针。它是SEH中一个至关重要的参数,但是现在你可以忽略它。
  • style=”font-size: 14pt”>except_handler回调函数的第三个参数是一个指向CONTEXT
    构的指针。此结构在WINNT.H中定义,它代表某个特定线程的寄存器值。图1显示了CONTEXT结构的成员。当用于SEH时,CONTEXT结构表示
    异常发生时寄存器的值。顺便说一下,这个CONTEXT结构就是GetThreadContext和SetThreadContext这两个API中使用
    的那个CONTEXT结构。

    typedef struct _CONTEXT
    {
    DWORD ContextFlags;
    DWORD Dr0;
    DWORD Dr1;
    DWORD Dr2;
    DWORD Dr3;
    DWORD Dr6;
    DWORD Dr7;
    FLOATING_SAVE_AREA FloatSave;
    DWORD SegGs;
    DWORD SegFs;
    DWORD SegEs;
    DWORD SegDs;
    DWORD Edi;
    DWORD Esi;
    DWORD Ebx;
    DWORD Edx;
    DWORD Ecx;
    DWORD Eax;
    DWORD Ebp;
    DWORD Eip;
    DWORD SegCs;
    DWORD EFlags;
    DWORD Esp;
    DWORD SegSs;
    } CONTEXT;

  • style=”font-size: 14pt”>_except_handler回调函数的第四个参数被称为DispatcherContext。它暂时也可以被忽略。

  • style=”font-size: 14pt”>到现在为止,你头脑中已经有了一个当异常发生时会被操作系统调用的回调函数的模型了。这个回调函数带四个参数,其中三个指向其它结构。在这些结构中,一些域比较重要,其它的就不那么重要。这里的关键是_exept_handler回调函数接收到操作系统传递过来的许多有价值的信息,例如异常的类型和发生的地址。使用这些信息,异常回调函数就能决定下一步做什么。

  • 我来说,现在就写一个能够显示_except_handler作用的样例程序是再诱人不过的了。但是我们还缺少一些关键信息。特别是,当错误发生时操作系
    统是怎么知道到哪里去调用这个回调函数的呢?答案是还有一个称为EXCEPTION_REGISTRATION的结构。通篇你都会看到这个结构,所以不要
    跳过这一部分。我唯一能找到的EXCEPTION_REGISTRATION结构的正式定义是在Visual
    C++运行时库源代码中的EXSUP.INC文件中:

    typedef struct _EXCEPTION_REGISTRATION_RECORD
    {

     PEXCEPTION_REGISTRATION_RECORD Next;
     
     PEXCEPTION_DISPOSITION Handler;
     
    

    } EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;


  • 个结构在WINNT.H的NT_TIB结构的定义中被称为_EXCEPITON_REGISTARTION_RECORD。唉,没有一个地方能够找到
    _EXCEPTION_REGISTRATION_RECORD的定义,所以我不得不使用EXSUP.INC中这个汇编语言的结构定义。这是我前面所说
    SEH未公开的一个证据。(读者可以使用内核调试器,如KD或SoftICE并加载调试符号来查看这个结构的定义。

  • 下图是在KD中的结果:

    style=”font-size: 14pt”>图片 6

  • 下图是在SoftICE中的结果: 

    style=”font-size: 14pt”>图片 7

  • style=”font-size: 14pt”> 当异常发生时,操作系统是如何知道到哪里去调用回调函数的呢?实际
    上,EXCEPTION_REGISTARTION结构由两个域组成,第一个你现在可以忽略。第二个域handler,包含一个指向
    _except_handler回调函数的指针。这让你离答案更近一点,但现在的问题是,操作系统到哪里去找
    EXCEPTION_REGISTATRION结构呢?

  • style=”font-size: 14pt”>要回答这个问题,记住结构化异常处理是基于线程的这一点是非常有用的。也就是说,每个线程有它自己的异常处理回调函数。在1996年五月的Under
    The
    Hood专栏中,我介绍了一个关键的Win32数据结构——线程信息块(Thread
    Information/Environment
    Block,TIB或TEB)
    。这个结构的某些域在Windows NT、Windows
    95、Win32s和OS/2上是相同的。TIB的
    第一个DWORD是一个指向线程的EXCEPTION_REGISTARTION结构的指针。在基于Intel处理器的Win32平台上,FS寄存器总是
    指向当前的TIB。因此在FS:[0]处你可以找到一个指向EXCEPTION_REGISTARTION结构的指针。

  • 现在为止,我们已经有了足够的认识。当异常发生时,系统查找出错线程的TIB,获取一个指向EXCEPTION_REGISTRATION结构的指针。在
    这个结构中有一个指向_except_handler回调函数的指针。现在操作系统已经知道了足够的信息去调用_except_handler函数,如图
    2所示

    typedef struct _TEB
    {

    NT_TIB         Tib;                        /* 00h */
        typedef struct _NT_TIB 
        {
            struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
                typedef struct _EXCEPTION_REGISTRATION_RECORD
                {
                     PEXCEPTION_REGISTRATION_RECORD Next;
                     
                     PEXCEPTION_DISPOSITION Handler;
                            enum EXCEPTION_DISPOSITION 
                            typedef enum _EXCEPTION_DISPOSITION
                            {
                                     ExceptionContinueExecution     = 0,
                                     ExceptionContinueSearch         = 1,
                                     ExceptionNestedException         = 2,
                                     ExceptionCollidedUnwind         = 3
    
                            } EXCEPTION_DISPOSITION;
    
                            //异常的回调函数的样子如下:
                            EXCEPTION_DISPOSITION __cdecl _except_handler( 
                                                                            struct _EXCEPTION_RECORD *ExceptionRecord,
                                                                                    typedef struct _EXCEPTION_RECORD 
                                                                                    {
                                                                                           DWORD ExceptionCode;

                                                                                           DWORD ExceptionFlags;

                                                                                           struct _EXCEPTION_RECORD *ExceptionRecord;

                                                                                           PVOID ExceptionAddress;

                                                                                           DWORD NumberParameters;

                                                                                           DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

                                                                                     } EXCEPTION_RECORD;

                                                                            void * EstablisherFrame,

                                                                            struct _CONTEXT *ContextRecord,
                                                                                    typedef struct _CONTEXT
                                                                                    {
                                                                                        DWORD ContextFlags;
                                                                                        DWORD Dr0;
                                                                                        DWORD Dr1;
                                                                                        DWORD Dr2;
                                                                                        DWORD Dr3;
                                                                                        DWORD Dr6;
                                                                                        DWORD Dr7;
                                                                                        FLOATING_SAVE_AREA FloatSave;
                                                                                        DWORD SegGs;
                                                                                        DWORD SegFs;
                                                                                        DWORD SegEs;
                                                                                        DWORD SegDs;
                                                                                        DWORD Edi;
                                                                                        DWORD Esi;
                                                                                        DWORD Ebx;
                                                                                        DWORD Edx;
                                                                                        DWORD Ecx;
                                                                                        DWORD Eax;
                                                                                        DWORD Ebp;
                                                                                        DWORD Eip;
                                                                                        DWORD SegCs;
                                                                                        DWORD EFlags;
                                                                                        DWORD Esp;
                                                                                        DWORD SegSs;
                                                                                    } CONTEXT;

                                                                            void * DispatcherContext
                                                                          );
                     
                } EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
            
            PVOID StackBase;
            PVOID StackLimit;
            PVOID SubSystemTib;
            
            _ANONYMOUS_UNION union 
            {
                PVOID FiberData;
                DWORD Version;
            } DUMMYUNIONNAME;
            
            PVOID ArbitraryUserPointer;
            
            struct _NT_TIB *Self;
            
        } NT_TIB,*PNT_TIB;
        
    PVOID EnvironmentPointer;               /* 1Ch */
    CLIENT_ID Cid;                          /* 20h */         //进程ID
    PVOID ActiveRpcHandle;                  /* 28h */
    PVOID ThreadLocalStoragePointer;        /* 2Ch */
    struct _PEB *ProcessEnvironmentBlock;   /* 30h */         //指向PEB
    ULONG LastErrorValue;                   /* 34h */
    ULONG CountOfOwnedCriticalSections;     /* 38h */
    PVOID CsrClientThread;                  /* 3Ch */
    struct _W32THREAD* Win32ThreadInfo;     /* 40h */
    ULONG User32Reserved[0x1A];             /* 44h */
    ULONG UserReserved[5];                  /* ACh */
    PVOID WOW32Reserved;                    /* C0h */
    LCID CurrentLocale;                     /* C4h */
    ULONG FpSoftwareStatusRegister;         /* C8h */
    PVOID SystemReserved1[0x36];            /* CCh */
    LONG ExceptionCode;                     /* 1A4h */
    struct _ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer; /* 1A8h */
    UCHAR SpareBytes1[0x28];                /* 1ACh */
    GDI_TEB_BATCH GdiTebBatch;              /* 1D4h */
    CLIENT_ID RealClientId;                 /* 6B4h */
    PVOID GdiCachedProcessHandle;           /* 6BCh */
    ULONG GdiClientPID;                     /* 6C0h */
    ULONG GdiClientTID;                     /* 6C4h */
    PVOID GdiThreadLocalInfo;               /* 6C8h */
    ULONG Win32ClientInfo[62];              /* 6CCh */
    PVOID glDispatchTable[0xE9];            /* 7C4h */
    ULONG glReserved1[0x1D];                /* B68h */
    PVOID glReserved2;                      /* BDCh */
    PVOID glSectionInfo;                    /* BE0h */
    PVOID glSection;                        /* BE4h */
    PVOID glTable;                          /* BE8h */
    PVOID glCurrentRC;                      /* BECh */
    PVOID glContext;                        /* BF0h */
    NTSTATUS LastStatusValue;               /* BF4h */
    UNICODE_STRING StaticUnicodeString;     /* BF8h */
    WCHAR StaticUnicodeBuffer[0x105];       /* C00h */
    PVOID DeallocationStack;                /* E0Ch */
    PVOID TlsSlots[0x40];                   /* E10h */
    LIST_ENTRY TlsLinks;                    /* F10h */
    PVOID Vdm;                              /* F18h */
    PVOID ReservedForNtRpc;                 /* F1Ch */
    PVOID DbgSsReserved[0x2];               /* F20h */
    ULONG HardErrorDisabled;                /* F28h */
    PVOID Instrumentation[14];              /* F2Ch */
    PVOID SubProcessTag;                    /* F64h */
    PVOID EtwTraceData;                     /* F68h */
    PVOID WinSockData;                      /* F6Ch */
    ULONG GdiBatchCount;                    /* F70h */
    BOOLEAN InDbgPrint;                     /* F74h */
    BOOLEAN FreeStackOnTermination;         /* F75h */
    BOOLEAN HasFiberData;                   /* F76h */
    UCHAR IdealProcessor;                   /* F77h */
    ULONG GuaranteedStackBytes;             /* F78h */
    PVOID ReservedForPerf;                  /* F7Ch */
    PVOID ReservedForOle;                   /* F80h */
    ULONG WaitingOnLoaderLock;              /* F84h */
    ULONG SparePointer1;                    /* F88h */
    ULONG SoftPatchPtr1;                    /* F8Ch */
    ULONG SoftPatchPtr2;                    /* F90h */
    PVOID *TlsExpansionSlots;               /* F94h */
    ULONG ImpersionationLocale;             /* F98h */
    ULONG IsImpersonating;                  /* F9Ch */
    PVOID NlsCache;                         /* FA0h */
    PVOID pShimData;                        /* FA4h */
    ULONG HeapVirualAffinity;               /* FA8h */
    PVOID CurrentTransactionHandle;         /* FACh */
    PTEB_ACTIVE_FRAME ActiveFrame;          /* FB0h */
    PVOID FlsData;                          /* FB4h */
    UCHAR SafeThunkCall;                    /* FB8h */
    UCHAR BooleanSpare[3];                  /* FB9h */
} TEB, *PTEB; 

style=”font-size: 14pt”>图片 8

 图片 9

  •  把
    这些小块知识拼凑起来,我写了一个小程序来演示上面这个对操作系统层面的结构化异常处理的简化描述,如图3的MYSEH.CPP所示。它只有两个函数。
    main函数使用了三个内联汇编块。第一个内联汇编块通过两个PUSH指令(“PUSH
    handler”和“PUSH
    FS:[0]”)在堆栈上创建了一个EXCEPTION_REGISTRATION结构。PUSH
    FS:[0]这条指令保存了先前的FS:[0]中的值作为这个结构的一部分,但这在此刻并不重要。重要的是现在堆栈上有一个8字节的
    EXCEPTION_REGISTRATION结构。紧接着的下一条指令(MOV
    FS:[0],ESP)使线程信息块中的第一个DWORD指向了新的EXCEPTION_REGISTRATION结构。(注意堆栈操作)。

    #include “stdafx.h”
    #define WIN32_LEAN_AND_MEAN
    #include
    #include

    DWORD scratch;

    EXCEPTION_DISPOSITION __cdecl _except_handler(

                                                struct _EXCEPTION_RECORD *ExceptionRecord,
                                                void * EstablisherFrame,
                                                struct _CONTEXT *ContextRecord,
                                                void * DispatcherContext 
                                              )
    

    {

    unsigned i;
    // 指明是我们让流程转到我们的异常处理程序的
    printf( "Hello from an exception handler\n" );
    // 改变CONTEXT结构中EAX的值,以便它指向可以成功进写操作的位置
    ContextRecord-> = (DWORD)&scratch;
    // 告诉操作系统重新执行出错的指令
    return ExceptionContinueExecution;
    

    }

    int main()
    {

    DWORD handler = (DWORD)_except_handler;
    __asm
    { 
        // 创建EXCEPTION_REGISTRATION结构:
        push handler        // handler函数的地址
        push FS:[0]            // 前一个handler函数的地址
        mov FS:[0],ESP        // 安装新的EXECEPTION_REGISTRATION结构
    }
    __asm
    {
        mov eax,0            // 将EAX清零
        mov [eax], 1        // 写EAX指向的内存从而故意引发一个错误
    }
    printf( "After writing!\n" );
    __asm
    { 
        // 移去我们的EXECEPTION_REGISTRATION结构
        mov eax,[ESP]        // 获取前一个结构
        mov FS:[0], EAX        // 安装前一个结构
        add esp, 8            // 将我们的EXECEPTION_REGISTRATION弹出堆栈
    }
    return 0;
    

    }


  • 果你想知道我为什么把EXCEPTION_REGISTRATION结构创建在堆栈上而不是使用全局变量,我有一个很好的理由可以解释它。实际上,当你使
    用编译器的__try/__except语法结构时,编译器自己也把EXCEPTION_REGISTRATION结构创建在堆栈上。我只是简单地向你展
    示了如果使用__try/__except时编译器做法的简化版。


  • 到main函数,第二个__asm块通过先把EAX寄存器清零(MOV
    EAX,0)然后把此寄存器的值作为内存地址让下一条指令(MOV
    [EAX],1)向此地址写入数据而故意引发一个错误。最后的__asm块移除这个简单的异常处理程序:它首先恢复了FS:[0]中先前的内容,然后把
    EXCEPTION_REGISTRATION结构弹出堆栈(ADD ESP,8)。

  • 在假若你运行MYSEH.EXE,就会看到整个过程。当MOV
    [EAX],1这条指令执行时,它引发一个访问违规。系统在FS:[0]处的TIB中查找,然后发现了一个指向
    EXCEPTION_REGISTRATION结构的指针。在MYSEH.CPP中,在这个结构中有一个指向_except_handler函数的指针。
    系统然后把所需的四个参数(我在前面已经说过)压入堆栈,接着调用_except_handler函数。

  • 旦进入_except_handler,这段代码首先通过一个printf语句表明“哈!是我让它转到这里的!”。接着,_except_handler
    修复了引发错误的问题——即EAX寄存器指向了一个不能写的内存地址(地址0)。修复方法就是改变CONTEXT结构中的EAX的值使它指向一个允许写的
    位置。在这个简单的程序中,我专门为此设置了一个DWORD变量(scratch)。_except_handler函数最后的动作是返回
    ExceptionContinueExecution这个值,它在EXCPT.H文件中定义。

  • 操作系统看到返回值为ExceptionContinueExecution时,它将其理解为你已经修复了问题,而引起错误的那条指令应该被重新执行。由
    于我的_except_handler函数已经让EAX寄存器指向一个合法的内存,MOV
    [EAX],1指令再次执行,这次main函数一切正常。看,这也并不复杂,不是吗?

二丶从FS寄存器中,查看TEB线程内容,以及异常链表

我们为什么要知道TEB的内容

是这样的,我们以前的筛选器异常,什么异常都会去处理的.但是我们觉着很不足,因为我们不知道具体的那个函数出现了异常,所以我们要对异常处理作进一步的升级

我们要知道那个函数出现了异常才可以.

那么怎么知道那个函数出现了异常哪,那么这就和FS里面的TEB里面的内容有关了.

TEB 也就是线程相关

我们使用WinDbg看下TEB的内容

 图片 10

图片 11

 

我们看到了第一个框,WinDbg已经帮我们解释出来了(如果解释不出来,请看下自己的符号路径是否下载了,具体设置在熟悉WinDbg的博客中有讲解,以及现在的dt命令也有讲解)

第一个框,存放的是异常信息,我们还可以DT 一下,进去看一下

第二个框,我们可以看到是和进程相关的.

那么第一个框我们先DT 一

 图片 12

可以看出,这个地方是存放异常的地方,那么我们现在再次进入后面的结构体

注意,后面这个结构体,是未公开的,也就是微软不让我们自己用的.但是使用WinDbg解析符号我们得到了,或者我们去MSDN上搜索一下,也是搜索不到了.这个都是通过逆向得来的

,那现在我们看下这个表,显示的是异常信息表,我们DT 进去看

 图片 13

进去后

 图片 14

 

进去后,我们发现了,他是一个链表,next,指向了下一次的异常信息结构体

,而第二个就是一个函数指针,注意这个我们是可以查到的.打开VC6.0
把后面的结构体复制过去,然后使用VA 插件的GO功能,可以看到是什么结果

 图片 15

可以看出,这个结构体保存的是返回值信息,我们也可以去WinDbg中DT一下看下

 图片 16

 

因为是未公开的,所以只知道返回值是什么意思,

第一个是代表,我不处理,继续执行(这个筛选器异常已经讲过了)

第二个是我已经处理了.

看了上面介绍的怎么多,可能不知道什么意思

其实SHE(结构化异常)
就是使用内联汇编,给每个函数注册一个筛选器异常,然后每个函数都有自己的回调函数,而回调函数是第上面截图的第二个参数Handler,这个是一个函数指针.

因为未公开的,所以不知道.

但是我们也可以找得到,还是在VC6.0中定义上面那个结构体,然后GO过去

图片 17

我们在上面找到的只是返回值,但是在下面寻找的时候,我们发现,使用的上面typedef定义的结构体

用来定义这个Handler了.

关于注册,关于注册,我们下面细讲,但是现在我们先熟悉一下段寄存器FS的使用

终止处理

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

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

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

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

二丶什么是异常展开

上面我们说了异常展开,也把我们的代码贴出来了.那么现在思考一个问题

当 fun1函数调用fun2函数 

的时候,fun2函数也注册一个SEH筛选器异常,(注册相当于往链表头插入)

例如下面的代码

void fun1()
{
    __asm
    {

        push offset HANDLER1
        push fs:[0]
        mov fs:[0],esp
    }

    fun2();
     char *p = NULL;
     *p = 1;
    __asm
    {
        pop fs:[0]
        add esp,4
        ret
    }
}
void fun2()
{
    __asm
    {
      push offset HANDLER2  //注册回调函数
      push fs:[0]           //压入旧的链表指针
      mov fs:[0],esp       //新的位置变成当前的SEH
    }
    char *p =NULL;
    *p = 1;
    //取消注册,和上面一样,不写了,为了节省空间
}

那么我们知道,现在的链表头是Fun2,也就是
Fun2链表中的next位置指向了Fun1的位置,回调函数也是fun2的

那么我们现在想想,如果fun2出现了异常,而fun2的回调函数是处理不了这个异常的,那么会交给fun1去处理

这个没问题吧,但是你想,fun2交给fun1处理的时候,取消注册是不可能在执行了.

也就是说,现在的fun2
是链表头,并没有断开连接,或者卸载这个函数,那么如果这个时候fun1出现了问题怎么办?

操作系统当出现异常的时候,会依次遍历这个链表,此时的Fun2已经是无效的了,我们并不能让它去调用.而是应该把异常的链表的首地址,重置为当前的fun1所在的位置.

看下图:

图片 18

那么这种操作,就叫做异常展开,简单来说就是 fun1 调用fun2
fun2出现了异常,自己的异常链表来不及卸载,此时只能交给fun1去处理,那么现在我们应该把链表的位置重置为fun1的异常链表,fun2的不在需要了.否则操作系统调用的时候则是调用了一个错误的地址.

说到释放的时候我们上面说了,操作系统会根据错误标志2,来接着调用一次我们的异常回调函数,这就是因为在操作系统帮我们卸载这个异常链表,但是会依次的调用一次我们的回调函数,通知我们,该释放资源的释放资源,该处理的处理,给我们一次释放资源的机会.

三丶熟悉段寄存器的使用,创建反调试程序

 

还记得我们上次,也就是第一次dt的时候,花了两个框吗,我们看到了一个PEB

PEB就是和进程相关的,不知道的我下方再次贴下图

 图片 19

 

我们先进去看下他有什么好玩的

 图片 20

 

进去之后,看到这里有一个检测Dbg调试的功能,那我们内联汇编使用一下FS寄存器,写一个调试检测是否调试.

下面写的代码可能不懂,因为你必须去看看雪的那篇帖子,才知道FS中到底是什么

 图片 21

 

 

这里截图一部分,我们大概要知道是什么.

看下以下代码

 

#include "stdafx.h"

#include <STDLIB.H>



int main(int argc, char* argv[])

{



    char isDbg;

    __asm

    {

        mov eax,fs:[0x18]            //找到teb的位置

        mov eax,[eax + 0x30]        //teb + 30找到PEB的位置,对其取内容得到第一个首地址

        mov eax,[eax+0x2]            //首地址 + 偏移找到debug的位置对其取内容

        mov isDbg,al                //求出是否在调试                           

    }

    printf("%d\r\n",isDbg);

    system("pause");

}

 

,接着看下下面的图片

 图片 22

 

 

首先介绍一下我这次些联汇编是什么意思

mov eax,fs:[0x18] 对照看雪的部分截图我得到了
TEB的位置,而刚才的TEB我们也dt看了以下

现在再看下

 图片 23

 

第二步,mov eax,[eax + 0x30]

从之句话中,我们得出了PEB指向的内容,也就是 DT _PEB ,得到第一个地址.

第三步: mov eax,[eax + 0x2]

这句话代表的意思则是,我要从 PEB的首地址 + 2个偏移
然后得出里面的内容是什么.

而我们看下PEB里面是什么

 图片 24

 

这个正是我们要取出来的判断是否在调试的标志,而因为我们 eax +
0x2的出来的是它的地址,但是我们有对它取内容了,所以结果放在了eax当中,如果不同,可以自己调试一下看看.

现在因为他是UChar类型,也就是无符号类型,所以一个字节,会放在al当中,所以我们把al的值,给了变量了.

第四步:输出我们变量的值是什么.

我们看下我们使用VC调试的时候输出什么

首先调试起来

 图片 25

 

单步一下

 图片 26

 

结果输出的是1

那么我们不调试,直接运行起来,看下结果是什么

 图片 27

 

结果是0,那么现在就好办了,我们可以开辟个线程,然后判断这个标志,如果为1,代表被调试了

那么我们就要让软件崩溃,开线程崩溃,为什么要崩溃,因为你让软件退出的话,会逆向的人,它会在ExitProcess的位置下段点,然后回溯,就可以找到你判断标志位的原因,而现在你可以判断标志位,然后如果为1我就开启一个线程,而这个线程我随便让它访问个错误的值,比如

给指针为NULL,然后再给NULL赋值,注意,只有当标志位1才开启,不为1不开启,这样崩溃了,他就会以为C05,而不调试的时候,你软件就是正常的.

当然上面的代码我是通过TEB寻得PEB地址然后加了02偏移,你也可以直接写

第30个下标就是PEB,

mov eax,fs:[0x30]

mov eax,[eax +0x2]

mov isdbg,al

一样可以.

而且你也可以隐藏模块,下方也有模块的链表.我们也可以字节遍历这个链表,找到自己的模块,然后隐藏.

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到用户栈上,然后到用户态来找,来找这些异常分发函数;

 

四丶SEH结构化异常处理详解

上面我们说了很多,主要就是为了SHE结构化的讲解,比如FS寄存器的使用,因为当你会使用的时候,我们为每一个函数注册一个结构化异常处理就简单明了了.

那么我们开始注册一个异常处理

注册的意思:

        
我们上面第二步已经把异常的处理的链表找出了了,我们也知道了第二个参数是函数指针.

        
既然我们每个函数都注册一个异常处理,也就是要往这个链表中插入一个异常链

注意: 我们是往头上插入

 

(注意,只能在VC6.0中使用,高版本会有别的方法)

第一,我们要想一个问题,既然我们要注册一个结构化异常处理

下面且看我写一下异常处理的代码注册的代码;

#include "stdafx.h"
#include <WINDOWS.H>
#include <STDLIB.H>
#include <WINNT.H>


EXCEPTION_DISPOSITION __cdecl HANDLER1(
                                       struct _EXCEPTION_RECORD *ExceptionRecord,
                                       void * EstablisherFrame,
                                       struct _CONTEXT *ContextRecord,
                                       void * DispatcherContext)
{

    MessageBox(NULL,"我处理了异常\r\n",NULL,NULL);
    return ExceptionContinueSearch;
}
void fun1()
{
    __asm
    {
        push offset HANDLER1
        push fs:[0]
        mov fs:[0],esp
    }


     char *p = NULL;
     *p = 1;
    __asm
    {
        pop fs:[0]
        add esp,4
        ret
    }
}

int main(int argc, char* argv[])
{

    fun1();    
    system("pause");

}

 

请先看下上面的代码

我们一开始的内联汇编,是要先注册

__asm

{

         push  函数指针

         push  fs:[0]

         mov fs:[0],esp

}

 

这句话什么意思,因为函数从右向左传递参数

请看下图

 图片 28

 

我们首先取得了fs:[0] 也就是第一个异常链的位置

我们dt一下看看

 图片 29

 

0偏移是_NT_TIB

我们接着dt一下这个

 图片 30

 

发现了0偏移就是异常链表,是一个指针,所以我们可以直接push fs:[0]了

那么这句话什么意思,

我们使用OD调试一下我们的程序看下

 图片 31

 

单步调试一下看下什么结果

我们也要调到FS 的数据区位置

 图片 32

 

单步调试,跟着走,看下会有什么结果

 图片 33

 

首先是压入的函数的地址,我们跳转过去看下 ctrl + G

 

图片 34

 

OD自动帮我们标出来了结构异常处理程序,

现在看下第二句,压入FS地址的0的内容.也就是旧的异常链表

 图片 35

现在栈顶位置,然后重新赋值给FS:[0]的位置

 图片 36

 

现在,我们这三行的意思就是往fs[0]位置的异常链表的头部插入一个链表

现在的FS:[0]的位置是我们当前的位置,那么调用的时候会调用我们当前注册的HANDLE1的回调函数,当我们把这个链表注销后,才会把以前的链表的位置换回去

如果不懂,看下面图片:

 图片 37

 

如果真的不理解,那么内存布局多看几遍,其实把FS:[0]位置改为我们的栈的位置,而以前的异常链表的位置我们已经保存了.

所以下面可以pop fs:[0] 
把我们第一个栈顶的位置,也就是保存的以前的异常链表的位置,换回去了.

现在我们试下我们程序的正常运行

 图片 38

 

####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)  

 三丶异常处理的顺序

异常处理处理发生的时候,会有顺序的

1.系统首先发送给调试器 调试器优先级最高

2.如果没有调试器,系统会继续查找线程相关的异常处理,

3.每个线程相关的异常处理例程,可以处理或者不处理这个异常,如果不处理,并且安装了多个线程相关的处理例程,可交给连起来的其它例程处理

4.不处理这个异常,在判断程序是否在调试状态,如果在就接着给调试器

5.如果没有的话,或者不处理,那么操作系统就会调用筛选器异常

6.如果没有,那么系统会调用默认的异常处理,也就是崩溃的的界面

7.在终结之前,对其展开操作,然后依次调用设置的SEH链表中的回调函数,给予一次最后清理的机会.

五丶C++ 中的try catch 语法的实现

我们学过C++的都知道,C++中有一个语法叫做try catch

也可以 throw 一个异常出来

只不过一个是主动抛异常,一个是被动的抛异常

现在假设,我们fun1 函数里面调用了fun2,(fun2也不注册异常处理)

我们fun2出现了异常,但是我们不想处理怎么办.

那么它会往上面一层寻找,那么上面一层,也就是我们注册的fun1的异常处理的位置,会调用对应的fun1的回调函数

那么我们现在试一下.

 图片 39

 

而Fun2()

 图片 40

 

那么我们运行起来,看下信息框来了没有.

 图片 41

 

发现来了,那么这个就是异常处理中的 throw的原理,会往上层查找.

课堂资料就是注册SEH的几行代码,请根据博客编写,自己手动敲下

 

 

博客园IBinary原创 博客连接:

转载请注明出处,谢谢

 

关键字 __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来看一看从内核态到用户态:

四丶主动引发异常

我们说过throw这个语句会抛出一个异常,其实底层调用的也是API

void RaiseException(DWORD dwExeptionCode,
         DWORD   dwExceptionFlages
          DWORD nNumberOfArguments,
          Const DWORD * lpArguments

前两个分别是退出代码,和错误标志,这个在筛选器异常已经讲过了

最后两个参数是用户自定义的.throw这个语法就是调用的这个API

异常处理程序

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

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

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

图片 42

五丶自动展开操作

我们说过,异常展开的时候,我们自己也可以去做,也可以交给操作系统做,而操作系统做的时候也是调用的API

RtIUnwind  具体可以查询下MSDN.想了解底层自己查询一下,不多做讲解.

 

 

关于可处理异常,以及异常的第二个参数的应用,明天讲解,怕一下 讲解太多

异常处理基本流程

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

 

 作者:IBinary
出处:
版权所有,欢迎保留原文链接进行转载:)

 

 

图片 43

.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());
}

图片 44

image.png

图片 45

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:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 64]
00cff878 00db1e5d seh__!__scrt_common_main_seh+0x150 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 253]
00cff880 00db2178 seh__!__scrt_common_main+0xd [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 296]
00cff888 74978654 seh__!mainCRTStartup+0x8 [f:\dd\vctools\crt\vcstartup\src\startup\exe_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单步走)

图片 46

image.png

0:000> k                                //栈回溯!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ChildEBP RetAddr  
0057f7cc 008a215e seh___!main+0x91 [e:\总复习\总复习\软件调试\软件调试\seh演示.cpp @ 18]
0057f7e0 008a1fc0 seh___!invoke_main+0x1e [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 64]
0057f838 008a1e5d seh___!__scrt_common_main_seh+0x150 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 253]
0057f840 008a2178 seh___!__scrt_common_main+0xd [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 296]
0057f848 759a8654 seh___!mainCRTStartup+0x8 [f:\dd\vctools\crt\vcstartup\src\startup\exe_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;

图片 47

image.png

这个结构体的我第一个字段就是异常代码:c0000094是除0异常的代码,代表除0异常,异常结构体里面有一个导致异常的地址,这是CPU记录下来的,上图中是0040108b;这个地址是导致除0异常的那个地址,

识别寄存器上下文(Context)就看0001007F;Context第一个字段是mask字段,来标记哪些寄存器是有效哪些寄存器是无效的,

图片 48

image.png

图片 49

image.png

图片 50

image.png

有了context结构体,可以这样回到上下文,可以这样:

.cxr xxxxxxxx(0001007f所对应的地址)

正是单步这一刹那,正是cpu找出这一触发指令,导致除0,然后cpu报告异常,把这个异常地址(此处为0040108b)压到栈上,这是why我们能够知道准确除0的原因,然后在内核分发异常的时候,也把这个著名的结构体,通过栈复制到用户态,

图片 51

image.png

通过这条指令可以看到cpu寄存器的上下文(图中的0018fa68是context的第一个字段对应地址),

图片 52

image.png

综上:内核把重要的异常结构体和context上下文结构体复制到用户态,使得用户态知道异常的详细信息
微软公布的一些API:GetExceptionCode()可以从栈上取得异常代码

图片 53

image.png

结构化异常处理是有能力让软件回到导致异常的位置重新执行.

发表评论

电子邮件地址不会被公开。 必填项已用*标注