未处理异常和C++异常

——Windows核心编程学习手札之二十五

当一个异常过滤器返回EXCEPTION_CONTINUE_SEARCH标识符时是告诉系统继续上溯调用树,寻找另外的异常过滤器,但当每个过滤器都返回EXCEPTION_CONTINUE_SEARCH标识符,出现了所谓“未处理异常”。

每个线程开始执行,实际是利用kernel32.dll中的一个函数来调用BaseProcessStart或BaseThreadStart,这两个函数实际是一样的,区别在于一个函数用于进程的主线程(primary thread):

VOID BaseProcessStart(PPROCESS_START_ROUTINE pfnStartAddr){

__try{

ExitThread((pfnStartAddr)());

}

__except(UnhandledExceptionFilter(GetExceptionInfomation())){

ExitProcess(GetExceptionCode());

}

//note:we never get here

}

另一个函数用于进程的所有辅助线程(Secondary thread):

VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr,PVOID pvParam){

__try{

ExitThread((pfnStartAddr)( pvParam));

}

__except(UnhandledExceptionFilter(GetExceptionInfomation())){

ExitProcess(GetExceptionCode());

}

//note:we never get here

}

这两个函数都包含一个SHE框架,每个函数都有一个try块,并从这个try块里调用主线程或辅助线程的进入点函数。当线程引发一个异常时,所有过滤器都返回EXCEPTION_CONTINUE_SEARCH时,将会自动调用一个由系统提供的特殊过滤器函数:

LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);

这个函数负责显示一个消息框,指出有一个进程的线程存在未处理的异常,并且能让用户结束或调试这个进程。在消息框的异常描述之后,提供用户两个选择:

1)选择“ok”按钮,将导致UnhandledExceptionFilter返回EXCEPTION_CONTINUE_SEARCH,这将引起全局展开,所有的finally块都要执行,然后BaseProcessStart或BaseThreadStart中的处理程序执行。这两个处理程序都是ExitProcess,退出进程,退出的代码是异常代码,这是进程的线程结束了进程本身不是操作系统,意味这程序员可以控制这种行为并改变它。

2)选择“Cancel”按钮,UnhandledExceptionFilter试图加载一个调试程序,并将这个调试程序挂接在进程上,通过将调试程序附在进程上,可以检查全局、局部和静态变量的状态,设置断点,检查调用树,重新启动进程,以及调试一个进程可以做的任何事情。

注意:上面是在用户方式(user-mode)下的程序开发,对于运行在内核方式(kernel-mode)出现未处理异常,未处理异常是在操作系统中或更可能在设备驱动程序中,而不是在应用程序中,这样的未处理异常表示一个严重的程序错误(bug)!如果一个低级虚拟内存函数产生一个异常,系统查找是否有内核方式异常过滤器准备处理这个异常,如果系统找不到,则异常是未处理的,如果一个未处理异常发生在内核方式,让系统继续运行是不安全的,所以系统在这种情况下不会调用UnhandledExceptionFilter函数,而是显示所谓的蓝屏死机(Blue Screen of Death),显示画屏切换到只包含文本的蓝屏视频方式,并且计算机被停机(balt),显示的文本告诉是哪个设备驱动程序被加载,并且该模块中包含引发未处理异常的代码。

当程序员选择cancel按钮时,告诉UnhandledExceptionFilter函数对进程进行调试,随时将调试程序连接到任何进程的能力称为即时调试(Just-in-time Debugging)。在内部,UnhandledExceptionFilter调用调试程序,需要查看下面的注册表子关键字:

HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/WindowsNT/

CurrentVersion/AeDebug

该关键字里,有一个Debugger的数值,安装Visual Studio时被设置成下面的值:

C:/Program Filese/Microsoft Visual Studio/Common/MSDev98/Bin/msdev.exe

-p %ld  –e %ld

在windows98中,这些值不存放在注册表中,而是存放在Win.ini文件中。

