美文网首页
Windows操作系统的异常分发过程

Windows操作系统的异常分发过程

作者: bluewind1230 | 来源:发表于2018-02-10 21:13 被阅读0次
    image.png
    Visual studio中的异常对话框(如上图所示),从上至下分别是:C,C++异常,.net异常,Native异常,Win32异常
    windows操作系统会给每个异常一个独立的异常代码,
    c++异常就是hardcode层的.msc的ASCII码为0xe06d7363
    .net异常就是hardcode层的.com的ASCII码
    下面是Windows操作系统分发异常的内核函数(KiDispatchException):
    image.png
    KiDispatchException是Windows系统分发异常的一个枢纽,Windows下的异常分为两个异常分发(图中为伪代码),if(FirstChance)表示第一轮的异常分发,第一轮的分发,它的分发顺序是什么呢,它里面有个判断:
    if(PsGetCurrentProcess()->DebugPort==0)如果当前的调试端口为空,调试端口是支持用户态调试的,调试端口为空,表示当前进程不再被用户态调试,就是说没有用户态调试器,没有用到用户态调试器的时候这里会有一个判断,意思是说:要不要把这个异常分发给内核调试器(如下图所示)
    image.png
    某些时候,没有用户态调试器,但是整个系统有内核调试器,内核调试器有时候会用来调试特殊的用户态问题!这个时候就分发给内核调试器就是有价值的,
    image.png
    如果当前这个用户态异常,对应的这个进程,没有用户态调试器的时候,会考虑分给内核调试器的!
    image.png

    break的意思是说,在整个分发异常过程中,如果一个人处理了,其他人就不分发了!如果KD(内核调试器)处理了这个异常,这里一break,那就结束这个异常分发了!如果KD不分发这个异常,那就继续往下分,那就是著名的DbgkForwardException,这个是分发给用户态调试子系统,DbgkForwardException是内核里面支持用户态调试的内核函数,FirstChance表示第一轮分发!
    如果第一轮调试器handle了,就return !异常分发的总的原则是一轮一轮的询问,如果有人说处理了,那么就不再分发!假设有调试器的情况下,调试器对于第一轮异常,会判断,对于int 3这样的调试异常,是专门给调试器的,调试器就会handle掉,直接return,第一轮就分发好了;
    如果是应用程序的问题,调试器具有一定的灵活性,可以断下来,也可以不断下来!调试器可以设置,对于Visio Studio,对于C++异常,它可能在第一轮收到会收到通知,但是它不处理,因为应用程序它可能自己throw,自己catch,这时候就会向下执行的时候呢,简单理解:它就会把这个异常信息复制到用户态,
    然后交给用户态的函数继续分发(见下图),这里是把异常的上下文和异常的记录拷贝到用户态的栈,把著名的陷阱帧里面的Eip改掉,改成用户态函数KeUserExceptionDispatcher(Ntdlll里面的),整个作用就是把陷阱帧里的程序指针(Eip)改掉,等一下异常返回的时候,CPU就会返回到KeUserExceptionDispatcher这个地址,即异常返回,CPU就飞到KeUserExceptionDispatcher这里,从内核态飞到用户态,飞到用户态的这个位置继续分发这个异常,

    image.png
    小结:

    对于第一轮异常,先有对内核调试的支持,然后再分给调试器的第一轮,如果调试器对第一轮不处理,如果调试器对第一轮不处理,
    if(DbgkForwardException(TrapFrame,DebugEvent,FirstChance)!=0)那边
    接着上边:如果调试器对第一轮不处理,那么就分到用户态做第一轮的分发(6'53''),因为整个块都是第一轮if(FirstChance),第一轮:先给内核调试,再给调试器第一轮,最后再复制到用户态(7'03"),复制到用户态,(TrapFrame->Eip = KeUserExceptionDispatcher)复制到用户态这里的第一轮的目的是给大家的那些try...catch异常处理器和向量化异常处理,即应用程序的向量化处理,

    image.png
    如果第一轮还是没有人处理,那就会下面的第二轮分发,第二轮分发还是先给调试器,而且告诉调试器,这是最后的处理机会,然后再给异常端口(第二个if),异常端口通常是Windows子系统服务进程监视的,异常端口收到之后就会把这个进程杀掉,然后还不处理,就是很特殊的情况,直接调用ZwTerminateProcess();它就会把进程在内核态给悄悄杀掉,
    image.png
    所以整个用户应用程序的异常分发,先是第一轮,第一轮不处理再第二轮,每一轮都是先给调试器,第一轮的时候,调试器如果不处理,会给应用程序自己的处理代码,
    image.png
    下面是一个小程序抛出的一个c++异常:
    image.png
    这个c++异常,我们的编译器会把它翻译成vc_throw这样的一个特殊函数,
    这个特殊函数内部再调用_CxxThrowException,这个函数内部就会调用kernel32!RaiseException,这个API就会继续调用系统调用,进入到内核里面去了,就进入到内核栈(内核之旅将不会被看到),进入内核态,走我们刚才讲的内核分发函数转了一圈之后,内核调试器不处理,用户态调试器对第一轮也不处理,然后call到用户态,把异常信息复制到用户态,程序指针指到ntdll!KiUserExceptionDispatcher(用户态的分发函数),开始用户态分发,用户态分发的时候,会找应用程序里的程序处理器,找到异常处理器之后,就开始Execute这些handler(就是ntdll!ExecuteHandler),这些handler通常是我们try......catch......;try......except......写的这些handler,图中正在执行c++的异常处理函数过程;
    怎么找到异常处理器呢,这就是著名的结构化异常处理器Fs:[0]链条;
    image.png
    在Windows中的每个线程,都有这样一个特殊的链条,FS:[0]这样的一个特殊的段寄存器指向的线程信息块(TEB)(这个特殊的数据结构),偏移0的地方,指向的是这样的一个特殊的链条,这个链条的每个节点,都是异常注册结构,每个异常注册结构,指向一个handler函数,再指向自己的前项指针,可以想象,发生异常的时候,看下面一个演示:
    image.png
    try中的代码可能发生异常,发生异常的过程中,就会评估过滤表达式,过滤表达式的值决定要不要处理异常处理块,过滤表达式的返回值,一般为下面三个值:
    image.png

    `

    相关文章

      网友评论

          本文标题:Windows操作系统的异常分发过程

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