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
这里,从内核态飞到用户态,飞到用户态的这个位置继续分发这个异常,
小结:
对于第一轮异常,先有对内核调试的支持,然后再分给调试器的第一轮,如果调试器对第一轮不处理,如果调试器对第一轮不处理,
if(DbgkForwardException(TrapFrame,DebugEvent,FirstChance)!=0)
那边
接着上边:如果调试器对第一轮不处理,那么就分到用户态做第一轮的分发(6'53''),因为整个块都是第一轮if(FirstChance)
,第一轮:先给内核调试,再给调试器第一轮,最后再复制到用户态(7'03"),复制到用户态,(TrapFrame->Eip = KeUserExceptionDispatcher
)复制到用户态这里的第一轮的目的是给大家的那些try...catch异常处理器和向量化异常处理,即应用程序的向量化处理,
如果第一轮还是没有人处理,那就会下面的第二轮分发,第二轮分发还是先给调试器,而且告诉调试器,这是最后的处理机会,然后再给异常端口(第二个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
`
网友评论