24.1  程序的结构

(1)try/except框架

__try{

   //被保护的代码块
……
} __except(except fileter/*异常过滤程序*/){
//异常处理程序
}

(2)说明

  ①当__try块中的代码发生异常时,__except()中的过滤程序就被调用。

  ②过滤程序可以是一个简单的表达式或一个函数(返回值应为EXCEPTION_CONTINUE_SEARCH、EXCEPT_CONTINUE_EXECUTE或EXCEPT_EXECUTE_HANDLER之一)

  ③过滤表达式中可以调用GetExceptionCode和GetExceptionInformation函数取得正在处理的异常信息。但这两个函数不能在异常处理程序中使用。

  ④与try/finally不同,try/except中可以使用return、goto、continue和break,它们并不会导致局部展开。

24.2 异常过滤程序

(1)返回值

标识

值

说明

EXCEPTION_EXECUTE_HANDLER

1

执行except花括号内代码,同时执行全局展开。最后程序从except花括号后面的第一句代码继续运行。

EXCEPTION_CONTINUE_SEARCH

0

向外层查找带except的try块,并调用对应的异常过滤程序。

EXCEPTION_CONTINUE_EXECUTE

-1

重新执行导致异常的那条CPU指令本身。

(2)全局展开——异常过滤程序返回EXCEPTION_EXECUTE_HANDLER是会执行全局展开。

  ①当某个__try块中的代码触发了异常时(也可能是__try块中调用的函数中引发异常),操作系统会从最靠近引发异常代码的地方开始从下层往上层查找__except块(这里的层是指__try块的嵌套层),对于找到的每一个__except块,会先计算它的异常过滤器,如果过滤器返回EXCEPTION_CONTINUE_SEARCH,则说明此__except块不处理此类异常,需要继续往上层查找,如果某过滤器返回EXCEPTION_EXECUTE_HANDLER则说明此__except块可以处理此类异常,即找到了异常的处理代码,此时停止查找,但是在执行该__except块中的异常处理代码之前,要先进行全局展开。

  ②全局展开的过程与查找__except块的过程类似,只不过这次是查找从底层向上查找__finally块,查找过程中遇到的每一个__finally块中的代码都被执行,直到查找到前面说的处理异常的__except块那一层停止,这时全局展开完成,然后执行__except块中的异常处理代码。

  ③执行完异常处理代码之后,指令流从__except块后的第一条指令开始。从这里也可以看出全局展开也是为了保证__finally语义的正确性,因为指令流从引发异常代码转到到__except异常处理代码时也导致了指令流从__try块嵌套层中所有与__finally对应的__try块中流出,由前面的__finally语义说明可知,此时必须要执行全局展开过程以包成__finally语义的正确性。

(3)停止全局展开——将return置于finally块中可阻止全局展开。【未定义行为,VC2013直接报错了!】

(4)慎用EXCEPTION_CONTINUE_EXECUTION

  ①尝试修复错误,出现失败的实例分析

  *pchBuffer = TEXT("J");//C/C++语句

  //编译后的产生的机器指令