这个值告诉系统要将哪个程序(这里是msdev.exe)作为调试程序运行,当然也可以选择其他调试程序。UnhandledExceptionFilter还在命令行里向调试程序传递两个参数,第一个参数是被调试进程ID;第二个参数规定一个可继承的手工复位事件,这个事件是由UnhandledExceptionFilter按无信号状态建立的,厂商必须实现自己的调试程序,这样才能认识指定进程ID和事件句柄的-p和-e选项。

在进程ID和事件句柄都合并到这个串中后,UnhandledExceptionFilter通过调用CreateProcess来执行调试程序,这是,调试程序进程开始运行并检查它的命令行参数,如果存在-p选项,调试程序取得进程ID,并通过调用DebugActiveProcess将自身挂接到该进程上:

BOOL DebugActiveProcess(DWORD dwProcessID);

一旦调试程序完成自身的挂接,操作系统将被调试者(debuggee)的状态通报给调试程序。在调试程序完全初始化后,要再检查它的命令行,找-e选项,如该项存在,调试程序取得相应事件句柄并调用SetEvent。

另外,不必在调试进程之前等待异常的出现,可以随时将一个调试程序连接在任何进程上,只需运行MSDEV –p PID,其中PID是要调试的进程的ID,实际在Windows2000的Task Manager里选择一个进程,并选择debug菜单,就将引起Task Manager去查看前面的注册表子关键字,调用CreateProcess,并传递所选定的进程ID作为参数,在这里,Task Manage为事件句柄传送0值。

如果异常发生时,不想在屏幕上显示消息框,有下面几个方法:

1)强制进程终止运行

为防止UnhandledExceptionFilter显示异常消息框,可以调用SetErrorMode函数,并向它传递一个SEM_NOGPFAULTERRORBOX标识符:

UNINT SetErrorMode(UINT fuErrorMode);

当调用UnhandledExceptionFilter函数来处理异常时,看到已经设置了这个标志,就立即返回EXCEPT_EXECUTE_HANDLER,这将导致全局展开并执行BaseProcessStart或BaseThreadStart中的处理程序,该处理程序结束进程。

2)包装一个线程函数

在主线程进入点函数 (main、wmain、WinMain、wWinMain)的整个内容安排一个try-except块,保证异常过滤器的结果值总是EXCEPT_EXECUTE_HANDLER,这样就保证异常总能得到处理,防止系统再调用UnhandledExceptionFilter函数。缺点是只能捕捉主线程中发生的异常,而其他辅助线程则不能覆盖。

3)包装所有线程函数

Windows提供了SetUnhandledExceptionFilter函数可以按SHE格式包装所有线程函数。

PTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(

PTOP_LEVEL_EXCEPTION_FILTER pTopLevelExceptionFilter);

在进程调用这个函数后,进程的任何线程发生一个未处理异常,就会导致调用程序自己的异常过滤器,需要将这个过滤器地址作为参数传递给SetUnhandledExceptionFilter。过滤器函数原型是:

LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);

3)自动调用调试程序

在设置调试程序的注册表同一个关键字里,有个Auto值,该值用来规定UnhandledExceptionFilter是应该显示消息框还是仅启动调试程序,如果Auto设置为1,UnhandledExceptionFilter就不显示消息框向用户报告异常,而是立即调用调试程序,如设置为0,就按照上面所说的。

UnhandledExceptionFilter函数的内部执行情况:

1)如果发生一个存取违规并且是由于试图写内存(相对于读内存)引起的,系统要查看是不是要修改一个exe模块或dll模块中的资源,默认下资源是只读的,试图修改资源会引起存取异常。然而16位Windows允许修改资源,从兼容性考虑,32位和64位也应允许修改资源,所以当想要修改资源时,UnhandledExceptionFilter调用VirtualProtect,将资源页上的保护改成PAGE_READWRITE,并返回EXCEPTION_CONTINUE_EXECUTION;

