未处理异常和C + +异常(上)

前一章讨论了当一个异常过滤器返回 E X C E P T I O N _ C O N T I N U E _ S E A R C H时会发生什么事情。返回EXCEPTION_CONTINUE_SEARCH 是告诉系统继续上溯调用树,去寻找另外的异常过滤器。但是当每个过滤器都返回E X C E P T I O N _ C O N T I N U E _ S E A R C H时会出现什么情况呢?在这种情况下,就出现了所谓的“未处理异常”(Unhandled exception)。

在第6章里我们已经知道,每个线程开始执行,实际上是利用 K e r n e l 3 2 . d l l中的一个函数来调用B a s e P r o c e s s S t a r t或B a s e T h r e a d S t a r t。这两个函数实际是一样的,区别在于一个函数用于进程的主线程(Primary thread):

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

注意这两个函数都包含一个S E H框架。每个函数都有一个t r y块,并从这个t r y块里调用主线程或辅助线程的进入点函数。所以,当线程引发一个异常,所有过滤器都返回 E X C E P T I O N _C O N T I N U E _ S E A R C H时,将会自动调用一个由系统提供的特殊过滤器函数: U n h a n d l e dE x c e p t i o n F i l t e r。

这个函数负责显示一个消息框,指出有一个进程的线程存在未处理的异常,并且能让用户

结束或调试这个进程。

下面是写个测试代码来看下:

测试代码,引发异常,但是没有用__except去处理。根据上面的那个创建进程的函数,会先调用U n h a n d l e dE x c e p t i o n F i l t e r弹个窗,点击关闭,然后全局展开,执行finally的代码。

25.1 即时调试

随时将调试程序连接到任何进程的能力称为即时调试(Just-in-time Debugging)。这里我们对它如何工作稍加说明:当程序员点击 C a n c e l按钮,就是告诉U n h a n d l e d E x c e p t i o n F i l t e r函数对进程进行调试。在内部,U n h a n d l e d E x c e p t i o n F i l t e r调用调试程序,这需要查看下面的注册表子关键字:

在这个子关键字里,?有一个名为D e b u g g e r的数值,在安装Visual Studio时被设置成下面的值:

这一行代码是告诉系统要将哪一个程序(这里是 M S D e v. ? e x e)作为调试程序运行。当然也可以选择其他调试程序。U n h a n d l e d E x c e p t i o n F i l t e r还在这个命令行中向调试程序传递两个参数。第一个参数是被调试进程的 I D。第二个参数规定一个可继承的手工复位事件,这个事件是由U n h a n d l e d E x c e p t i o n F i l t e r按无信号状态建立的。厂商必须实现他们的调试程序,这样才能认识指定进程I D和事件句柄的- p和- e选项。

在进程 I D和事件句柄都合并到这个串中之后, U n h a n d l e d E x c e p t i o n F i l t e r通过调用C r e a t e P r o c e s s来执行调试程序。?这时,调试程序进程开始运行并检查它的命令行参数。如果存在- p选项,调试程序取得进程I D,并通过调用D e b u g A c t i v e P r o c e s s将自身挂接在该进程上。

BOOL DebugActiveProcess(DWORD dwProcessID);

一旦调试程序完成自身的挂接,操作系统将被调试者(d e b u g g e e)的状态通报给调试程序。例如,系统将告诉调试程序,在被调试的进程中有多少线程?哪些 D D L加载到被调试进程的地址空间中?调试程序需要花时间来积累这些数据,以准备调试进程。在这些准备工作进行的时候,U n h a n d l e d E x c e p t i o n F i l t e r中的线程必须等待。为此,这要调用 Wa i t F o r S i n g l e O b j e c t函数并传递已经建立的手工复位事件的句柄作为参数。这个事件是按无信号状态建立起来的,所以被调试进程的线程要立即被挂起以等待事件。