MOV EAX,DWORD PTR[pchBuffer]
MOV WORD PTR[EAX], 'J' //导致异常的指令。当异常过滤程序捕获该异常后,修正
//pchBuffer,让其指向一个正确的地址。并让系统重新
//执行第二要CPU指令。问题在于寄存器不可能自动更新
//以反映变量pchBuffer的更新,于是该异常又致另一个导
//异常,程序陷入了死循环

  ②虚拟内存结合SEH可实现按需调拨存储器,有时能写出运行速度快和高效的应用程序(见第15章的《如何预订大块地址空间和为地址空间稀疏调拨存储器》

  ③系统为线程栈建了一个SEH框。当线程试图访问栈中尚未调拨存储器的区域时,会引发一个异常。系统内部的异常过滤程序会捕获到该异常并在内部调用VirtualAlloc为线程栈调拨更多的存储,并且返回EXCEPTION_CONTINUE_EXECUTION让原先抛出异常的指令重新执行下去。

【SEHAndMemory】演示虚拟内存的按需调拨

#include <windows.h>
#include <tchar.h>
#include <locale.h> #define PAGELIMIT 80
LPBYTE lpNxtPage;
DWORD dwPages = ;
DWORD dwPageSize;//页面大小,一般为4KB INT PageFualtExceptionFilter(DWORD dwCode){
LPVOID lpvResult; //不是非法访问内存
if (dwCode !=EXCEPTION_ACCESS_VIOLATION){
return EXCEPTION_EXECUTE_HANDLER;//执行except块的异常处理程序代码
} //当超过指定的页面数时
if (dwPages >=PAGELIMIT){
return EXCEPTION_EXECUTE_HANDLER;//执行except块的异常处理程序代码
} //非法访问内存,则为预订的空间提交下一页物理存储器
lpvResult = VirtualAlloc((LPVOID)lpNxtPage, dwPageSize, MEM_COMMIT, PAGE_READWRITE);
if (lpvResult == NULL){
return EXCEPTION_EXECUTE_HANDLER;//执行except块的异常处理程序代码
} //提交成功
dwPages++;
lpNxtPage += dwPageSize; _tprintf(_T("第%d页提交成功!\n"), dwPages);
return EXCEPTION_CONTINUE_EXECUTION; //重新执行触发异常的那条CPU指令
} int main(){
_tsetlocale(LC_ALL, _T("chs")); LPVOID lpvBase;LPTSTR lpPtr;BOOL bSuccess;
SYSTEM_INFO sSysInfo;
GetSystemInfo(&sSysInfo);
dwPageSize = sSysInfo.dwPageSize; _tprintf(_T("CPU页面大小为%dKB.\n"), sSysInfo.dwPageSize / ); //预订存储器
lpvBase = VirtualAlloc(NULL, PAGELIMIT*dwPageSize, MEM_RESERVE, PAGE_NOACCESS); lpPtr = (LPTSTR)(lpNxtPage = (LPBYTE)lpvBase);
for (DWORD i = ; i < PAGELIMIT*dwPageSize/sizeof(TCHAR);i++){
__try{
lpPtr[i] = _T('a');//写入一个字节的数据
}
__except (PageFualtExceptionFilter(GetExceptionCode())){
_tprintf(_T("异常被处理\n"));
//ExitProcess(GetLastError());
}
} bSuccess = VirtualFree(lpvBase, , MEM_RELEASE);
_tprintf(_T("释放操作%s.\n"), bSuccess ? _T("成功") : _T("失败"));
_tsystem(_T("PAUSE"));
return ;
}

24.3 GetExceptionCode

(1)GetExceptionCode是个内联函数,其代码直接嵌入到被调用的地方(注意与函数调用的区别),它的返回值表明刚刚发生的异常的类型(定义在WinBase.h中,如EXCEPTION_ACCESS_VIOLATION)

(2)该函数只能在异常过滤程序里(即__except之后的小括号内)或者异常处理程序的代码里调用(__except块后面的花括号内),但不能在异常过滤函数中使用。

//合法代码

__try{

y = 0;

x = / y;

}

__except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?

EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){ //在__except块的小括内使用,合法

switch (GetExceptionCode()){ //__except块的花括中使用,合法!

......

}

}

//非法代码

LONG MyFilter(void){}

{

//在异常过滤函数中使用GetExceptionCode,不合法!

return ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ?

EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH);

}

__try{

y = 0;

x = 4 / y;

}

__except(MyFilter()){ //可改成将GetExceptionCode作为参数传给MyFilter的形式。

//处理异常

}

(3)异常错误代码的规则

位

31-30

29

28

27-16

15-0

内容

严重性

Microsoft/

Customer

保留位

设备代码

异常代码

含义

0=Success

1=Informational

2=Warning

3=error

0=Mircosoft所定义的代码

1=Customer所定义的代码

一直为0

前256个值为Micorsoft所保留。(如FACILITY_NULL(0)表示该异常可以在系统任何设备出现,并不只发生在一些特定的设备上)

由Microsoft/

Customer所定义的代码

24.4 GetExceptionInformation

