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

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

对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.00411BE8
00411BB8    test esi,esi                                                           ; 检查申请空间是否为0
00411BBA    jnz short test.00411BD3                                                ; 申请的对象大小不为0则jmp
00411BBC    inc esi                                                                ; 在C++中空类会占一个字节,这是为了让对象的实例能够相互区别。
00411BBD    jmp short test.00411BD3
00411BBF    call test._query_new_modecale<__int64>teeam_error_category>::_Immortal>
00411BC4    test eax,eax
00411BC6    je short test.00411BE8
00411BC8    push esi
00411BC9    call test._callnewhryEnumProcExchar,std::char_traits<char> >::xsputnut>
00411BCE    pop ecx
00411BCF    test eax,eax
00411BD1    je short test.00411BE8
00411BD3    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.RtlAllocateHeap
00411BE2    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 new
00402CB7    add esp,4
00402CBA    mov dword ptr ss:[ebp-0x10],eax                                        ; 堆空间首地址
00402CBD    mov dword ptr ss:[ebp-0x4],0x0
00402CC4    cmp dword ptr ss:[ebp-0x10],0x0                                        ; 检查是否申请成功
00402CC8    je short test.00402CF8
00402CCA    mov eax,dword ptr ss:[ebp-0x10]                                        ; eax = 堆空间首地址
00402CCD    mov dword ptr ds:[eax],0x2                                             ; 堆空间的前四字节 = 申请的对象的个数,这里为2
00402CD3    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 + 4
00402CE7    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]赋值为NULL
00402CFF    mov eax,dword ptr ss:[ebp-0x14]                                        ; eax = 对象首地址

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

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

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

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

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


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

