破译vtable转储
我正在“玩”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 (* )(...))-0x00000000000000010
和 C::_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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我不能 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 类型继承了两个不同的集合名为
setInt
和getInt
的函数 - 一个到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 aC
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 anA*
, you'll need to adjust theB*
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 theA
vtable this pointer is 0 (since the vtable for A is at the start of the object, and the same is true for theB
vtable (since it also lives at the start of the object. However, notice that theC
vtable has two parts - the first part is the vtable forA
, and its first crazy entry is zero as well (since if you're at theA
vtable, you don't need to do any adjustments). However, after the first half of this table is what appears to be theB
vtable, and notice that its first entry is hex value-0x10
. If you look at theC
object layout, you'll notice that theB
vtable pointer is 16 bytes after theA
vtable pointer. This-0x10
value might be the corrective offset you need to skip back over theB
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 theC
type inherits two different sets of functions namedsetInt
andgetInt
- one throughA
and one throughB
. 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!
这是通过 c++filt 运行的转储:
不知道
(int (*)(...))-0x00000000000000010
和(int (*)(...))0 是什么
是。C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int)
部分是“在存在多重或虚拟继承的情况下优化虚拟函数调用”,如此处。Here's your dump ran through c++filt:
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.