1

I'm calling virtual functions from the address in the virtual table as an exercise to test my understanding of the concept. However, as soon as I thought I made a breakthrough in my understanding of the virtual method table, I run into another issue that I just don't understand.

In the code below, I've created a class called Car which contains a member variable x and two virtual functions, first and second. Now, I call these two virtual methods by hacking through the virtual table. The first function returns the correct answer, but the second returns some random value or garbage instead of what it was initialized to be.

#include <cstdio>

class Car
{
private:
    int x;

    virtual int first()
    {
        printf("IT WORKS!!\n");
        int num = 5;
        return num;
    }
    virtual int second()
    {
        printf("IT WORKS 2!!\n");
        //int num  = 5;
        return x;
    }


public:

    Car(){
        x = 2;
    }
};

int main()
{
    Car car;
    void* carPtr = &car;
    long **mVtable =(long **)(carPtr);

    printf("VTable: %p\n", *mVtable);
    printf("First Entry of VTable: %p\n", (void*) mVtable[0][0]);
    printf("Second Entry of VTable: %p\n", (void*) mVtable[0][1]);

    if(sizeof(void*) == 8){
        printf("64 bit\n");
    }

    int (*firstfunc)() = (int (*)()) mVtable[0][0];
    int x = firstfunc();    

    int (*secondfunc)() = (int (*)()) mVtable[0][1];
    int x2 = secondfunc();

    printf("first: %d\nsecond: %d", x, x2);
    return 0;
}

If someone can point me to what I'm doing wrong that would be appreciated. Also, since this works differently across compilers, I'm testing it on http://cpp.sh/ using c++14.

That code out outputs, where the "garbage" second output is subject to change:

VTable: 0x400890
First Entry of VTable: 0x400740
Second Entry of VTable: 0x400720
64 bit
IT WORKS!!
IT WORKS 2!!
first: 5
second: -888586240 
legendaryz
  • 53
  • 6
  • 1
    For which instance of `Car` are you calling function `second` through pointer `secondfunc`? –  Sep 06 '18 at 18:10
  • Could you cast to member function pointers instead of function pointers? – François Andrieux Sep 06 '18 at 18:43
  • You did not provide the address of the object before calling its methods (by x86/AMD ABI first six arguments are placed in registers). – Nikita Kniazev Sep 06 '18 at 22:13
  • Ok. I understand why it's not working now. However, now I need to figure out how I can actually do it. I don't know if it's even possible but the wiki suggests it is. https://en.wikipedia.org/wiki/Virtual_method_table#Invocation – legendaryz Sep 07 '18 at 14:44

3 Answers3

6

Methods are indeed generally implemented as regular functions, but they need to receive the this pointer to access the data of a specific instance - in facts, when you invoke a method over an instance a pointer to the instance gets passed as a hidden parameter.

In your code you aren't passing it in, so the method just returns garbage - it's probably using whatever happens to be in a register or on the stack as if it was the instance pointer; you are lucky enough that it doesn't plainly crash.

You may try changing your prototypes to accept a Car* parameter and pass &car to it, but it may or may not work, depending on the calling convention used by your compiler/platform:

  • on Win32/x86/VC++, for example, methods use the stdcall calling convention (or cdecl for variadics), but receive the this pointer in ecx, something you cannot emulate through a regular function call;
  • on the other hand, x86 gcc just handles them as cdecl functions, passing this implicitly as if it was the last parameter.
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • One of the few cases where it's polite to say "this is garbage" – Tim Randall Sep 06 '18 at 19:24
  • The wiki I was referencing suggests it can be done in a strange way. Not sure if it's even syntactically correct. https://en.wikipedia.org/wiki/Virtual_method_table#Invocation – legendaryz Sep 07 '18 at 14:46
2

Methods are functions, but method pointers are generally not function pointers.

The calling convention of calling methods does not always agree with the calling convention of calling functions.

We can get around this. With yet more undefined behavior, but that works at least sometimes.

MSVC clang g++

Code:

template<class Sig>
struct fake_it;

template<class R, class...Args>
struct fake_it<R(Args...)>{
    R method(Args...);

