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

时间:2014-07-09 08:51:32

标签: c++ member-functions

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

更新

我不知道vTable中的INDEX 我不知道地址

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

我想挂钩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;
}

在调试模式下,上面的代码输出跳转表的地址。 在发布模式下,&amp; cls :: fn2 会返回 0x00401058 ,这会指向一些优化代码:

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

两者都不是真正的地址。无论如何要做到这一点?

感谢。

3 个答案:

答案 0 :(得分:3)

不要轻易放弃!

虽然其他答案在说C ++语言不允许您以便携方式执行此操作时是正确的,但在您的特定情况下,这可能会使这更合理要做。

关键是ID3DXFont是一个COM接口,并且这些工作的确切二进制细节与用于访问它们的语言分开指定。因此,虽然C ++没有说明你在该指针的另一端发现了什么,但是COM 确实说那里有一个函数指针数组在那里指定的顺序和指定的调用约定。这允许我告诉你DrawText函数的索引是 3 14(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]

答案 1 :(得分:2)

在便携式附近没有任何东西。你尝试使用 &cls::fn2无法正常工作,因为结果必须适用于案例 与(pCls->*fn)()类似,即使pCls指向派生类 它覆盖了这个功能。 (指向成员函数的指针是 复杂的野兽,识别功能是否 虚拟与否,并提供不同的信息 这个。如果你正在尝试MSC,请注意你 必须指定/vmg指向成员函数的指针才能工作 正确。)

即使对于给定的实现,您也需要一个实例 正确的类型。鉴于此, if 你知道类的布局,和 虚拟功能表的布局,您可以追踪它。 通常,指向虚函数表的指针是 课堂上的第一个字,虽然不能保证。和 通常,功能将按照它们的顺序出现 声明。然而,还有其他信息 指向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 ++在多重继承的情况下使用trampolines 示例(在这种情况下,返回的地址将是 蹦床的地址)。它可能不适用于下一个 g ++的主要版本。 (对于我手边的MSC版本, 用2替换索引1似乎有效。但是,再次, 我只尝试过很简单的案例。绝对没有 保证。)

基本上,你从不想做这样的事情 生产代码。但是,如果您正在尝试,它会很有用 了解编译器的工作原理。

编辑:

为什么要重新编辑?只是因为你有地址 (也许),它并不意味着你可以调用这个函数。您 如果没有对象,则无法调用成员函数 在任何数量的事情上,你可能无法通过 功能对象。 (例如,对于MSC,对象将会 通常在ECX中通过。)

答案 2 :(得分:1)

如此wiki page中所述:

  

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

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