在我试图恢复数据结构的程序中,我发现了以下奇怪的 (ARM) 反汇编代码:
ctor_1:
ldr r1, =vtable_base
str r1, [r0] ;r0 always contains object instance ptr
;... more setup
bx lr
ctor_2:
push {r4,lr}
mov r4, r0
bl ctor_1
ldr r1, =vtable_derived
str r1, [r0] ;vtable override in derived class
add r0, r0, #0x20
bl obj_ctor ;calls an object's ctor at r0+0x20
ldr r1, =vtable_derived_so
str r1, [r0, 0x20] ;overrides object vtable
;...
pop {r4,lr}
bx lr
到目前为止看起来一切正常。在调用基类 ctor 之后,似乎有一个派生类覆盖了 vptr。首先在其中初始化内部子对象obj_ctor
,然后将 vtable 设置为派生子对象。第一个奇怪的事情是为什么ctor_2
不直接调用子对象的派生构造函数,而后者又首先设置基础子对象。我想这是因为调用已被编译器内联。
然而,当整个对象再次被子类化时,事情就会变得棘手:
ctor_3:
push {r4,lr}
mov r4, r0
bl ctor_2
ldr r1, =vtable_derived2
ldr r2, =vtable_derived2_so
str r1, [r0] ;vtable to the new subclass
str r2, [r0, 0x20] ;what??
;...
pop {r4,lr}
bx lr
我完全不知道这怎么可能。子类如何“更改”已经在超类中设置的成员类型(甚至绝对不是指针)?确认ctor_2
和ctor_3
创建两个有效的不透明对象。
我是否误解了 vtables 在反汇编中的工作原理?编译器能否从有效的 C++ 生成这样的代码?
我不知道这是否重要,但是符号ctor_2
和ctor_2
调用的ctor_3
对象实际上是不同的,尽管执行的是完全相同的代码(可能是因为不同的构造函数?)。
编辑:
这是析构函数的样子:
dtor_1:
push {r4, lr}
ldr r1, =vtable_base
str r1, [r0] ;why overwrite the vtable with the same value?
;...calls to delete for heap objects
pop {r4, lr}
bx lr
dtor_2:
push {r4, lr}
mov r4, r0
ldr r1, =vtable_derived
ldr r2, =vtable_derived_so
str r1, [r0]
str r2, [r0, #0x20]
add r0, r0, #0x20
bl dtor_base_so
mov r0, r4
bl dtor_1
pop {r4, lr}
bx lr
dtor_3:
push {r4, lr}
mov r4, r0
ldr r1, =vtable_derived2
ldr r2, =vtable_derived2_so
str r1, [r0]
str r2, [r0, #0x20]
;...
bl dtor_2
pop {r4, lr}
bx lr
如您所见,vtables 被相同的值覆盖。没有调用dtor_derived2_so
,因此 vtable 覆盖似乎是不必要的。更有趣的是,当子对象应该被销毁时,总是会调用dtor_base_so
和 not dtor_derived_so
。我检查了derived_so
and的 vtables,derived2_so
它们有以下两个析构函数:
dtor_derived_so:
ldr r12, =0xFFFFFFE0 ;-0x20
add r0, r0, r12
b dtor_2
dtor_derived2_so:
ldr r12, =0xFFFFFFE0 ;-0x20
add r0, r0, r12
b dtor_3
当他们被调用时,他们会立即调用相应的 dtor。由于它们引用了应该销毁对象的固定位置,因此子对象似乎只存在于derived2
的类中。这里发生了什么?如果子对象被销毁,为什么要强制对象销毁?或者我们这里有一个虚拟继承的特例吗?
下面是虚表:
vtable_base:
dcd 0x82016D20 ;dtor_1
dcd 0x82016CE0 ;dtor_1 (destruct and free)
dcd 0x82016BF8
dcd 0x82016C98
dcd 0x82016BB8
dcd 0x82016B78
vtable_derived:
dcd 0x8201691C ;dtor_2
dcd 0x820168D8 ;dtor_2 (destruct and free)
dcd 0x82016BF8
dcd 0x8201686C
dcd 0x8201682C
dcd 0x820167F8
dcd 0x820167C4
vtable_derived2:
dcd 0x82016364 ;dtor_3
dcd 0x82016320 ;dtor_3 (destruct and free)
dcd 0x82016BF8
dcd 0x8201686C
dcd 0x8201682C
dcd 0x820167F8
dcd 0x820167C4
vtable_base_so:
dcd 0x82015CE8 ;dtor_base_so
dcd 0x82015CC4 ;dtor_base_so (destruct and free)
vtable_derived_so:
dcd 0x82017178 ;dtor_derived_so
dcd 0x82017168 ;dtor_derived_so (destruct and free)
vtable_derived2_so:
dcd 0x820171B8 ;dtor_derived2_so
dcd 0x820171A8 ;dtor_derived2_so (destruct and free)