    using mptr = decltype(&fake_it::method);
};
template<class R, class...Args>
struct fake_it<R(Args...) const> {
    R method(Args...) const;

    using mptr = decltype(&fake_it::method);
};

template<class Sig>
using method_ptr = typename fake_it<Sig>::mptr;

template<class Sig>
struct this_helper {
    using type=fake_it<Sig>*;
};
template<class Sig>
struct this_helper<Sig const>{
    using type=fake_it<Sig> const*;
};

template<class Sig>
using this_ptr = typename this_helper<Sig>::type;

now this test code:

Car car;
void* carPtr = &car;
auto **mVtable = (uintptr_t **)(carPtr);
printf("VTable: %p\n", *mVtable);
printf("First Entry of VTable: %p\n", (void*)mVtable[0][0]);
printf("Second Entry of VTable: %p\n", (void*)mVtable[0][1]);

if(sizeof(void*) == 8){
    printf("64 bit\n");
}

auto firstfunc = to_method_ptr<int()>(mVtable[0][0]);
int x = (this_ptr<int()>(carPtr)->*firstfunc)();    

auto secondfunc = to_method_ptr<int()>(mVtable[0][1]);
int x2 = (this_ptr<int()>(carPtr)->*secondfunc)();

printf("first: %d\nsecond: %d", x, x2);

The code above relies on method pointers being a pair of function pointer and a second section that if all 0s is non-virtual dispatch, and the vtable to contain just the function pointer component.

So we can reconstruct a method pointer from the data in the vtable by padding a buffer with 0s, then interpreting the memory as a method pointer.

To get the call to work, we create a fake type with a method that matches our signature, then cast our pointer to that type and invoke it with a member function pointer reconstructed from our original type's vtable.

This, we hope, mimics the calling convention of that the compiler uses for other method calls.


In clang/g++ non-virtual method pointers are two pointers with the second one ignored. Virtual method pointers, I believe, use the second pointer-sized data.

In MSVC, non-virtual method pointers are the size of one pointer. Virtual method pointers with a virtual inheritance tree are not the size of one pointer. I believe this violates the standard (that requires that member pointers be inter-castable between).

In both cases, the vtable appears to store the first half of each non-virtual method pointer.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Looks extremely hacky but I like it. Will try it out later. However, honestly, I'm particularly interested in the approach they take on the wiki. Not sure if it's just cynically incomplete bs or not. https://en.wikipedia.org/wiki/Virtual_method_table#Invocation – legendaryz Sep 07 '18 at 14:42
  • @legendaryz You cannot replicate the MSVC calling convention through a function call; you can replicate it using this hack. And it also replicates the clang/g++ calling conventions with the same hack. – Yakk - Adam Nevraumont Sep 07 '18 at 14:46
  • Oh ok. I see. Thanks. – legendaryz Sep 07 '18 at 15:11
0

The constructor, which sets x = 2, doesn't run when you call a function pointer directly into the vtable. You're returning uninitialized memory from second, which can be anything.

Govind Parmar
  • 20,656
  • 7
  • 53
  • 85
  • No, the ctor runs directly in main. – o11c Sep 06 '18 at 18:17
  • @o11c The function is called directly without any reference to which instance of the class it is being called from (i.e. `this`), so `x` is uninitialized since the constructor is not run *in the context of the call to `mVTable[0][1]`* – Govind Parmar Sep 06 '18 at 18:18
  • 2
    `Car car` is constructed, but the function that's called isn't given a `this` pointer that points to `car`. We're in undefined behavior territory – Tim Randall Sep 06 '18 at 18:20
  • 5
    @TimRandall the undefined behavior here started way before not passing `this`... – Matteo Italia Sep 06 '18 at 18:21
  • Pretty sure it's all unconventional, but there has to be a way to do it. I can retrieve the value of the x in a similar manner from the object. So I think, @TimRandall explanation makes sense. It also lines up with the other answers. P.S. the wiki I'm referencing https://en.wikipedia.org/wiki/Virtual_method_table#Invocation – legendaryz Sep 07 '18 at 14:52