C++反汇编 0x01

虚函数

编译器会将该类中所有虚函数的首地址保存在一张地址表中,这张表被称为虚函数地址表,简称虚表。编译器还会在类的首部添加一个隐藏数据成员,称为虚表指针。保存着虚表的首地址

#include <stdio.h>

class Person {
public:
virtual int getAge() { //虚函数定义
return age;
}
virtual void setAge(int age) { //虚函数定义
this->age = age;
}
~Person() {
printf("~Person()\n");
}
private:
int age;
};

int main(int argc, char* argv[]) {
Person person;
person.setAge(20);
Person* ptr=&person;
ptr->setAge(22);
printf("%d\n", person.getAge());
return 0;
}
.text:00000001400016E0    ; __unwind { // __CxxFrameHandler4_0
.text:00000001400016E0 mov [rsp+arg_8], rdx
.text:00000001400016E5 mov [rsp+arg_0], ecx
.text:00000001400016E9 sub rsp, 48h
.text:00000001400016ED lea rcx, [rsp+48h+var_18] ; this
.text:00000001400016F2 call j_??0Person@@QEAA@XZ ; Person::Person(void)
.text:00000001400016F2
.text:00000001400016F7 nop
.text:00000001400016F8 mov edx, 14h ; age
.text:00000001400016FD lea rcx, [rsp+48h+var_18] ; this
.text:0000000140001702 call j_?setAge@Person@@UEAAXH@Z ; Person::setAge(int)
.text:0000000140001702
.text:0000000140001707 lea rax, [rsp+48h+var_18]
.text:000000014000170C mov [rsp+48h+var_20], rax
.text:0000000140001711 mov rax, [rsp+48h+var_20] ;rax为this
.text:0000000140001716 mov rax, [rax] ;rax为虚表指针
.text:0000000140001719 mov edx, 16h ;参数:setAge(22)
.text:000000014000171E mov rcx, [rsp+48h+var_20] ;this指针用来寻址age成员
.text:0000000140001723 call qword ptr [rax+8] ;利用虚表调用setAge
.text:0000000140001723
.text:0000000140001726 lea rcx, [rsp+48h+var_18] ; this
.text:000000014000172B call j_?getAge@Person@@UEAAHXZ ; Person::getAge(void)
.text:000000014000172B
.text:0000000140001730 mov edx, eax
.text:0000000140001732 lea rcx, aD ; "%d\n"
.text:0000000140001739 call j_printf
.text:0000000140001739
.text:000000014000173E mov [rsp+48h+var_28], 0
.text:0000000140001746 lea rcx, [rsp+48h+var_18] ; this
.text:000000014000174B call j_??1Person@@QEAA@XZ ; Person::~Person(void)
.text:000000014000174B
.text:0000000140001750 mov eax, [rsp+48h+var_28]
.text:0000000140001754 add rsp, 48h
.text:0000000140001758 retn

编译器自动添加构造函数,把类首部赋值为虚表指针

.rdata:0000000140007DE8 ??_7Person@@6B@ dq offset j_?getAge@Person@@UEAAHXZ, offset j_?setAge@Person@@UEAAXH@Z, 0

.text:0000000140001510 ; void __fastcall Person::Person(Person *this)
.text:0000000140001510 ??0Person@@QEAA@XZ proc near ; CODE XREF: Person::Person(void)↑j
.text:0000000140001510
.text:0000000140001510 arg_0= qword ptr 8
.text:0000000140001510
.text:0000000140001510 mov [rsp+arg_0], rcx
.text:0000000140001515 mov rax, [rsp+arg_0]
.text:000000014000151A lea rcx, ??_7Person@@6B@ ; const Person::`vftable'
.text:0000000140001521 mov [rax], rcx
.text:0000000140001524 mov rax, [rsp+arg_0]
.text:0000000140001529 retn

析构函数会再次给类首部赋值虚表指针,让其指向自身的虚表首地址,

子类以后可能会被其他类继承成为父类,子类执行完析构函数执行父类的析构,此时this指针处虚表是子类的,如果不修改虚表就会执行到子类析构

.text:0000000140001570   ; void __fastcall Person::~Person(Person *this)
.text:0000000140001570 ??1Person@@QEAA@XZ proc near ; CODE XREF: Person::~Person(void)↑j
.text:0000000140001570 ; DATA XREF: .pdata:ExceptionDir↓o
.text:0000000140001570
.text:0000000140001570 arg_0= qword ptr 8
.text:0000000140001570
.text:0000000140001570 mov [rsp+arg_0], rcx
.text:0000000140001575 sub rsp, 28h
.text:0000000140001579 mov rax, [rsp+28h+arg_0]
.text:000000014000157E lea rcx, ??_7Person@@6B@ ; const Person::`vftable'
.text:0000000140001585 mov [rax], rcx
.text:0000000140001588 lea rcx, _Format ; "~Person()\n"
.text:000000014000158F call j_printf
.text:000000014000158F
.text:0000000140001594 add rsp, 28h
.text:0000000140001598 retn

同时可以发现直接使用对象调用自身虚函数时,被编译成直接call虚函数,不走虚表,效率高

通过指针或者引用(引用只是在编译期间多了检测的指针)调用虚函数是从虚表取出虚函数指针再调用

类与父类之间关系

在内存中的数据排列:先安排父类的数据,后安排子类新定义的数据。父类中声明为私有(private)的成员,子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在。C++语法规定的访问控制仅限于编译层面,在编译的过程中由编译器进行语法检查

父类数据在前面方便了使用父类函数,直接传递子类this指针即可

对于子类无构造析构函数,父类有构造和析构函数,子类会提供默认构造和析构函数

构造:基类→基类的派生类→……→当前类。
析构:当前类→基类的派生类→……→基类。

#include <stdio.h>

class Person { // 基类—" 人 " 类
public:
Person() {
showSpeak();
}
virtual ~Person() {
showSpeak();
}
virtual void showSpeak() {
printf("Speak Person\r\n");
}
};

class Chinese : public Person { // 中国人:继承自人类
public:
Chinese() {}
virtual ~Chinese() {}
virtual void showSpeak() { // 覆盖基类虚函数
printf("Speak Chinese\r\n");
}
};

class American : public Person { //美国人:继承自人类
public:
American() {}
virtual ~American() {}
virtual void showSpeak() { //覆盖基类虚函数
printf("Speak American\r\n");
}
};

class German : public Person { //德国人:继承自人类
public:
German() {}
virtual ~German() {}
virtual void showSpeak() { //覆盖基类虚函数
printf("Speak German\r\n");
}
};

void speak(Person* person) { //根据虚表信息获取虚函数首地址并调用
person->showSpeak();
}

int main(int argc, char* argv[]) {
Chinese chinese;
American american;
German german;
speak(&chinese);
speak(&american);
speak(&german);
return 0;
}

子类会按照父类虚函数定义的顺序排列属于自己的虚表

image-20231026094850173

调用构造或析构函数第一步都是将虚表指针换为该类的虚表指针,来调用到属于自己的函数

析构函数没有被定义为虚函数,那么编译器会按指针的类型调用父类的析构函数,从而引发错误

显式调用析构函数时不能马上释放堆内存,所以在析构函数的代理函数中通过一个参数控制是否释放内存,便于程序员管理析构函数的调用。

#include <stdio.h>
#include <new.h>

class Person { // 基类—" 人 " 类
public:
Person() {}
virtual ~Person() {}
virtual void showSpeak() {}
};

class Chinese : public Person { // 中国人:继承自人类
public:
Chinese() {}
virtual ~Chinese() {}
virtual void showSpeak() { // 覆盖基类虚函数
printf("Speak Chinese\r\n");
}
};

int main(int argc, char* argv[]) {
Person* p = new Chinese;
p->~Person(); //显式调用析构函数
//将堆内存中p指向的地址作为Chinese的新对象的首地址,并调用Chinese的构造函数。这
//样可以重复使用同一个堆内存,以节约内存空间
p = new (p) Chinese();
delete p;

return 0;
}
22:   p->~Person(); //显式调用析构函数
0000000140001772 mov rax,qword ptr [p] ;rax=对象地址
0000000140001777 mov rax,qword ptr [rax] ;rax=虚表指针
000000014000177A xor edx,edx ;参数0只调用析构函数不释放
000000014000177C mov rcx,qword ptr [p] ;this指针
0000000140001781 call qword ptr [rax] ;虚表第一项:代理析构函数
23: //将堆内存中p指向的地址作为Chinese的新对象的首地址,并调用Chinese的构造函数。这
24: //样可以重复使用同一个堆内存,以节约内存空间
25: p = new (p) Chinese();
0000000140001783 mov rdx,qword ptr [p]
0000000140001788 mov ecx,8
000000014000178D call operator new (0140001203h)
0000000140001792 mov qword ptr [rsp+48h],rax
0000000140001797 mov rcx,qword ptr [rsp+48h];this指针
000000014000179C call Chinese::Chinese (01400011CCh)
00000001400017A1 mov qword ptr [p],rax

000000014000178D call operator new (0140001203h) 这里new也不是完整的new

   167:         _Writable_bytes_(_Size) void* _Where) noexcept
