• 第八章——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结束进程
     
  • 相关阅读:
    打开CAD时出现“acvmtools.arx ARX命令中发生异常
    VS2010编译错: #error : This file requires _WIN32_WINNT to be #defined at least to 0x0403...的解决方法
    CAD中的相对坐标和绝对坐标
    CAD中的文本编排操作
    使用VS2008,VS2010编译64位的应用程序
    已知两切线和半径画圆弧和圆
    AutoCAD2012启动错误 1308 源文件未找到
    C++中的static修饰的变量和函数
    VS2010创建C++静态链接库创建和使用
    Python中同时用多个分隔符分割字符串的问题
  • 原文地址:https://www.cnblogs.com/Tempt/p/10218883.html
Copyright © 2020-2023  润新知