Search code examples
c++g++abivtablevirtual-inheritance

What is a "virtual thunk" to a virtual function that inherits from a virtual base class?


Something went wrong when I try to access the memory layout of a derived class object which inherits from a virtual base class.
Programming environment: GNU/Linux 3.19.0-32-generic, x86_64
Compiler: gcc 4.8.4

//virtual base class
class Base {
public :
    virtual void f() {
        cout << "Base::f()" << endl;
    }
private:
    long x;
};

//derived class
class Derived : public virtual Base {
public:
    virtual void f() {
        cout << "Derived::f()" << endl;
    }
private:
    long y;
};

int main() {
    typedef void (*FUNC)(void);
    Derived d;

    //In my machine, sizeof(long) == sizeof(pointers). My code below is neither portable nor concise. You can just read the annotation.

    //dereference the first element of the first virtual function table(equals to *(vptr1->slot[0]))
    cout << hex << *((long*)*((long*)(&d) + 0) + 0) << endl;
    ((FUNC)*((long*)*((long*)(&d) + 0) + 0))();//invoke Derived::f()

    //dereference the first element of the second virtual function table(equals to *(vptr2->slot[0]))
    cout << hex << *((long*)*((long*)(&d) + 2) + 0) << endl;
    ((FUNC)*((long*)*((long*)(&d) + 2) + 0))();//maybe Derived::f()?

    return 0;
}

When I run the code, I got "segment fault":

400c12
Derived::f()
400c3c
segment fault

So I disassembly the executable file.
I found the function <_ZTv0_n24_N7Derived1fEv> in 0x400c3c:

0000000000400c3c <_ZTv0_n24_N7Derived1fEv>:
  400c3c:       4c 8b 17                mov    (%rdi),%r10
  400c3f:       49 03 7a e8             add    -0x18(%r10),%rdi
  400c43:       eb cd                   jmp    400c12 <_ZN7Derived1fEv>
  400c45:       90                      nop

Demangle the symbol in my terminal:

> c++filt _ZTv0_n24_N7Derived1fEv
virtual thunk to Derived::f()

Then what is a virtual thunk to Derived::f()?Why is it there?


Solution

  • Virtual thunk of some function is a helper function that fixes the this parameter before calling the actual function. Look at this example:

    Derived *d = new Derived();
    // d now points to some address, e.g. 0x6eac40
    
    d->f(); // This calls _ZN7Derived1fEv (Derived::f() directly)
    
    Base *b = d;
    // b now points to some other address (!), e.g. 0x6eac50
    
    b->f(); // This calls _ZTv0_n24_N7Derived1fEv (the virtual thunk
            // of Derived::f()), which subtracts some amount from `this`
            // and then jumps to the _ZN7Derived1fEv (Derived::f())
    

    A Base object in memory looks somehow like this:

          * Pointer to part of Base vtable with Base's virtual functions.
              This vtable contains Base::f()
    
          * Data of Base class (variable `x`)
    

    A Derived object in memory looks somehow like this:

       |> * Pointer to part of Derived vtable with Derived's virtual functions.
       |>     This vtable contains the Derived::f()
       |>
    |> |> * Pointer to part of Derived vtable with the same layout as Base vtable.
    |> |>     This vtable contains the thunk of Derived::f()
    |> |>
    |> |> * Data of Base class (variable `x`)
    |  |>
    |  |> * Data of Derived class (variable `y`)
    |  |
    |  \ This is complete Derived object.
    |    The `d` pointer points at the beginning of this.
    |
    \ This is the part of Derived object that can act as a Base object.
      The `b` pointer points at beginning of this.
    

    PS: Now it should be also clear why calling _ZTv0_n24_N7Derived1fEv on the d pointer crashes. That function works only when given this pointer that is pointing inside the Derived object - into the part of it that can be used like a Base object.