168: {
00000001400015C0 48 89 54 24 10 mov qword ptr [rsp+10h],rdx
00000001400015C5 48 89 4C 24 08 mov qword ptr [rsp+8],rcx
169: (void)_Size;
170: return _Where;
00000001400015CA 48 8B 44 24 10 mov rax,qword ptr [rsp+10h]
171: }

直接把堆返回回来了

多重继承

#include <stdio.h>

class Sofa {
public:
Sofa() {
color = 2;
}

virtual ~Sofa() { // 沙发类虚析构函数
printf("virtual ~Sofa()\n");
}

virtual int getColor() { // 获取沙发颜色
return color;
}
virtual int sitDown() { // 沙发可以坐下休息
return printf("Sit down and rest your legs\r\n");
}
protected:
int color; // 沙发类成员变量
};

//定义床类
class Bed {
public:
Bed() {
length = 4;
width = 5;
}

virtual ~Bed() { //床类虚析构函数
printf("virtual ~Bed()\n");
}

virtual int getArea() { //获取床面积
return length * width;
}

virtual int sleep() { //床可以用来睡觉
return printf("go to sleep\r\n");
}
protected:
int length; //床类成员变量
int width;
};

//子类沙发床定义,派生自 Sofa 类和 Bed 类
class SofaBed : public Sofa, public Bed {
public:
SofaBed() {
height = 6;
}

virtual ~SofaBed() { //沙发床类的虚析构函数
printf("virtual ~SofaBed()\n");
}

virtual int sitDown() { //沙发可以坐下休息
return printf("Sit down on the sofa bed\r\n");
}

virtual int sleep() { //床可以用来睡觉
return printf("go to sleep on the sofa bed\r\n");
}

virtual int getHeight() {
return height;
}
protected:
int height;
};

