实验环境 不做特殊说明均为vs2022 x64 release版本 关闭优化,固定基址,关闭canary
类特性 没有数据成员的类是占一个字节的,为了实例化后可以调用函数成员
类的静态数据成员与局部静态变量类似,存放的位置和全局变量一致。只是编译器增加了作用域的检查,在作用域之外不可见。
this指针 this指针中保存了所属对象的首地址,需要访问类数据成员时通过rcx传递this指针,默认调用约定称为thiscall
#include <stdio.h> class Person {public: void setAge (int age) { this->age = age; } public: int age; }; int main (int argc, char * argv[]) { Person person; person.setAge(5 ); printf ("Person : %d\n" , person.age); return 0 ; }
.text:0000000140001450 ; int __fastcall main(int argc, char **argv) .text:0000000140001450 main proc near ; CODE XREF: main_0↑j .text:0000000140001450 ; DATA XREF: .pdata:000000014000900C↓o .text:0000000140001450 .text:0000000140001450 var_18= Person ptr -18h .text:0000000140001450 arg_0= dword ptr 8 .text:0000000140001450 arg_8= qword ptr 10h .text:0000000140001450 .text:0000000140001450 mov [rsp+arg_8], rdx .text:0000000140001455 mov [rsp+arg_0], ecx ; 保护参数 .text:0000000140001459 sub rsp, 38h .text:000000014000145D mov edx, 5 ; 参数为5 .text:0000000140001462 lea rcx, [rsp+38h+var_18] ; this指针指向person的首地址 .text:0000000140001467 call j_?setAge@Person@@QEAAXH@Z ; Person::setAge(int) .text:0000000140001467 .text:000000014000146C mov edx, [rsp+38h+var_18.age] .text:0000000140001470 lea rcx, _Format ; "Person : %d\n" .text:0000000140001477 call j_printf .text:0000000140001477 .text:000000014000147C xor eax, eax .text:000000014000147E add rsp, 38h .text:0000000140001482 retn .text:0000000140001482 .text:0000000140001482 main endp
.text:00000001400013C0 ; void __fastcall Person::setAge(Person *this, int age) .text:00000001400013C0 ?setAge@Person@@QEAAXH@Z proc near ; CODE XREF: Person::setAge(int)↑j .text:00000001400013C0 .text:00000001400013C0 arg_0= qword ptr 8 .text:00000001400013C0 arg_8= dword ptr 10h .text:00000001400013C0 .text:00000001400013C0 mov [rsp+arg_8], edx ;参数:5 .text:00000001400013C4 mov [rsp+arg_0], rcx ; this指针指向person首地址 .text:00000001400013C9 mov rax, [rsp+arg_0] ; rax=this指针 .text:00000001400013CE mov ecx, [rsp+arg_8] ; rcx=5 .text:00000001400013D2 mov [rax], ecx ; this->age=5 .text:00000001400013D4 retn
对象作为函数参数 进入函数前预先保留对象使用的栈空间,类中没有定义拷贝构造函数,编译器会对原对象与临时对象中的各数据成员直接进行数据复制,形成一个临时对象,称为默认复制构造函数,这种复制方式属于浅拷贝,如果类带有指针变量,并有动态内存分配,它必须有一个拷贝构造函数否则会有问题
#include <stdio.h> #include <string.h> class Person {public: Person() { name = new char [32 ]; age = 18 ; if (name != NULL ) { strcpy (name, "tom" ); } } ~Person() { if (name != NULL ) { delete[] name; name = NULL ; } } const char * getName () { return name; } private: char * name; int age; }; void show (Person obj) { printf (obj.getName()); } int main (int argc, char * argv[]) { Person person; show(person); return 0 ; }
.text:0000000140001650 var_48= dword ptr -48 h .text:0000000140001650 var_40= Person ptr -40 h .text:0000000140001650 obj= Person ptr -28 h .text:0000000140001650 arg_0= dword ptr 8 .text:0000000140001650 arg_8= qword ptr 10 h .text:0000000140001650 .text:0000000140001650 ; FUNCTION CHUNK AT .text:0000000140005260 SIZE 00000018 BYTES .text:0000000140001650 .text:0000000140001650 ; __unwind { .text:0000000140001650 mov [rsp+arg_8], rdx .text:0000000140001655 mov [rsp+arg_0], ecx .text:0000000140001659 push rsi .text:000000014000165 A push rdi .text:000000014000165B sub rsp, 58 h .text:000000014000165F lea rcx, [rsp+68 h+var_40] ; this .text:0000000140001664 call j_??0 Person@@QEAA@XZ ; Person::Person(void ) .text:0000000140001664 .text:0000000140001669 nop .text:000000014000166 A lea rax, [rsp+68 h+obj] .text:000000014000166F lea rcx, [rsp+68 h+var_40] .text:0000000140001674 mov rdi, rax .text:0000000140001677 mov rsi, rcx .text:000000014000167 A mov ecx, 10 h .text:000000014000167F rep movsb ; 采用默认拷贝构造函数在rsp+68 +obj浅拷贝生成person复制品作为参数 .text:0000000140001681 lea rcx, [rsp+68 h+obj] ; obj .text:0000000140001686 call j_?show@@YAXVPerson@@@Z ; show(Person) .text:0000000140001686 .text:000000014000168B mov [rsp+68 h+var_48], 0 .text:0000000140001693 lea rcx, [rsp+68 h+var_40] ; this .text:0000000140001698 call j_??1 Person@@QEAA@XZ ; Person::~Person(void ) .text:0000000140001698 .text:000000014000169 D mov eax, [rsp+68 h+var_48] .text:00000001400016 A1 add rsp, 58 h .text:00000001400016 A5 pop rdi .text:00000001400016 A6 pop rsi .text:00000001400016 A7 retn
.text:00000001400015B 0 ; void __fastcall show (Person obj) .text:00000001400015B0 ?show@@YAXVPerson@@@Z proc near ; CODE XREF: show(Person)↑j .text:00000001400015B 0 ; DATA XREF: .pdata:000000014000B 018↓o .text:00000001400015B 0 .text:00000001400015B 0 arg_0= qword ptr 8 .text:00000001400015B 0 .text:00000001400015B 0 ; FUNCTION CHUNK AT .text:0000000140005240 SIZE 00000018 BYTES .text:00000001400015B 0 .text:00000001400015B 0 ; __unwind { .text:00000001400015B 0 mov [rsp+arg_0], rcx ;临时对象地址 .text:00000001400015B 5 sub rsp, 28 h .text:00000001400015B 9 mov rcx, [rsp+28 h+arg_0] ; 临时对象地址 this .text:00000001400015B E call j_?getName@Person@@QEAAPEBDXZ ; Person::getName(void ) .text:00000001400015B E .text:00000001400015 C3 mov rcx, rax ; _Format .text:00000001400015 C6 call j_printf .text:00000001400015 C6 .text:00000001400015 CB nop .text:00000001400015 CC mov rcx, [rsp+28 h+arg_0] ; this .text:00000001400015 D1 call j_??1 Person@@QEAA@XZ ; Person::~Person(void ) .text:00000001400015 D1 .text:00000001400015 D6 add rsp, 28 h .text:00000001400015 DA retn
采用了浅拷贝将同一个堆地址赋值给name,show里调用了析构释放了堆,main里的也析构造成double free crash
第一种解决方法
设置引用计数,在进入复制构造函数时,记录类对象被复制引用的次数。当对象被销毁时,检查这个引用计数中保存的引用复制次数是否为0。如果是,则释放申请的资源,否则引用计数减1。
第二种解决方案
使用拷贝构造函数深拷贝数据
classname (const classname &obj) { }
Person(const Person &obj) { this->age = obj.age; int len = strlen (obj.name); this->name = new char [len + sizeof (char )]; strcpy (this->name, obj.name); }
.text:00000001400016B 0 ; __int64 __fastcall main (int argc, char **argv) .text:00000001400016B0 main proc near ; CODE XREF: main_0↑j .text:00000001400016B 0 ; DATA XREF: .pdata:000000014000B 018↓o .text:00000001400016B 0 .text:00000001400016B 0 var_48= dword ptr -48 h .text:00000001400016B 0 var_40= qword ptr -40 h .text:00000001400016B 0 var_38= Person ptr -38 h .text:00000001400016B 0 obj= Person ptr -30 h .text:00000001400016B 0 var_20= qword ptr -20 h .text:00000001400016B 0 arg_0= dword ptr 8 .text:00000001400016B 0 arg_8= qword ptr 10 h .text:00000001400016B 0 .text:00000001400016B 0 ; FUNCTION CHUNK AT .text:0000000140005260 SIZE 00000018 BYTES .text:00000001400016B 0 .text:00000001400016B 0 ; __unwind { .text:00000001400016B 0 mov [rsp+arg_8], rdx .text:00000001400016B 5 mov [rsp+arg_0], ecx .text:00000001400016B 9 sub rsp, 68 h .text:00000001400016B D lea rcx, [rsp+68 h+obj] ; this .text:00000001400016 C2 call j_??0 Person@@QEAA@XZ ; Person::Person(void ) .text:00000001400016 C2 .text:00000001400016 C7 nop .text:00000001400016 C8 lea rax, [rsp+68 h+var_20] .text:00000001400016 CD mov [rsp+68 h+var_40], rax .text:00000001400016 D2 lea rdx, [rsp+68 h+obj] ; obj .text:00000001400016 D7 mov rcx, [rsp+68 h+var_40] ; this .text:00000001400016 DC call j_??0 Person@@QEAA@AEBV0@@Z ; Person::Person(Person const &)深拷贝,返回新对象 .text:00000001400016 DC .text:00000001400016E1 mov [rsp+68 h+var_38.name], rax .text:00000001400016E6 mov rcx, [rsp+68 h+var_38.name] ; obj .text:00000001400016 EB call j_?show@@YAXVPerson@@@Z ; show(Person) .text:00000001400016 EB .text:00000001400016F 0 mov [rsp+68 h+var_48], 0 .text:00000001400016F 8 lea rcx, [rsp+68 h+obj] ; this .text:00000001400016F D call j_??1 Person@@QEAA@XZ ; Person::~Person(void ) .text:00000001400016F D .text:0000000140001702 mov eax, [rsp+68 h+var_48] .text:0000000140001706 add rsp, 68 h .text:000000014000170 A retn
.text:0000000140003140 ; void __fastcall Person::Person (Person *this, const Person *obj) .text:0000000140003140 ??0Person@@QEAA@AEBV0@@Z proc near ; CODE XREF: Person::Person(Person const &)↑j .text:0000000140003140 ; DATA XREF: .pdata:000000014000B 2AC↓o .text:0000000140003140 .text:0000000140003140 var_48= byte ptr -48 h .text:0000000140003140 var_44= dword ptr -44 h .text:0000000140003140 var_40= qword ptr -40 h .text:0000000140003140 var_38= qword ptr -38 h .text:0000000140003140 var_30= qword ptr -30 h .text:0000000140003140 var_28= qword ptr -28 h .text:0000000140003140 var_20= qword ptr -20 h .text:0000000140003140 var_18= qword ptr -18 h .text:0000000140003140 arg_0= qword ptr 8 .text:0000000140003140 arg_8= qword ptr 10 h .text:0000000140003140 .text:0000000140003140 mov [rsp+arg_8], rdx .text:0000000140003145 mov [rsp+arg_0], rcx ; this .text:000000014000314 A sub rsp, 68 h .text:000000014000314 E mov rax, [rsp+68 h+arg_0] ; this .text:0000000140003153 mov rcx, [rsp+68 h+arg_8] ; obj .text:0000000140003158 mov ecx, [rcx+8 ] ; obj.age .text:000000014000315B mov [rax+8 ], ecx ; this->age = obj.age; .text:000000014000315 E mov rax, [rsp+68 h+arg_8] ; obj .text:0000000140003163 mov rax, [rax] ; rax=obj.name .text:0000000140003166 mov [rsp+68 h+var_28], rax .text:000000014000316B mov [rsp+68 h+var_40], 0F FFFFFFFFFFFFFFFh .text:000000014000316B .text:0000000140003174 .text:0000000140003174 loc_140003174: ; CODE XREF: Person::Person(Person const &)+47 ↓j .text:0000000140003174 inc [rsp+68 h+var_40] ; rsp+68 h+var_40是局部变量len .text:0000000140003179 mov rax, [rsp+68 h+var_28] .text:000000014000317 E mov rcx, [rsp+68 h+var_40] .text:0000000140003183 cmp byte ptr [rax+rcx], 0 .text:0000000140003187 jnz short loc_140003174 ; rsp+68 h+var_40是局部变量len .text:0000000140003187 .text:0000000140003189 mov rax, [rsp+68 h+var_40] .text:000000014000318 E mov [rsp+68 h+var_44], eax ; int len = strlen (obj.name); .text:0000000140003192 movsxd rax, [rsp+68 h+var_44] .text:0000000140003197 inc rax .text:000000014000319 A mov rcx, rax ; size .text:000000014000319 D call j_??_U@YAPEAX_K@Z ; operator new[](unsigned __int64) .text:000000014000319 D .text:00000001400031 A2 mov [rsp+68 h+var_20], rax .text:00000001400031 A7 mov rax, [rsp+68 h+arg_0] .text:00000001400031 AC mov rcx, [rsp+68 h+var_20] .text:00000001400031B 1 mov [rax], rcx ; this->name = new char [len + sizeof (char )]; .text:00000001400031B 4 mov rax, [rsp+68 h+arg_8] .text:00000001400031B 9 mov rax, [rax] .text:00000001400031B C mov [rsp+68 h+var_30], rax .text:00000001400031 C1 mov rax, [rsp+68 h+arg_0] .text:00000001400031 C6 mov rax, [rax] .text:00000001400031 C9 mov [rsp+68 h+var_38], rax .text:00000001400031 CE mov rax, [rsp+68 h+var_38] .text:00000001400031 D3 mov [rsp+68 h+var_18], rax .text:00000001400031 D3 .text:00000001400031 D8 .text:00000001400031 D8 loc_1400031D8: ; CODE XREF: Person::Person(Person const &)+CF↓j .text:00000001400031 D8 mov rax, [rsp+68 h+var_30] .text:00000001400031 DD movzx eax, byte ptr [rax] .text:00000001400031E0 mov [rsp+68 h+var_48], al .text:00000001400031E4 mov rax, [rsp+68 h+var_38] .text:00000001400031E9 movzx ecx, [rsp+68 h+var_48] .text:00000001400031 EE mov [rax], cl .text:00000001400031F 0 mov rax, [rsp+68 h+var_30] .text:00000001400031F 5 inc rax .text:00000001400031F 8 mov [rsp+68 h+var_30], rax .text:00000001400031F D mov rax, [rsp+68 h+var_38] .text:0000000140003202 inc rax .text:0000000140003205 mov [rsp+68 h+var_38], rax .text:000000014000320 A cmp [rsp+68 h+var_48], 0 .text:000000014000320F jnz short loc_1400031D8 ; strcpy (this->name, obj.name); .text:000000014000320F .text:0000000140003211 mov rax, [rsp+68 h+arg_0] ; 返回新对象 .text:0000000140003216 add rsp, 68 h .text:000000014000321 A retn
对象作为返回值 不能用寄存器返回的采用在调用者函数里开辟局部空间,通过拷贝构造函数赋值,同样会存在作为函数参数时的一样的问题
#include <stdio.h> class Person {public: int count; int buffer[10 ]; }; Person getPerson () { Person person; person.count = 10 ; person.buffer[0 ] = 'g' ; return person; } int main (int argc, char * argv[]) { Person person; person = getPerson(); printf ("%d %d %d" , person.count, person.buffer[0 ], person.buffer[9 ]); return 0 ; }
汇编太长了,不贴了,伪代码基本可以把汇编实现反应出来
main里开辟局部变量,作为getPerson参数,返回后再复制给v4再给v3
没搞懂为什么不直接用result,还要再给v4赋值一下
__int64 __fastcall main(int argc, char **argv) { int v3[11]; // [rsp+20h] [rbp-A8h] BYREF char v4[44]; // [rsp+50h] [rbp-78h] BYREF Person result; // [rsp+80h] [rbp-48h] BYREF qmemcpy(v4, getPerson(&result), sizeof(v4)); qmemcpy(v3, v4, sizeof(v3)); j_printf("%d %d %d", (unsigned int)v3[0], (unsigned int)v3[1], (unsigned int)v3[10]); return 0i64; } Person *__fastcall getPerson(Person *result) { LODWORD(result->name) = 10; HIDWORD(result->name) = 'g'; return result; }
构造函数调用时机 局部对象 隐式传入this指针,返回this指针
堆对象 #include <stdio.h> class Person {public: Person() { age = 20 ; } int age; }; int main (int argc, char * argv[]) { Person* p = new Person; p->age = 21 ; printf ("%d\n" , p->age); return 0 ; }
new完后会判断是否申请失败,决定是否调用构造函数,malloc不负责触发构造函数,它也不是运算符,无法进行运算符重载。
.text:0000000140003010 main proc near ; CODE XREF: main_0↑j .text:0000000140003010 ; DATA XREF: .pdata:000000014000B270↓o .text:0000000140003010 .text:0000000140003010 var_28= qword ptr -28h .text:0000000140003010 var_20= qword ptr -20h .text:0000000140003010 var_18= qword ptr -18h .text:0000000140003010 var_10= qword ptr -10h .text:0000000140003010 arg_0= dword ptr 8 .text:0000000140003010 arg_8= qword ptr 10h .text:0000000140003010 .text:0000000140003010 ; FUNCTION CHUNK AT .text:0000000140005240 SIZE 0000001D BYTES .text:0000000140003010 .text:0000000140003010 ; __unwind { // __CxxFrameHandler4_0 .text:0000000140003010 48 89 54 24 10 mov [rsp+arg_8], rdx .text:0000000140003015 89 4C 24 08 mov [rsp+arg_0], ecx .text:0000000140003019 48 83 EC 48 sub rsp, 48h .text:000000014000301D B9 04 00 00 00 mov ecx, 4 ; size .text:0000000140003022 E8 F2 DF FF FF call j_??2@YAPEAX_K@Z ; operator new(unsigned __int64) .text:0000000140003022 .text:0000000140003027 48 89 44 24 20 mov [rsp+48h+var_28], rax .text:000000014000302C 48 83 7C 24 20 00 cmp [rsp+48h+var_28], 0 .text:0000000140003032 74 11 jz short loc_140003045 ;跳过构造函数 .text:0000000140003032 .text:0000000140003034 48 8B 4C 24 20 mov rcx, [rsp+48h+var_28] ; this .text:0000000140003039 E8 3D E2 FF FF call j_??0Person@@QEAA@XZ ; Person::Person(void) .text:0000000140003039 .text:000000014000303E 48 89 44 24 28 mov [rsp+48h+var_20], rax .text:0000000140003043 EB 09 jmp short loc_14000304E .text:0000000140003043 .text:0000000140003045 ; --------------------------------------------------------------------------- .text:0000000140003045 .text:0000000140003045 loc_140003045: ; CODE XREF: main+22↑j .text:0000000140003045 48 C7 44 24 28 00 00 00 00 mov [rsp+48h+var_20], 0 .text:0000000140003045 .text:000000014000304E .text:000000014000304E loc_14000304E: ; CODE XREF: main+33↑j .text:000000014000304E 48 8B 44 24 28 mov rax, [rsp+48h+var_20]
参数对象&&返回对象 全局对象&&静态对象 析构函数调用时机 局部对象 传递this指针
堆对象 #include <stdio.h> class Person {public: Person() { age = 20 ; } ~Person() { printf ("~Person()\n" ); } int age; }; int main (int argc, char * argv[]) { Person* person = new Person(); person->age = 21 ; printf ("%d\n" , person->age); delete person; return 0 ; }
调用delete时如果判断为0直接跳过析构函数,调用代理析构函数时,传入了两参数 tish指针和1
.text:00000001400016E5 mov [rsp+58h+var_20], rax ; 对象地址 .text:00000001400016EA cmp [rsp+58h+var_20], 0 .text:00000001400016F0 jz short loc_140001708 .text:00000001400016F0 .text:00000001400016F2 mov edx, 1 ; unsigned int .text:00000001400016F7 mov rcx, [rsp+58h+var_20] ; this .text:00000001400016FC call j_??_GPerson@@QEAAPEAXI@Z ; Person::`scalar deleting destructor'(uint)
第二个参数1表示应该如何析构,例如下面的情况
int main (int argc, char * argv[]) { Person *objs = new Person[3 ]; delete[] objs; return 0 ; }
new申请时申请了20大小,比Person[3]多了堆空间的首地址处的8字节来内容保存对象数量
.text:000000014000169D mov ecx, 14h ; size .text:00000001400016A2 call j_??_U@YAPEAX_K@Z ; operator new[](unsigned __int64) .text:00000001400016A2 .text:00000001400016A7 mov [rsp+68h+var_38], rax .text:00000001400016AC cmp [rsp+68h+var_38], 0 .text:00000001400016B2 jz short loc_1400016FF .text:00000001400016B2 .text:00000001400016B4 mov rax, [rsp+68h+var_38] .text:00000001400016B9 mov qword ptr [rax], 3 ;首地址处的8字节保存对象数量
调用代理构造函数(第一个对象地址,对象大小,对象个数,构造函数地址),代理构造函数去一个一个构造对象
.text:00000001400016C0 mov rax, [rsp+68h+var_38] .text:00000001400016C5 add rax, 8 ......... .text:00000001400016D5 lea r9, j_??0Person@@QEAA@XZ ; constructor .text:00000001400016DC mov r8d, 3 ; count .text:00000001400016E2 mov edx, 4 ; size .text:00000001400016E7 mov rcx, rax ; ptr .text:00000001400016EA call j_??_L@YAXPEAX_K1P6AX0@Z2@Z ; `eh vector constructor iterator'(void *,unsigned __int64,unsigned __int64,void (*)(void *),void (*)(vo
调用代理析构函数(第一个对象地址,释放对象类型标志:1为释放单个对象,3为释放对象数组,0表示单个对象仅仅执行析构函数,不释放堆空间,2调用对象数组析构函数,不释放堆空间)
delete[]参数二为3,delete为1
.text:000000014000172E mov edx, 3 ; unsigned int .text:0000000140001733 mov rcx, [rsp+68h+var_28] ; this .text:0000000140001738 call j_??_EPerson@@QEAAPEAXI@Z ; Person::`vector deleting destructor'(uint)
参数对象&&返回对象 全局对&&静态对象 Reference C++反汇编与逆向分析技术揭秘