zoukankan      html  css  js  c++  java
  • 第八章——Windows下异常处理-异常处理基本概念

    前言:
    中断和异常的区别,中断是由外部硬件设备或异步事件产生的。异常是由内部事件产生,可以分为故障,陷阱和终止三类。
    由CPU引发的异常成为硬件异常,例如访问一个无效的内存地址。由操作系统或应用程序引发的异常成为软件异常。
    我们也可以主动抛出一个异常,通过RaiseException()函数
    void WINAPI RaiseException(
      _In_       DWORD     dwExceptionCode,                //标识所引发异常的代码
      _In_       DWORD     dwExceptionFlags,                //异常是否继续执行的标识
      _In_       DWORD     nNumberOfArguments,        //附加信息
      _In_ const ULONG_PTR *lpArguments                  //附加信息
    );
     
    异常处理基本过程
    1.IDT
        在windows启动后,在保护模式下,有中断或者异常发生时,CPU会通过中断描述符表来查找处理函数(IDT表)
        IDT表共有256项,在32位下每个IDT项的长度为8字节,在64位下长度为64字节,操作系统会在启动阶段初始化这个表
        IDT的位置和长度由CPU和IDTR寄存器藐视,IDTR寄存器有48位,其中高32位是基地址。低16位是表的长度
     
        IDT的每一项都是一个门结构,其中有三种门:
    • 任务门:用于CPU任务切换(TSS)
    • 中断门:用于描述中断处理程序入口
    • 陷阱门:主要用于描述异常处理程序入口
     
    2.异常处理的准备工作
    一、当有中断或异常发生时,CPU会根据中断类型号转而执行对应的中断处理程序。CPU会在IDT中查找对应的函数来处理,各个异常处理函数不仅仅处理异常还需要将异常信息封装,以便对后续处理(_EXCEPTION_RECORD结构体记录封装的异常信息)
    这个结构体主要描述了异常的代码,异常标志,还有异常发生的地址,没有描述异常发生时,具体的异常环境。具体的异常环境在另一个结构体_KTRAP_FRAME(陷阱帧)中,这里面包括了每个寄存器的状态,这个结构体通常在内核中,而我们编写调试器的时候,通常用的是context结构体
    二、上述总而言之是对异常信息的封装,封装完成后,系统会调用nt!KiDispatchException来处理异常,所以分析KiDispatchException函数就可以了解异常是如何被处理的
          函数原型:
                    KiDispatchException (
                        IN PEXCEPTION_RECORD ExceptionRecord,            //异常结构信息(就是上面_EXCEPTION_RECORD结构体内容)
                        IN PKEXCEPTION_FRAME ExceptionFrame,              
                        IN PKTRAP_FRAME TrapFrame,                                //发送异常的陷阱帧(内核:_KTRAP_FRAME,三环:context)
                        IN KPROCESSOR_MODE PreviousMode,                 //发送异常时CPU模式是内核还是用户
                        IN BOOLEAN FirstChance                                        //是否第一次发生异常
                        )
                        
    3.内核态的异常处理过程
        PreviousMode字段是KernelMode时候,表示内核模式下产生异常,具体处理步骤:
        ①系统会先检测是否有内核调试器,如果没有,就跳过这一步,如果有,就把异常处理的权限交给内核调试器,并且注明是第一次来执行的这个异常(FirstChance),内核调试器如果处理了该异常就继续回到原来异常地方继续执行,如果没有处理则发生中断,将控制权交给用户,用户决定是否继续处理
        ②如果不存在内核调试器,或者第一次的异常没有被处理,系统就会调用RtDispatchException,这里会根据用户注册的SEH异常处理结构来处理(注意,内核态下只有SEH)
        ③上述过后,如果异常处理了,程序继续运行,如果第一次没有处理,则进行第二次异常处理,系统会再将控制权交给内核调试器
        ④如果不能存在内核调试器,或者第二次处理失败了,这时系统就会调用KeBugCheckEx产生一个错误码为"KERNEL_MODE_EXCEPTION_NOT_HANDLED"蓝屏错误
     
    4.用户态的异常处理过程
        PreviousMode字段是UserMode时候,表示用户模式下产生异常,此时KiDispatchException函数仍然会检测内核调试器是否存在,如果内核调试器存在,系统还是会将控制权交给内核调试器进行处理,内核调试器对用户态的程序是可以调试,并且还不依赖进程的调试窗口。但是在大多数情况下,内核调试器是不调试用户态程序,所以KiDispatchException函数还是会和内核调试器一样,分两次在用户态下处理异常信息,具体处理步骤:
        ①如果存在异常的程序被调试,系统会将异常信息发送给正常调试的用户态调试器,给调试器一次机会,如果没有被调试,跳过此步
        ②如果不存在用户调试器,或者调试器未处理该异常,那么栈上放置EXCEPTION_RECORD和CONTEXT,并将控制权返回用户态的KiDispatchException函数,这一步涉及SEH,VEH顶级异常处理,如果调试器存在,顶级异常处理函数就会被跳过,否则就会被顶级处理函数接管
        ③如果RtlDispatchException函数在调用用户态的异常处理过程中未处理该异常,那么异常处理过程会再次返回kisdispathchexception,进行第二次异常分发
        ④,如果第二次还没有处理,则 kisdispathchexception会尝试将异常分发给进程的异常端口进行处理,该端口由csrss.exe进行监听,如果监听到错误,则会显示一个应用程序错误,如果调试器还不能附加其上,则会调用exitprocess结束进程
     
  • 相关阅读:
    阿里P8架构师谈:阿里双11秒杀系统如何设计?
    秒杀系统设计的知识点
    秒杀系统架构优化思路
    秒杀系统解决方案
    Entity Framework Code First (七)空间数据类型 Spatial Data Types
    Entity Framework Code First (六)存储过程
    Entity Framework Code First (五)Fluent API
    Entity Framework Code First (四)Fluent API
    Entity Framework Code First (三)Data Annotations
    Entity Framework Code First (二)Custom Conventions
  • 原文地址:https://www.cnblogs.com/Tempt/p/10218883.html
Copyright © 2011-2022 走看看