int main(int argc, char* argv[]) {
SofaBed sofabed;
Sofa* sofa = &sofabed;
Bed* bed = &sofabed;
return 0;
}

数据成员的排列顺序由继承父类的顺序决定,从左向右依次排列。

子类中实现的虚函数会在虚表里替换,没实现的依旧是父类虚函数

image-20231026120014414

vs2022里的监视窗口不是真实内存布局,只不过这次凑巧一样,还是要从内存窗口看

构造函数会把this指针调整到各个父类部分,调用父类的构造函数,然后再把SofaBed的虚表指针赋值到各个父类部分

.text:0000000140001620    ; __unwind { // __CxxFrameHandler4_0
.text:0000000140001620 mov [rsp+arg_0], rcx
.text:0000000140001625 sub rsp, 28h
.text:0000000140001629 mov rcx, [rsp+28h+arg_0] ; this
.text:000000014000162E call j_??0Sofa@@QEAA@XZ ; Sofa::Sofa(void)
.text:000000014000162E
.text:0000000140001633 nop
.text:0000000140001634 mov rax, [rsp+28h+arg_0]
.text:0000000140001639 add rax, 10h ;调整到Bed this位置
.text:000000014000163D mov rcx, rax ; this
.text:0000000140001640 call j_??0Bed@@QEAA@XZ ; Bed::Bed(void)
.text:0000000140001640
.text:0000000140001645 mov rax, [rsp+28h+arg_0]
.text:000000014000164A lea rcx, ??_7SofaBed@@6BSofa@@@ ; const SofaBed::`vftable'{for `Sofa'}
.text:0000000140001651 mov [rax], rcx
.text:0000000140001654 mov rax, [rsp+28h+arg_0]
.text:0000000140001659 lea rcx, ??_7SofaBed@@6BBed@@@ ; const SofaBed::`vftable'{for `Bed'}
.text:0000000140001660 mov [rax+10h], rcx
.text:0000000140001664 mov rax, [rsp+28h+arg_0]
.text:0000000140001669 mov dword ptr [rax+20h], 6
.text:0000000140001670 mov rax, [rsp+28h+arg_0]
.text:0000000140001675 add rsp, 28h
.text:0000000140001679 retn
.text:0000000140001679 ; } // starts at 140001620

