未处理异常和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. flex布局个人总结

    <html> <div class="box1"> <span>1</span> <span>2</span> ...

  2. [ONTAK2010] Peaks 加强版

    [ONTAK2010] Peaks 加强版 题目大意:原题变为强制在线查询 Solution 读入山高,排序后依然建立树链,初始化并查集,初始化重构树新节点标号为\(n+1\) 读入边,按照边权从小到 ...

  3. 死磕生菜 -- lettuce 间歇性发生 RedisCommandTimeoutException 的深层原理及解决方案

    0x00 起源 项目的一些微服务集成了 Spring Data Redis,而底层的 Redis 客户端是 lettuce,这也是默认的客户端.微服务在某些环境中运行很正常,但在另一些环境中运行就会间 ...

  4. 使用dcmtk库读取.dcm文件并获取信息+使用OpenCV显示图像

    借助VS2013和OpenCV的绘图功能,在工程DICOMReader.sln中实现了对单张.dcm图像的读取与显示,以下是详细步骤. 前期准备工作 编译器:VS2013 库:dcmtk-3.6.0( ...

  5. UML和设计模式原则总结

    UML总结: uml就是统一建模语言,包括语义概念 标记符号和指南 具有静态 动态 环境上的和组织性的部分 .它不是编程语言.uml预览它涉及的主要领域有结构性(静态视图,用例视图,构件图,实现视图, ...

  6. 微信开发者工具导入 wepy 项目“app.json 未找到”报错解决方法

    版本信息: 微信开发者工具:1.03.2101150 wepy:2.0 wepy/cli:6.14.8 问题描述 按照 wepy 文档中的步骤新建项目: $ npm install @wepy/cli ...

  7. 简易计算器实现:while循环+switch语句

    个人练习: 写一个计算器,要求实现加减乘除功能,并且能循环接收新的数据,通过用户交互实现(即Scanner对象) 用到了 while循环 switch语句,实现了数据的循环输入并计算!!!!妙啊!!! ...

  8. 痞子衡嵌入式:关于恩智浦入驻B站的一些思考

    故事起源于这周五的一封公司邮件,标题是"恩智浦B站首支原创视频播放量破万",公司Marcom部门特地群发了这个邮件给全体员工,并鼓励大家积极DIY工作相关的有趣视频,为公司这个萌新 ...

  9. gRPC在 ASP.NET Core 中应用学习(二)

    前言: 上一篇文章中简单的对gRPC进行了简单了解,并实现了gRPC在ASP.NET Core中服务实现.客户端调用:那么本篇继续对gRPC的4中服务方法定义.其他使用注意点进一步了解学习 一.gRP ...

  10. Fundamentals of Power Electronics 目录

    Fundamentals of Power Electronics Translated By Siwei Yang (前六章翻译自Edition 2,后面部分翻译自Edition 3) Part I ...