1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 逆向看C++ new申请堆对象的构造 析构函数调用

逆向看C++ new申请堆对象的构造 析构函数调用

时间:2019-03-09 23:36:48

相关推荐

逆向看C++ new申请堆对象的构造 析构函数调用

转自:coNgY1

先来放总结,以后回看的时候方便回忆:

对new 而言,如果是空类也会分配一字节。

对 new x[],这种在地址最前面会多分配四字节的空间来保存分配的对象个数。

new x[]这种会进行调用构造代理函数进行调用构造函数。

delete 分三种情况,delete x,delete []x ,delete 多继承。分别对应的参数为1 , 3 , 0不同情况不同处理

析构代理函数调用顺序和构造代理函数相反,从堆空间最后的一个对象开始和到第一个。

Test为大小为0x28的类

实验的语句: Test * p = new Test[2]; delete []p;

首先对new操作符进行分析:

00411BB0 mov esi,dword ptr ss:[ebp+0x8] ; new的参数 --- 申请的空间大小00411BB3 ja short test.00411BE800411BB8 test esi,esi ; 检查申请空间是否为000411BBA jnz short test.00411BD3 ; 申请的对象大小不为0则jmp00411BBC inc esi ; 在C++中空类会占一个字节,这是为了让对象的实例能够相互区别。00411BBD jmp short test.00411BD300411BBF call test._query_new_modecale<__int64>teeam_error_category>::_Immortal>00411BC4 test eax,eax00411BC6 je short test.00411BE800411BC8 push esi00411BC9 call test._callnewhryEnumProcExchar,std::char_traits<char> >::xsputnut>00411BCE pop ecx00411BCF test eax,eax00411BD1 je short test.00411BE800411BD3 push esi ; 申请的空间大小00411BD4 push 0x0 ; 默认分配00411BD6 push dword ptr ds:[__acrt_heapve_startup_lockmalt,1>::id,_Mbstatet> >:>; 内存将被分配到的堆的句柄00411BDC call dword ptr ds:[<&KERNEL32.HeapAlloc>]; ntdll.RtlAllocateHeap00411BE2 test eax,eax ; eax为指向内存块的指针00411BE4 je short test.00411BBF ; 分配失败eax = 0就跳

一直跟进,到最后可以发现C++的new操作符实际上是调用HeapAlloc 这个API进行分配堆空间,并且new一个对象数组时候,会多分配4字节空间,具体干吗的这四字节后面分析。而且当一个类是空类时候(size = 0),C++一样会强行分配一个字节给它,这是为了让对象实例能实例化,相互区分。

再分析Test * p = new Test[2];

00402CB0 push 0x54 ; new Test[2],一个Test大小为0x28,两个为0x50,另外首地址4字节为保存对象的总个数00402CB2 call new[] ; call new00402CB7 add esp,400402CBA mov dword ptr ss:[ebp-0x10],eax; 堆空间首地址00402CBD mov dword ptr ss:[ebp-0x4],0x000402CC4 cmp dword ptr ss:[ebp-0x10],0x0; 检查是否申请成功00402CC8 je short test.00402CF800402CCA mov eax,dword ptr ss:[ebp-0x10]; eax = 堆空间首地址00402CCD mov dword ptr ds:[eax],0x2; 堆空间的前四字节 = 申请的对象的个数,这里为02CD3 push Test::~Test; push 析构函数00402CD8 push Test::Test ; push 构造函数00402CDD push 0x2 ; push 对象的个数00402CDF push 0x28 ; push 一个对象的大小00402CE1 mov ecx,dword ptr ss:[ebp-0x10]; ecx = 堆空间首地址00402CE4 add ecx,4 ; ecx + 400402CE7 push ecx ; push 真正对象的首地址(申请的空间地址+4)00402CE8 call test.`eh vector constructor iterator'f_iterator<char,std::char_tr>; call 构造代理函数00402CED mov edx,dword ptr ss:[ebp-0x10]; edx = 申请的堆空间首地址00402CF0 add edx,4 ; 首地址+4 = 首个对象地址00402CF3 mov dword ptr ss:[ebp-0x14],edx; [ebp-0x14]保存首个对象地址00402CF6 jmp short test.00402CFF ; 跳过申请堆空间失败处理00402CF8 mov dword ptr ss:[ebp-0x14],0x0; 申请堆空间失败,将[ebp-0x14]赋值为NULL00402CFF mov eax,dword ptr ss:[ebp-0x14]; eax = 对象首地址

可以看到首先就会调用call new,先进行堆空间分配0x54 = 4 + 2 * size(Test)

如果堆空间分配成功的话便会将多分配的前四字节赋值为对象数组的成员个数,这里是2,分配失败的话直接对指针赋值NULL

接着便会调用构造代理函数(便于对对象数组调用构造函数,而不用一个对象写一个构造函数)

构造代理函数有五个参数 (堆空间+4 = 真对象首地址 , 一个对象的大小 , 对象的个数 , 构造函数地址 ,析构函数地址)

调用成功则会将堆空间地址+4 = 真对象首地址 赋值给指针保存。

再继续分析下构造代理函数:

00409D48 push 0x1000409D4A push test.00432B2800409D4F call test.__SEH_prolog4or_codealse 't''or<char> >::deallocatetor()<<la>; SEH安装00409D54 xor ebx,ebx00409D56 mov dword ptr ss:[ebp-0x20],ebx00409D59 mov byte ptr ss:[ebp-0x19],bl00409D5C mov dword ptr ss:[ebp-0x4],ebx00409D5F cmp ebx,dword ptr ss:[ebp+0x10]; for循环,[ebp+0x10] = 循环次数00409D62 je short test.00409D7E00409D64 mov ecx,dword ptr ss:[ebp+0x14]; ecx = 构造函数地址00409D67 call test._guard_check_icallow_argv_wildcardslock<char>,std::allocator>; 没什么作用00409D6C mov ecx,dword ptr ss:[ebp+0x8] ; ecx = 申请的对象首地址00409D6F call dword ptr ss:[ebp+0x14] ; call 构造函数00409D72 mov eax,dword ptr ss:[ebp+0xC] ; eax = 一个对象的大小00409D75 add dword ptr ss:[ebp+0x8],eax ; 将申请的对象地址+一个对象的大小 --> 指向下一个对象首地址00409D78 inc ebx ; ebx = index00409D79 mov dword ptr ss:[ebp-0x20],ebx; [ebp-0x20]保存进行了几个对象的构造00409D7C jmp short test.00409D5F00409D7E mov al,0x100409D80 mov byte ptr ss:[ebp-0x19],al00409D83 mov dword ptr ss:[ebp-0x4],-0x09D8A call test.00409D9D00409D8F call test.__SEH_epilog4 initializer for 'initlocks''::allocator<char> >; 卸载SEH00409D94 retn 0x14

分析可以知道就是在一个for循环中调用构造函数,每次调用就将地址加上一个对象的大小,进行下一个对象的构造函数调用

最后分析下delete[]:

00402D24 push 0x3 ; 释放对象的参数标志,1为单个对象delete,3为对象数组delete [],0表示只执行析构函数00402D26 mov ecx,dword ptr ss:[ebp-0x18] ; ecx = 对象地址00402D29 call test.Test::`vector deleting destructor'se_types<char,std::allocat> ; 释放申请的堆空间

首先push一个参数标志,1为delete x 这种单个对象释放 , 3 为delete x[] ,这中对象数组释放 , 0 代表只是执行构造函数,不释放堆空间(在多继承时候再进行分析)。

分析这个释放空间的CALL:

00402DF0 > 55 push ebp00402DF1 8BEC mov ebp,esp00402DF3 6A FF push -0x100402DF5 68 A03E4200push test.00423EA000402DFA 64:A1 00000000 mov eax,dword ptr fs:[0]00402E00 50 push eax00402E01 64:8925 0000000>mov dword ptr fs:[0],esp ; SEH的安装00402E08 51 push ecx; ecx = 对象首地址00402E09 894D F0 mov dword ptr ss:[ebp-0x10],ecx00402E0C 8B45 08 mov eax,dword ptr ss:[ebp+0x8] ; 释放标记00402E0F 83E0 02 and eax,0x2 ; 检查是否为300402E12 74 41 je short test.00402E55 ; 不为3则跳00402E14 68 902B4000push test.Test::~Teststreambuf<char,std::c>; push 析构函数00402E19 8B4D F0 mov ecx,dword ptr ss:[ebp-0x10] ; ecx = 对象首地址00402E1C 8B51 FC mov edx,dword ptr ds:[ecx-0x4] ; edx = [ecx-4] = 对象数组成员的个数00402E1F 52 push edx; push 对象的个数00402E20 6A 28 push 0x28 ; 单个对象的大小00402E22 8B45 F0 mov eax,dword ptr ss:[ebp-0x10]00402E25 50 push eax; push 对象首地址00402E26 E8 8F6B0000call test.`eh vector destructor iterator'r>; 析构代理函数00402E2B 8B4D 08 mov ecx,dword ptr ss:[ebp+0x8] ; 释放标记00402E2E 83E1 01 and ecx,0x1 ; 是否为100402E31 74 1A je short test.00402E4D00402E33 8B55 F0 mov edx,dword ptr ss:[ebp-0x10] ; edx = 对象首地址00402E36 6B42 FC 28imul eax,dword ptr ds:[edx-0x4],0x28 ; eax = 对象个数 * 单个对象大小00402E3A 83C0 04 add eax,_Init_thread_epochointersr$tp_stat>; eax + 4 = 对象的总大小 + 保存对象个数申请的4字节00402E3D 50 push eax; push 之前堆空间申请的总大小,但是在delete中没有用到这个参数00402E3E 8B4D F0 mov ecx,dword ptr ss:[ebp-0x10]00402E41 83E9 04 sub ecx,_Init_thread_epochointersr$tp_stat>; ecx - 4 也等于堆空间首地址00402E44 51 push ecx; push 这个地址00402E45 E8 F7680000call test.operator delete[]os_error'std::n>; 内部调用HeapFree00402E4A 83C4 08 add esp,0x800402E4D 8B45 F0 mov eax,dword ptr ss:[ebp-0x10]00402E50 83E8 04 sub eax,_Init_thread_epochointersr$tp_stat>; 堆空间首地址00402E53 EB 21 jmp short test.00402E7600402E55 8B4D F0 mov ecx,dword ptr ss:[ebp-0x10] ; 单个对象的释放处理00402E58 E8 33FDFFFFcall test.Test::~Teststreambuf<char,std::c>; 直接调用析构函数00402E5D 8B55 08 mov edx,dword ptr ss:[ebp+0x8]00402E60 83E2 01 and edx,0x1 ; 判断是单个对象释放还是只调用析构函数00402E63 74 0E je short test.00402E7300402E65 6A 28 push 0x2800402E67 8B45 F0 mov eax,dword ptr ss:[ebp-0x10]00402E6A 50 push eax00402E6B E8 9E6E0000call test.operator delete~_Locinfo:char_tr>; 释放单个对象00402E70 83C4 08 add esp,0x800402E73 8B45 F0 mov eax,dword ptr ss:[ebp-0x10]00402E76 8B4D F4 mov ecx,dword ptr ss:[ebp-0xC]00402E79 64:890D 0000000>mov dword ptr fs:[0],ecx ; SEH卸载00402E80 8BE5 mov esp,ebp00402E82 5D pop ebp; test.__argvoney_get<wchar_t,std::istreambuf_iterator<wchar_t,std::char_traits<wchar_t> > >::idsigned short> > >::id00402E83 C2 0400 retn 4 ; ret 4

这个CALL首先对push的参数标记进行比较,如果是3的话就调用00402E26处的构造代理函数进行调用析构函数,然后再进行调用HeapFree进行堆空间的释放,如果是1的话,直接调用析构函数并且释放空间,如果是0的话,就只进行调用析构函数,不释放堆空间。

再分析下析构代理函数

004099BA > 6A 0C push 0xC004099BC 68 C82A4300push test.00432AC8004099C1 E8 CA090000call test.__SEH_prolog4or_codealse 't''or<>; SEH安装004099C6 C645 E7 00mov byte ptr ss:[ebp-0x19],0x0004099CA 8B5D 0C mov ebx,dword ptr ss:[ebp+0xC] ; ebx = 单个对象大小004099CD 8BC3 mov eax,ebx ; eax = 单个对象大小004099CF 8B7D 10 mov edi,dword ptr ss:[ebp+0x10] ; edi = 对象个数004099D2 0FAFC7imul eax,edi ; eax = 个数*大小 = 总大小004099D5 8B75 08 mov esi,dword ptr ss:[ebp+0x8] ; 对象首地址004099D8 03F0 add esi,eax ; esi = 对象尾部004099DA 8975 08 mov dword ptr ss:[ebp+0x8],esi004099DD 8365 FC 00and dword ptr ss:[ebp-0x4],0x0 ; and -2,0 ???004099E1 8BC7 mov eax,edi ; eax = 对象个数004099E3 4F dec edi; 对象个数-1004099E4 897D 10 mov dword ptr ss:[ebp+0x10],edi ; [ebp+0x10]保存还没释放的对象个数004099E7 85C0 test eax,eax ; 检查对象是否已经释放完毕004099E9 74 14 je short test.004099FF004099EB 2BF3 sub esi,ebx ; 对象从后往前释放004099ED 8975 08 mov dword ptr ss:[ebp+0x8],esi004099F0 8B4D 14 mov ecx,dword ptr ss:[ebp+0x14] ; ecx = 析构函数地址004099F3 E8 01060000call test._guard_check_icallow_argv_wildca>; no use004099F8 8BCE mov ecx,esi ; ecx = 要释放的对象地址004099FA FF55 14 call dword ptr ss:[ebp+0x14]; call 析构函数004099FD ^ EB E2 jmp short test.004099E1004099FF B0 01 mov al,0x100409A01 8845 E7 mov byte ptr ss:[ebp-0x19],al00409A04 C745 FC FEFFFFF>mov dword ptr ss:[ebp-0x4],-0x09A0B E8 14000000call test.00409A2400409A10 E8 C1090000call test.__SEH_epilog4 initializer for 'i>; SEH卸载00409A15 C2 1000 retn 0x10

可以发现,这儿析构代理函数和构造代理函数不同,析构代理函数是从最后一个堆空间的对象开始调用,而构造函数是从第一个堆空间的对象进行调用。其余的都是相同,就是一个for循环知道所有的对象都调用完析构函数。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。