首页
论坛
课程
招聘
[求助]虚继承中的析构函数
2013-11-2 22:38 19516

[求助]虚继承中的析构函数

2013-11-2 22:38
19516
《C++反汇编与逆向分析技术揭秘》2012年12月第1版第5次印刷
第12.4节, 菱形继承, 代码清单12-16 菱形结构的子类析构
在调用析构函数前会有调整this指针的操作, 书上注释也有标出来, 我也测试过了, 跟虚继承有关, 但本人很想知道这样调整的意义或者说原因, 希望老师与各位达人知道的帮助解释一样, 有相关文章的说明更好, 下面列出(代码清单12-16中)调整this指针的主要代码行
...
00401AF0 add ecx, 20h
(接下来call CSoftBed::~CSoftBed)
...
00401BAB sub ecx, 14h
...
00401BBD add ecx, 10h
(接下来call CBed::~CBed)
(00401BAB sub ecx, 14h 与00401BBD add ecx, 10h其实就是sub ecx, 4h)
...
00401BCF sub ecx, 14h
(接下来call CSoft::~CSoft)
...

【公告】【iPhone 13、ipad、iWatch】11月15日中午12:00,看雪·众安 2021 KCTF秋季赛 正式开赛【攻击篇】!!!文末有惊喜~

收藏
点赞0
打赏
分享
最新回复 (3)
雪    币: 32
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
青v云 活跃值 2013-11-3 10:17
2
0
这是我以前看这里时自己分析的
class A {
public:
    __declspec(noinline) A() { printf("ctor in A\n"); }
    __declspec(noinline) virtual void A1() { printf("A1 in A\n"); }
    __declspec(noinline) virtual void A2() { printf("A2 in A\n"); }
    __declspec(noinline) virtual ~A() { printf("dtor in A\n"); }
};

class B : virtual public A {
public:
    __declspec(noinline) B() { printf("ctor in B\n"); }
    __declspec(noinline) virtual void B1() { printf("B1 in B\n"); }
    __declspec(noinline) virtual ~B() { printf("dtor in B\n"); }
};

class C : virtual public A {
public:
    __declspec(noinline) C() { printf("ctor in C\n"); }
    __declspec(noinline) virtual void C1() { printf("C1 in C\n"); }
    __declspec(noinline) ~C() { printf("dtor in C\n"); }
};

class X {
public:
    __declspec(noinline) X() { printf("ctor in X\n"); }
    __declspec(noinline) virtual void X1() { printf("X1 in X\n"); }
    __declspec(noinline) virtual ~X() { printf("dtor in X\n"); }
};

class Y : virtual public X {
public:
    __declspec(noinline) Y() { printf("ctor in Y\n"); }
    __declspec(noinline) virtual void Y1() { printf("Y1 in Y\n"); }
    __declspec(noinline) virtual ~Y() { printf("dtor in Y\n"); }
};

class Z : virtual public X {
public:
    __declspec(noinline) Z() { printf("ctor in Z\n"); }
013019D0  push        ebp  
013019D1  mov         ebp,esp  
013019D3  push        ecx  
013019D4  push        esi  
013019D5  mov         esi,ecx  
013019D7  push        13032F4h  
013019DC  mov         eax,dword ptr [esi+4]             ; 此时虚基类表指针已经被子类设置好了
013019DF  mov         dword ptr [esi],13033ACh          ; 设置自己的虚表
013019E5  mov         eax,dword ptr [eax+4]  
013019E8  mov         dword ptr [ebp-4],0  
013019EF  mov         dword ptr [eax+esi+4],130341Ch    ; 设置虚基类的虚表
013019F7  call        dword ptr ds:[130303Ch]  
013019FD  add         esp,4  
01301A00  mov         eax,esi  
01301A02  pop         esi  
01301A03  mov         esp,ebp  
01301A05  pop         ebp  
01301A06  ret         4  

    __declspec(noinline) virtual void Z1() { printf("Z1 in Z\n"); }
    __declspec(noinline) virtual ~Z() { printf("dtor in Z\n"); }
00171A20  mov         eax,dword ptr [ecx-4]         ; ecx指向的是对象首地址+8处, 所以这里实际上是在取虚基类表指针
00171A23  mov         dword ptr [ecx-8],1733ACh     ; 设置虚表指针
00171A2A  mov         eax,dword ptr [eax+4]  
00171A2D  push        17330Ch  
00171A32  mov         dword ptr [eax+ecx-4],17341Ch ; 设置虚基类的虚表指针
00171A3A  call        dword ptr ds:[17303Ch]  
00171A40  pop         ecx  
00171A41  ret  
};