析构函数也是一样套路,不过是先把SofaBed的虚表指针赋值到各个父类部分调用析构函数,在把this指针调整到各个父类部分,调用父类的构造函数

指针赋值也是一样的套路

抽象类

#include <stdio.h>
class AbstractBase {
public:
AbstractBase() {
printf("AbstractBase()");
}
virtual void show() = 0; //定义纯虚函数
};

class VirtualChild : public AbstractBase { //定义继承抽象类的子类
public:
virtual void show() { //实现纯虚函数
printf("抽象类分析\n");
}
};

int main(int argc, char* argv[]) {
VirtualChild obj;
obj.show();
return 0;
}

纯虚函数的虚表项为_purecall_0,是编译器保证未定义的纯虚函数被调用提供的用于结束程序的函数

.rdata:0000000140007BB8 DE 15 00 40 01 00 00 00 00 00+??_7AbstractBase@@6B@ dq offset _purecall_0, 0

虚继承(菱形继承)

image-20231026123306196

从这位大哥:https://lonelyenderman.top/archives/723 偷来的图,感觉比较有意思

羊和驼都继承了动物的类成员,当羊驼想要使用时,会产生数据冗余和二义性,引出了虚继承

#include <stdio.h>

//定义家具类,虚基类,等同于类 羊驼
class Furniture {
public:
Furniture() {
printf("Furniture::Furniture()\n");
price = 0;
}
virtual ~Furniture() { //家具类的虚析构函数
printf("Furniture::~Furniture()\n");
}

virtual int getPrice() { //获取家具价格
printf("Furniture::getPrice()\n");
return price;
};
protected:
int price; //家具类的成员变量
};

//定义沙发类,继承自类 Furniture,等同于类 羊
class Sofa : virtual public Furniture {
public:
Sofa() {
printf("Sofa::Sofa()\n");
price = 1;
color = 2;
}
virtual ~Sofa() { //沙发类虚析构函数
printf("Sofa::~Sofa()\n");
}
virtual int getColor() { //获取沙发颜色
printf("Sofa::getColor()\n");
return color;
}
virtual int sitDown() { //沙发可以坐下休息
return printf("Sofa::sitDown()\n");
}
protected:
int color; // 沙发类成员变量
};

//定义床类,继承自类 Furniture,等同于类 驼
class Bed : virtual public Furniture {
public:
Bed() {
printf("Bed::Bed()\n");
price = 3;
length = 4;
width = 5;
}

virtual ~Bed() { //床类的虚析构函数
printf("Bed::~Bed()\n");
}

virtual int getArea() { //获取床面积
printf("Bed::getArea()\n");
return length * width;
}

virtual int sleep() { //床可以用来睡觉
return printf("Bed::sleep()\n");
}
protected:
int length; //床类成员变量
int width;
};

//子类沙发床的定义,派生自类 Sofa 和类 Bed,等同于类 羊驼
class SofaBed : public Sofa, public Bed {
public:
SofaBed() {
printf("SofaBed::SofaBed()\n");
height = 6;
}
virtual ~SofaBed() { //沙发床类的虚析构函数
printf("SofaBed::~SofaBed()\n");
}

virtual int sitDown() { //沙发可以坐下休息
return printf("SofaBed::sitDown()\n");
}

virtual int sleep() { //床可以用来睡觉
return printf("SofaBed::sleep()\n");
}

virtual int getHeight() {
printf("SofaBed::getHeight()\n");
return height;
}
protected:
int height; //沙发类的成员变量
};

int main(int argc, char* argv[]) {
SofaBed sofabed;
Furniture* p1 = &sofabed; //转换成虚基类指针
Sofa* p2 = &sofabed; //转换成父类指针
Bed* p3 = &sofabed; //转换成父类指针
printf("%p %p %p\n", p1, p2, p3);
return 0;
}

sofabed内存布局

