13.3 异常类型为对象的处理流程
C++中所抛出的异常类型不仅可以是基本数据类型,还可以是对象。如果抛出的异常为对象,则需要进行相关处理。代码清单13-6所示为抛出的异常为对象的C++源码。
代码清单13-6 抛出的异常为对象的C++源码
class CExcepctionBase{
public:
CExcepctionBase(){
printf("CExcepctionBase()\r\n");
}
~CExcepctionBase(){
printf("~CExcepctionBase()\r\n");
}
};
class CExcepction:public CExcepctionBase{
public:
CExcepction(int nErrID){
m_nErrorId=nErrID;
printf("CExcepction(int nErrID)\r\n");
}
CExcepction(CExcepction&Excepction){
printf("CExcepction(CExcepction&Excepction)\r\n");
m_nErrorId=Excepction.m_nErrorId;
}
int GetErrorId(){//获取错误码
return m_nErrorId;
}
private:
int m_nErrorId;
};
//抛出异常对象
void ExcepctionObj()
{
int nThrowErrorCode=119;
printf("请输入测试错误码:\n");
scanf("%d",&nThrowErrorCode);
try{
if(nThrowErrorCode==110){
CExcepction myStru(110);
throw&myStru;//抛出异常对象的指针
}
else if(nThrowErrorCode==119){
CExcepction myStru(119);
throw myStru;//抛出异常对象
}
else if(nThrowErrorCode==120){
CExcepction*pMyStru=new CExcepction(120);
throw pMyStru;//抛出异常对象
}
else{
throw CExcepction(nThrowErrorCode);//抛出异常对象
}
}
catch(CExcepction e){//异常处理
printf("catch(CExcepction&e)\n");
printf("ErrorId:%d\n",e.GetErrorId());
}
catch(CExcepction*p){//异常处理
printf("catch(CExcepction*e)\n");
printf("ErrorId:%d\n",p->GetErrorId());
}
}
代码清单13-6中抛出了各种异常对象的指针和引用等类型,C++的异常处理过程会根据抛出异常对象的类型的不同指定不同的处理流程。代码清单13-4在地址标号0x004038D4处调用了函数CatchIt,这个函数完成了两大功能:
使用BuildCatchObject函数对抛出的异常对象进行处理,如代码清单13-7所示。
使用__FrameUnwindToState函数处理展开流程,如代码清单13-8所示。
代码清单13-7 BuildCatchObject分析—Debug版
//函数原型
void__cdecl BuildCatchObject(struct EXCEPTION_RECORD*pExcept,
struct EHRegistrationNode*pRN,
const struct_msRttiDscr*pCatch,
const struct CatchTableType*pConv)
;部分代码分析略
;arg_8中保存了_msRttiDscr表结构指针pCatch,将arg_8重命名为pCatch
00403EE6 mov eax,[ebp+arg_8]
;检查_msRttiDscr表结构中的catch块要捕捉的类型pType是否为空
00403EE9 cmp dword ptr[eax+4],0
00403EED jz short loc_403F06;跳转到函数返回地址,重命名RET_JMP
00403EEF mov ecx,[ebp+pCatch]
00403EF2 mov edx,[ecx+4];获取pType并保存到edx中
00403EF5 movsx eax, byte ptr[edx+8];获取TypeDescriptor表结构中的name
00403EF9 test eax, eax
00403EFB jz RET_JMP
00403EFD mov ecx,[ebp+pCatch]
00403F00 cmp dword ptr[ecx+8],0;检查_msRttiDscr表结构中的成员
;dispCatchObjOffset
00403F04 jnz short loc_403F0B
RET_JMP:
00403F06 jmp RETN_BUILD
loc_403F0B:
00403F0B mov edx,[ebp+pCatch]
00403F0E mov eax,[edx+8];eax保存了dispCatchObjOffset
;arg_4中保存指向EHRegistrationNode表结构的指针pRN,将arg_4重命名为pRN
00403F11 mov ecx,[ebp+arg_4]
00403F14 lea edx,[ecx+eax+0Ch];计算对象所在的栈空间
00403F18 mov[ebp+var_1C],edx;将var_1C重命名为ppCatchBuffer
00403F1B mov[ebp+var_4],0
00403F22 mov eax,[ebp+pCatch]
00403F25 mov ecx,[eax];获取标记信息
00403F27 and ecx,8;检查异常对象的指针类型是否为引用或者指针
00403F2A test ecx, ecx;检查标记,判断异常对象构造类型
00403F2C jz short loc_403F86
;相关检查代码分析略
00403F55 mov edx,[ebp+ppCatchBuffer]
00403F58 mov eax,[ebp+arg_0];arg_0保存指针pExcept,重命名pExcept
00403F5B mov ecx,[eax+18h]
;将pExcet指向的异常对象复制到ppCatchBuffer所指向的内存空间
00403F5E mov[edx],ecx
00403F60 mov edx,[ebp+arg_C];arg_c保存指针pConv,重命名为pConv
00403F63 add edx,8;获取偏移信息thisDisplacement
00403F66 push edx
00403F67 mov eax,[ebp+ppCatchBuffer]
00403F6A mov ecx,[eax]
00403F6C push ecx
00403F6D call___AdjustPointer;调整对象指针
00403F72 add esp,8
00403F75 mov dx,[ebp+ppCatchBuffer]
00403F78 mov[edx],eax;重新设置对象指针
00403F7A jmp short loc_403F81;结束异常对象构造过程
;部分代码分析略
loc_403F86:
00403F86 mov eax,[ebp+pConv]
00403F89 mov ecx,[eax];获取标记信息
00403F8B and ecx,1;指针类型,简单对象复制
00403F8E test ecx, ecx
00403F90 jz short loc_40400A
;相关检查代码分析略
00403FB9 mov edx,[ebp+pConv]
00403FBC mov eax,[edx+14h];获取类的大小sizeOrOffset
00403FBF push eax
00403FC0 mov ecx,[ebp+pExcept]
00403FC3 mov edx,[ecx+18h];获取pExcet指向的异常对象
00403FC6 push edx;src
00403FC7 mov eax,[ebp+ppCatchBuffer]
00403FCA push eax;dst
;将pExcet指向的异常对象复制到ppCatchBuffer所指向的内存空间
00403FCB call_memmove
00403FD0 add esp,0Ch
;调整对象指针部分的代码分析略
;部分代码分析略
loc_40400A:
0040400A mov edx,[ebp+pConv]
;检查pCopyFunction,判断是否为拷贝构造
0040400D cmp dword ptr[edx+18h],0
00404011 jnz short loc_404070
;相关检查代码分析略
0040405C call_memmove;直接复制对象信息
00404061 add esp,0Ch
00404064 jmp short loc_40406B;结束异常对象构造过程
;部分代码分析略
;相关检查代码分析略
0040409B mov eax,[ebp+pConv]
0040409E mov ecx,[eax+18h]
004040A1 push ecx;ppCatchBufferfn
004040A2 call_ValidateExecute;检查拷贝构造函数在内存中的属性是否可执行
004040A7 add esp,4
004040AA test eax, eax
004040AC jz short loc_40410E;若可执行,则跳转,并结束异常对象构造过程
004040AE mov edx,[ebp+pConv]
004040B1 mov eax,[edx];获取标记信息
004040B3 and eax,4;指针类型,有虚基类的处理
004040B6 test eax, eax
004040B8 jz short loc_4040E5
;部分代码分析略
004040D2 push eax;void*
004040D3 mov ecx,[ebp+pConv]
004040D6 mov edx,[ecx+18h];获取拷贝构造函数pCopyFunction
004040D9 push edx;void*
004040DA mov eax,[ebp+ppCatchBuffer]
004040DD push eax;void*
;有虚基类的拷贝构造函数调用
004040DE call_CallMemberFunction2
004040E3 jmp short loc_40410C;结束异常对象的构造过程
loc_4040E5:
;部分代码分析略
004040FB push eax;void*
004040FC mov ecx,[ebp+pConv]
004040FF mov edx,[ecx+18h];获取拷贝构造函数pCopyFunction
00404102 push edx;void*
00404103 mov eax,[ebp+ppCatchBuffer]
00404106 push eax;void*
;无虚基类的拷贝构造函数调用
00404107 call_CallMemberFunction1
;部分代码分析略
RETN_BUILD:
;部分代码分析略
0040413A retn
0040413A BuildCatchObject endp
代码清单13-7对BuildCatchObject函数进行了粗略分析,在此函数中共有四种不同的对象产生方式,分别为引用或指针直接赋值、简单对象直接复制、有虚表基类拷贝构造函数、无虚表基类拷贝构造函数。
代码清单13-8__FrameUnwindToState函数分析—Debug版
;函数原型
;int__cdecl__FrameUnwindToState(
EHRegistrationNode*pRN,
void*pDC,
struct FuncInfo*pFuncInfo,
int targetState)
___FrameUnwindToState proc near
;部分代码分析略
00403B66 mov eax,[ebp+pRN]
00403B69 mov ecx,[eax+EHRegistrationNode.dwState];当前框架状态值
00403B6C mov[ebp+dwState],ecx
loc_403B6F:;循环起始处
00403B6F mov edx,[ebp+dwState]
00403B72 cmp edx,[ebp+targetState];0FFFFFFFFh;栈展开数下标值是否为-1
00403B75 jz Exit;若为-1,则表示结束
00403B7B cmp[ebp+dwState],0FFFFFFFFh
00403B7F jle short loc_403B95;检查当前框架的状态值是否为-1
00403B81 mov eax,[ebp+pFuncInfo]
00403B84 mov ecx,[ebp+dwState]
00403B87 cmp ecx,[eax+FuncInfo.maxState];最大的状态,栈展开数
;比较注册异常的ID是否小于栈展开个数,如果大于等于则不属于该异常,不属于该异常则调用inconsistency
弹出错误对话框
00403B8A jge short loc_403B95
00403B8C mov[ebp+var_20],0
00403B93 jmp short loc_403B9D
loc_403B95:;显示错误信息
00403B95 call terminate_0
00403B9A mov[ebp+var_20],eax
00403B9D mov[ebp+lpEstablisherFrame],0
00403BA4 mov edx,[ebp+pFuncInfo]
;获取指向栈展开函数表的指针UnwindMapEntry*并保存到eax中
00403BA7 mov eax,[edx+FuncInfo.pUnwindMap]
00403BAA mov ecx,[ebp+dwState]
00403BAD cmp dword ptr[eax+ecx*8+4],0;判断展开栈的函数指针是否为NULL
00403BB2 jz short loc_403BD0
00403BB4 push 103h;n4
00403BB9 mov edx,[ebp+pRN]
00403BBC push edx;lpEstablisherFrame
00403BBD mov eax,[ebp+pFuncInfo]
;获取指向栈展开函数表的指针UnwindMapEntry*并保存到ecx中
00403BC0 mov ecx,[eax+FuncInfo.pUnwindMap]
00403BC3 mov edx,[ebp+dwState]
00403BC6 mov eax,[ecx+edx*8+4]
00403BCA push eax;获取栈展开函数表中的函数指针lpCatchFun
00403BCB call CallSettingFrame;调用指定栈展开函数表中的函数指针(析构函数)
;部分代码分析略
loc_403BF0:
00403BF0 mov edx,[ebp+pFuncInfo]
;获取指向栈展开函数表的指针UnwindMapEntry*并保存到eax中
00403BF3 mov eax,[edx+FuncInfo.pUnwindMap]
00403BF6 mov ecx,[ebp+dwState]
00403BF9 mov edx,[eax+ecx*8]
00403BFC mov[ebp+dwState],edx;获得栈展开数组中指定元素的ID
00403BFF jmp loc_403B6F;跳转到循环起始处
00403C04 Exit:
;部分代码分析略
00403C6B retn
00403C6B___FrameUnwindToState endp
在代码清单13-8中,将FuncInfo表结构与EHRegistrationNode结构中记录的相关栈展开信息进行对比和判断,以检索在展开过程中需要调用的函数。
栈展开与异常对象生产的流程执行完毕后,由CallCatchBlock函数完成catch块的调用工作。最后由_JumpToContinuation函数跳转回catch结束地址,完成异常处理的全过程。