構造函數和析構函數在不在 vftable 裡面?

代碼詳情 請點擊 :C++ code
- 50 lines 查看

如圖, 我獲取類A的 vftable之後,按道理說, pVMF[0] 應該是構造函數才對呀, 構造函數是排在第一位的, 可是printf 的結果證明pVMF[0]指向的是 A::play() 的內存地址...

那麼問題來了, 構造函數 和析構函數躲在內存的什麼地方??

怎麼找到他們的內存地址???


前面的回答都很靠譜的提到了重點:只有虛函數才會出現在虛函數表裡;而且虛函數表裡的項的順序並不保證跟類里虛函數的聲明順序一致。編譯器實現和ABI有決定vtable layout的自由。

C++的類的構造函數永遠不是虛的。它只應該在兩種場景使用,

  • 在new表達式中被調用:new表達式 = operator new調用 + 構造函數調用。這裡總是指定某個特定的類來new的,整個行為不會多態,所以也沒有讓它成為虛函數的必要性。
    • 不過確實有時候大家會希望根據構造的參數的不同而實質上選擇某個子類來new。這種需求常常通過工廠函數(factory function)來解決。
  • 被子類的構造器的初始化列表調用。C++里類繼承關係是靜態確定的,子類永遠知道自己的基類是哪些,所以這個調用也不可能是虛的。

析構函數則可能是虛的。跟構造與析構是對稱的過程,也正因為如此所以一個不會為虛而另一個可能為虛:

  • 構造:以new表達式為例,這是從一個非多態的信息到一個可多態的指針的操作
  • 析構:以delete表達式為例,這是一個從可多態的指針要找到具體類型信息的操作

===========================================

至於如何在程序里找出構造函數和析構函數的地址。構造函數的地址是肯定不會出現在vtable里的了。那咋搞?

題主給問題打上了逆向工程的標籤,而且看來環境是MSVC,那如果目標文件里還有符號表的話,其實最直觀的辦法就是去符號表裡把構造函數的decorated name對應的地址項找出來。

具體咋操作還得看題主到底是在什麼條件下需要找出這個地址來做什麼。


我說一個辦法,寫好測試代碼,然後用windbg調試運行,在能獲得this指針(對象首地址)的函數里下個斷點,然後假設對象地址是a,那在windbg里輸入dd a,輸出的第一個地址就是虛表指針了,假設虛表指針為b,那 dds b,就會輸出虛函數表了,如下圖所示:

------------------------------------------------------------------------

更新一下,評論里孫明琦問"mangle過的函數名有辦法恢復嗎",由於評論沒法貼圖,我在這裡回答一下:

如圖所示,看紅框1,是虛表裡的函數地址,並不是真正的函數地址,而紅框2里的地址,才是真正的函數地址,symopt里的SYMOPT_UNDNAME是默認開啟的,所以紅框2里的函數名是沒有經過mangle的.那麼紅框1里的地址是什麼呢?我們看下圖:

原來只有一條指令,jmp到C::play函數.

所以結論就是,這個?playCUAEXXZ並不是源代碼里的函數名經過"標準的mangle"的結果,所以目前的windbg是沒有辦法進行"恢復"的, 鬼知道編譯器是根據什麼規則生成的這個符號,反正用UnDecorateSymbolName是解不出來

ps: 以上是在debug模式下的結果


當一個類的所有函數都標記了virtual的時候,構造函數不在,析構函數在。


當且僅當函數是虛函數時候,其地址就會/才會放進vtable。

構造/析構函數和普通成員函數的區別是:在new/delete時候,編譯器會根據對象類型「自動」調用;子類知道繼承體系,也就知道父類的構造/析構函數。


只有虛函數才會存進虛函數表裡

C++不能通過正常的語法找到構造函數和析構函數的地址


vtable中只包含虛函數,R大已經說的很清楚了。

補充一點:只有編譯器來決定vtable中的順序以及vtable放在內存中的哪個位置(也就是你的類對應的結構體的內存模型里vtable的位置)。所以作者的代碼上,只能保證在指定的某個編譯器上來保證取到的是vtable。


謝謝樓上二位的指導. 該試驗的代碼在

#include "stdafx.h"
#include &
#include &

using namespace std;

class A
{
public:
A() {
}

~A(){ cout &<&< "~A" &<&< endl; } virtual void play() { cout &<&< "Aplay" &<&< endl; } virtual void play2() { cout &<&< "Aplay2" &<&< endl; } void shit() { cout &<&< "shit" &<&< endl; } virtual void play3() { cout &<&< "Aplay3" &<&< endl; } virtual void play4() { cout &<&< "Aplay4" &<&< endl; } virtual void play5() { cout &<&< "Aplay5" &<&< endl; } int m; }; class B : public A { public: virtual void play() { cout &<&< "Bplay" &<&< endl; } virtual void play2() { cout &<&< "Bplay2" &<&< endl; } virtual void play3() { cout &<&< "Bplay3" &<&< endl; } }; class C : public B { public: virtual void play() { cout &<&< "Cplay" &<&< endl; } virtual void play2() { cout &<&< "Cplay2" &<&< endl; } }; // 定義類 成員函數的 type typedef void(__fastcall *pfnClass_func)(void *); // 該函數用於輸出 vftable void PrintVft(void* b) { // 獲取對象b的 vftable DWORD *pVMT = reinterpret_cast&(*(DWORD *)b);

cout &<&< "Vtable is " &<&< endl; int i = 0; // 定義一個函數指針 (用於指向類的成員函數) pfnClass_func vft_func; while (pVMT[i] != NULL) // 判斷 vftable 的第 i 項是否為空 { cout &<&< "NUM " &<&< i+1 &<&< "Function " &<&< endl; cout &<&< "-------&>";

// 將 函數指針 指向 vftable 第 i 個成員函數
vft_func = (pfnClass_func)pVMT[i];
// 調用 vftable 第 i 個成員函數
vft_func(NULL);
i++;
}
cout &<&< "End" &<&< endl; } int main() { C c; PrintVft(c); return 0; }

print 出來的結果,

另外,經過測試, 如果 析構函數, 用 virtual 修飾, 那麼也會被加入到 vftable 進去

感謝樓上各大神幫助


只有virtual的才會在vtable里

構造函數不行

析構函數加了virtual可以

而且

vtable里項的順序似乎並沒有嚴格的規定,不同ide可能有不同的結果?

vtable里項的值似乎也不是裸的函數地址,至少之前我用vc玩的時候發現還包了1層,因為跟進彙編後又跳轉了1次才到真實地址


構造函數和析構函數不在vtable,不含虛函數的類直接都沒有vtable的。


推薦閱讀:

如何優雅地做一個使用OS X的黑客?

TAG:逆向工程 | C | 軟體逆向工程 |