image-20231026124846358

.text:0000000140003550 ; __int64 __fastcall main(int argc, char **argv)
.text:0000000140003550 main proc near ; CODE XREF: main_0↑j
.text:0000000140003550 ; DATA XREF: .pdata:000000014000B390↓o
.text:0000000140003550
.text:0000000140003550 var_88= dword ptr -88h
.text:0000000140003550 var_80= qword ptr -80h
.text:0000000140003550 var_78= qword ptr -78h
.text:0000000140003550 var_70= qword ptr -70h
.text:0000000140003550 var_68= qword ptr -68h
.text:0000000140003550 var_60= qword ptr -60h
.text:0000000140003550 SofaBed= SofaBed ptr -58h
.text:0000000140003550 arg_0= dword ptr 8
.text:0000000140003550 arg_8= qword ptr 10h
.text:0000000140003550
.text:0000000140003550 mov [rsp+arg_8], rdx
.text:0000000140003555 mov [rsp+arg_0], ecx
.text:0000000140003559 sub rsp, 0A8h
.text:0000000140003560 mov edx, 1 ; 构造虚基类的标志:1构造,0不构造
.text:0000000140003565 lea rcx, [rsp+0A8h+SofaBed] ; this
.text:000000014000356A call j_??0SofaBed@@QEAA@XZ ; SofaBed::SofaBed(void)
.text:000000014000356A
.text:000000014000356F lea rax, [rsp+50h]
.text:0000000140003574 test rax, rax
.text:0000000140003577 jnz short loc_140003584
.text:0000000140003577
.text:0000000140003579 mov [rsp+0A8h+var_80], 0
.text:0000000140003582 jmp short loc_140003597
.text:0000000140003582
.text:0000000140003584 ; ---------------------------------------------------------------------------
.text:0000000140003584
.text:0000000140003584 loc_140003584: ; CODE XREF: main+27↑j
.text:0000000140003584 mov rax, qword ptr [rsp+0A8h+SofaBed.baseclass_0.gap8] ; 取出对象Sofa虚基类偏移表
.text:0000000140003589 movsxd rax, dword ptr [rax+4] ; 取出对象中Sofa类虚基类偏移表第二项,虚基类对象首地址相对于虚基类偏移表的偏移值。
.text:000000014000358D lea rax, [rsp+rax+0A8h+SofaBed.baseclass_0.gap8] ; 找到虚基类
.text:0000000140003592 mov [rsp+0A8h+var_80], rax
.text:0000000140003592
.text:0000000140003597
.text:0000000140003597 loc_140003597: ; CODE XREF: main+32↑j
.text:0000000140003597 mov rax, [rsp+0A8h+var_80]
.text:000000014000359C mov [rsp+0A8h+var_60], rax ; Furniture* p1 = &sofabed; //转换成虚基类指针
.text:00000001400035A1 lea rax, [rsp+0A8h+SofaBed]
.text:00000001400035A6 mov [rsp+0A8h+var_68], rax ; Sofa* p2 = &sofabed; //转换成父类指针
.text:00000001400035AB lea rax, [rsp+0A8h+SofaBed]
.text:00000001400035B0 test rax, rax
.text:00000001400035B3 jz short loc_1400035C5
.text:00000001400035B3
.text:00000001400035B5 lea rax, [rsp+0A8h+SofaBed]
.text:00000001400035BA add rax, 18h ; 直接加偏移获取到Bed首地址
.text:00000001400035BE mov [rsp+0A8h+var_78], rax
.text:00000001400035C3 jmp short loc_1400035CE
.text:00000001400035C3
.text:00000001400035C5 ; ---------------------------------------------------------------------------
.text:00000001400035C5
.text:00000001400035C5 loc_1400035C5: ; CODE XREF: main+63↑j
.text:00000001400035C5 mov [rsp+0A8h+var_78], 0
.text:00000001400035C5
.text:00000001400035CE
.text:00000001400035CE loc_1400035CE: ; CODE XREF: main+73↑j
.text:00000001400035CE mov rax, [rsp+0A8h+var_78]
.text:00000001400035D3 mov [rsp+0A8h+var_70], rax ; Bed* p3 = &sofabed; //转换成父类指针
.text:00000001400035D8 mov r9, [rsp+0A8h+var_70]
.text:00000001400035DD mov r8, [rsp+0A8h+var_68]
.text:00000001400035E2 mov rdx, [rsp+0A8h+var_60]
.text:00000001400035E7 lea rcx, aPPP ; "%p %p %p\n"
.text:00000001400035EE call j_printf
.text:00000001400035EE
.text:00000001400035F3 mov [rsp+0A8h+var_88], 0
.text:00000001400035FB lea rcx, [rsp+0A8h+SofaBed] ; this
.text:0000000140003600 call j_??_DSofaBed@@QEAAXXZ ; SofaBed::`vbase destructor'(void)
.text:0000000140003600
.text:0000000140003605 mov eax, [rsp+0A8h+var_88]
.text:0000000140003609 add rsp, 0A8h
.text:0000000140003610 retn

Sofa虚基类偏移表存储两个值-8 和 0x30

image-20231026125459339

  • 第一项为-8,即虚基类偏移表所属类对应的对象首地址相对于虚基类偏移表的偏移值;
  • 第二项保存的是虚基类对象首地址相对于虚基类偏移表的偏移值。

虚基类放到了一个公共的位置,羊和驼类里原本存放动物的位置现在存放虚基偏移表指针,利用偏移来寻址虚基类

多个虚基类时,会在虚基类偏移表中依次记录它们的偏移量。

虚继承子类构造函数

虚继承子类构造函数传入了参数1:this指针,参数2:构造虚基类的标志:1构造,0不构造,防止重复构造虚基类

.text:0000000140001860 ; void __fastcall SofaBed::SofaBed(SofaBed *this)
.text:0000000140001860 ??0SofaBed@@QEAA@XZ proc near ; CODE XREF: SofaBed::SofaBed(void)↑j
.text:0000000140001860 ; DATA XREF: .pdata:000000014000B048↓o
.text:0000000140001860
.text:0000000140001860 var_18= dword ptr -18h
.text:0000000140001860 sofabed_this= qword ptr 8
.text:0000000140001860 flag= dword ptr 10h
.text:0000000140001860
.text:0000000140001860 ; FUNCTION CHUNK AT .text:00000001400057E0 SIZE 0000002A BYTES
.text:0000000140001860 ; FUNCTION CHUNK AT .text:0000000140005810 SIZE 0000001C BYTES
.text:0000000140001860
.text:0000000140001860 ; __unwind { // __CxxFrameHandler4_0
.text:0000000140001860 mov [rsp+flag], edx
.text:0000000140001864 mov [rsp+sofabed_this], rcx
.text:0000000140001869 sub rsp, 38h
.text:000000014000186D mov [rsp+38h+var_18], 0
.text:0000000140001875 cmp [rsp+38h+flag], 0
.text:000000014000187A jz short loc_1400018B9 ; 为0不构造虚基类,防止重复构造虚基类
.text:000000014000187A
.text:000000014000187C mov rax, [rsp+38h+sofabed_this] ; rax=this指针
.text:0000000140001881 lea rcx, ??_8SofaBed@@7BSofa@@@ ; const SofaBed::`vbtable'{for `Sofa'}
.text:0000000140001888 mov [rax+8], rcx ; 设置父类Sofa中的虚基类偏移表
.text:000000014000188C mov rax, [rsp+38h+sofabed_this]
.text:0000000140001891 lea rcx, ??_7SofaBed@@6BFurniture@@@+10h ; const SofaBed::`vftable'{for `Furniture'}
.text:0000000140001898 mov [rax+20h], rcx ; 设置父类Bed中的虚基类偏移
.text:000000014000189C mov rax, [rsp+38h+sofabed_this]
.text:00000001400018A1 add rax, 38h ; '8' ; 调整this指针为虚基类this指针
.text:00000001400018A5 mov rcx, rax ; this
.text:00000001400018A8 call j_??0Furniture@@QEAA@XZ ; Furniture::Furniture(void)
.text:00000001400018A8
.text:00000001400018AD nop
.text:00000001400018AE mov eax, [rsp+38h+var_18] ; eax=0
.text:00000001400018B2 or eax, 1 ; eax=1
.text:00000001400018B5 mov [rsp+38h+var_18], eax
.text:00000001400018B5
.text:00000001400018B9
.text:00000001400018B9 loc_1400018B9: ; CODE XREF: SofaBed::SofaBed(void)+1A↑j
.text:00000001400018B9 xor edx, edx ; 给Sofa传入0,不再构造虚基类,上面已经构造过了
.text:00000001400018BB mov rcx, [rsp+38h+sofabed_this] ; this
.text:00000001400018BB
.text:00000001400018C0
.text:00000001400018C0 loc_1400018C0: ; CODE XREF: .text:00000001400010DC↑j
.text:00000001400018C0 call j_??0Sofa@@QEAA@XZ ; Sofa::Sofa(void)
.text:00000001400018C0
.text:00000001400018C5 nop
.text:00000001400018C6 ; try {
.text:00000001400018C6 mov rax, [rsp+38h+sofabed_this]
.text:00000001400018CB add rax, 18h
.text:00000001400018CF xor edx, edx
.text:00000001400018D1 mov rcx, rax ; this
.text:00000001400018D4 call j_??0Bed@@QEAA@XZ ; Bed::Bed(void)
.text:00000001400018D4
.text:00000001400018D9 mov rax, [rsp+38h+sofabed_this] ; 下面就是设置各个类的虚表指针
.text:00000001400018DE lea rcx, ??_7SofaBed@@6BSofa@@@ ; const SofaBed::`vftable'{for `Sofa'}
.text:00000001400018E5 mov [rax], rcx
.text:00000001400018E8 mov rax, [rsp+38h+sofabed_this]
.text:00000001400018ED lea rcx, ??_7SofaBed@@6BBed@@@ ; const SofaBed::`vftable'{for `Bed'}
.text:00000001400018F4 mov [rax+18h], rcx
.text:00000001400018F8 mov rax, [rsp+38h+sofabed_this]
.text:00000001400018FD mov rax, [rax+8]
.text:0000000140001901 movsxd rax, dword ptr [rax+4]
.text:0000000140001905 mov rcx, [rsp+38h+sofabed_this]
.text:000000014000190A lea rdx, ??_7SofaBed@@6BFurniture@@@ ; const SofaBed::`vftable'{for `Furniture'}
.text:0000000140001911 mov [rcx+rax+8], rdx
.text:0000000140001916 lea rcx, aSofabedSofabed ; "SofaBed::SofaBed()\n"
.text:000000014000191D call j_printf
.text:000000014000191D
.text:0000000140001922 mov rax, [rsp+38h+sofabed_this]
.text:0000000140001927 mov dword ptr [rax+30h], 6
.text:0000000140001927 ; } // starts at 1400018C6
.text:000000014000192E mov rax, [rsp+38h+sofabed_this]
.text:0000000140001933 add rsp, 38h
.text:0000000140001937 retn

