美文网首页
9.结构化异常处理演示

9.结构化异常处理演示

作者: bluewind1230 | 来源:发表于2018-02-11 22:42 被阅读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继续执行!
    
    

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

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

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

    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)  
    

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


    image.png image.png 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单步走)


    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;

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

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

    image.png image.png image.png

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

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

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


    image.png

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


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

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

    相关文章

      网友评论

          本文标题:9.结构化异常处理演示

          本文链接:https://www.haomeiwen.com/subject/jvpltftx.html