在 MSVC ABI 中,我如何可靠地找到仅给定 (void*) 的 vtable? [英] In the MSVC ABI, how do I reliably find the vtable given only a (void*)?

查看:32
本文介绍了在 MSVC ABI 中,我如何可靠地找到仅给定 (void*) 的 vtable?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题专门针对不可移植的 MSVC ABI 内容.

This question is specifically about non-portable MSVC ABI stuff.

我正在尝试用明显不可移植但不神奇的 C++ 编写 C++ 的 typeid 等价物.对于 Itanium ABI(在 Linux/Mac 上使用),这非常简单:

I'm trying to write the equivalent of C++'s typeid in obviously-nonportable-yet-not-magic C++. For the Itanium ABI (as used on Linux/Mac), it's super easy:

const std::type_info& dynamicast_typeid(void *mdo)
{
    std::type_info **vptr = *reinterpret_cast<std::type_info ***>(mdo);
    std::type_info *typeinfo_ptr = vptr[-1];
    return *typeinfo_ptr;
}

所以现在我正在查看 64 位 MSVC ABI,并且该死的,我被难住了.对于非常简单的类,那些以 vfptr 开头的偏移量为 0 的类,几乎和 Itanium 一样简单:

So now I'm looking at the 64-bit MSVC ABI, and dang it, I'm stumped. For very simple classes, the ones that start with a vfptr at offset 0, it's almost as easy as Itanium:

const std::type_info& dynamicast_typeid(void *mdo)
{
    int *rtti_complete_object_locator = ((int ***)mdo)[0][-1];
    char *result = (char *) rtti_complete_object_locator;
    result -= rtti_complete_object_locator[5];
    result += rtti_complete_object_locator[3];
    return *(std::type_info*)result;
}

(此代码基于 Wine 项目的 __RTtypeid.)

(This code is based on the Wine project's __RTtypeid.)

问题在于某些 C++ 类以偏移量 0 处的 vfptr 开头!有时它们以 vbptr 开头.

The problem is that some C++ classes don't start with a vfptr at offset 0! Sometimes they start with a vbptr.

struct Class1 { virtual ~Class1() {} };
struct Class2 : virtual Class1 {};

Class1 以 vfptr 开头;Class2 以 vbptr 开头.

Class1 starts with a vfptr; Class2 starts with a vbptr.

当一个类以 vbptr 开头时,我相信总是这样,它的第一个虚拟基础子对象(它在布局顺序中的第一个,因此它的最叶")总是有一个 vfptr 在its 偏移量为 0.所以如果我知道我正在处理一个以 vbptr 开头的类,我想改为这样做:

When a class starts with a vbptr, I believe it's always the case that its first virtual base subobject (its first in layout order, and thus its "leafiest") will always have a vfptr at its offset 0. So if I know I'm dealing with a class that starts with a vbptr, I want to do this instead:

const std::type_info& dynamicast_typeid_for_vbptr_class(void *mdo)
{
    int first_vbase_offset = ((int**)mdo)[0][1];
    mdo = (char*)mdo + first_vbase_offset;
    int *rtti_complete_object_locator = ((int ***)mdo)[0][-1];
    char *result = (char *) rtti_complete_object_locator;
    result -= rtti_complete_object_locator[5];
    result += rtti_complete_object_locator[3];
    return *(std::type_info*)result;
}

我已经确定 MSVC 确实生成相当于

    if constexpr(IS_VBPTR_CLASS) {
        int first_vbase_offset = ((int**)mdo)[0][1];
        mdo = (char*)mdo + first_vbase_offset;
    }
    return __RTtypeid(mdo);

编译 C++ 表达式时 typeid(x) — 其中 IS_VBPTR_CLASS 是伪代码,编译器神奇地知道 x 是否有 vbptr,基于 x 的静态类型以及编译器知道每种类型的布局这一事实."

when compiling the C++ expression typeid(x) — where that IS_VBPTR_CLASS is pseudocode for "the compiler magically knows whether x has a vbptr, based on the static type of x and the fact that the compiler knows the layout of every type."

但是,就我而言,我知道 x 的静态类型,即使我知道,我也不知道如何找出(从在 C++ 中,使用模板元编程)无论 x 是否以 vbptr 开头.

However, in my case I don't know the static type of x, and even if I did, I don't know how to find out (from within C++, with template metaprogramming) whether x starts with a vbptr or not.

最后,我继续用

const std::type_info& dynamicast_typeid(void *mdo)
{
    while (((int**)mdo)[0][0] == 0) {
        mdo = (char *)mdo + ((int**)mdo)[0][1];
    }
    int *rtti_complete_object_locator = ((int ***)mdo)[0][-1];
    char *result = (char *)complete_object_locator;
    result -= rtti_complete_object_locator[5];
    result += rtti_complete_object_locator[3];
    return *(std::type_info*)result;
}

才发现存储在Class1 in Class2"的vftable中的typeinfo保存了子对象类型Class1的typeinfo,而不是最衍生的类型Class2!所以还有另一个拼图缺失.

only to discover that the typeinfo stored in the vftable for "Class1 in Class2" holds the typeinfo for the subobject type Class1, not for the most-derived-type Class2! So there's another piece of the puzzle missing.

所以我的问题简而言之:给定 (void*)&object_of_type_class2,我如何检索 typeid(Class2)?

So my question in a nutshell: Given (void*)&object_of_type_class2, how do I retrieve typeid(Class2)?

推荐答案

我现在有了一些可行的方法!

I've got something that works now!

函数 dynamicast_to_mdodynamic_cast(p) 等效——它调整 p 以指向其最衍生的对象.

The function dynamicast_to_mdo does the equivalent of dynamic_cast<void*>(p) — it adjusts p to point to its most derived object.

函数dynamicast_typeidtypeid(p) 等效——它从p 的vtable 中获取类型信息.我只是在使用我在问题中给出的软糖/黑客,实际上我不确定为什么几个小时前我得到了错误的答案;我想当我看到错误的类型信息时,可能是因为我不小心尝试获取对已销毁堆栈变量的悬空引用的 typeid.

The function dynamicast_typeid does the equivalent of typeid(p) — it fetches the typeinfo from p's vtable. I'm just using the fudge/hack that I gave in my question, and I'm actually not sure why I was getting wrong answers a few hours ago; I think when I was seeing the wrong typeinfo, it may have been because I was accidentally trying to take the typeid of a dangling reference to a destroyed stack variable.

// 64-bit MSVC ABI
void *dynamicast_to_mdo(void *p)
{
    if (((int**)p)[0][0] == 0) {
        p = (char *)p + ((int**)p)[0][1];
    }
    int *complete_object_locator = ((int ***)p)[0][-1];
    int mdoffset = complete_object_locator[1];
    void *adjusted_this = static_cast<char *>(p) - mdoffset;
    return adjusted_this;
}

// 64-bit MSVC ABI
const std::type_info& dynamicast_typeid(void *p)
{
    if (((int**)p)[0][0] == 0) {
        p = (char *)p + ((int**)p)[0][1];
    }
    int *complete_object_locator = ((int ***)p)[0][-1];

    char *result = (char *)complete_object_locator;
    result -= complete_object_locator[5];
    result += complete_object_locator[3];
    return *(const std::type_info*)result;
}

这篇关于在 MSVC ABI 中,我如何可靠地找到仅给定 (void*) 的 vtable?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