13.4 识别异常处理
通过对VC++异常处理的分析,可将其处理流程总结为以下9个步骤:
1)在函数入口处设置异常回调函数,其回调函数先设置eax为FuncInfo数据的地址,然后跳往___CxxFrameHandler。
2)异常的抛出由__CxxThrowException函数完成,该函数使用了两个参数,一个是抛出异常的关键字throw的参数的指针,另一个是抛出信息类型的指针(ThrowInfo*)。
3)在异常回调函数中,可以得到异常对象的地址和对应ThrowInfo数据的地址以及FunInfo表结构的地址。根据所记录的异常类型,进行try块的匹配工作。
4)如果没有找到try块,则析构异常对象,返回ExceptionContinueSearch,继续下一个异常回调函数的处理。
5)当找到对应的try块时,通过TryBlockMapEntry表结构中的pCatch指向catch信息表,用ThrowInfo表结构中的异常类型遍历查找与之匹配的catch块,比较关键字名称(如整型为.h,单精度浮点为.m),找到有效的catch块。
6)执行栈展开操作,并产生catch块中使用的异常对象(有4种不同的产生方法)。
7)正确析构所有生命期已结束的对象。
8)跳转到catch块,执行catch块代码。
9)调用_JumpToContinuation函数,返回所有catch语句块的结束地址。
根据上面的步骤,以一个典型的异常处理结构为例,对异常处理进行进一步讲解,如代码清单13-9所示。
代码清单13-9 典型的异常处理结构
//异常处理基类
class CExcepctionBase{
public:
virtual char*GetExcepctionInfo()=0;
};
//除零异常类
class CDiv0Excepction:public CExcepctionBase{
public:
CDiv0Excepction(){
printf("CExcepctionDiv0()\r\n");
}
virtual~CDiv0Excepction(){
printf("~CDiv0Excepction()\r\n");
}
virtual char*GetExcepctionInfo(){
return"div zero excepction";
}
};
//访问异常类
class CAccessExcepction:public CExcepctionBase{
public:
CAccessExcepction(){
printf("CAccessExcepction()\r\n");
}
virtual~CAccessExcepction(){
printf("~CAccessExcepction()\r\n");
}
virtual char*GetExcepctionInfo(){
return"access excepction";
}
};
//C++异常处理结构
void TestExcepction(int n)
{
try{
//以下抛出各个基本类型的异常
if(1==n)
{
throw 3;
}
if(2==n)
{
throw 3.0f;
}
if(3==n)
{
throw'3';
}
if(4==n)
{
throw 3.0;
}
//以下抛出异常对象
if(5==n)
{
throw CDiv0Excepction();
}
if(6==n)
{
throw CAccessExcepction();
}
//这里是抛出异常对象的指针
if(7==n)
{
CAccessExcepction excAccess;
throw&excAccess;
}
}
//处理各类异常
catch(int n){
printf("catch int%d\r\n",n);
}
catch(float f){
printf("catch float%f\r\n",f);
}
catch(char c){
printf("catch char%c\r\n",c);
}
catch(double d){
printf("catch double%f\r\n",d);
}
catch(CExcepctionBase&exc){
printf("catch error%s\r\n",exc.GetExcepctionInfo());
}
catch(CAccessExcepction*pExc){
printf("catch error%s\r\n",pExc->GetExcepctionInfo());
}
catch(……){
printf("catch……\r\n");
}
//异常处理结束
printf("Test end!\r\n");
}
int main(int argc, char*argv[])
{
for(int i=1;i<=8;i++)
{
TestExcepction(i);
}
return 0;
}
使用Release选项组,将以上代码编译后通过IDA载入,先找到TestExcepction的位置,在入口处会发现以下代码:
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 push 0FFFFFFFFh
.text:00401005 push offset unknown_libname_9;不难发现这里是典型的注册SEH句柄的代码
.text:0040100A mov eax, large fs:0
.text:00401010 push eax
.text:00401011 mov large fs:0,esp
在地址00401005处的代码是异常处理句柄,不妨双击unknown_libname_9跟进去看看,对应的代码如下:
.text:00408398 unknown_libname_9 proc near
.text:00408398 mov eax, offset stru_409848
.text:0040839D jmp___CxxFrameHandler
.text:0040839D unknown_libname_9 endp
看到___CxxFrameHandler后,可以确认,这里的异常注册是编译器产生的(参考步骤1),接着看TestExcepction函数的其他关键代码(快捷键Esc)。先看第一个跳转处:
.text:00401021 cmp eax,1
.text:00401024 mov[ebp+var_10],esp
.text:00401027 mov[ebp+var_4],0
.text:0040102E jnz short loc_401045
.text:00401030 lea eax,[ebp+var_18]
.text:00401033 push offset unk_409838
.text:00401038 push eax
.text:00401039 mov[ebp+var_18],3
.text:00401040 call__CxxThrowException@8;_CxxThrowException(x, x)
.text:00401045 loc_401045:
地址0040102E处开始一个单分支结构,在此单分支结构中有个函数调用,调用目标为__CxxThrowException@8。参考步骤2),可以得知此处为throw语句;根据地址00401038处的代码(push eax)还可以得知,throw参数的地址在eax中;在地址00401030处,显示eax的值为[ebp+var_18]的地址;在地址00401039处,有指令“mov[ebp+var_18],3”。这里IDA没有显示dword ptr,在函数入口代码前,定义了“var_18=dword ptr-18h”,因此可以确定这里的throw语句的参数为4字节长度,详细类型未知。
先不在throw类型上过多纠缠,继续往下看。
.text:00401045 cmp eax,2
.text:00401048 jnz short loc_40105F
.text:0040104A lea ecx,[ebp+var_1C]
.text:0040104D push offset dword_409828
.text:00401052 push ecx
.text:00401053 mov[ebp+var_1C],40400000h
.text:0040105A call__CxxThrowException@8;_CxxThrowException(x, x)
.text:0040105F loc_40105F:
同理,在0040105A处又有一条throw语句,其参数为[ebp+var_1C],在函数入口代码前,定义了“var_1C=dword ptr-1Ch”,故也可以确定这里的throw的参数为4字节长度,详细类型未知。
接下来,可以看到:
.text:0040105F cmp eax,3
.text:00401062 jnz short loc_401076
.text:00401064 lea edx,[ebp+var_11]
.text:00401067 push offset unk_409818
.text:0040106C push edx
.text:0040106D mov[ebp+var_11],33h
.text:00401071 call__CxxThrowException@8;_CxxThrowException(x, x)
.text:00401076 loc_401076:
相信大家对同类代码已经很熟悉了,这不再重复讲解。根据以上粗体代码所示,大家应该知道去查看var_11的定义,在函数入口代码前,定义var_11为“var_11=byte ptr-11h”,可以确定这里的throw语句的参数为1字节长度,详细类型未知。
接着看下去,还是大同小异。
.text:00401076 cmp eax,4
.text:00401079 jnz short loc_401097
.text:0040107B lea eax,[ebp+var_40]
.text:0040107E push offset unk_409808
.text:00401083 push eax
.text:00401084 mov[ebp+var_40],0
.text:0040108B mov[ebp+var_3C],40080000h
.text:00401092 call__CxxThrowException@8;_CxxThrowException(x, x)
.text:00401097 loc_401097:
值得一提的是,这里的throw语句的参数为[ebp+var_40]的地址,而对[ebp+var_40]和[ebp+var_3C]赋值的地址是00401084处和0040108B处,这两个内存单元是连续的,而且是在push和call__CxxThrowException@8指令之间,单纯看var_40的类型会得到dword ptr的定义,无法解释var_3C赋值的理由。先别着急,类型的问题先放一放,现在首先需要得到的是try、throw和catch语句的结构。
接下来的代码就有点意思了。
.text:00401097 cmp eax,5
.text:0040109A jnz short loc_4010BE
.text:0040109C push offset aCexcepctiondiv;"CExcepctionDiv0()\r\n"
.text:004010A1 mov[ebp+var_20],offset off_4090D0
.text:004010A8 call_printf
.text:004010AD add esp,4
.text:004010B0 lea ecx,[ebp+var_20]
.text:004010B3 push offset unk_4097F8
.text:004010B8 push ecx
.text:004010B9 call_CxxThrowException@8;_CxxThrowException(x, x)
.text:004010BE loc_4010BE:
这里的throw语句的参数为[ebp+var_20]的地址,而[ebp+var_20]的内容在004010A1处被设置为offset off_4090D0,下面看一下off_4090D0是个什么数据。
.rdata:004090D0 off_4090D0 dd offset sub_401210
.rdata:004090D4 dd offset sub_401220
sub_401210和sub_401220明显是函数的地址,说明在off_4090D0处存放了两个函数指针,下面观察这两个函数,首先看sub_401210:
.text:00401210 sub_401210 proc near
.text:00401210 mov eax, offset aDivZeroExcepct;"div zero excepction"
.text:00401215 retn
.text:00401215 sub_401210 endp
很明显,这个函数返回字符串“div zero excepction”。接着看sub_401220:
.text:00401220 sub_401220 proc near
.text:00401220 arg_0=byte ptr 4
.text:00401220 push esi
.text:00401221 mov esi, ecx
.text:00401223 push offset aCdiv0excepctio;"~CDiv0Excepction()\r\n"
.text:00401228 mov dword ptr[esi],offset off_4090D0
.text:0040122E call_printf
.text:00401233 mov al,[esp+8+arg_0]
.text:00401237 add esp,4
.text:0040123A test al,1
.text:0040123C jz short loc_401247
.text:0040123E push esi;lpMem
.text:0040123F call delete
.text:00401244 add esp,4
.text:00401247
.text:00401247 loc_401247:;CODE XREF:sub_401220+1Cj
.text:00401247 mov eax, esi
.text:00401249 pop esi
.text:0040124A retn 4
.text:0040124A sub_401220 endp
在sub_401220中,地址00401221处直接使用了ecx,说明ecx是用来传参的;在00401228处,回写off_4090D0的地址,而这个地址函数指针数组的首地址;地址00401233、0040123A、0040123C、0040123F处的指令结合起来判定参数最低位是否为1,若为1,则执行delete来释放esi,即参数ecx中的地址内容,若不为1,则不释放。以上种种迹象表明,这是个成员函数,而且是个虚析构函数。
回到分析TestExcepction函数的地方:
.text:00401097 cmp eax,5
.text:0040109A jnz short loc_4010BE
.text:0040109C push offset aCexcepctiondiv;"CExcepctionDiv0()\r\n"
.text:004010A1 mov[ebp+var_20],offset off_4090D0
.text:004010A8 call_printf
.text:004010AD add esp,4
.text:004010B0 lea ecx,[ebp+var_20]
.text:004010B3 push offset unk_4097F8
.text:004010B8 push ecx
.text:004010B9 call__CxxThrowException@8;_CxxThrowException(x, x)
.text:004010BE loc_4010BE:
现在可以确定,从地址004010A1到地址004010AD都是内联的构造函数的实现代码(下画线处),其构造的对象地址为ebp+var_20,这个地址传给了ecx,并作为throw语句的参数进行传递,说明此处的throw语句的参数为对象类型。
同理可识别接下来的代码:
.text:004010BE cmp eax,6
.text:004010C1 jnz short loc_4010E5
.text:004010C3 push offset aCaccessexcepct;"CAccessExcepction()\r\n"
.text:004010C8 mov[ebp+var_24],offset off_4090C8
.text:004010CF call_printf
.text:004010D4 add esp,4
.text:004010D7 lea edx,[ebp+var_24]
.text:004010DA push offset unk_4097E8
.text:004010DF push edx
.text:004010E0 call__CxxThrowException@8;_CxxThrowException(x, x)
.text:004010E5;---------------------------------------------------------------
.text:004010E5
.text:004010E5 loc_4010E5:
观察offset off_4090C8的内容即可确定004010C3至004010D4为对象的构造函数的代码,其地址为ebp+var_24,这个地址成为throw语句的参数,故这里的throw语句的参数也是一个对象,观察对象的虚表地址,可以确认,这个对象和上例中的对象所属类型不同。
接下来看后面的代码。
.text:004010E5 cmp eax,7
.text:004010E8 jnz loc_4011C9
.text:004010EE push offset aCaccessexcepct;"CAccessExcepction()\r\n"
.text:004010F3 mov[ebp+var_60],offset off_4090C8
.text:004010FA call_printf
.text:004010FF add esp,4
.text:00401102 lea ecx,[ebp+var_28]
.text:00401105 lea eax,[ebp+var_60]
.text:00401108 push offset dword_4097D8
.text:0040110D push ecx
.text:0040110E mov byte ptr[ebp+var_4],1
.text:00401112 mov[ebp+var_28],eax
.text:00401115 call__CxxThrowException@8;_CxxThrowException(x, x)
.text:0040111A;--------------------------------------------------------------
.text:0040111A
.text:0040111A loc_40111A:
按上面的方法,很容易看出004010EE到004010FF是内联构造函数的代码,结合虚表地址可发现其对象类型与上例相同。大家要注意地址00401102处、00401105处和00401112处的三条指令,其作用是将对象取地址存放到[ebp+var_28]处,并将[ebp+var_28]作为参数,由ecx传递给throw语句(地址0040110D处的指令)。这说明此处的throw语句的参数为对象的地址。
继续分析下面的代码:
.text:0040111A loc_40111A:;DATA XREF:.rdata:stru_409898
.text:0040111A mov edx,[ebp+var_2C]
.text:0040111D push edx
.text:0040111E push offset aCatchIntD;"catch int%d\r\n"
.text:00401123 call_printf
.text:00401128 add esp,8
.text:0040112B mov eax, offset loc_4011C9
.text:00401130 retn
.text:00401131;----------------------------------------------------------------
.text:00401131
.text:00401131 loc_401131:;DATA XREF:.rdata:stru_409898 o
.text:00401131 fld[ebp+var_30]
.text:00401134 sub esp,8
.text:00401137 fstp qword ptr[esp+4+var_4]
.text:0040113A push offset aCatchFloatF;"catch float%f\r\n"
.text:0040113F call_printf
.text:00401144 add esp,0Ch
.text:00401147 mov eax, offset loc_4011C9
.text:0040114C retn
……
这里的代码很特别,首先是标号,如地址0040111A处和00401131处的两个标号,IDA以注释的形式给出了引用其标号的地址,可以发现引用这两个标号的都是.rdata节所处的内存位置;其次是返回值,很容易发现不合理的地方,这里两个标号处的返回值都是某代码的地址,难道是函数返回函数指针?接下来对照函数入口查看返回前的栈平衡代码。
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 push 0FFFFFFFFh
.text:00401005 push offset unknown_libname_9
.text:0040100A mov eax, large fs:0
.text:00401010 push eax
.text:00401011 mov large fs:0,esp
明显不合理的地方是:返回前栈顶没有平衡。
先看看返回值到底去了什么地方,接下来很多代码都有同样的返回值。
.text:0040112B mov eax, offset loc_4011C9
.text:00401130 retn
它们的返回值都是loc_4011C9,先来看看loc_4011C9是“何方神圣”。
.text:004011C9 loc_4011C9:;CODE XREF:TestExcepction+E8
.text:004011C9;DATA XREF:TestExcepction+12B
.text:004011C9 push offset aTestEnd;"Test end!\r\n"
.text:004011CE call_printf
.text:004011D3 mov ecx,[ebp+var_C]
.text:004011D6 add esp,4
.text:004011D9 mov large fs:0,ecx
.text:004011E0 pop edi
.text:004011E1 pop esi
.text:004011E2 pop ebx
.text:004011E3 mov esp, ebp
.text:004011E5 pop ebp
.text:004011E6 retn
.text:004011E7 TestExcepction endp
结合函数入口代码,很容易看出004011D9到004011E6之间的汇编指令对应的函数功能是:对函数TestExcepction执行栈顶平衡操作并返回,同时IDA也给予了正确的提示(见地址004011E7)[1]。参考本节开始处总结的异常处理步骤中的4)~8)步,可以得知这里其实是catch的处理代码。了解了异常处理的内部行为后,可总结出其特征为:
;catch语句块的开始标志
CATCH1_BEGIN:;IDA以注释的方式提示,对这个标号的引用不在代码节中(通常是.rdata节)
;catch语句块的实现代码。如果catch语句块中的代码存在对catch参数的引用,就有机会得知参数的类型
;catch语句块的结尾标志
;不正常的返回,栈顶没有正确平衡
mov eax, ALL_CATCH_END;每个catch块的返回值为当前try语句对应的所有catch的末尾地址
retn
CATCH2_BEGIN:
……
mov eax, ALL_CATCH_END
retn
……;其他的catch语句块
ALL_CATCH_END:
……
;最后会正常地平衡栈顶,还原fs:[0],并返回
……
retn
根据以上的总结,可以在IDA中修改catch语句块对应标号的名称,这里将0040111A处的标号loc_40111A修改为CATCH1_BEGIN。修改完毕后,如下所示:
;第一个catch块的起始处
.text:0040111A CATCH1_BEGIN:;DATA XREF:.rdata:stru_409898
.text:0040111A mov edx,[ebp+var_2C]
.text:0040111D push edx
.text:0040111E push offset aCatchIntD;"catch int%d\r\n"
.text:00401123 call_printf
.text:00401128 add esp,8
.text:0040112B mov eax, offset ALL_CATCH_END
.text:00401130 retn
;第一个catch块的结尾处
.text:00401131;------------------------------------------------------------
;第二个catch块的起始处
.text:00401131 CATCH2_BEGIN:;DATA XREF:.rdata:stru_409898 o
.text:00401131 fld[ebp+var_30]
.text:00401134 sub esp,8
.text:00401137 fstp qword ptr[esp+4+var_4]
.text:0040113A push offset aCatchFloatF;"catch float%f\r\n"
.text:0040113F call_printf
.text:00401144 add esp,0Ch
.text:00401147 mov eax, offset ALL_CATCH_END
.text:0040114C retn
;第二个catch块的结尾处
……
;当前try语句对应的所有catch块的末尾处
.text:004011C9 ALL_CATCH_END:
.text:004011C9;DATA XREF:TestExcepction+12B o……
.text:004011C9 push offset aTestEnd;"Test end!\r\n"
.text:004011CE call_printf
.text:004011D3 mov ecx,[ebp+var_C]
.text:004011D6 add esp,4
.text:004011D9 mov large fs:0,ecx
.text:004011E0 pop edi
.text:004011E1 pop esi
.text:004011E2 pop ebx
.text:004011E3 mov esp, ebp
.text:004011E5 pop ebp
.text:004011E6 retn
现在看起来可读性是不是强了很多?
接下来讨论一下throw语句和catch语句的参数类型的判定问题。对于类型为对象的情况,重点考察对象中所保存的虚函数表指针即可,确定了虚函数表所属的类型,即可判定对象的类型。对于基本数据类型和简单对象结构(不存在虚函数的情况),最简单的办法就是使用调试器,先在IDA中分析出try/throw/catch的结构,确定每个catch的边界,然后在调试器中为每个catch语句块的首地址设置断点,并修改代码以触发throw语句,之后开始运行,触发异常后开始观察每个throw和catch的对应关系。如果环境不允许运行和调试,那么就需要分析者对编译器的异常处理技术细节有所了解。对于VC++6.0,可以考察throw的第二个参数(ThrowInfo*类型),或IDA提示中引用catch语句的地址(CatchTableType类型),根据图13-11(异常回调与异常抛出的结构关系图)找到TypeDescriptor表结构,即可找到类型定义甚至自定义类型的名称。比如上例中的代码:
.text:00401021 cmp eax,1
.text:00401024 mov[ebp+var_10],esp
.text:00401027 mov[ebp+var_4],0
.text:0040102E jnz short loc_401045
.text:00401030 lea eax,[ebp+var_18]
.text:00401033 push offset unk_409838
.text:00401038 push eax
.text:00401039 mov[ebp+var_18],3
.text:00401040 call__CxxThrowException@8;_CxxThrowException(x, x)
.text:00401045 loc_401045:
这里是throw语句处的反汇编代码,考察throw的第二个参数可以得到对应ThrowInfo信息的地址,这里是unk_409838。接着看unk_409838的定义:
.rdata:00409838 unk_409838 db 0;DATA XREF:TestExcepction+33
.rdata:00409839 db 0
.rdata:0040983A db 0
.rdata:0040983B db 0
.rdata:0040983C dd 0
.rdata:00409840 dd 0
.rdata:00409844 dd offset dword_4097D0
按Shift+F9组合键,打开IDA中结构体的定义窗口,按Insert键创建结构体,将其命名为ThrowInfo,按13.1节的定义输入内容,定义完毕后,回到反汇编窗口,单击unk_409838,按Alt+Q组合键,在类型选择界面中选中刚刚定义的ThrowInfo,得到如下代码:
.rdata:00409838 stru_409838 ThrowInfo<0,0,0,4097D0h>;DATA XREF:
TestExcepction+33
将stru_409838的名称修改为常用的规范名称,这里修改为TI_1,得到:
.rdata:00409838 TI_1 ThrowInfo<0,0,0,4097D0h>;DATA XREF:TestExcepction+33o
ThrowInfo表结构中的第四项是pCatchTableTypeArray,在TI_1中对应的地址是4097D0h,此处是:
.rdata:004097D0 dd 1
.rdata:004097D4 dd offset unk_4097B0
按Shift+F9组合键,增加对pCatchTableTypeArray类型的定义。pCatchTableTypeArray是CatchTableTypeArray类型的指针,按13.1节中CatchTableTypeArray的定义在IDA中创建结构体,将其命名为CatchTableTypeArray,单击地址004097D0处,然后按Alt+Q组合键更改定义,得到:
.rdata:004097D0 CatchTableTypeArray<1,4097B0h>
根据CatchTableTypeArray的定义得知,CatchTableTypeArray数组中只有一项元素,地址为4097B0h,其类型为CatchTableType**。在地址4097B0h处,按Shift+F9组合键,增加对CatchTableType的定义,按Alt+Q组合键将此地址定义为CatchTableType:
.rdata:004097B0 CatchTableType<1,40A120h,0,0FFFFFFFFh,0>
根据CatchTableType的定义得知,第二项为pTypeInfo,它指向异常类型结构(TypeDescriptor),在地址40A120H处继续观察:
.data:0040A120 off_40A120 dd offset off_4090E0;DATA XREF:.rdata:stru_409898
.data:0040A124 dd 0
.data:0040A128 a_h db'.H',0
地址04A120H处是TypeDescriptor表结构的内容,0040A128对应的是此结构的TypeDes-criptor:name项。对于基本数据类型而言,每个类型都有其名称代号,如int用“.H”表示,float用“.M”表示,char用“.D”表示,double用“.N”表示等(其他类型请读者自己尝试寻找)。对于结构体和类而言,这个字符串中包含了类名称。
最后讲解如何从catch语句入手得到每个catch语句的参数信息。以地址00401180处的catch块代码为例:
.text:00401180 loc_401180:
.text:00401180 mov ecx,[ebp+var_34]
.text:00401183 mov eax,[ecx]
.text:00401185 call dword ptr[eax]
.text:00401187 push eax
.text:00401188 push offset aCatchErrorS;"catch error%s\r\n"
.text:0040118D call_printf
.text:00401192 add esp,8
.text:00401195 mov eax, offset ALL_CATCH_END
.text:0040119A retn
首先观察00401180处的注释(上面代码中以下画线处),这里的注释指示了此标号的参考引用位置,双击stru_409898进入此处查看:
.rdata:00409898 stru_409898_msRttiDscr<0,offset off_40A120,-44,offset CATCH1_BEGIN>
.rdata:00409898;DATA XREF:.rdata:stru_409880
.rdata:00409898_msRttiDscr<0,offset off_40A110,-48,offset CATCH2_BEGIN>
.rdata:00409898_msRttiDscr<0,offset stru_40A100,-18,offset loc_40114D>
.rdata:00409898_msRttiDscr<0,offset stru_40A0F0,-72,offset loc_401165>
.rdata:00409898_msRttiDscr<8,offset stru_40A0B0,-52,offset loc_401180>
.rdata:00409898_msRttiDscr<0,offset stru_40A070,-56,offset loc_40119B>
.rdata:00409898_msRttiDscr<0,0,0,offset loc_4011B6>
这里是个_msRttiDscr类型的数组,这个类型会被IDA识别出来,根据结构定义可知这个结构的最后一项是CatchProc,即catch语句块起始处对应标号的位置。这个catch的标号为loc_401180,查询这个数组中的每一项_msRttiDscr元素,找到其CatchProc值等于loc_401180的一项。很快就找到了这项在地址00409898处,参考13.1节中对_msRttiDscr类型的定义,得知_msRttiDscr:pType中保存了TypeDescriptor表结构的地址,太好了。这个catch语句对应的_msRttiDscr信息中的pType值为stru_40A0B0,到stru_40A0B0中看看:
.data:0040A0B0 stru_40A0B0 dq offset off_4090E0;DATA XREF:.rdata:stru_409898
.data:0040A0B8 a_?avcexcepctio db'.?AVCExcepctionBase@@',0
其TypeDescriptor:name域的值为“.?AVCExcepctionBase@@”,这表示参数为CExcepctionBase类型,或者该类型的引用。如果需要区分参数是否为引用,可以查阅CatchTableType表结构中的pCopyFunction成员的值。如果为0,就是引用,否则就是拷贝构造函数的地址。当catch参数为某对象指针时,其名称前多一个字符“P”,对于本例,当参数为指针时,其TypeDescriptor:name域的值变为“.?PAVCExcepctionBase@@”。
[1]在很多情况下,IDA对函数返回处的代码边界的判定有误,需要人工参与分析,然后在IDA中正确设置函数边界。方法是:先选择需要设置的函数名,按Alt+P组合键,然后在设置窗口中指定函数的Start address和End address即可。