Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vTables and function pointers pointing to different addresses

I was recently reading an article in the bitsquid blog about how to manage memory and the author began to talk about the vtable and how there is a pointer added to the class by the compiler. Here is the link for the article. So because I hardly knew anything about the vtalbe I began to search the web for an explanation. I came accross this link. Based on what I read I made the following code:

char cache[24];

printf("Size of int = %d\n", sizeof(int));
printf("Size of A = %d\n", sizeof(A));

A* a = new(cache)A(0,0);
printf("%s\n",cache);
printf("vTable    : %d\n",*((int*)cache));
printf("cache addr: %d\n",&cache);

int* funcPointer = (int*)(*((int*)cache));
printf("A::sayHello: %d\n",&A::sayHello);
printf("funcPointer: %d\n",*funcPointer);

A is a class with two integers members and a virtual function sayHello().

EDIT : Here is the class definition:

class A {
public:
    int _x;
    int _y;
public:
    A(int x, int y) : _x(x), _y(y){ }
    virtual void sayHello() { printf("Hello You!"); }
};

Basically what I was trying to do is see if the pointer inside the vtable would point to the same place as where the address I get from &A::sayHello, but the thing is that when I run the program, the address inside the pointer in the vtable and the sayHello() address always has a difference of 295. Does anyone know why this could be happening? Is there some kind of header that's added that I'm missing? I'm running visual studio express 2008 on a 64 bit machine.

From what I have debugged the address that is returned by *funcPointer is the true address of the function sayHello(). But why is the &A::sayHello() returning a different address?

like image 625
Kunashu Avatar asked Jan 17 '23 00:01

Kunashu


2 Answers

C++ has an interesting feature :

If you take a pointer to a virtual function and use it, the virtual function will be resolved and called.

Let's take a simple example

struct A
{
    virtual DoSomething(){ printf("A"); }
};

struct B: public A
{
    virtual DoSomething() { printf("B"); }
};

void main()
{
    A * a, b;
    void (A::*pointer_to_function)();

    pointer_to_function = &A::DoSomething;
    a = new A;
    b = new B;

    (a.*pointer_to_function)() //print "A"
    (b.*pointer_to_function)() //print "B"
}

So, the address you see using &A::DoSomething is the address of the trampoline, not the real function address.

If you go to assembly, you'll see that function does something like that (register may change but ecx which represent the this pointer ):

mov eax, [ecx] ; Read vtable pointer
lea edx, [eax + 4 * function_index ] ; function_index being the index of function in the vtable
call edx  
like image 137
crazyjul Avatar answered Feb 26 '23 19:02

crazyjul


Note that this is all implementation defined!

Whilst some implementations may use a trampoline function, this is not the only way to do it, and not how gcc implements it

With gcc, if you run the code you posted, you'll get this:

A::sayHello: 1

Because instead of storing the address of a trampoline function, the member function pointer for a virtual function is stored as { vtable offset + 1, this-ptr offset }, and what you're printing out is the first word of that. (see http://sourcery.mentor.com/public/cxx-abi/abi.html#member-pointers) for more details).

In this case, sayHello is the only vtable entry, and so the vtable offset is 0. 1 is added to flag this member function pointer as being a virtual member function.

If you inspect the assembley for doing the member function pointer calls when compiled with g++, you'll get some instructions at the call site that calculate the address of the function to be called if it's a virtual member function pointer:

        (a->*pointer_to_function)(); //print "A"
Load the first word of the member function pointer into rax:
      4006df:       48 8b 45 c0             mov    -0x40(%rbp),%rax
Check the lower bit:
      4006e3:       83 e0 01                and    $0x1,%eax
      4006e6:       84 c0                   test   %al,%al
If non-virtual skip the next bit:
      4006e8:       74 1b                   je     400705 <main+0x81>
virtual case, load the this pointer offset and add the this pointer (&a):
      4006ea:       48 8b 45 c8             mov    -0x38(%rbp),%rax
      4006ee:       48 03 45 e0             add    -0x20(%rbp),%rax
rax is now the real 'this' ptr. dereference to get the vtable ptr:
      4006f2:       48 8b 10                mov    (%rax),%rdx
Load the vtable offset and subtract the flag:
      4006f5:       48 8b 45 c0             mov    -0x40(%rbp),%rax
      4006f9:       48 83 e8 01             sub    $0x1,%rax
Add the vtable offset to the addr of the first vtable entry (rdx):
      4006fd:       48 01 d0                add    %rdx,%rax
Dereference that vtable entry to get a real function pointer:
      400700:       48 8b 00                mov    (%rax),%rax
Skip the next line:
      400703:       eb 04                   jmp    400709 <main+0x85>

non-virt case, load the function address from the member function pointer:
      400705:       48 8b 45 c0             mov    -0x40(%rbp),%rax

Load the 'this' pointer offset:
      400709:       48 8b 55 c8             mov    -0x38(%rbp),%rdx
Add the actual 'this' pointer:
      40070d:       48 03 55 e0             add    -0x20(%rbp),%rdx
And finally call the function:
      400711:       48 89 d7                mov    %rdx,%rdi
      400714:       ff d0                   callq  *%rax
like image 22
je4d Avatar answered Feb 26 '23 18:02

je4d