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