在调试程序完全初始化之后,它要再检查它的命令行,找 - e选项。如果该选项存在,调试程序取得相应的事件句柄并调用 S e t E v e n t。调试程序可以直接使用事件的句柄值,因为事件句柄具有创建的可继承性,并且被调试进程对 U n h a n d l e d E x c e p t i o n F i l t e r函数的调用也使调试程序进程成为一个子进程。设定这个事件将唤醒被调试进程的线程。被唤醒的线程将有关未处理异常的信息传递给调试程序。调试程序接收这些通知并加载相应的源代码文件,再将自身放在引发异常的指令位置上。

还有,不必在调试进程之前等待异常的出现。可以随时将一个调试程序连接在任何进程上,只需运行“MSDEV -p PID”,其中P I D是要调试的进程的 I D。实际上,利用 Windows 2000Task Manager,做这些事很容易。当观察P r o c e s s标记栏时,可以选择一个进程,点击鼠标右键,并选择D e b u g菜单选项。这将引起 Task Manager去查看前面讨论过的注册表子关键字,调用C r e a t e P r o c e s s,并传递所选定的进程的 I D作为参数。在这里,Task Manager为事件句柄传送0值。

尝试了下那个函数,结果如下:

25.2 关闭异常消息框

有时候,在异常发生时,你可能不想在屏幕上显示异常消息框。例如,你可能不想让这些消息框出现在你产品的发售版本中。如果出现了消息框,很容易导致最终用户意外地启动调试程序来调试你的程序。最终用户只需点击一下消息框中的 C a n c e l按钮,就进入了不熟悉的、令人恐惶的区域 — 调试程序。可以使用几种不同的方法来防止这种消息框的出现。

25.2.1 强制进程终止运行

为防止U n h a n d l e d E x c e p t i o n F i l t e r显示异常消息框,可以调用下面的S e t E r r o r M o d e l函数,并向它传递一个S E M _ N O G P FA U LT E R R O R B O X标识符:

UINT SetErrorMode(UINT fuErrorMode);

然后,当调用U n h a n d l e d E x c e p t i o n F i l t e r函数来处理异常时,看到已经设置了这个标志,就会立即返回E X C E P T I O N _ E X E C U T E _ H A N D L E R。这将导致全局展开并执行B a s e P r o c e s s S t a r t或B a s e T h r e a d S t a r t中的处理程序。该处理程序结束进程。

主线程里异常不会崩溃

在其他线程里,也不会崩溃,相当于这个设置是整个进程的。

25.2.2 包装一个线程函数

使用另外一种办法也可以避免出现这个消息框,就是针对主线程进入点函数( m a i n、

w m a i n、Wi n M a i n或w Wi n M a i n)的整个内容安排一个t r y - e x c e p t块。保证异常过滤器的结果值总是E X C E P T I O N _ E X E C U T E _ H A N D L E R,这样就保证异常得到处理,防止了系统再调用U n h a n d l e d E x c e p t i o n F i l t e r函数。

在你的异常处理程序中,你可以显示一个对话框,在上面显示一些有关异常的诊断信息。

用户可以记录下这些信息,并通报给你公司的客户服务部门,以便能够找到程序的问题根源。

你应该建立这个对话框,这样用户只能结束程序而不能调用调试程序。

这种方法的缺点是它只能捕捉进程的主线程中发生的异常。如果其他线程在运行,并且其中有一个线程发生了一个未处理异常,系统就要调用内部的 U n h a n d l e d E x c e p t i o n F i l t e r函数。为了改正这一点,需要在所有的辅助线程进入点函数中包含t r y - e x c e p t块。

25.2.3 包装所有的线程函数

Wi n d o w s还提供另外一个函数,S e t U n h a n d l e d E x c e p t i o n F i l t e r,利用它可以按S E H格式包装所有的线程函数:

PTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(

PTOP_LEVEL_EXCEPTION_FILTER pTopLevelExceptionFilter);

