!C++异常的实现 - ZH

更新时间:2023-11-26 04:20:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

http://bbs.driverdevelop.com/htm_data/87/0407/72938.html how a c++ compiler implements exception handling

有人应该知道有这么一个同名的文章介绍vc 6怎么实现c++异常的文章.....在这里小弟不才.借用下这个名字,介绍vc.net 2003怎么实现这个技术.

先说这些代码的由来,首先呢,你得写一个c++得程序,然后选择静态链接,然后用ida 反汇编就ok,用 vc 本身作调试器就ok,softice这种家伙,也没有用得必要了. 当然你也许能发现eh.obj这样的文件,其实代码就在那几个obj里面.

要想继续看下去,你得了解windows得seh技术,得熟练掌握32位汇编,因为这里并没有什么源代码给你看,全部是反汇编得结果...自然对c++异常的语法,以及c++异常规范,你必须要有足够的了解才行.

因为我ida反汇编以后加上注释的那几个idb文件已经不见了,只是剩下了自己用汇编写的代码,所以这里出现的代码都是nasm格式的汇编代码,同时搭配使用c32.mac这个文件,这个文件我有修改增加其宏的功能,名字都很简单明了,希望那些宏不会对大家的阅读产生障碍.

然后呢,要说说一些基本的概念.

你应该要知道__stdcall,__cdecl,__thiscall这些的调用与被调用规则,应该要知道在一个c函数里面哪些寄存器的值是要保护的,哪些寄存器是不用保护的.应该要明白mov fs:[0],eax是在干什么,你要明白windows的seh是怎么完成的.有了足够的准备以后往下看吧..

seh是windows平台上的一种异常处理方式,通过预先安装一个处理函数,到发生异常的时候跳转到处理函数里面的方式完成异常处理,这里不要把__try,__except等同到seh,他们只是一种简单的处理模型,并不代表了seh本身,seh本身只是在异常发生的时候跳转到一个函数而已.

当我们写下throw 0;这样的语句的时候,编译器会为我们产生下面的这些代码

mov [ebp-10],0 lea eax,[ebp-10] push __TI1H push eax

call __CxxThrowException@8

上面这些代码调用了一个叫__CxxThrowException@8的函数

看它的名字就知道它应该是一个使用stdcall调用法则,有两个参数的函数.它的原型

void __stdcall _CxxThrowException(void *pObject,_s_ThrowInfo const* pThrowInfo)

第一个参数就是throw 后面的那个object的地址,第二个参数是一个叫throwInfo的结构,它的定义如下(能在vc的watch窗口里面看到,你输入(_s_ThrowInfo*)0就能看到它的成员定

义了)

; throw info

struc _s_ThrowInfo

.attributes : resd 1 ; properites

.pmfnUnwind : resd 1 ; destructor for thrown object .pForwardCompat : resd 1 ; compat handler address .pCatchableTypeArray : resd 1 ; catchable type array endstruc

这个结构定义是使用nasm的语法,resd表示它是dword大小的,后面的1表示一个dword.

上面看到的那个__TI1H就是这个结构.

关于它的几个成员.我后面都有注释,唯一要说的就是最后一个成员,它表示这个throw出来的object它能转换成哪些其他的object以便被catch语句捕获,你应该要知道catch语句捕获的原则.

_CxxThrowException函数是一个外部的函数,它应该由c++的lib提供,它在设置好一些工作以后应该调用os提供的RaiseException的函数,然后os取得控制权,交到应用程序安装的seh handler上面,

那么vc为我们安装的那个函数是什么呢?

它会是一个诸如__ehhandler$_main:这样的函数 它简单的mov eax,__TI1H+48h

然后jmp到一个叫___CxxFrameHandler的函数 这个函数原型如下

int __CxxFrameHandler(struct EHExceptionRecord * pExcept,struct EHRegistrationNode * pRN,

; struct _CONTEXT * pContext,void * pDC)

这个函数使用什么样子的调用法则是无关紧要的,因为它要么不返回,要么os会另外的设置esp,所以你使用cdcel还是stdcall都没有关系,唯一要保证的就是它的名字,它也应该要由c++的lib提供(当然我假定你是这个lib的提供者).

它使用的几个参数,首先是eax,它是一个_s_FuncInfo的指针,然后是一个EEHExceptionRecord的指针,它其实跟seh使用的ExceptionRecord差不多,只是vc在它里面加入了些其他的成员,剩下的参数也同它一样,你应该要知道这些大都是seh传递给你的参数,你也应该要知道,他们会在你调用RaiseException的时候设置好. 先给出这些结构的定义. ;exception record

struc EHExceptionRecord

.ExceptionCode : resd 1 ; exception code .ExceptionFlags : resd 1 ; exception flags

.ExceptionRecord : resd 1 ; exception record .ExceptionAddress : resd 1 ; exception address .NumberParameters : resd 1 ; number of param .magicNumber : resd 1 ; magic number

.pExceptionObject : resd 1 ; exception object pointer .pThrowInfo : resd 1 ; throw info pointer endstruc

;function info

struc _s_FuncInfo

.magicNumber : resd 1 ; magic number

.maxState : resd 1 ; max state in the function .pUnwindMap : resd 1 ; unwind function list .nTryBlocks : resd 1 ; how many try blocks .pTryBlockMap : resd 1 ; try block list endstruc

