虛函數(shù)(代碼段地址)被存放在虛函數(shù)表中,調(diào)用虛函數(shù)的流程是這樣子的:先獲取虛函數(shù)表的首地址,然后根據(jù)目標(biāo)虛函數(shù)在虛函數(shù)表的位置(offset偏移)取出虛函數(shù)表中的虛函數(shù)地址,最后去call這個虛函數(shù)(地址),就完成虛函數(shù)的調(diào)用。這個虛函數(shù)調(diào)用的流程在匯編代碼中可以最直觀的反映出來。
在排查軟件異常或崩潰時,我們時常要借助匯編代碼的上下文去輔助分析問題。讀懂C++虛函數(shù)調(diào)用的匯編代碼實(shí)現(xiàn),對于搞懂匯編代碼的上下文時很有好處的。今天我們就來看看虛函數(shù)調(diào)用的匯編代碼實(shí)現(xiàn)。
比如如下的C++代碼:
// 1、虛接口類中定義了純虛接口 class IContactInterface { //... virtual BOOL IsLoadFinish() == 0; // 虛接口 //... } // 2、子類中實(shí)現(xiàn)虛接口 class Contact : public IContactInterface { //... BOOL IsLoadFinish(); //... } // 3、new出子類的對象,存放到父類的指針變量中 IContactInterface* g_pContactPtr = new Contact; // 4、獲取子類對象的接口實(shí)現(xiàn) IContactInterface* GetContactPtr() { return g_pContactPtr; } // 5、調(diào)用GetContactPtr獲取子類的對象去調(diào)用虛函數(shù)IsLoadFinish GetContactPtr()->IsLoadFinish();
上述C++代碼片中,主要包含了以下幾點(diǎn):
1)定義了虛接口類IContactInterface,在類中有個IsLoadFinish虛函數(shù);
2)定義了一個子類Contact,繼承于IContactInterface接口類,并實(shí)現(xiàn)了虛函數(shù)IsLoadFinish;
3)new出一個Contact類的對象,賦值給父類IContactInterface的指針變量;
4)調(diào)用GetContactPtr接口獲取IContactInterface指針變量,調(diào)用虛函數(shù)IsLoadFinish。
其中,GetContactPtr()->IsLoadFinish()這句虛函數(shù)調(diào)用的匯編代碼如下所示:
.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
現(xiàn)在我們就來詳細(xì)解讀一下這段實(shí)現(xiàn)虛函數(shù)調(diào)用的匯編代碼。
1、匯編代碼段1:
.text:005103EB call ds:__imp__GetContactPtr
.text:005103F1 mov [ebp+var_2AF4], eax
.text:005103F7 mov ecx, [ebp+var_2AF4]
調(diào)用GetContactPtr接口,返回IContactInterface*指針,其中存放的是子類Contact對象。GetContactPtr接口返回的IContactInterface*指針中的值,放到eax寄存器中。
在C++的匯編代碼中,調(diào)用函數(shù)的返回值是放到eax寄存器中的。
所以eax中存放的是子類Contact對象的地址。然后將eax中存放的子類Contact對象的地址,放到函數(shù)棧內(nèi)存[ebp+var_2AF4]中。然后又將子類Contact對象的地址放到ecx寄存器中。
2、匯編代碼段2:
.text:005103FD mov edx, [ecx]
代碼運(yùn)行至此,此時ecx中存放的就是子類Contact對象的地址。該句匯編代碼是以ecx中的值作為內(nèi)存地址,取出該地址中的值放到edx寄存器中。
在C++中,C++類中如果有虛函數(shù),則類中會掩藏一個虛函數(shù)指針成員變量,是排放在C++類所有數(shù)據(jù)成員的首位,所以C++對象的地址,就是虛函數(shù)表指針變量的內(nèi)存首地址(是指針變量的地址,不是指針變量中的值),所以此處的[ecx]操作,是取出虛函數(shù)表指針變量中存放的內(nèi)容,即虛函數(shù)表的首地址。所以edx寄存器中存放的是Contact對象中的虛函數(shù)表的首地址。
C++類中如果有虛函數(shù),則該類中會掩藏一個用來存放虛函數(shù)表地址的虛函數(shù)表指針變量,該虛函數(shù)表指針變量放置在該C++對象所有數(shù)據(jù)成員的首位,所以C++對象的地址就是其虛函數(shù)指針變量的內(nèi)存首地址。
3、匯編代碼段3:
.text:005103FF mov ecx, [ebp+var_2AF4]
回到整段匯編代碼最開始的地方,[ebp+var_2AF4]中存放的是子類Contact對象的地址。因?yàn)橄旅嬉{(diào)用Contact類對象的虛函數(shù),調(diào)用時要將Contact類對象的地址傳給被調(diào)用的函數(shù),即this指針。C++匯編代碼中,是通過ecx寄存器將類對象的地址傳給被調(diào)用函數(shù),所以此句代碼是為下面調(diào)用類Contact的虛函數(shù)IsLoadFinish做準(zhǔn)備的。
在調(diào)用C++類的函數(shù)時,是通過ecx寄存器傳遞C++類對象的地址的,即this指針的存放的C++對象的地址。
4、匯編代碼段4:
.text:00510405 mov eax, [edx+78h]
.text:00510408 call eax
接著上面,當(dāng)前edx寄存器中存放的值是虛函數(shù)表的首地址(虛函數(shù)表指針中存放的值),78h是目標(biāo)虛函數(shù)IsLoadFinish在虛函數(shù)中的偏移,所以對edx+78h地址取址,是讀出虛函數(shù)表該位置存放的就是目標(biāo)虛函數(shù)IsLoadFinish的地址(代碼段地址),所以執(zhí)行這行代碼后,eax寄存器中存放的就是目標(biāo)虛函數(shù)IsLoadFinish的地址,然后call eax就是調(diào)用虛函數(shù)IsLoadFinish了。
在C++中,函數(shù)名稱其實(shí)就是該函數(shù)在代碼段的首地址,去call這個首地址,就是去調(diào)用這個虛函數(shù)了。要注意區(qū)分?jǐn)?shù)據(jù)段地址和代碼段的地址。
到此這篇關(guān)于詳解如何實(shí)現(xiàn)C++虛函數(shù)調(diào)用匯編代碼的文章就介紹到這了,更多相關(guān)C++ 虛函數(shù)內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/chenlycly/article/details/121046234