虚继承子类代理析构函数

.text:00000001400019F0 ; void __fastcall SofaBed::`vbase destructor'(SofaBed *this)
.text:00000001400019F0 ??_DSofaBed@@QEAAXXZ proc near ; CODE XREF: SofaBed::`vbase destructor'(void)↑j
.text:00000001400019F0 ; DATA XREF: .pdata:000000014000B060↓o
.text:00000001400019F0 ; .pdata:000000014000B06C↓o
.text:00000001400019F0
.text:00000001400019F0 sofabed_this= qword ptr 8
.text:00000001400019F0
.text:00000001400019F0 mov [rsp+sofabed_this], rcx
.text:00000001400019F5 sub rsp, 28h
.text:00000001400019F9 mov rax, [rsp+28h+sofabed_this]
.text:00000001400019FE add rax, 38h ; '8'
.text:0000000140001A02 mov rcx, rax ; this
.text:0000000140001A05 call j_??1SofaBed@@UEAA@XZ ; SofaBed::~SofaBed(void)
.text:0000000140001A05
.text:0000000140001A0A mov rax, [rsp+28h+sofabed_this]
.text:0000000140001A0F add rax, 38h ; '8'
.text:0000000140001A13 mov rcx, rax ; this
.text:0000000140001A16 call j_??1Furniture@@UEAA@XZ ; Furniture::~Furniture(void)
.text:0000000140001A16
.text:0000000140001A1B add rsp, 28h
.text:0000000140001A1F retn

先依次执行两个父类Bed和Sofa的析构函数,然后执行虚基类的析构函数

Reference

C++反汇编与逆向分析技术揭秘