在进程调用这些函数之后,进程的任何线程中若发生一个未处理的异常,就会导致调用程序自己的异常过滤器。需要将这个过滤器的地址作为参数传递给 S e t U n h a n d l e d E x c e p t i o n F i l t e r。过滤器函数原型必须是下面的样子:

LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo);

你可能会注意到这个函数同U n h a n d l e d E x c e p t i o n F i l t e r函数的形式是一样的。程序员可以在自己的异常过滤器中执行任何想做的处理,但要返回三个 E X C E P T I O N _ *标识符中的一个。表2 5 - 1给出了当返回各标识符时所发生的事。

为了使 U n h a n d l e d E x c e p t i o n F i l t e r函数再成为默认的过滤器,可以调用 S e t U n h a n d l e dE x c e p t i o n F i l t e r并传递 N U L L给它。而且,每当设置一个新的未处理的异常过滤器时,SetUnhandled ExceptionFilter就返回以前安装的异常过滤器的地址。如果 Unhandled ExceptionF i l t e r是当前所安装的过滤器,则这个返回的地址就是 N U L L。    如果你自己的过滤器要返回E X C E P T I O N _ C O N T I N U E _ S E A R C H,你就应该调用以前安装的过滤器,其地址通过S e t U n h a n d l e d E x c e p t i o n F i l t e r函数返回。

以下是一个测试例子:

25.2.4 自动调用调试程序

现在再介绍关闭U n h a n d l e d E x c e p t i o n F i l t e r消息框的最后一种方法。在前面提到的同一个注册表子关键字里,还有另外一个数据值,名为 A u t o。这个值用来规定U n h a n d l e d E x c e p t i o n F i l t e r是应该显示消息框,还是仅启动调试程序。如果 A u t o设置成1,U n h a n d l e d E x c e p t i o n F i l t e r就不显示消息框向用户报告异常,而是立即调用调试程序。如果 A u t o子关键设置成 0,U n h a n d l e d E x c e p t i o n F i l t e r就显示异常消息框,并按前面描述的那样操作。

25.3 程序员自己调用U n h a n d l e d E x c e p t i o n F i l t e r

U n h a n d l e d E x c e p t i o n F i l t e r函数是一个公开的、文档完备的 Wi n d o w s函数,程序员可以直接在自己的代码中调用这个函数。这里是使用这个函数的一个例子:

在F u n c a d e l i c函数中,t r y块中的一个异常导致E x p F l t r函数被调用。G e t E x c e p t i o n I n f o r m a t i o n的返回值作为参数传递给 E x p F l t r函数。在异常过滤器内,要确定异常代码并与 E X C E P T I O N _A C C E S S _ V I O L AT I O N相比较。如果发生一个存取违规,异常过滤器改正这个问题,并从过滤器返回E X C E P T I O N _ C O N T I N U E _ E X E C U T I O N。这个返回值导致系统从最初引起异常的指令继续执行。

如果发生了其他异常,E x p F l t r调用U n h a n d l e d E x c e p t i o n F i l t e r,将E X C E P T I O N _ P O I N T E R S结构的地址传递给它作为参数。U n h a n d l e d E x c e p t i o n F i l t e r显示消息框,可使程序员结束进程或开始调试进程。U n h a n d l e d E x c e p t i o n F i l t e r的返回值再由E x p F l t r返回。