2)如果已经调用了SetUnhandledExceptionFilter指定自己的过滤器,UnhandledExceptionFilter就调用指定的过滤器,如果自己的过滤器返回EXCEPTION_EXECUTE_HANDLER或EXECEPTION_CONTINUE_EXECUTION,UnhandledExceptionFilter就将这个值返回给系统,如果没有设置自己的未处理异常过滤器,或自己的未处理异常过滤器返回EXCEPTION_CONTINUE_SEARCH,则进入第3步;

3)如果进程是在调试程序下运行,就返回EXCEPTION_CONTINUE_SEARCH。调试程序显示一个消息框并运行调试进程(IsDebuggerPresent函数用来确定一个进程是否正在被调试);

4)如果进程中一个线程以SEM_NOGPFAULTERRORBOX标志为参数调用SetErrorCode,UnhandledExceptionFilter就返回EXCEPTION_EXECUTE_HANDLER;

5)如果进程在一个作业job里并且作业的限制信息设定了JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION标志,则UnhandledExceptionFilter返回EXCEPTION_EXECUTE_HANDLER;

6)UnhandledExceptionFilter查阅注册表并取出Auto值,如果是1,到第7步,如是0,则向用户显示一个消息框;

7)UnhandledExceptionFilter产生调试程序,首先调用CreateEvent建立一个无信号的、手工复位的事件,这个事件的句柄可继承,然后从注册表中取出Debugger值,调用sprintf把它粘帖到进程ID(通过调用GetCurrentProcessID函数得到)和事件句柄里。STARTUPINFO的lpDesktop成员也设置成”Winsta0//Default”,这样调试程序就出现在交互式桌面上。

8)当调试程序完成初始化,就设置事件句柄,这将唤醒UnhandledExceptionFilter中的线程,这样进程就在调试程序下运行,UnhandledExceptionFilter返回EXCEPTION_CONTINUE_SEARCH。

SHE(结构化异常处理)可用于任何编程语言的操作系统设施,而C++异常只能用于编写C++代码。如果是编写C++程序,应使用C++异常处理而不是结构化异常处理,理由是C++异常处理是语言的一部分,编译器知道C++类对象是什么,也就是说编译器能够自动生成代码来调用C++对象析构函数,保证对象的清除。

不过,Microsoft Visual C++编译器也利用了操作系统的SHE实现了C++异常处理,所以当建立一个C++的try块时,编译器就生成一个SHE的__try块,一个C++的catch测试变成一个SHE异常过滤器,并且catch中的代码变成SHE的__except块中的代码,实际上,C++的throw语句,编译器生成windows对应的RaiseException函数的调用,用于throw语句的变量传递给RaiseException作为附加的参数,如上,C++异常在内部是由SHE实现的。

正常情况下,C++异常处理不能使程序从硬件异常中恢复,硬件违规就是存取违规或零作除数这种异常,但microsoft已经对其编译器增加了这种支持能力。

