获取虚成员函数的真实地址(或vTable中的索引)

get the real address(or index in vTable) of virtual member function

本文关键字:vTable 索引 实地址 成员 函数 真实 获取      更新时间:2023-10-16

在c++中有没有办法得到成员函数的实际地址,或者vTable中的索引 ?

更新:

我不知道索引在vTable不知道地址

这是为什么我想知道这个:

我想挂钩函数ID3DXFont->DrawText的DirectX。如果我知道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)

都不是真实地址。怎么做呢?

谢谢。

别轻言放弃!

虽然其他答案都说c++语言不允许以可移植的方式实现这一点是正确的,但在您的特定情况下,有一个重要因素可能使这成为更合理的事情。

关键是ID3DXFont是一个COM接口,这些工作的确切二进制细节是与用于访问它们的语言分开指定的。因此,虽然c++没有说明在指针的另一端会发现什么,但COM 确实说那里有一个虚函数表,其中包含按指定顺序和指定调用约定的函数指针数组。这允许我告诉你,DrawText函数的索引是

314 (drawtextta)或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)是[http://www.codeproject.com/Articles/153096/Intercepting-Calls-to-COM-Interfaces]div [http://goodrender.googlecode.com/svn/trunk/include/d3dx9core.h]

没有任何东西接近便携。你尝试使用&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::fn1n"; }
    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的派生类型,它返回重写函数的地址。

这是否一直有效,我不能说;我认为c++在多重继承的情况下使用了trampolines, for示例(在这种情况下,返回的地址将是蹦床地址)。也许下一个就行不通了g++的主要版本。(对于我手头的MSC版本,用1替换索引2似乎有效。但是再一次,我只尝试了非常简单的情况。绝对没有担保。)

基本上,你不会想要做这样的事情生产代码。不过,如果你想这么做,它会很有用理解编译器是如何工作的 编辑:

你在编辑为什么吗?就因为你有地址(也许),这并不意味着你可以调用这个函数。你如果没有对象,就不能调用成员函数在任何一件事情上,你都可能无法通过函数对象。(例如,在MSC中,宾语will(通常在ECX中传递)

如本wiki页面所述:

每当类定义虚函数(或虚方法)时,大多数编译器将一个隐藏的成员变量添加到指向a的类中所谓的虚拟方法表(VMT或Vtable)。这个VMT基本上是指向(虚)函数的指针数组。

据我所知,你不能访问Vtable,编译器甚至不知道表中的条目数。