(1)GetExceptionInformation可获取异常发生时,系统向发生异常的线程栈中压入的EXCEPTION_RECORD、CONTEXT和EXCEPTION_POINTERS结构中的异常信息或CPU有关的信息

(2)这个函数只能在异常过滤程序中调用(即__except块的小括号),因为EXCEPT_RECORD、CONTEXT和EXCEPTION_POINTER数据结构只有在系统计算异常过滤程序时才有效。一旦控制流被转移到其他地方,这些栈上的数据结构会被销毁。但我们可以自己保存他们,以备后用。

//保存栈中异常信息的方法
void FuncSkunk(){
//声明一些可以保存异常信息的结构体,须在try块外面声明
EXCEPTION_RECORD SavedExceptRec;
CONTEXT SavedContext; __try{ }
__except ( //注意逗号表达式,取最后一个表达式为整个表达式的值。
SavedExceptRec = *(GetExceptionInformation())->ExceptionRecord,
SavedContext = *(GetExceptionInformation())->ContextRecord,
EXCEPTION_EXECUTE_HANDLER){
//异常处理
}
}

(3)EXCEPTION_RECORD结构体——刚发生的异常的详细信息

字段

说明

DWORD ExceptionCode

异常代码,就是GetExceptionCode函数的返回值

DWORD ExceptionFlags

异常标志

0—表示继续的异常;EXCEPTION_NONCONTINUABLE—不可继续的异常,如果程序试图在一个不可继续的异常之后继续执行,会引发EXCEPTION_NONCONTINUABLE_EXCEPTION异常。

PEXCEPTION_RECORD pExceptionRecord

指向另一个未处理异常的EXCEPTION_RECORD结构。(即嵌套异常发生时,异常会形成异常链)

PVOID ExceptionAddress

导致异常的CPU指令的地址

DWORD NumberParameters

ExceptionInformation数组里元素的个数。对绝大部分的异常来说,这个值为0。

ULONG_PTR ExceptionInformation

[EXCEPTION_MAXIMUM_PARAMETERS]

描述异常的附加参数数组,对绝大部分的异常来说,这个数组元素都未定义。

24.5 软件异常——RaiseException函数

参数

说明

DWORD dwExceptionCode

要抛出异常的标识符,可参考《异常错误代码规则》来编写

DWORD dwExceptionFlags

必须下列两者之一

0:

EXCEPTION_NONCONTINUABLE:异常不可继续,即不能再异常过滤程序中返回EXCEPTION_CONTINUE_EXECUTE,否则重新执行那条导致错误的CPU指令会继续抛出一个新的EXCEPTION_NONCONTINUABLE_EXCEPTION异常。

DWORD nNumberOfArguments

用来传递有关抛出异常的附加信息。一般不需要。可将nNumberOfArgument设为0。pArguments设为NULL。

Const ULONG_PTR* pArguments

返回值

void

【RaiseException程序】——演示自己抛出的软件异常

#include <tchar.h>
#include <windows.h> DWORD FilterFunction(){
_tprintf(_T("")); //第1句被输出的语句
return EXCEPTION_EXECUTE_HANDLER;
} int main(){
__try{
__try{
RaiseException(, , , NULL);
}
__finally{
_tprintf(_T("")); //第2句被输出的语句
}
}
__except (FilterFunction()){
_tprintf(_T("3\n")); //第3句被输出的语句
}
_tsystem(_T("PAUSE"));
return 0;
}

