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