class D : public B, public C, public Y, public Z {
    int val_;
public:
    __declspec(noinline) D() { printf("ctor in D, %d\n", &val_); }
01301A80  push        ebp  
01301A81  mov         ebp,esp  
01301A83  sub         esp,8  
01301A86  push        ebx  
01301A87  push        esi  
01301A88  mov         esi,ecx  
01301A8A  push        edi  
01301A8B  lea         ecx,[esi+28h]  
01301A8E  mov         dword ptr [ebp-8],0  
01301A95  mov         dword ptr [esi+4],1303424h  
01301A9C  mov         dword ptr [esi+0Ch],13033F8h  
01301AA3  mov         dword ptr [esi+14h],13033F8h  
01301AAA  mov         dword ptr [esi+1Ch],1303438h      ; 设置4个直接父类的虚基类表指针
01301AB1  call        A::A (013016B0h)  
01301AB6  lea         ecx,[esi+30h]  
01301AB9  call        X::X (013018A0h)  
01301ABE  push        ecx  
01301ABF  mov         ecx,esi  
01301AC1  call        B::B (01301740h)  
01301AC6  push        ecx  
01301AC7  lea         ecx,[esi+8]  
01301ACA  call        C::C (013017F0h)  
01301ACF  push        ecx  
01301AD0  lea         ecx,[esi+10h]  
01301AD3  call        Y::Y (01301920h)  
01301AD8  push        ecx  
01301AD9  lea         ecx,[esi+18h]  
01301ADC  call        Z::Z (013019D0h)  
01301AE1  mov         edx,esi  
01301AE3  mov         dword ptr [esi+8],1303394h        ; 设置C类虚表指针
01301AEA  mov         eax,dword ptr [edx+4]  
01301AED  mov         dword ptr [edx],13033F0h          ; 设置B类和子类共用的虚表指针
01301AF3  mov         dword ptr [esi+10h],130338Ch      ; 设置Y类虚表指针
01301AFA  mov         dword ptr [esi+18h],1303414h      ; 设置Z类虚表指针
01301B01  mov         eax,dword ptr [eax+4]  
01301B04  mov         dword ptr [eax+edx+4],1303404h    ; 设置A类虚表指针
01301B0C  mov         eax,dword ptr [edx+4]  
01301B0F  mov         eax,dword ptr [eax+8]  
01301B12  mov         dword ptr [eax+edx+4],13033E4h    ; 设置X类虚表指针
01301B1A  mov         eax,dword ptr [edx+4]  
01301B1D  mov         ecx,dword ptr [eax+4]  
01301B20  lea         eax,[ecx-24h]  
01301B23  mov         dword ptr [ecx+edx],eax           ; vtordisp
01301B26  mov         eax,dword ptr [edx+4]  
01301B29  mov         ecx,dword ptr [eax+8]  
01301B2C  lea         eax,[ecx-2Ch]  
01301B2F  mov         dword ptr [ecx+edx],eax           ; vtordisp
01301B32  lea         eax,[edx+20h]  
01301B35  push        eax  
01301B36  push        1303318h  
01301B3B  call        dword ptr ds:[130303Ch]  
01301B41  add         esp,8  
01301B44  mov         eax,esi  
01301B46  pop         edi  
01301B47  pop         esi  
01301B48  pop         ebx  
01301B49  mov         esp,ebp  
01301B4B  pop         ebp  
01301B4C  ret         4  