Windows核心编程 第2 5章 未处理异常和C ++异常(上)的更多相关文章

  1. 【windows核心编程】 第六章 线程基础

    Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ①    一个是线程的内核 ...

  2. Windows核心编程:第4章 进程

    Github https://github.com/gongluck/Windows-Core-Program.git //第4章 进程.cpp: 定义应用程序的入口点. // #include &q ...

  3. Windows核心编程:第14章 探索虚拟内存

    Github https://github.com/gongluck/Windows-Core-Program.git //第14章 探索虚拟内存.cpp: 定义应用程序的入口点. // #inclu ...

  4. Windows核心编程:第5章 作业

    Github https://github.com/gongluck/Windows-Core-Program.git //第5章 作业.cpp: 定义应用程序的入口点. // #include &q ...

  5. Windows核心编程 第十五章 在应用程序中使用虚拟内存

    第1 5章 在应用程序中使用虚拟内存 Wi n d o w s提供了3种进行内存管理的方法,它们是: • 虚拟内存,最适合用来管理大型对象或结构数组. • 内存映射文件,最适合用来管理大型数据流(通常 ...

  6. Windows核心编程:第13章 内存体系结构

    Github https://github.com/gongluck/Windows-Core-Program.git //第13章 内存体系结构.cpp: 定义应用程序的入口点. // #inclu ...

  7. Windows核心编程:第11章 Windows线程池

    Github https://github.com/gongluck/Windows-Core-Program.git //第11章 Windows线程池.cpp: 定义应用程序的入口点. // #i ...

  8. Windows核心编程:第9章 用内核对象进行线程同步

    Github https://github.com/gongluck/Windows-Core-Program.git //第9章 用内核对象进行线程同步.cpp: 定义应用程序的入口点. // #i ...

  9. Windows核心编程:第7章 线程调度、优先级和关联性

    Github https://github.com/gongluck/Windows-Core-Program.git //第7章 线程调度.优先级和关联性.cpp: 定义应用程序的入口点. // # ...

随机推荐

  1. MyBatis(四):自定义持久层框架优化

    本文所有代码已上传至码云:https://gitee.com/rangers-sun/mybatis 修改IUserDao.UserMapper.xml package com.rangers; im ...

  2. 使用函数式语言实践DDD

    长期以来我都在实践OOP,进而通过OOP来实现DDD,特别是如何通过面向对象的技巧来建立一个领域模型.OO的一些特性在建立领域模型时显得恰如其分,能否掌握OO的技巧,对创建领域模型有着至关重要的作用. ...

  3. 【译】Visual Studio 的 Razor 编辑器的改进

    自从我们在一个通用的 Razor 语言服务器上发布了 Visual Studio 的一个新的实验性 Razor 编辑器的第一个预览版以来,已经过去了6个月,现在是时候更新一下我们的进展了.团队一直在努 ...

  4. 解决图片把父元素向下撑大大约3px问题

    现象  bug: 图片在div\li\dt 等  图片把父元素向下撑大大约3px  <style>         img {             width: 30%; //这里由于 ...

  5. 【python+selenium的web自动化】- 针对上传操作的实现方案

    如果想从头学起selenium,可以去看看这个系列的文章哦! https://www.cnblogs.com/miki-peng/category/1942527.html 关于上传操作 ​ 上传有两 ...

  6. 10 分钟轻松学会 Jackson 反序列化自动适配子类

    作者:丁仪 来源:https://chengxuzhixin.com/blog/post/Jackson-fan-xu-lie-hua-zi-dong-shi-pei-zi-lei.html json ...

  7. C++11正则表达式

  8. 使用docker搭建sonarqube

    sonarqube是一款代码质量检查工具,使用sonar扫描我们写过的代码,可以有助于检查出代码的bug.规范性和健壮性,有助于提高我们的代码质量. 一.安装docker 安装完成之后,命令行输入 d ...

  9. java例题_34 用指正对三个数排序

    1 /*34 [程序 34 三个数排序] 2 题目:输入 3 个数 a,b,c,按大小顺序输出. 3 程序分析:利用指针方法. 4 */ 5 6 /*分析 7 * 指针方法的本质是按地址传值,将a,b ...

  10. 使用 Github Actions artifact 在 workflow job 之间共享数据

    (AgileConfig)[https://github.com/kklldog/AgileConfig] 在使用 react 编写UI后,变成了一个彻彻底底的前后端分离的项目,上一次解决了把reac ...