00409D48    push 0x10
00409D4A    push test.00432B28
00409D4F    call test.__SEH_prolog4or_codealse 't''or<char> >::deallocatetor()<<la>; SEH安装
00409D54    xor ebx,ebx
00409D56    mov dword ptr ss:[ebp-0x20],ebx
00409D59    mov byte ptr ss:[ebp-0x19],bl
00409D5C    mov dword ptr ss:[ebp-0x4],ebx
00409D5F    cmp ebx,dword ptr ss:[ebp+0x10]                                        ; for循环,[ebp+0x10] = 循环次数
00409D62    je short test.00409D7E
00409D64    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 = index
00409D79    mov dword ptr ss:[ebp-0x20],ebx                                        ; [ebp-0x20]保存进行了几个对象的构造
00409D7C    jmp short test.00409D5F
00409D7E    mov al,0x1
00409D80    mov byte ptr ss:[ebp-0x19],al
00409D83    mov dword ptr ss:[ebp-0x4],-0x2
00409D8A    call test.00409D9D
00409D8F    call test.__SEH_epilog4 initializer for 'initlocks''::allocator<char> >; 卸载SEH
00409D94    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 ebp
00402DF1    8BEC            mov ebp,esp
00402DF3    6A FF           push -0x1
00402DF5    68 A03E4200     push test.00423EA0
00402DFA    64:A1 00000000  mov eax,dword ptr fs:[0]
00402E00    50              push eax
00402E01    64:8925 0000000>mov dword ptr fs:[0],esp                   ; SEH的安装
00402E08    51              push ecx                                   ; ecx = 对象首地址
00402E09    894D F0         mov dword ptr ss:[ebp-0x10],ecx
00402E0C    8B45 08         mov eax,dword ptr ss:[ebp+0x8]             ; 释放标记
00402E0F    83E0 02         and eax,0x2                                ; 检查是否为3
00402E12    74 41           je short test.00402E55                     ; 不为3则跳
00402E14    68 902B4000     push 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 8F6B0000     call test.`eh vector destructor iterator'r>; 析构代理函数
00402E2B    8B4D 08         mov ecx,dword ptr ss:[ebp+0x8]             ; 释放标记
00402E2E    83E1 01         and ecx,0x1                                ; 是否为1
00402E31    74 1A           je short test.00402E4D
00402E33    8B55 F0         mov edx,dword ptr ss:[ebp-0x10]            ; edx = 对象首地址
00402E36    6B42 FC 28      imul 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 F7680000     call test.operator delete[]os_error'std::n>; 内部调用HeapFree
00402E4A    83C4 08         add esp,0x8
00402E4D    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.00402E76
00402E55    8B4D F0         mov ecx,dword ptr ss:[ebp-0x10]            ; 单个对象的释放处理
00402E58    E8 33FDFFFF     call 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.00402E73
00402E65    6A 28           push 0x28
00402E67    8B45 F0         mov eax,dword ptr ss:[ebp-0x10]
00402E6A    50              push eax
00402E6B    E8 9E6E0000     call test.operator delete~_Locinfo:char_tr>; 释放单个对象
00402E70    83C4 08         add esp,0x8
00402E73    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,ebp
00402E82    5D              pop ebp                                    ; test.__argvoney_get<wchar_t,std::istreambuf_iterator<wchar_t,std::char_traits<wchar_t> > >::idsigned short> > >::id
00402E83    C2 0400         retn 4				       ; ret 4

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


再分析下析构代理函数

004099BA >  6A 0C           push 0xC
004099BC    68 C82A4300     push test.00432AC8
004099C1    E8 CA090000     call test.__SEH_prolog4or_codealse 't''or<>; SEH安装
004099C6    C645 E7 00      mov byte ptr ss:[ebp-0x19],0x0
004099CA    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    0FAFC7          imul 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],esi
004099DD    8365 FC 00      and dword ptr ss:[ebp-0x4],0x0             ; and -2,0 ???
004099E1    8BC7            mov eax,edi                                ; eax = 对象个数
004099E3    4F              dec edi                                    ; 对象个数-1
004099E4    897D 10         mov dword ptr ss:[ebp+0x10],edi            ; [ebp+0x10]保存还没释放的对象个数
004099E7    85C0            test eax,eax                               ; 检查对象是否已经释放完毕
004099E9    74 14           je short test.004099FF
004099EB    2BF3            sub esi,ebx                                ; 对象从后往前释放
004099ED    8975 08         mov dword ptr ss:[ebp+0x8],esi
004099F0    8B4D 14         mov ecx,dword ptr ss:[ebp+0x14]            ; ecx = 析构函数地址
004099F3    E8 01060000     call test._guard_check_icallow_argv_wildca>; no use
004099F8    8BCE            mov ecx,esi                                ; ecx = 要释放的对象地址
004099FA    FF55 14         call dword ptr ss:[ebp+0x14]               ; call 析构函数
004099FD  ^ EB E2           jmp short test.004099E1
004099FF    B0 01           mov al,0x1
00409A01    8845 E7         mov byte ptr ss:[ebp-0x19],al
00409A04    C745 FC FEFFFFF>mov dword ptr ss:[ebp-0x4],-0x2
00409A0B    E8 14000000     call test.00409A24
00409A10    E8 C1090000     call test.__SEH_epilog4 initializer for 'i>; SEH卸载
00409A15    C2 1000         retn 0x10
可以发现,这儿析构代理函数和构造代理函数不同, 析构代理函数是从最后一个堆空间的对象开始调用,而构造函数是从第一个堆空间的对象进行调用。其余的都是相同,就是一个for循环知道所有的对象都调用完析构函数。


代码验证一下,完全O98K


最后又测试了

这样一段代码,分析发现,new操作时候直接将tt的地址传递给了构造代理函数,从而使tt的第一位成为了1-->表示多少个对象

打印出来结果为1 1  , 1 2 ,说明tt的两位数据被移动到了后四字节,第一位数据添加上了对象个数

猜你喜欢

转载自blog.csdn.net/a893574301/article/details/80372465