C++ 虚函数 详解怎样实现C++虚函数调用汇编代码
link-初扬 人气:0虚函数(代码段地址)被存放在虚函数表中,调用虚函数的流程是这样子的:先获取虚函数表的首地址,然后根据目标虚函数在虚函数表的位置(offset偏移)取出虚函数表中的虚函数地址,最后去call这个虚函数(地址),就完成虚函数的调用。这个虚函数调用的流程在汇编代码中可以最直观的反映出来。
在排查软件异常或崩溃时,我们时常要借助汇编代码的上下文去辅助分析问题。读懂C++虚函数调用的汇编代码实现,对于搞懂汇编代码的上下文时很有好处的。今天我们就来看看虚函数调用的汇编代码实现。
比如如下的C++代码:
// 1、虚接口类中定义了纯虚接口 class IContactInterface { //... virtual BOOL IsLoadFinish() == 0; // 虚接口 //... } // 2、子类中实现虚接口 class Contact : public IContactInterface { //... BOOL IsLoadFinish(); //... } // 3、new出子类的对象,存放到父类的指针变量中 IContactInterface* g_pContactPtr = new Contact; // 4、获取子类对象的接口实现 IContactInterface* GetContactPtr() { return g_pContactPtr; } // 5、调用GetContactPtr获取子类的对象去调用虚函数IsLoadFinish GetContactPtr()->IsLoadFinish();
上述C++代码片中,主要包含了以下几点:
1)定义了虚接口类IContactInterface,在类中有个IsLoadFinish虚函数;
2)定义了一个子类Contact,继承于IContactInterface接口类,并实现了虚函数IsLoadFinish;
3)new出一个Contact类的对象,赋值给父类IContactInterface的指针变量;
4)调用GetContactPtr接口获取IContactInterface指针变量,调用虚函数IsLoadFinish。
其中,GetContactPtr()->IsLoadFinish()这句虚函数调用的汇编代码如下所示:
.text:005103EB call ds:__imp__GetContactPtr
.text:005103F1 mov [ebp+var_2AF4], eax
.text:005103F7 mov ecx, [ebp+var_2AF4]
.text:005103FD mov edx, [ecx]
.text:005103FF mov ecx, [ebp+var_2AF4]
.text:00510405 mov eax, [edx+78h]
.text:00510408 call eax
现在我们就来详细解读一下这段实现虚函数调用的汇编代码。
1、汇编代码段1:
.text:005103EB call ds:__imp__GetContactPtr
.text:005103F1 mov [ebp+var_2AF4], eax
.text:005103F7 mov ecx, [ebp+var_2AF4]
调用GetContactPtr接口,返回IContactInterface*指针,其中存放的是子类Contact对象。GetContactPtr接口返回的IContactInterface*指针中的值,放到eax寄存器中。
在C++的汇编代码中,调用函数的返回值是放到eax寄存器中的。
所以eax中存放的是子类Contact对象的地址。然后将eax中存放的子类Contact对象的地址,放到函数栈内存[ebp+var_2AF4]中。然后又将子类Contact对象的地址放到ecx寄存器中。
2、汇编代码段2:
.text:005103FD mov edx, [ecx]
代码运行至此,此时ecx中存放的就是子类Contact对象的地址。该句汇编代码是以ecx中的值作为内存地址,取出该地址中的值放到edx寄存器中。
在C++中,C++类中如果有虚函数,则类中会掩藏一个虚函数指针成员变量,是排放在C++类所有数据成员的首位,所以C++对象的地址,就是虚函数表指针变量的内存首地址(是指针变量的地址,不是指针变量中的值),所以此处的[ecx]操作,是取出虚函数表指针变量中存放的内容,即虚函数表的首地址。所以edx寄存器中存放的是Contact对象中的虚函数表的首地址。
C++类中如果有虚函数,则该类中会掩藏一个用来存放虚函数表地址的虚函数表指针变量,该虚函数表指针变量放置在该C++对象所有数据成员的首位,所以C++对象的地址就是其虚函数指针变量的内存首地址。
3、汇编代码段3:
.text:005103FF mov ecx, [ebp+var_2AF4]
回到整段汇编代码最开始的地方,[ebp+var_2AF4]中存放的是子类Contact对象的地址。因为下面要调用Contact类对象的虚函数,调用时要将Contact类对象的地址传给被调用的函数,即this指针。C++汇编代码中,是通过ecx寄存器将类对象的地址传给被调用函数,所以此句代码是为下面调用类Contact的虚函数IsLoadFinish做准备的。
在调用C++类的函数时,是通过ecx寄存器传递C++类对象的地址的,即this指针的存放的C++对象的地址。
4、汇编代码段4:
.text:00510405 mov eax, [edx+78h]
.text:00510408 call eax
接着上面,当前edx寄存器中存放的值是虚函数表的首地址(虚函数表指针中存放的值),78h是目标虚函数IsLoadFinish在虚函数中的偏移,所以对edx+78h地址取址,是读出虚函数表该位置存放的就是目标虚函数IsLoadFinish的地址(代码段地址),所以执行这行代码后,eax寄存器中存放的就是目标虚函数IsLoadFinish的地址,然后call eax就是调用虚函数IsLoadFinish了。
在C++中,函数名称其实就是该函数在代码段的首地址,去call这个首地址,就是去调用这个虚函数了。要注意区分数据段地址和代码段的地址。
加载全部内容