    __declspec(noinline) virtual void A2() { printf("A2 in D\n"); }
    __declspec(noinline) virtual void B1() { printf("B1 in D\n"); }
    __declspec(noinline) virtual void X1() { printf("X1 in D\n"); }
    __declspec(noinline) virtual void Y1() { printf("Y1 in D\n"); }
    __declspec(noinline) virtual void Z1() { printf("Z1 in D\n"); }
    __declspec(noinline) virtual void D1() { printf("C1 in D\n"); }
    __declspec(noinline) virtual ~D() { printf("dtor in D\n"); }
01301BB0  push        ebx  
01301BB1  push        esi  
01301BB2  push        edi  
01301BB3  mov         edi,ecx                           ; ecx是A类子对象首地址, 也就是D类对象首地址+28h处
01301BB5  push        1303370h  
01301BBA  mov         eax,dword ptr [edi-24h]           ; 这里实际是取+4h处的虚基类表指针
01301BBD  mov         dword ptr [edi-28h],13033F0h      ; 设置D类和B类共用的虚表指针
01301BC4  mov         dword ptr [edi-20h],1303394h      ; 设置C类的虚表指针
01301BCB  mov         dword ptr [edi-18h],130338Ch      ; 设置Y类虚表指针
01301BD2  mov         dword ptr [edi-10h],1303414h      ; 设置Z类虚表指针
01301BD9  mov         eax,dword ptr [eax+4]  
01301BDC  mov         dword ptr [eax+edi-24h],1303404h  ; 设置A类虚表指针
01301BE4  mov         eax,dword ptr [edi-24h]  
01301BE7  mov         eax,dword ptr [eax+8]  
01301BEA  mov         dword ptr [eax+edi-24h],13033E4h  ; 设置X类虚表指针
01301BF2  mov         eax,dword ptr [edi-24h]  
01301BF5  mov         ecx,dword ptr [eax+4]  
01301BF8  lea         eax,[ecx-24h]  
01301BFB  mov         dword ptr [ecx+edi-28h],eax       ; vtordisp
01301BFF  mov         eax,dword ptr [edi-24h]  
01301C02  mov         edx,dword ptr [eax+8]  
01301C05  lea         eax,[edx-2Ch]  
01301C08  mov         dword ptr [edx+edi-28h],eax       ; vtordisp
01301C0C  call        dword ptr ds:[130303Ch]  
01301C12  add         esp,4  
01301C15  lea         ecx,[edi-8]                       ; 传入这几个析构函数的this实际指向的是this+8的位置
01301C18  call        Z::~Z (01301A20h)  
01301C1D  lea         ecx,[edi-10h]  
01301C20  call        Y::~Y (01301970h)  
01301C25  lea         ecx,[edi-18h]  
01301C28  call        C::~C (01301840h)  
01301C2D  lea         ecx,[edi-20h]  
01301C30  pop         edi  
01301C31  pop         esi  
01301C32  pop         ebx  
01301C33  jmp         B::~B (01301790h)                ; 按继承列表相反的顺序析构各直接父类子对象

};

// 这里是delete调用的A类虚表中的析构代理
00171D20  sub         ecx,dword ptr [ecx-4]     ; 此处ecx是A类子对象的首地址, ecx-4处是vtordisp, 在构造函数中被初始化为0
00171D23  jmp         D::`scalar deleting destructor' (0171C40h)  

00171C40  push        ebp  
00171C41  mov         ebp,esp  
00171C43  push        esi  
00171C44  push        edi  
00171C45  lea         edi,[ecx-28h]  
00171C48  lea         ecx,[edi+28h]             ; 传递给D类析构函数的this是指向A类子对象的
01301C4B  call        D::~D (01301BB0h)        ; 先执行子类的析构函数
01301C50  lea         ecx,[edi+30h]  
01301C53  call        X::~X (013018D0h)        ; 然后才是虚基类析构
01301C58  lea         ecx,[edi+28h]  
01301C5B  call        A::~A (013016F0h)        ; 虚基类析构按照继承的相反顺序
01301C60  test        byte ptr [ebp+8],1  
01301C64  je          D::`scalar deleting destructor'+30h (01301C70h)  
01301C66  push        edi  
01301C67  call        dword ptr ds:[13030B4h]   ; 释放堆空间
00171C6D  add         esp,4  
00171C70  mov         eax,edi  
00171C72  pop         edi  
00171C73  pop         esi  
00171C74  pop         ebp  
00171C75  ret         4  

