破译vtable转储

发布于 2024-10-10 09:32:38 字数 1845 浏览 0 评论 0原文

我正在“玩”C++ 中的虚拟继承,我想知道类对象是如何布局的。 我有这三个类:(

class A {
private:
    int a;
public:
    A() {this->a = 47;}
    virtual void setInt(int x) {this->a = x;}
    virtual int getInt() {return this->a;}
    ~A() {this->a = 0;}
};

class B {
private:
    int b;
public:
    B() {b = 48;}
    virtual void setInt(int x) {this->b = x;}
    virtual int getInt() {return this->b;}
    ~B() {b = 0;}
};

class C : public A, public B {
private:
    int c;
public:
    C() {c = 49;}
    virtual void setInt(int x) {this->c = x;}
    virtual int getInt() {return this->c;}
    ~C() {c = 0;}
};

我认为它们是正确的:p)

我将 -fdump-class-hierarchy 与 g++ 一起使用,我得到了这个

Vtable for A
A::_ZTV1A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::_ZTV1A) + 16u)

Vtable for B
B::_ZTV1B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::_ZTV1B) + 16u)

Vtable for C
C::_ZTV1C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& _ZTI1C)
48    C::_ZThn16_N1C6setIntEi
56    C::_ZThn16_N1C6getIntEv

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::_ZTV1C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::_ZTV1C) + 48u)

现在那些到底是什么 (int (* )(...))-0x00000000000000010C::_ZThn16_N1C6setIntEi 和 (int (*)(...))0?? 有人可以解释一下转储吗?

谢谢。

I am "playing" with virtual inheritance in C++, and I want to know how a class object is laid out.
I have those three classes:

class A {
private:
    int a;
public:
    A() {this->a = 47;}
    virtual void setInt(int x) {this->a = x;}
    virtual int getInt() {return this->a;}
    ~A() {this->a = 0;}
};

class B {
private:
    int b;
public:
    B() {b = 48;}
    virtual void setInt(int x) {this->b = x;}
    virtual int getInt() {return this->b;}
    ~B() {b = 0;}
};

class C : public A, public B {
private:
    int c;
public:
    C() {c = 49;}
    virtual void setInt(int x) {this->c = x;}
    virtual int getInt() {return this->c;}
    ~C() {c = 0;}
};

(I think they are correct :p)

I used -fdump-class-hierarchy with g++, and I got this

Vtable for A
A::_ZTV1A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::_ZTV1A) + 16u)

Vtable for B
B::_ZTV1B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::_ZTV1B) + 16u)

Vtable for C
C::_ZTV1C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& _ZTI1C)
48    C::_ZThn16_N1C6setIntEi
56    C::_ZThn16_N1C6getIntEv

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::_ZTV1C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::_ZTV1C) + 48u)

Now what the heck are those (int (*)(...))-0x00000000000000010 and C::_ZThn16_N1C6setIntEi and (int (*)(...))0??
Can someone explain the dump?

Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夏尔 2024-10-17 09:32:38

我不能 100% 确定这个答案是正确的,但这是我的最佳猜测。

当您有一个多重且非虚拟继承的类时,该类的布局通常是第一个基本类型的完整对象,然后是第二个基本类型的完整对象,然后是对象本身的数据。如果您查看 B,您可以看到 A 对象的 vtable 指针,如果您查看 C,您可以看到 A 和 B 对象都有指向 vtable 的指针。

