【问题标题】:What is a "virtual thunk" to a virtual function that inherits from a virtual base class?什么是从虚拟基类继承的虚拟函数的“虚拟 thunk”?
【发布时间】:2016-08-27 12:29:47
【问题描述】:

当我尝试访问从虚拟基类继承的派生类对象的内存布局时出现问题。
编程环境:GNU/Linux 3.19.0-32-generic, x86_64
编译器: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;
}

当我运行代码时,出现“段错误”:

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

所以我反汇编了可执行文件。
我在 0x400c3c 中找到了函数 <_ztv0_n24_n7derived1fev>:

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

在我的终端中去除符号:

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

那么什么是对 Derived::f() 的虚拟 thunk?为什么会存在呢?

【问题讨论】:

  • 您的问题是“什么是虚拟 thunk”还是“为什么会出现段错误”?
  • @xtofl 前者。
  • 你为什么要这样做?
  • @n.m.标准没有说明编译器如何实现虚拟继承。我只是想知道。

标签: c++ g++ abi vtable virtual-inheritance


【解决方案1】:

某些函数的虚拟 thunk 是一个辅助函数,它在调用实际函数之前修复 this 参数。看这个例子:

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())

内存中的Base 对象看起来像这样:

      * Pointer to part of Base vtable with Base's virtual functions.
          This vtable contains Base::f()

      * Data of Base class (variable `x`)

内存中的Derived 对象看起来像这样:

   |> * 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:现在应该也清楚为什么在d 指针上调用_ZTv0_n24_N7Derived1fEv 会崩溃。该函数仅在给定 this 指向 Derived 对象内部的指针时才有效 - 指向可以像 Base 对象一样使用的部分。

【讨论】:

    【解决方案2】:

    问题中有一个不清楚的地方。 在“virtual thunk to Derived::f()”中,我认为“add -0x18(%r10),%rdi”不能修复这个指针,因为Derive对象的开头和它的子对象(Base)之间的偏移量不是 24 (0x18)。

    【讨论】:

    • 哦,我知道。在这里它需要以间接的方式获得偏移量。 “vtbl - 0x18”指向虚函数表中的偏移值。然后通过添加偏移来纠正“this”。
    猜你喜欢
    • 2016-05-20
    • 1970-01-01
    • 2016-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 2015-09-09
    • 1970-01-01
    相关资源
    最近更新 更多