2014-07-09 31 views
6

在C++中有什麼辦法可以獲得真實地址的成員函數,或索引的vTable?得到虛擬成員函數的真實地址(或索引)

更新時間:

我不知道該指數的VTable我不知道地址

這裏的爲什麼我想知道這一點:

我想掛鉤DirectX的函數ID3DXFont-> DrawText。如果我知道vTable中的DrawText的索引,我可以將其替換爲掛鉤。但如何獲得索引?如果它能夠獲得真實地址,我可以在vTable中搜索它以獲取索引。

而不是特別的ID3DXFont-> DrawText,可能在將來會有一些其他功能,所以我想寫一個通用的鉤子函數。

這裏是我試過到目前爲止:

#include <iostream> 
using namespace std; 

struct cls { 
    virtual int fn1() { 
     cout << "fn1 called" << endl; 
     return 1; 
    } 
    virtual int fn2() { 
     cout << "fn2 called" << endl; 
     return 2; 
    } 
}; 

template <typename fn_t> 
DWORD fn_to_addr(fn_t fn) { // convert function to DWORD for printing 
    union U { 
     fn_t fn; 
     DWORD addr; 
    }; 
    U u; 
    u.fn = fn; 
    return u.addr; 
} 

int main() { 
    cls c; 

    DWORD addr = fn_to_addr(&cls::fn2); 
    cout << hex << addr << endl; 
} 

在調試模式下,上面的代碼輸出跳錶的地址。 而在釋放模式中,& CLS :: FN2回報0x00401058,其中指出了一些優化代碼:

00401058 . mov  eax, dword ptr [ecx] // get vptr 
0040105A . jmp  dword ptr [eax+4] // jmp to the second function (fn2) 

兩者都不是真實地址。無論如何要做到這一點?

謝謝。

+3

這是一個非常有趣的問題。由於您使用的是虛擬方法,因此它取決於您使用的編譯器。 – mathk

+0