因为对象是这样布局的,这意味着如果您有一个指向 C 对象的 B* 指针,则该指针实际上不会位于该对象的基址处。目的;相反,它会指向中间的某个地方。这意味着,如果您需要将对象转换为 A*,则需要对 B* 指针进行一定程度的调整,以将其跳回到对象的开头。物体。为了做到这一点,编译器需要在某处编码您需要跳回以到达对象开头的字节数。我认为第一个 (int(*)(...)) 实际上只是您需要查看才能到达对象开头的原始字节数。如果您会注意到,对于 A 虚函数表,该指针为 0(因为 A 的虚函数表位于对象的开头,对于 B 也是如此) vtable(因为它也位于对象的开头。但是,请注意 C vtable 有两部分 - 第一部分是 A 的 vtable,其第一个部分是 A 的 vtable疯狂的条目也为零(因为如果您位于 A vtable,则不需要进行任何调整)但是,在该表的前半部分之后似乎是B vtable,请注意它的第一个条目是十六进制值 -0x10 如果您查看 C 对象布局,您会注意到这一点。 B vtable 指针位于 A vtable 指针之后 16 个字节。此 -0x10 值可能是您需要跳过的校正偏移量。 B vtable 指针返回到对象的根。

每个 vtable 的第二个疯狂条目似乎是指向 vtable 本身的指针。请注意,它始终等于 vtable 对象的地址(比较 vtable 的名称和它所指向的内容)。如果您想要进行任何类型的运行时类型识别,这将是必要的,因为这通常涉及查看 vtable 的地址(或至少是靠近其前面的地址)。

最后,至于为什么在 C vtable 的末尾有神秘命名的 setInt 和 getInt 函数,我很确定这是因为 C 类型继承了两个不同的集合名为 setIntgetInt 的函数 - 一个到 A,一个到 B。如果我不得不猜测,这里的修改是为了确保编译器内部可以区分两个虚拟函数。

希望这有帮助!

I'm not 100% sure that this answer is correct, but here's my best guess.

When you have a class that inherits multiply and non-virtually, the layout of the class is usually a complete object of the first base type, then a complete object of the second base type, then the data for the object itself. If you look at B, you can see the vtable pointer for the A object, and if you look at C you can see that there's pointers into the vtable for both the A and B objects.

Because the objects are laid out this way, it means that if you have a B* pointer pointing at a C object, the pointer will actually not be at the base of the object; rather it will be pointing somewhere in the middle. This means that if you ever need to cast the object to an A*, you'll need to adjust the B* pointer some amount to skip it back to the start of the object. In order to do this, the compiler needs to encode somewhere the number of bytes you need to skip back to get to the start of the object. I think that the very first (int(*)(...)) is actually just a raw number of bytes you need to look at to get to the start of the object. If you'll notice, for the A vtable this pointer is 0 (since the vtable for A is at the start of the object, and the same is true for the B vtable (since it also lives at the start of the object. However, notice that the C vtable has two parts - the first part is the vtable for A, and its first crazy entry is zero as well (since if you're at the A vtable, you don't need to do any adjustments). However, after the first half of this table is what appears to be the B vtable, and notice that its first entry is hex value -0x10. If you look at the C object layout, you'll notice that the B vtable pointer is 16 bytes after the A vtable pointer. This -0x10 value might be the corrective offset you need to skip back over the B vtable pointer to get back to the root of the object.

The second crazy entry of each vtable seems to be a pointer to the vtable itself. Notice that it's always equal to the address of the vtable object (compare the name of the vtable and what it's pointing at). This would be necessary if you wanted to do any sort of runtime type identification, since that usually involves looking at the address of the vtable (or at least something near the front of it).

Finally, as for why there's the cryptically-named setInt and getInt functions at the end of the C vtable, I'm pretty sure that's because the C type inherits two different sets of functions named setInt and getInt - one through A and one through B. If I had to guess, the mangling here is to ensure that the compiler internals can differentiate between the two virtual functions.

Hope this helps!

烟燃烟灭 2024-10-17 09:32:38

这是通过 c++filt 运行的转储:

Vtable for A
A::vtable for A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::vtable for A) + 16u)

Vtable for B
B::vtable for B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::vtable for B) + 16u)

Vtable for C
C::vtable for C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& typeinfo for C)
48    C::non-virtual thunk to C::setInt(int)
56    C::non-virtual thunk to C::getInt()

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::vtable for C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::vtable for C) + 48u)

不知道 (int (*)(...))-0x00000000000000010(int (*)(...))0 是什么 是。
C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int) 部分是“在存在多重或虚拟继承的情况下优化虚拟函数调用”,如此处

Here's your dump ran through c++filt:

Vtable for A
A::vtable for A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::vtable for A) + 16u)

Vtable for B
B::vtable for B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::vtable for B) + 16u)

Vtable for C
C::vtable for C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& typeinfo for C)
48    C::non-virtual thunk to C::setInt(int)
56    C::non-virtual thunk to C::getInt()

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::vtable for C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::vtable for C) + 48u)

No idea what the (int (*)(...))-0x00000000000000010 and (int (*)(...))0 are.
The C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int) part is an "optimization of virtual function calls in the presence of multiple or virtual inheritance" as described here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文