概述
以下内容引述《windows核心编程》
未处理异常
异常过滤函数返回EXCEPTION_CONTINUE_SEARCH,系统会继续在调用树的上层寻找异常过滤函数。如果每个异常过滤程序都返回EXCEPTION_CONTINUE_SEARCH,我们就会遇到所谓的未处理异常。
微软提供了函数SetUnhandleExceptionFilter,它给我们处理异常的最后机会,否则widows会正式认为这个异常没有得到处理。
PTOP_LEVEL_EXCEPTION_FILTER SetUnhandleExceptionFilter(
PTOP_LEVEL_EXCEPTION_FILTER pTopLevelExceptionFilter);
在进程初始化调用这个函数,一旦调用这个函数,进程中任意线程抛出未处理异常都会导致我们制定的最上层异常过滤函数执行。这个异常过滤函数必须与下面类似
LONG WINAPI TopLevelUnhandledExceptionFilter(PEXCETIOIN_POINTERS pExceptionInfo);
在异常过滤函数中,只要返回一下三个EXCEPTION_*标志符之一,就可以做任何我们想要做的处理。
在异常过滤函数中,尽可能少地实现代码逻辑,不要再申请动态内存。
标识符 | 对应结果 |
---|---|
EXCEPTION_EXECUTE_HANDLER | 进程在不给用户任何通知的情况下悄然终止。注意全局展开会被引发,所以finnaly代码块会执行 |
EXCEPTION_CONTINUE_EXECUTION | 从抛出异常的指令继续执行,我们可以修改由PEXCEPTION_POINTERS参数指向的异常信息。如果我们不修正异常的错误,同样的异常还会再一次被抛出,于是进程将进入异常不断被抛出的无限循环 |
EXCEPTION_CONTINUE_SEARCH | 异常不会得到任何处理 |
当我们设置新的未处理异常时,SetUnhandledExceptionFilter返回上次安装的异常过滤后程序的地址。
如果程序使用的是C/C++运行库,在进程入口点函数开始执行前,C/C++运行函数就会安装一个默认的全局异常过滤函数,即__CxxUnhandledExceptionFilter。
这个函数很简单,首先检查异常是不是属于C++异常,如果是,则在结束的时候调用abort函数;如果不是,则会返回一个EXCEPTION_CONTINUE_SEARCH,来让windows处理这个未处理异常。
如果调用SetUnhandledExceptionFilter来安装我们自己的全局异常过滤程序,其返回的地址即为__CxxUnhandledExceptionFilter的地址。
如果过滤程序即将返回EXCEPTION_CONTINUE_SEARCH,可能想在此之前调用之前的全局异常过滤程序,它的地址可以通过SetUnhandledExceptionFilter的返回值得到。
在windows程序中,每个线程的执行都是从NTDLL.dll中的函数BaseThreadStart开始的
void BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) {
__try {
ExitThread((pfnStartAddr)(pvParam));
}
__except(UnhandledExceptionFilter(GetExceptionInformation())) {
ExitProcess(GetExceptionCode());
}
}
这个函数包含一个结构化异常处理(SEH):它首先进入try代码块,然后在try块中调用线程/程序的入口点函数。
所以如果任一线程抛出一个异常,并且所有安装的异常过滤程序都返回EXCEPTION_CONTINUE_SEARCH,系统提供的一个特殊函数将被自动调用,即UnhandledExceptionFilter
LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);
和普通异常函数一样,这个函数返回三个EXCEPTION_*异常函数
标识符 | 对应的系统操作 |
---|---|
EXCEPTION_EXECUTE_HANDLER | 发生一个全局展开,所有挂起的finally块执行。针对未处理异常,BaseThreadStart的异常程序将调用ExitProcess,于是进程会悄然终止。 |
EXCEPTION_CONTINUE_EXECUTION | 执行从抛出异常的指令继续 |
EXCEPTION_CONTINUE_SEARCH | 要么出错进程已经在调试器的控制之下,要么默认调试器将被附着到出错进程。系统会将该异常通知调试器,所以程序会正好停在发生异常的地方。 |
UnhandledExceptionFilter详解
UnhandledExceptionFilter函数在处理异常时会按顺序执行5个步骤,执行完这些步骤后,UnhandledExceptionFilter会将执行控制交给Windows错误报告(WER)
- 允许对资源进行写入操作并继续执行
如果线程的写操作而引起非法访问异常,UnhandledExceptionFilter调用VirtualProtect将资源页的保护属性设置为PAGE_READWRITE,并返回EXCEPTION_CONTINUE_EXECUTION,以允许失败的指令再次执行; - 将未处理异常报告给调试器
UnhandledExceptionFilter首先检查当前应用程序是否在调试器控制下,如果是,返回EXCEPTION_CONTINUE_SEARCH,windows通知调试器,调试器通过ExceptionInformation成员定位异常。代码中可以调用IsDebuggerPresent检测当前进程是否处于调试器; - 通知设置的全局过滤函数
如果应用程序已经调用SetUnhandledExceptionFilter来指定全局异常过滤程序,UnhandledExceptionFilter将调用这个函数。如果异常函数返回值为EXCEPTION_EXECUTE_HANDLER或者EXCEPTION_CONTINUE_EXECUTION,UnhandledExceptionFilter将直接返回这个值给系统。如果异常函数返回EXCEPTION_CONTINUE_SEARCH,执行4 - 将未处理异常报告给调试器
调试器将再次被调用 - 终止进程
如果进程中某个线程曾调用SetErrorMode设置标志SEM_NOGPFAULTERRORBOX,那么UnhandledExceptionFilter会返回EXCEPTION_EXECUTE_HANDLER。
向量化异常和继续处理程序
SEH机制是一种基于代码框的(frame-based)机制,即每次当线程进入一个try块时,系统会在链表里加入一个节点。如果发生异常,系统一次访问链表中的每个代码框——从最晚进入的try块一直到最早进入的try块——并寻找每个try块的catch处理程序。一旦找到一个catch处理程序,系统再次访问链表,执行finally块。当展开结束后,系统从链表中移除所有的代码框。
Windows也提供了向量化异常处理(vectored exception handle,VEH)机制,它与SEH协同工作。程序可以注册一个函数,而不需要依赖于与语言有关的关键字。每当异常发生或一个未处理异常脱离标准SEH的控制时,这个函数就会被调用。
AddVectoredExceptionHandler函数负责异常处理程序的注册,注册的异常处理程序会被添加到函数列表中,当进程中的线程发生异常时,这些函数会被调用。
PVOID AddVectoredExceptionHandler(
ULONG bFirstInTheList,
PVECTORED_EXCEPTION_HANDLER pfnHandler);
// pfnHandler需要有以下签名
LONG WINAPI ExceptionHandler(struct _EXCEPTION_POINTERS* pExceptionInfo);
如果给bFirstInTheList参数传递的值是0,那么通过pfnHandler传递的异常处理函数就会添加到列表的尾端,如果传递给bFirstInTheList的值非0,函数就会被置于内部列表的头部。
当异常发生时,系统在执行SEH过滤程序之前,将按列表顺序逐个调用这些函数。一旦某个函数能够纠正发生的问题,应该马上返回EXCEPTION_CONTINUE_EXECUTION,已让抛出异常的指令再次执行。
只要某个向量化处理程序返回EXCEPTION_CONTINUE_EXECUTION,SEH过滤程序就不会有处理异常的机会。
如果异常处理函数不能纠正问题,应该返回EXCEPTION_CONTINUE_SEARCH,让列表中的其他处理函数有机会去处理异常。
如果所有的异常处理函数都返回EXCEPTION_CONTINUE_SEARCH,SEH过滤程序就会执行。
注意!VEH过滤函数不能返回EXCEPTION_EXECUTE_HANDLER。
删除之前安装的VEH异常处理函数
ULONG RemoveVectoredExceptionHandler(PVOID pHandler);
pHandler从之前AddVectoredExceptionhandler的返回值得到。
除了能在SEH之前处理异常,VEH还让我们能在未处理异常发生时得到通知。
// 注册一个继续处理程序
PVOID AddVectoredContinueHandler(
ULONG bFirstInTheList,
PVECTORED_EXCEPTION_HANDLER pfnHandler);
// 删除一个继续处理程序
ULONG RemoveVectoredContinueHandler(PVOID pHandler);
当未处理异常发生时,系统会按照列表逐个执行继续处理程序函数,需要特别说明的是,这些处理程序的执行时在SetUnhandledExceptionFilter所安装的全局异常处理程序返回EXCEPTION_CONTINUE_SEARCH之后才开始的。
一个继续处理程序可以返回EXCEPTION_CONTINUE_EXECUTION来停止它后面的继续处理程序的执行,并让系统再次执行失败指令。或者返回EXCEPTION_CONTINUE_SEARCH让系统执行在它后面的处理程序。
C++异常与结构化异常的比较
SEH由操作系统所提供,在任何语言中都可以使用。
C++异常处理只有在编写C++代码时才可以使用。
在创建一个C++ try块时,编译器会为我们生成一个SEH __try块,C++的catch语句对应SEH异常过滤程序,catch块中的代码则对应SEH __except块中的代码。
编译器也会为C++ throw语句生成对Windows RaiseException函数的调用,throw语句所使用的变量则成为RaiseException的附加参数。
// c++
void ChunkyFunky() {
try {
// Try body
...
throw 5;
}
catch(int x) {
// catch body
...
}
...
}
// windows
void ChunkyFunky() {
__try {
// Try body
...
RaiseException(Code=0xE06D7363,
Flag=EXCEPTION_NONCONTIUNUEABLE,
Args=5);
}
__except((ArgType==Integer)?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH) {
// catchbody
...
}
...
}
C++异常在其内部是通过结构化异常来实现的,我们可以借助这一特性使用两种机制。
最后
以上就是凶狠盼望为你收集整理的windows未处理异常未处理异常的全部内容,希望文章能够帮你解决windows未处理异常未处理异常所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复