未处理异常和C++异常——Windows核心编程学习手札之二十五相关推荐

  1. 异常处理程序和软件异常——Windows核心编程学习手札之二十四

    异常处理程序和软件异常 --Windows核心编程学习手札之二十四 CPU负责捕捉无效内存访问和用0除一个数值这种错误,并相应引发一个异常作为对错误的反应,CPU引发的异常称为硬件异常(hardwar ...

  2. 窗口消息——Windows核心编程学习手札之二十六

    窗口消息 --Windows核心编程学习手札之二十六 Windows允许一个进程至多建立10000个不同类型的用户对象(user object):图符.光标.窗口类.菜单.加速键表等,当一个线程调用一 ...

  3. 插入DLL和挂接API——Windows核心编程学习手札之二十二

    插入DLL和挂接API --Windows核心编程学习手札之二十二 如下情况,可能要打破进程的界限,访问另一个进程的地址空间: 1)为另一个进程创建的窗口建立子类时: 2)需要调试帮助时,如需要确定另 ...

  4. DLL的高级操作技术——Windows核心编程学习手札之二十

    DLL的高级操作技术 --Windows核心编程学习手札之二十 显示加载DLL模块: HINSTANCE LoadLibrary(PCTSTR pszDLLPathName); HINSTANCE L ...

  5. 结束处理程序——Windows核心编程学习手札之二十三

    结束处理程序 --Windows核心编程学习手札之二十三 使用SEH可以只关注程序要完成任务,而运行中发生的错误,系统将会发现并通知.Windows引入SHE是为了便于操作系统的开发,使用SHE所造成 ...

  6. 线程本地存储器——Windows核心编程学习手札之二十一

    线程本地存储器 --Windows核心编程学习手札之二十一 C/C++运行期库使用线程本地存储器,运行期库是在多线程应用程序出现前设计的,因此运行期库里的大多数函数是用于单线程应用程序的.函数strt ...

  7. Unicode——Windows核心编程学习手札之二

    Unicode --Windows核心编程学习手札之二 处理软件本地化的核心在于处理不同的字符集.文本串一直作为一系列单字节字符进行编码,并在结尾处放上一个零,当调用strlen函数时,获取以/0结尾 ...

  8. 《Windows核心编程》读书笔记二十五章 未处理异常,向量化异常处理与C++异常

    第二十五章  未处理异常,向量化异常处理与C++异常 本章内容 25.1 UnhandledExceptionFilter函数详解 25.2 即时调试 25.3 电子表格示例程序 25.4 向量化异常 ...

  9. 线程与内核对象的同步——Windows核心编程学习手札之九

    线程与内核对象的同步 --Windows核心编程学习手札之九 用户方式下的线程同步机制具有速度快的特点,但有其局限性,对于许多应用程序来说,并不合适.例如,互锁函数家族只能在单值上运行,根本无法使线程 ...

最新文章

  1. 第4关:非递归实现二叉树左右子树交换
  2. 搜索引擎之---Apache solr的实现
  3. T研究:国内云BPM市场规模尚小,预计2018年仅为3.29亿元
  4. matlab常用的代码,matlab常用代码
  5. HTML实现选择数据库字段,django项目中在后台获取了数据库的某一列,如何将其显示在html模板中的select标签内的option选项下?...
  6. Python 16th Day
  7. JQuery-Ztree 树插件下载 与 快速入门
  8. Codeforces Round #580 (Div. 1)(CF1205)A-E
  9. 机器人工程师入门知识框架(思维导图)
  10. 软件测试面试题:什么是Ramp up?你如何设置?
  11. python学习笔记——语法基础
  12. 【资料整理】高精地图
  13. np.percentile()函数
  14. STC单片机 闪烁灯,LED流水灯 程序讲解
  15. 内网渗透(五十二)之域控安全和跨域攻击-搭建和查看域信任关系
  16. 怎样找回u盘里误删的文件
  17. ROC阳性结果还是阴性结果?
  18. 浅谈react hook ( ref)
  19. 剑三游戏总是显示服务器繁忙,剑网三缘起:终究低估了老玩家的热情,服务器被挤爆...
  20. Sybase数据库常用时间操作

热门文章

  1. Java虚拟机详解(四)------垃圾收集器
  2. jquery text html width heigth的用法
  3. Java stackoverflowerror异常与outofmemoryerror异常区别
  4. 将CSS放头部,JS放底部,可以提高页面的性能的原因
  5. (转)jQuery.fn.extend与jQuery.extend到底区别在哪?
  6. linux下通过yum安装svn及实现SVN与WEB同步解决方案[阿里云]
  7. Ubuntu18.04安装Multiwfn
  8. OTA和Recovery系统升级流程介绍
  9. platform_device与platform_driver
  10. 手机通过WIFI连上ZXV10 H618B路由器但不能上网问题的解决