一般不可能。然而,閱讀[RTTI](http://en.wikipedia.org/wiki/Run-time_type_information),並解釋你爲什麼問你的問題....(爲什麼函數的機器地址對你很重要?) ! –

+0

@ aj3423你是什麼意思的「真實地址」?虛擬內存概念如何? –

回答

3

不要輕易放棄!

雖然其他答案在說C++語言不允許你以便攜方式做到這一點時是正確的,但是在你的特定情況下還有一個重要因素可能使得這是一個更合理的事情。

關鍵是ID3DXFont是一個COM接口,以及與用於訪問它們的語言分開指定這些工作的確切二進制細節。因此,雖然C++沒有說明你會在指針的另一端找到什麼,但COM 確實說有一個vtable,它有一個指定順序的函數指針數組和指定的調用約定。這使我可以告訴你,DrawText函數的索引是(DrawTextA)或15(DrawTextW),並且這在多年後仍然會在Visual C++ 28.0中成立。或者對於GCC 8.3.1來說:因爲COM是一個二進制接口規範,所有編譯器都應該以同樣的方式實現它(如果它們聲稱支持COM)。

查看下面的第二個鏈接,瞭解使用兩種不同方法的COM函數掛接的現成實現。方法#2最接近你的要求,但我認爲你可能想考慮第一個,因爲它涉及較少的巫術。

來源:

[http://msdn.microsoft.com/en-us/library/ms680573(v=vs.85).aspx] [http://www.codeproject.com/Articles/153096/Intercepting-Calls-to-COM-Interfaces] [http://goodrender.googlecode.com/svn/trunk/include/d3dx9core.h]

+0

爲什麼vtable中DrawText的索引在我的機器上是14(win7 32bit + vs2010 +調試模式) – aj3423

+0

@ aj3對不起,我假設函數在MSDN中按接口順序列出,但它們只是按字母順序排列。你是正確的:它是14(或UTF-16版本15)。答案已更新。但我向你保證,這不取決於你的機器,編譯器或編譯器設置。 – slajoie

+0

明白了,謝謝slajoie。 – aj3423

1

在提到本wiki page

每當一個類定義了虛擬函數(或方法),最 編譯器的隱藏成員變量添加到其指向一個 所謂的虛擬方法表中的類(VMT或Vtable)。這個VMT基本上是(虛擬)函數指針數組。

據我所知,你不能訪問Vtable,編譯器甚至不知道表中的條目數。

+0

實際上,編譯器比你更瞭解vtable的佈局。 –

+0

但編譯時編譯器不知道vtable中每個條目應該指向哪個函數,基類函數或派生類函數。這些條目是在運行時設置的。 – eladm26

+0

顯然,由於條目可以在不同的時間指向不同的功能。但_you_甚至不知道哪個入口(如果有的話)與您感興趣的功能相關。 –

2

沒有任何東西靠近便攜式。您嘗試使用 &cls::fn2無法正常工作,因爲結果必須在 類似(pCls->*fn)()的情況下工作,即使pCls指向覆蓋該函數的派生類 也是如此。 (指向成員函數的指針爲 複雜的野獸,它們確定函數是否爲 虛擬,並提供不同的信息,具體取決於此。如果你正在使用MSC實驗,要知道,你 必須指定/vmg爲指向成員函數正確工作 。)

即使對於一個給定的實現,需要 正確類型的實例。鑑於此,如果您知道類佈局,並且 虛擬函數表的佈局,您可以將其跟蹤。 通常,指向虛函數表的指針是類中的第一個單詞,儘管這不能保證。通常,功能將按照 聲明的順序顯示。然而,除了附加信息之外,像調用RTTI的指針 ,以及調用函數時可能需要的偏移量信息 來修復指針this(儘管 許多編譯器會爲此使用蹦牀)。對於64位的G ++ Windows下(CygWin的版本):

struct C 
{ 
    virtual ~C() {} 
    virtual void fn1() const { std::cout << "In C::fn1\n"; } 
    virtual void fn2() const {} 
}; 

void const* 
fn1ToAddr(C const* pC) 
{ 
    void const* const* vPtr = *reinterpret_cast<void const* const* const*>(pC); 
    return vPtr[2]; 
} 

fn1ToAddr回報fn1該對象的地址傳遞 它;如果對象類型爲C,則返回地址 C::fn1,並且如果它是覆蓋fn1, 的派生類型,則返回覆蓋函數的地址。

無論是否全部工作,我都不能說;我認爲 g ++在多繼承的情況下使用蹦牀,例如 示例(在這種情況下,返回的地址將是蹦牀的 地址)。它可能無法在g ++的主要版本 上運行。 (對於MSC的版本,我手邊, 替代指數21似乎工作。但同樣, 我只試過很簡單的情況下,絕對沒有任何 保證。)

基本上,你會從來沒有想在 產品代碼中做這樣的事情。但是,如果您想要了解編譯器如何工作,那麼它會很有用。

編輯:

重新編輯你的原因?僅僅因爲你有地址 (也許),這並不意味着你可以調用該函數。你不能在沒有對象的情況下調用成員函數,並且根據任何數量的東西,都可能無法傳遞 函數。 (以MSC爲例,對象將通過 通過ECX。)

+0

對不起,我沒有解釋爲什麼起初,線程現在已更新。我也不知道索引,如果我知道我可以得到地址。 – aj3423

+0

@ aj3423索引將因編譯器和下一個編譯器而異,正如我指出的那樣,可能不夠。對於簡單的情況,表中的條目按照在類中聲明的虛函數的順序進行(任何虛函數都是類的基類首先)。可能會有一些額外的條目,無論是在開始或結束時,每個條目可能包含一些額外的信息。當然,成員函數的調用順序可能與正常函數的調用順序不同(例如,MSC的情況)。 –