13.2 异常类型为基本数据类型的处理流程
到此,异常处理过程中所需要的结构信息全部介绍完毕。有了对异常处理的初步认识,接下来结合实践来深入了解异常处理的流程。先来看代码清单13-1。
代码清单13-1 异常处理流程—Debug版
//C++源码
int main(int argc, char*argv[]){
try{
throw 1;//抛出异常
}
catch(int e){
printf("触发int异常\r\n");
}
catch(float e){
printf("触发float异常\r\n");
}
return 0;
}
//C++源码与对应汇编代码讲解
int main(int argc, char*argv[]){
00401010 push ebp
00401011 mov ebp, esp
00401013 push 0FFh
;异常回调函数,__ehhandler$_main函数分析见代码清单13-2
00401015 push offset__ehhandler$_main(00413450)
0040101A mov eax, fs:[00000000]
00401020 push eax
00401021 mov dword ptr fs:[0],esp;注册异常回调处理函数
;Debug环境初始化部分略
try{
00401041 mov dword ptr[ebp-4],0
throw 1;//抛出异常
00401048 mov dword ptr[ebp-14h],1;设置异常编号
0040104F push offset__TI1H(00426630);压入异常结构
00401054 lea eax,[ebp-1Ch]
00401057 push eax;压入异常编号
;__CxxThrowException@8函数中调用API函数Raise Exleption
00401058 call__CxxThrowException@8(004017a0);调用异常分配函数
}
;异常捕获处理部分略
在代码清单13-1中,在进入main函数后,首先压入异常回调函数,用于在产生异常时接收并分配到对应的异常处理语句块中。进一步分析异常回调函数__ehhandler$_main,如代码清单13-2所示。
代码清单13-2 异常回调函数__ehhandler$_main分析—Debug版
=======================示例代码截取自IDA==========================
00413140__ehhandler$_main proc near;DATA XREF:_main+5o
;利用eax传参,将stru_426468重新命名为g_lpFuncInfo
00413140 mov eax, offset stru_426468
00413145 jmp___CxxFrameHandler
00413145__ehhandler$_main endp
;传入了异常处理的相关信息,函数___CxxFrameHandler的声明如下:
;int__cdecl__CxxFrameHandler(EXCEPTION_RECORD*pExcept,
EHRegistrationNode*pRN,
struct_CONTEXT*pContext,
void*pDC)
00401210 var_8=dword ptr-8
00401210 var_4=dword ptr-4
00401210 arg_0=dword ptr 8;参数1,pExcept
00401210 arg_4=dword ptr 0Ch;参数2,pRN
00401210 arg_8=dword ptr 10h;参数3,pContext
00401210 arg_C=dword ptr 14h
00401210
00401210 push ebp
00401211 mov ebp, esp;保存栈底,并重新设置栈底
00401213 sub esp,8;申请局部变量空间
00401216 push ebx
00401217 push esi
00401218 push edi;保存环境
00401219 cld;将DF位置0,每次操作后,esi、edi递增
;var_8局部变量保存g_lpFuncInfo首地址,重命名pFuncInfo
0040121A mov[ebp+pFuncInfo],eax
0040121D push 0;压入0作为参数
0040121F push 0;压入0作为参数
00401221 push 0;压入0作为参数
00401223 mov eax,[ebp+pFuncInfo]
00401226 push eax;压入pFuncInfo结构首地址作为参数
00401227 mov ecx,[ebp+arg_C]
0040122A push ecx
0040122B mov edx,[ebp+pContext]
0040122E push edx;压入pContext作为参数
0040122F mov eax,[ebp+arg_4]
00401232 push eax;压入pRN作为参数
00401233 mov ecx,[ebp+pExcept]
00401236 push ecx;压入pExcept作为参数
00401237 call___InternalCxxFrameHandler;调用异常处理函数
;部分代码分析略
0040124B retn
0040124B___CxxFrameHandler endp
代码清单13-2的主要工作是获取异常的相关信息,最后通过_InternalCxxFrameHandler进行异常的分类处理。该函数的参数含有FuncInfo结构、EHRegistrationNode结构以及EXCEPTION_RECORD结构。EXCEPTION_RECORD结构可通过MSDN查询,其结构说明如下:
EXCEPTION_RECORD struc
ExceptionCode dd?;异常类型,产生异常的错误编号
ExceptionFlags dd?;异常标记
lpExceptionRecord dd?;嵌套异常使用
ExceptionAddress dd?;异常产生地址
NumberParameters dd?;用于指定ExceptionInformation数组中的元素个数
MagicNumber dd?;存储异常处理的附加参数
EXCEPTION_RECORD ends
EHRegistrationNode的表结构说明如下:
EHRegistrationNode struc;(sizeof=0x10)
pNext dd?;指向链表的上一个节点EHRegistrationNode*
HandlerProc dd?;记录当前函数栈帧的异常回调函数
dwState dd?;记录当前函数栈帧的状态值
dwEbp dd?;记录当前函数栈帧的ebp值
EHRegistrationNode ends
_Internal Cxx FrameHandler函数主要完成了标记检查、展开、查找和派发等工作。下面通过代码清单13-3来详细分析此函数的相关流程。
代码清单13-3__InternalCxxFrameHandler分析—Debug版
//函数原型:
int__cdecl__InternalCxxFrameHandler(EXCEPTION_RECORD*pExcept,
EHRegistrationNode*pRN,
struct_CONTEXT*pContext,
void*pDC,
struct FuncInfo*pFuncInfo,
int CatchDepth,
int pMarkerRN,
int recursive);
004035C0___InternalCxxFrameHandler proc near
;部分代码分析略
004035C6 mov eax,[ebp+arg_10];arg_10对应pFuncInfo
004035C9 cmp dword ptr[eax],19930520h;对比标识符
004035CF jnz short loc_4035DA
004035D1 mov[ebp+var_8],0
004035D8 jmp short loc_4035E2;标识符正确,跳转到异常处理部分
;部分代码分析略
004035E2 loc_4035E2:
004035E2 mov ecx,[ebp+pExcept]
004035E5 mov edx,[ecx+4];获取异常标记
004035E8 and edx,66h
004035EB test edx, edx;比较异常是否为EXCEPTION_UNWIND
004035ED jz short loc_40361E;不等则跳转
;部分代码分析略
loc_40361E:
0040361E mov ecx,[ebp+arg_10]
00403621 cmp dword ptr[ecx+0Ch],0;检查FuncInfo结构中记录try块
数的dwTryCount成员
00403625 jz short loc_4036A6
00403627 mov edx,[ebp+pExcept]
;edx中保存了pExcept,取第一项ExceptionCode异常类型
0040362A cmp dword ptr[edx],0E06D7363h;0E06D7363h为C++异常错误码
00403630 jnz short loc_40367E
00403632 mov eax,[ebp+pExcept]
;eax中保存了pExcept,取MagicNumber,由此可见第一个附加参数为FuncInfo结构
00403635 cmp dword ptr[eax+14h],19930520h;存储异常处理的附加参数
0040363C jbe short loc_40367E
;部分代码分析略
loc_40367E:
;参数传递代码分析略
;调用查找try块与catch块的函数FindHandler
call?FindHandler@@YAXPAUEHExceptionRecord@@PAUEHRegistrationNode@@PAU_
CONTEXT@@PAXPBU_s_FuncInfo@@EH1@Z
;函数FindHandler的分析见代码清单13-4
;部分代码分析略
004036AE retn
004036AE___InternalCxxFrameHandler endp
通过对代码清单13-3的分析可知,函数__InternalCxxFrameHandler的主要功能是完成异常类型的检查,最终调用查找try块和catch块的函数FindHandler。这个函数是完成异常处理的关键部分,完成了查找try块中抛出的异常对应的catch语句块的过程,具体分析如代码清单13-4所示。
代码清单13-4 FindHandler分析—Debug版
//函数原型:
;FindHandler(EXCEPTION_RECORD*pExcept,
//异常记录信息(附加参数携带了异常对象指针和ThrowInfo对象指针)
EHRegistrationNode*pRN,
struct_CONTEXT*pContext,
void*pDC,
struct FuncInfo*pFuncInfo,//函数信息
int recursive,
int CatchDepth,
int pMarkerRN)
004036B0 push ebp
004036B1 mov ebp, esp
004036B3 sub esp,30h
004036B6 mov[ebp+var_8],0
;获取指向EHRegistrationNode表结构的指针pRN
004036BA mov eax,[ebp+arg_4]
;获取EHRegistrationNode表结构中的dwState
004036BD mov ecx,[eax+8];获取当前函数栈帧的状态值
004036C0 mov[ebp+var_4],ecx;将var_4重命名为dwState
004036C3 cmp[ebp+dwState],0FFFFFFFFh;检查当前框架的最大值是否为空
004036C7 jl short loc_4036DD
;获取指向FuncInfo表结构的指针pFuncInfo
004036C9 mov edx,[ebp+arg_10]
004036CC mov eax,[ebp+dwState];获取当前函数栈帧的状态值
004036CF cmp eax,[edx+4];检查是否超过当前框架的最大值
004036D2 jge short loc_4036DD
004036D4 mov[ebp+var_28],0
004036DB jmp short loc_4036E5
;部分代码分析略
loc_4036E5:
004036E5 mov ecx,[ebp+pExcept]
004036E8 cmp dword ptr[ecx],0E06D7363h;检查异常错误编号
004036EE jnz loc_40379E;跳向其他类型的异常处理,重命名为EX_OTHER_ONE
004036F4 mov edx,[ebp+pExcept]
;检查指定ExceptionInformation数组中的元素个数
004036F7 cmp dword ptr[edx+10h],3
004036FB jnz EX_OTHER_ONE
00403701 mov eax,[ebp+pExcept];存储异常处理的附加参数
00403704 cmp dword ptr[eax+14h],19930520h
0040370B jnz EX_OTHER_ONE
00403711 mov ecx,[ebp+pExcept]
00403714 cmp dword ptr[ecx+1Ch],0;检查ThrowInfo*指针
00403718 jnz EX_OTHER_ONE
0040371E cmp_pCurrentException,0;EHExceptionRecord*_pCurrentException
00403725 jnz short loc_40372C
00403727 jmp loc_403945;跳转到函数结尾处,结束函数调用
loc_40372C:
;部分代码分析略
loc_403764:
00403764 mov edx,[ebp+pExcept]
00403767 cmp dword ptr[edx],0E06D7363h;与上面的异常检查相似
;跳向其他类型异常处理,与上面的流程不同,重命名EX_OTHER_TWO
0040376D jnz EX_OTHER_TWO
;部分代码分析略
EX_OTHER_TWO:
00403797 mov[ebp+var_30],0
EX_OTHER_ONE:
0040379E mov eax,[ebp+pExcept];检查异常错误编号
004037A1 cmp dword ptr[eax],0E06D7363h
004037A7 jnz loc_403905
;部分代码分析略
;函数返回_GetRangeOfTrysToCheck try块的首地址TryBlockMapEntry*
004037DE call_GetRangeOfTrysToCheck
004037E3 add esp,14h
;===============for循环结构赋初值部分=================
;将try块信息列表(TryBlockMapEntry表结构的指针),保存到ebp+var_10中
004037E6 mov[ebp+var_10],eax;重命名ebp+pTryBlockMap
004037E9 jmp short loc_4037FD;跳转向循环语句块
;===============for循环步长计算部分===================
loc_4037EB:
004037EB mov edx,[ebp+var_14];edx中保存当前try块,重命名为curTry
004037EE add edx,1
004037F1 mov[ebp+curTry],edx;对当前try块加1
004037F4 mov eax,[ebp+pTryBlockMap]
004037F7 add eax,14h;加上TryBlockMapEntry表结构的长度
004037FA mov[ebp+pTryBlockMap],eax;
;===============for循环条件比较部分===================
loc_4037FD:
004037FD mov ecx,[ebp+curTry];获取当前try块
00403800 cmp ecx,[ebp+var_C];比较当前try与最后一个try块
00403803 jnb loc_4038E8
;===============for循环语句块部分===================
00403809 mov edx,[ebp+pTryBlockMap]
0040380C mov eax,[edx];获取TryBlockMapEntry表结构中的tryLow
成员
0040380E cmp eax,[ebp+dwState];检查异常是否发生在当前try块中
00403811 jg short loc_40381E;continue语句
00403813 mov ecx,[ebp+pTryBlockMap]
00403816 mov edx,[ebp+dwState]
;检查try块是否在状态索引内,ecx+4寻址到tryHigh
00403819 cmp edx,[ecx+4]
0040381C jle short loc_403820;找到对应的try块,进行相关处理
loc_40381E:
0040381E jmp short loc_4037EB;跳转到循环步长计算部分
00403820 mov eax,[ebp+pTryBlockMap]
;获取pCatchHandlerArray,指向_msRttiDscr结构的指针
00403823 mov ecx,[eax+10h]
00403826 mov[ebp+var_1C],ecx;将var_1C重命名为pCatchHandlerArray
00403829 mov edx,[ebp+pTryBlockMap]
0040382C mov eax,[edx+0Ch];获取成员dwCatchCount,这是用来记录
catch语句块个数的
;===============嵌套的for循环结构赋初值部分=============
loc_403820:;为第二层for循环赋初值
0040382F mov[ebp+var_24],eax;将var_24重命名为dwCatchCount
00403832 jmp short loc_403846
;===============嵌套的for循环步长计算部分===============
loc_403834:;第二层for循环步长计算
00403834 mov ecx,[ebp+dwCatchCount]
00403837 sub ecx,1
0040383A mov[ebp+dwCatchCount],ecx
0040383D mov edx,[ebp+pCatchHandlerArra]
00403840 add edx,10h
00403843 mov[ebp+pCatchHandlerArra],edx
00403846
;===============嵌套的for循环条件比较部分================
loc_403846:;第二层for循环条件比较
00403846 cmp[ebp+dwCatchCount],0
0040384A jle loc_4038E3
;===============嵌套的for循环语句块部分================
;第二层for循环语句块
00403850 mov eax,[ebp+pExcept]
00403853 mov ecx,[eax+1Ch];获取ThrowInfo表结构指针
00403856 mov edx,[ecx+0Ch];获取pCatchTableTypeArray
00403859 add edx,4
;获取pCatchTableTypeArray指向结构CatchTableTypeArray中的指针
;ppCatchTableType,将var_18重命名为ppCatchTableType
0040385C mov[ebp+var_18],edx
0040385F mov eax,[ebp+pExcept]
00403862 mov ecx,[eax+1Ch];同上
00403865 mov edx,[ecx+0Ch]
00403868 mov eax,[edx];获取CatchTableType数组包含的个数
;===============嵌套的for循环结构赋初值部分=============
;为第三层for循环赋初值
0040386A mov[ebp+var_20],eax;重命名var_20为dwCatchablesCount
0040386D jmp short loc_403881
;===============嵌套的for循环步长计算部分===============
loc_40386F:;第三层for循环步长计算
0040386F mov ecx,[ebp+dwCatchablesCount]
00403872 sub ecx,1
00403875 mov[ebp+dwCatchablesCount],ecx
00403878 mov edx,[ebp+ppCatchTableType]
0040387B add edx,4
0040387E mov[ebp+ppCatchTableType],edx
;===============嵌套的for循环语句块部分=================
;第三层for循环语句块
loc_403881:
00403881 cmp[ebp+dwCatchablesCount],0
00403885 jle short loc_4038DE;break
00403887 mov eax,[ebp+pExcept]
0040388A mov ecx,[eax+1Ch];传递ThrowInfo表结构指针
0040388D push ecx
0040388E mov edx,[ebp+ppCatchTableType]
00403891 mov eax,[edx]
00403893 push eax
00403894 mov ecx,[ebp+pCatchHandlerArray]
00403897 push ecx
;比较是否匹配catch块信息,若匹配,则返回1;若不匹配,则返回0
00403898 call TypeMatch;函数实现如代码清单13-5所示
0040389D add esp,0Ch
004038A0 test eax, eax;检查catch块的类型是否匹配
004038A2 jnz short loc_4038A6
004038A4 jmp short loc_40386F;若匹配失败,则继续检查
;===============第三层for循环结尾处====================
loc_4038A6:
;部分代码分析略
;此函数中完成了catch对象的构造与析构过程,并跳转到catch结束地址
004038D4 call CatchIt
004038D9 add esp,2Ch
004038DC jmp short loc_403943;跳转向函数结尾
loc_4038DE:
004038DE jmp loc_403834
;===============第二层for循环结尾处====================
loc_4038E3:
004038E3 jmp loc_4037EB
;===============第一层for循环结尾处====================
loc_4038E8:
;部分代码分析略
00403943 jmp short loc_4038E3
;部分代码分析略
00403945 loc_403945:
00403945 mov esp, ebp
00403947 pop ebp
00403948 retn
FindHandler endp
代码清单13-4对FindHandler的主要功能进行了分析,通过三层嵌套的for循环,完成了try块检查和catch块检查,利用TypeMatch函数完成了对异常匹配的判定并得到了结果,调用CatchIt完成了异常处理。CatchIt函数主要由四部分组成:异常对象的产生,析构try中的对象,跳转到对应的catch地址,返回到异常catch块的结尾地址。接下来通过对代码清单13-5中的TypeMatch函数的分析来查看catch的匹配检查过程。
代码清代13-5 TypeMatch分析—Debug版
;函数声明
;int__cdecl TypeMatch(
;const struct_msRttiDscr*pCatchHandlerArray,
;const struct CatchTableType*pCatchTableType,
;const struct ThrowInfo*pThrowInfo)
TypeMatch proc near
var_4=dword ptr-4
pCatchHandlerArray=dword ptr 8
pCatchTableType=dword ptr 0Ch
pFuncInfo=dword ptr 10h
;部分代码分析略
00407414 mov eax,[ebp+pCatchHandlerArray]
00407417 cmp dword ptr[eax+4],0;检查TypeDescriptor表结构
0040741B jz short loc_40742B;若为NULL,则直接返回1,表示匹配
;部分代码分析略
loc_407435:
00407435 mov ecx,[ebp+pCatchHandlerArray]
00407438 mov edx,[ebp+pCatchTableType]
0040743B mov eax,[ecx+4];获取pType
;将_msRttiDscr表结构中的pType与CatchTableType表结构中的pTypeInfo进行比较
0040743E cmp eax,[edx+4]
00407441 jz short loc_407467;若两个Type指向同一表结构,则跳转
00407443 mov ecx,[ebp+pCatchTableType]
00407446 mov edx,[ecx+4]
00407449 add edx,8;获取TypeDescriptor表结构中的name项
0040744C push edx;Str2
0040744D mov eax,[ebp+pCatchHandlerArray]
00407450 mov ecx,[eax+4]
00407453 add ecx,8;获取TypeDescriptor表结构中的name项
00407456 push ecx;Str1
00407457 call_strcmp;对比两个类型名称是否相同
0040745C add esp,8
0040745F test eax, eax;相同则跳转
00407461 jz short loc_407467
;部分代码分析略
loc_407467:;根据标记判断异常是否匹配
00407467 mov edx,[ebp+pCatchTableType]
0040746A mov eax,[edx];检查标记flag
0040746C and eax,2;检查是否已被捕获
0040746F test eax, eax
00407471 jz short loc_40747F
00407473 mov ecx,[ebp+pCatchHandlerArray]
00407476 mov edx,[ecx]
00407478 and edx,8;检查异常是否为引用类型
0040747B test edx, edx
0040747D jz short loc_4074B8
loc_40747F:
0040747F mov eax,[ebp+pThrowInfo]
00407482 mov ecx,[eax]
00407484 and ecx,1;检查抛出异常是否为常量
00407487 test ecx, ecx
00407489 jz short loc_407497
0040748B mov edx,[ebp+pCatchHandlerArray]
0040748E mov eax,[edx]
00407490 and eax,1;检查异常是否为常量
00407493 test eax, eax
00407495 jz short loc_4074B8
loc_407497:
00407497 mov ecx,[ebp+pThrowInfo]
0040749A mov edx,[ecx]
0040749C and edx,2;检查抛出异常是否为变量
0040749F test edx, edx
004074A1 jz short loc_4074AF
004074A3 mov eax,[ebp+pCatchHandlerArray]
004074A6 mov ecx,[eax]
004074A8 and ecx,2;检查异常是否为变量
004074AB test ecx, ecx
004074AD jz short loc_4074B8
loc_4074AF:
;设置对比结果,分析略
004074C5 retn
TypeMatch endp