第24章 SEH结构化异常处理_异常处理及软件异常的更多相关文章

  1. 第25章 SEH结构化异常处理_未处理异常及向量化异常

    25.1 UnhandledExceptionFilter函数详解 25.1.1 BaseProcessStart伪代码(Kernel32内部) void BaseProcessStart(PVOID ...

  2. 第23章 SEH结构化异常处理(3)_终止处理程序

    23.3 终止处理程序 23.3.1 程序的结构 (1)框架 __try{ //被保护的代码块 …… } __finally{ //终止处理 } (2)__try/__finally的特点 ①fina ...

  3. 第23章 SEH结构化异常处理(2)_编译器对系统SEH机制的封装

    23.2 编译器层面对系统SEH机制的封装 23.2.1 扩展的EXCEPTION_REGISTRATION级相关结构:VC_EXCEPTION_REGISTRATION (1)VC_EXCEPTIO ...

  4. 第23章 SEH结构化异常处理(1)_系统SEH机制

    23.1 基础知识 23.1.1 Windows下的软件异常 (1)中断和异常 ①中断是由外部硬件设备或异步事件产生的 ②异常是由内部事件产生的,可分为故障.陷阱和终止三类. (2)两种异常处理机制: ...

  5. 异常处理第三讲,SEH(结构化异常处理),异常展开问题

    异常处理第三讲,SEH(结构化异常处理),异常展开问题 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 不知道昨天有木有 ...

  6. 《Linux命令行与shell脚本编程大全》第十二章 使用结构化命令

    许多程序要就对shell脚本中的命令施加一些逻辑控制流程. 结构化命令允许你改变程序执行的顺序.不一定是依次进行的 12.1 使用if-then语句 如下格式: if command then     ...

  7. Linux编程 24 shell编程(结构化 if [ condition ] 数值比较,字符串比较)

    一.概述 接着上篇讲的结构化命令,最后讲到了test命令的另一种写法 if [ condition ],它的语法格式如下: --格式如下: if [ condition ] then commands ...

  8. Windows内核读书笔记——SEH结构化异常处理

    SEH是对windows系统中的异常分发和处理机制的总称,其实现分布在很多不同的模块中. SEH提供了终结处理和异常处理两种功能. 终结处理保证终结处理块中的程序一定会被执行 __try { //要保 ...

  9. Elasticsearch结构化搜索_在案例中实战使用term filter来搜索数据

    1.根据用户ID.是否隐藏.帖子ID.发帖日期来搜索帖子 (1)插入一些测试帖子数据 POST /forum/article/_bulk { "index": { "_i ...

随机推荐

  1. .NET Core应用程序的2种部署方式

    1. Portable 共享.NET Core运行时环境与程序集依赖,部署的目标机器上需要事先安装.NET Core SDK,这是.NET Core的默认部署方式. 2. Self-contained ...

  2. 自定义View_1_关于View,ViewGroup的测量和绘制流程

    自定义View(1) ------ 关于View,ViewGroup的测量和绘制流程 在Android当中,自定义控件属于比较高级的知识体系,今天我们就一起研究研究关于自定义View的那点事,看看它到 ...

  3. (九)play之yabe项目【发表博文】

    (九)play之yabe项目[发表博文] 博客分类: 框架@play framework   发表一篇博文 填充管理页面 从主页链接到管理页面时,只简单显示了登陆用户的名称 现在对显示的内容加以丰富 ...

  4. CKEditor与CKFinder的配置

    CKEditor与CKFinder的配置使用(一) 将CKEditor 与 CKFinder 的包含在项目中 从http://cksource.com网站上下载CKEditor与CKFinder,并将 ...

  5. C# Web Api 上传文件

    一. 使用默认方法上传文件: 1.Action: /// <summary> /// 上传文件 使用上传后的默认文件名称 /// 默认名称是BodyPart_XXXXXX,BodyPart ...

  6. SharePoint 2013 调用WCF服务简单示例

    内容比较简单,主要记录自己使用SharePoint 2013WCF服务遇到的小问题和小经验,分享给大家,希望能够给需要的人有所帮助.好吧,进入正题! 第一部分 SharePoint 2013调用自带W ...

  7. Creating External Lists From Code

    You can create an external list based on an entity (external content type) defined in SharePoint Bus ...

  8. nib文件的注册及加载

    1,注册 [(UIView *)view registerNib:[UINib nibWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle ...

  9. Python学习 windows下面安装Python和pip(一)

    windows下面安装Python和pip 安装Python 第一步,我们先来安装Python, https://www.python.org/downloads/ 这里选择的是2.7.10 第二步. ...

  10. .NET下的并行开发

    并行开发一直是程序员在开发项目中遇到的一道坎,但为了迎合硬件的升级,面对高端多核的处理器,并行编程势在必行.在.NET平台下的开发支持并行模式,下面用一个实际项目说明并行的高效率和神奇之处. 在优化中 ...