int main()
{
    D *pd = new D;
    pd->D1();

    B *pb = pd;
    pb->B1();

    C *pc = pd;
    pc->C1();

    A *pa = pd;
01301CB4  mov         eax,dword ptr [edi+4]     ; 取虚子类和B类共用的虚基类表的指针
01301CB7  mov         ecx,dword ptr [eax+4]     ; 取虚基类表中第二项, 也就是A类的偏移
01301CBA  add         ecx,4                     ; 再加上虚基类表指针距对象首地址的偏移, 得到虚基类子对象距子类对象首地址的偏移
01301CBD  add         ecx,edi                   ; 再加上子类对象首地址, 最终计算出虚基类子对象首地址
    pa->A1();
01301CBF  mov         eax,dword ptr [ecx]  
01301CC1  call        dword ptr [eax]  

    A *paa = pc;
01301CC3  mov         eax,dword ptr [edi+0Ch]   ; 取C类的虚基类表指针
01301CC6  mov         ecx,dword ptr [eax+4]     ; 取出表中第二项
01301CC9  add         ecx,0Ch                   ; 加上指针到子类对象首地址的偏移
01301CCC  add         ecx,edi                   ; 加上子类对象首地址, 得到虚基类子对象首地址
    paa->A2();
01301CCE  mov         eax,dword ptr [ecx]  
01301CD0  call        dword ptr [eax+4]  

    Y *py = pd;
    py->Y1();

    Z *pz = pd;
    pz->Z1();

    X *px = pd;
01301CE3  mov         eax,dword ptr [edi+4]  
01301CE6  mov         ecx,dword ptr [eax+8]     ; 取出表中的第三项, 也就是第二个虚基类的偏移
01301CE9  add         ecx,4  
01301CEC  add         ecx,edi  
    px->X1();
01301CEE  mov         eax,dword ptr [ecx]  
01301CF0  call        dword ptr [eax]  

    X *pxx = py;
01301CF2  mov         eax,dword ptr [edi+14h]  
01301CF5  mov         ecx,dword ptr [eax+4]     ; X是Y类的唯一一个虚基类, 所以使用Y类的虚基类表时, 它依旧是第二项
01301CF8  add         ecx,14h  
01301CFB  add         ecx,edi  
    pxx->X1();
01301CFD  mov         eax,dword ptr [ecx]  
01301CFF  call        dword ptr [eax] 

    delete pd;
01301D01  mov         eax,dword ptr [edi+4]  
01301D04  lea         ecx,[edi+4]  
01301D07  mov         eax,dword ptr [eax+4]  
01301D0A  add         ecx,eax  
01301D0C  push        1  
01301D0E  mov         eax,dword ptr [ecx]  
01301D10  call        dword ptr [eax+8]         ; 析构时用的析构代理是从第一个虚基类的虚表中查到的
01301D13  pop         edi  

    return 0;
}
雪    币: 1
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hzzlyz 活跃值 2013-11-3 18:57
3
0
首先, 感谢您的回答, 然还是没有解答我的疑问
针对您所分析的代码
1. 00171A20  mov         eax,dword ptr [ecx-4]         ; ecx指向的是对象首地址+8处, 所以这里实际上是在取虚基类表指针
==> 我想问的问题就是为什么ecx指向的是对象首地址+8处, 而不是直接的首地址

2. 01301AAA  mov         dword ptr [esi+1Ch],1303438h      ; 设置4个直接父类的虚基类表指针
==> 个人感觉这个注释不对, 应该是设置4个直接父类的虚函数偏移(按照书上说所的, 也就是vt_offset)
雪    币: 32
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
青v云 活跃值 2013-11-4 20:17
4
0
[QUOTE=hzzlyz;1236549]首先, 感谢您的回答, 然还是没有解答我的疑问
针对您所分析的代码
1. 00171A20  mov         eax,dword ptr [ecx-4]         ; ecx指向的是对象首地址+8处, 所以这里实际上是在取虚基类表指针
==> 我想问的问题就是为什么ecx指...[/QUOTE]

1. 我也没弄明白为什么传进来的是this+8, 但是分析D类的析构, 调用的时候ecx的确是this+8的

2. 可能是我用的术语不太规范...自己造的, 那个指针正确的叫法应该是vbptr, 表里是虚基类的偏移, 具体的看一下D的内存布局吧

class D size(52):
        +---
        | +--- (base class B)
 0      | | {vfptr}
 4      | | {vbptr}
        | +---
        | +--- (base class C)
 8      | | {vfptr}
12      | | {vbptr}
        | +---
        | +--- (base class Y)
16      | | {vfptr}
20      | | {vbptr}
        | +---
        | +--- (base class Z)
24      | | {vfptr}
28      | | {vbptr}
        | +---
32      | val_
        +---
36      | (vtordisp for vbase A)
        +--- (virtual base A)
40      | {vfptr}
        +---
44      | (vtordisp for vbase X)
        +--- (virtual base X)
48      | {vfptr}
        +---

D::$vftable@B@:
        | &D_meta
        |  0
 0      | &D::B1
 1      | &D::D1

D::$vftable@C@:
        | -8
 0      | &C::C1

D::$vftable@Y@:
        | -16
 0      | &D::Y1

D::$vftable@Z@:
        | -24
 0      | &D::Z1

D::$vbtable@B@:
 0      | -4
 1      | 36 (Dd(B+4)A)
 2      | 44 (Dd(D+4)X)

D::$vbtable@C@:
 0      | -4
 1      | 28 (Dd(C+4)A)

D::$vbtable@Y@:
 0      | -4
 1      | 28 (Dd(Y+4)X)

D::$vbtable@Z@:
 0      | -4
 1      | 20 (Dd(Z+4)X)

D::$vftable@A@:
        | -40
 0      | &A::A1
 1      | &(vtordisp) D::A2
 2      | &(vtordisp) D::{dtor}

D::$vftable@X@:
        | -48
 0      | &(vtordisp) D::X1
 1      | &(vtordisp) thunk: this-=8; goto D::{dtor}
游客
登录 | 注册 方可回帖
返回