В MSVC ABI, как мне надежно найти vtable, учитывая только (void*)?

Этот вопрос конкретно о непереносимых материалах MSVC ABI.

Я пытаюсь написать эквивалент C++ typeid в явно непортативном, но не волшебном C++. Для Itanium ABI (как используется в Linux/Mac) это очень просто:

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:

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.)

Проблема в том, что некоторые классы C++ не начинаются с vfptr со смещением 0! Иногда они начинаются с vbptr.

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

Class1 начинается с vfptr; Class2 начинается с vbptr.

Когда класс начинается с vbptr, я считаю, что это всегда тот случай, когда его первый виртуальный базовый подобъект (первый в порядке расположения и, следовательно, самый "листовой") всегда будет иметь vfptr со смещением 0. Так что, если я знаю, что ' Я имею дело с классом, который начинается с vbptr, я хочу сделать это вместо этого:

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 и тот факт, что компилятор знает макет каждого типа."

Тем не менее, в моем случае я не знаю статический тип x и даже если бы я это сделал, я не знаю, как выяснить (из C++, с помощью метапрограммирования шаблонов), x начинается с vbptr или нет.

Наконец, я пошел дальше и немного выдумал

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;
}

только чтобы обнаружить, что typeinfo, хранящаяся в vftable для "Class1 in Class2", содержит typeinfo для типа подобъекта Class1 не для самого производного типа Class2! Так что отсутствует еще одна часть головоломки.

Итак, мой вопрос в двух словах: учитывая (void*)&object_of_type_class2как мне получить typeid(Class2)?

1 ответ

У меня есть кое-что, что работает сейчас!

Функция dynamicast_to_mdo делает эквивалент dynamic_cast<void*>(p) - это регулирует p указать на его наиболее производный объект.

Функция dynamicast_typeid делает эквивалент typeid(p) - он получает typeinfo из pVtable. Я просто использую выдумку / хак, который я дал в своем вопросе, и я на самом деле не уверен, почему я получил неправильные ответы несколько часов назад; Я думаю, что когда я видел неправильную информацию о типе, это могло быть потому, что я случайно пытался взять typeid оборванной ссылки на уничтоженную переменную стека.

// 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;
}
Другие вопросы по тегам