;registration node

struc EHRegistrationNode

.pNext : resd 1 ; prev exception registration node .frameHandler : resd 1 ; this frame handler .state : resd 1 ; current state endstruc

上面的几个结构呢.首先要说的是ExceptionRecord,你应该要知道它里面由一个Parameters的数组,vc使用了里面的3个成员,分别赋予了不同的意思.这个很容易理解.

然后是RegisterationNode,vc多加入了一个叫state的域,如果你对__try,__except很熟悉的话,你会会心一笑,他其实跟tryLevel差不多,用来表示抛出这个异常时,程序运行到什么地方了.

至于function info,他表示的是你当前处于的这个函数的信息,他收集了发生异常的时候必须要完成的操作所需要的信息.

我希望你看到这里的时候还没有头晕,我选择先把这些东西一古老全抖出来然后再解释他的机制的方式,希望你还挺得住.

到这里,我要描述具体得实现思路了.

vc实现得c++异常是一种编译时行为,并不能归结到运行时行为,他通过编译器收集足够的信息来完成异常处理.

对于每个函数,vc收集一份他里面的try语句的位置信息,catch语句能catch住的object type信息,以及每个可能发生异常的情况下要完成的object destructor调用的信息,这些信息共

同的构成了上面的function info.

同时vc在throw语句执行的时候,抓取到(也是在编译时完成的)那个object的地址,收集到这个obj能转换的类型(catch判断的时候有用),然后移交控制权,同时vc还在运行时候记录当前执行到的语句的位置,这个数据会用来得到当前语句属于哪个try,当前这个地方要调用哪些destructor.

看个具体的例子

假设我有定义A,B两个class [code] void test() {

// 这里设置当前位置为-1,表示不处于任何一个try语句里面 try {

// 这里设置当前位置0,表示进入了第一个try里面 A testA;

// 这里设置当前位置1,表示如果在这里出现了问题,要调用类A的destructor MayThrowException1;

try {

// 这里设置当前位置2,表示进入第二个try B testB;

// 这里设置为3,要调用B的destructor MayThrowException2;

//这里会插入 B类的ddestructor调用,同时设置当前位置为1,表示退出了第二个try }

catch(...) {

// 这里也会有修改到当前位置的操作,因为在catch里面也会定义c++对象 }

// 这里同样插入A的destructor调用,设置当前位置-1 }

catch(...) { }

}[/code]

这个例子程序非常的简单,大家可以体会下这个当前位置的值的变换情况,他不仅仅是要表示try的位置,也要表示你必须要调用的destructor的信息.你也许要问这个当前位置保存到什么地方的?就在那个EHRegisterationNode里面,state就是用来保存这个的,你应该要知道他的位置就是[ebp-4],这个部分你要熟悉seh才能明白为什么是[ebp-4]

假想这么一个情况,上面的函数发生了一个c++异常,进入到异常处理函数里面,函数通过查看EHRegisterationNode->state的值,得到了当前程序运行的位置,先通过function info得到了当前所在的try block,这个是通过function info里面的try block list完成的, 他其实是一个数组,每个成员又是一个结构定义如下 ;try block entry

struc _s_TryBlockMapEntry .tryLow : resd 1 ; begin state .tryHi : resd 1 ; end state

.catchHi : resd 1 ; catch end state

.nCatches : resd 1 ; how many catches

.pHandlerArray : resd 1 ; cathe block entry list endstruc

tryHi跟tryLow定义了try语句所跨越的位置,比如上面的第一个try

的范围就是0到3,第二个是2到3,在list的array里面排列的顺序是从里到外的,所以,经过一些比较以后就能确定当前位于哪个try里面

知道了这个以后,从上面的_s_TryBlockMapEntry结构里面能得到跟这个try关联的catch语句信息,他们又构成了一个数组,通过比较那个throw object的类型跟catch语句的类型就能找到合适的catch语句跳转进去,如果没有找到,那么就应该回到上一个tryblock继续查找,这个任务的实现是靠编译器合理的布局try block map entry数组完成的,实际的工作只是需要遍历这个数组一一判断state是否落在tryLow还是tryHi里面,如果不是就继续下一项,如果是就查找catch语句,执行.

对于catch语句,vc也生成了一份信息,定义如下 ; catch block entry struc _s_HandlerType

.adjectives : resd 1 ; properites .pType : resd 1 ; type_info pointer

.dispCatchObj : resd 1 ; offset from ebp

.addressOfHandler : resd 1 ; handler address endstruc

最主要的就是那个pType成员,他是一个type_info指针,你应该要知道这个是个什么东西,addressOfHandler就是catch语句开始的指针.

现在我找到了异常发生的时候的程序运行位置,也知道了我程序写下的catch语句的信息,这个时候我就要开始一一比较我所throw出来的obj跟catch语句能catch的是否一致了.

这个部分比较复杂,涉及c++很多的语法

比如一个const的不能被非const的引用所catch等等

这些的判断就要借助_s_HandlerType.adjectives属性来判断了,vc收集好catch语句要求的obj的信息,const,volatile等等属性,你应该要知道这些属性并不能通过type_info来获取,你也应该知道非const的指针是能被const指针catch的等等语法.如果你明白了这些就能明白adjectives用来干什么了.

本文来源:https://www.bwwdw.com/article/gsnt.html

Top