Why is the function call to the virtual function using the address stored in the virtual method table returning garbage?

Why is the function call to the virtual function using the address stored in the virtual method table returning garbage?



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.


Car


#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: %pn", *mVtable);
printf("First Entry of VTable: %pn", (void*) mVtable[0][0]);
printf("Second Entry of VTable: %pn", (void*) mVtable[0][1]);

if(sizeof(void*) == 8)
printf("64 bitn");


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

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

printf("first: %dnsecond: %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






For which instance of Car are you calling function second through pointer secondfunc?

– Ivan
Sep 6 '18 at 18:10



Car


second


secondfunc






Could you cast to member function pointers instead of function pointers?

– François Andrieux
Sep 6 '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 6 '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. en.wikipedia.org/wiki/Virtual_method_table#Invocation

– legendaryz
Sep 7 '18 at 14:44





3 Answers
3



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: %pn", *mVtable);
printf("First Entry of VTable: %pn", (void*)mVtable[0][0]);
printf("Second Entry of VTable: %pn", (void*)mVtable[0][1]);

if(sizeof(void*) == 8)
printf("64 bitn");


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: %dnsecond: %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.






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. en.wikipedia.org/wiki/Virtual_method_table#Invocation

– legendaryz
Sep 7 '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 7 '18 at 14:46






Oh ok. I see. Thanks.

– legendaryz
Sep 7 '18 at 15:11



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.


this



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:


Car*


&car


stdcall


cdecl


this


ecx


cdecl


this






One of the few cases where it's polite to say "this is garbage"

– Tim Randall
Sep 6 '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. en.wikipedia.org/wiki/Virtual_method_table#Invocation

– legendaryz
Sep 7 '18 at 14:46



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.


x = 2


second






No, the ctor runs directly in main.

– o11c
Sep 6 '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 6 '18 at 18:18



this


x


mVTable[0][1]






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 6 '18 at 18:20


Car car


this


car






@TimRandall the undefined behavior here started way before not passing this... 🙄

– Matteo Italia
Sep 6 '18 at 18:21



this






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 en.wikipedia.org/wiki/Virtual_method_table#Invocation

– legendaryz
Sep 7 '18 at 14:52



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)