layout: post
title: 重識(shí)new
categories: C/C++
description: 重識(shí)new
keywords:
url: https://lichao890427.github.io/ https://github.com/lichao890427/
一喇颁、new primer
??new操作符并不是c++的專屬杈抢,c運(yùn)行庫(kù)有new.h頭文件。C++中的new操作符捅位,眾所周知是用來(lái)分配動(dòng)態(tài)內(nèi)存的,而要能達(dá)到“動(dòng)態(tài)”這種靈活性的特征羞芍,非堆區(qū)莫屬分井,因?yàn)槎褏^(qū)支持手動(dòng)分配。如果內(nèi)存空間不夠洪唐,new操作符返回空,或拋出異常(下面會(huì)講述3種new操作符吼蚁,常規(guī)new操作符凭需、定位new操作符和禁止拋出異常的new操作符,如果禁止拋異常肝匆,那么只好返回空了)下面代碼為MSDN中的粒蜈,檢測(cè)new分配是否成功,內(nèi)存分配失敗處理還可以通過(guò)_set_new_handler注冊(cè)分配異常函數(shù)結(jié)果
// insufficient_memory_conditions.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
#define BIG_NUMBER 100000000
int main() {
int *pI = new int[BIG_NUMBER];
if( pI == 0x0 ) {
cout << "Insufficient memory" << endl;
return -1;
}
}
??對(duì)于對(duì)象使用new操作符的情況旗国,生成的代碼執(zhí)行的流程一般為:先調(diào)用內(nèi)部合適的new函數(shù)枯怖,該函數(shù)最終調(diào)用內(nèi)存分配API進(jìn)行堆內(nèi)存分配,同時(shí)初始化一些方便內(nèi)存管理的結(jié)構(gòu)體和數(shù)據(jù)能曾。成功分配后度硝,將該地址視為this,如有必要?jiǎng)t設(shè)置虛表指針寿冕,之后調(diào)用構(gòu)造函數(shù)對(duì)該地址處對(duì)象進(jìn)行構(gòu)造蕊程。構(gòu)造函數(shù)一般也是執(zhí)行初始化功能而已,修改修改數(shù)據(jù)啥的驼唱。這些都是老生常談不再贅述藻茂。
??即使請(qǐng)求的空間是0字節(jié)大小,new也會(huì)返回不同的地址玫恳,也就說(shuō)總是在不同區(qū)域分配辨赐。這里注意和空類的區(qū)別,空類的大小是1京办,傳給new函數(shù)(該函數(shù)在之后給出)之后掀序,new函數(shù)接受到的參數(shù)只會(huì)>=1,而對(duì)于特殊情況:char* pch=new char[0];new函數(shù)接受的參數(shù)確實(shí)是0臂港,不過(guò)分配的空間并不是0字節(jié),因?yàn)榇嬖趦?nèi)存管理相關(guān)的結(jié)構(gòu)體也會(huì)占用內(nèi)存。
??new操作符有2種作用域审孽,一種是全局new县袱,一種是類作用域new。用戶在自定義類中可以重載自定義new函數(shù)佑力。代碼如下:
#include <malloc.h>
#include <memory.h>
class Blanks
{
public:
Blanks(){}
void *operator new( size_t stAllocateBlock, char chInit );
};
void *Blanks::operator new( size_t stAllocateBlock, char chInit )
{
void *pvTemp = malloc( stAllocateBlock );
if( pvTemp != 0 )
memset( pvTemp, chInit, stAllocateBlock );
return pvTemp;
}
// 對(duì)于Blanks對(duì)象式散,全局new操作符已被替換,因此下面的代碼將分配sizeof(Blanks)大小的空間并把數(shù)據(jù)賦值為0xa5
int main()
{
Blanks *a5 = new(0xa5) Blanks;
return a5 != 0;
}
二打颤、new primer plus
??new和delete操作符是通過(guò)一種稱為自由存儲(chǔ)區(qū)的內(nèi)存池分配內(nèi)存的暴拄,而new和delete操作符本身在編譯時(shí)會(huì)由編譯器選擇合適的new函數(shù)和delete函數(shù)進(jìn)行實(shí)現(xiàn)。C運(yùn)行庫(kù)的new函數(shù)會(huì)在失敗時(shí)拋出std::bad_alloc異常编饺,如果要使用不拋出異常的new版本乖篷,則需要鏈接nothrownew.obj,然而一旦鏈接了該文件透且,標(biāo)準(zhǔn)C++庫(kù)中的new就不起作用了撕蔼。new有2種語(yǔ)法形式
- [::] new [placement] new-type-name [new-initializer]
- [::] new [placement] ( type-name ) [new-initializer]
2種new函數(shù)原型為:
void* operator new( std::size_t _Count ) throw(bad_alloc);
void* operator new( std::size_t _Count, const std::nothrow_t& ) throw( );
void* operator new( std::size_t _Count, void* _Ptr ) throw( );
void *operator new[]( std::size_t _Count ) throw(std::bad_alloc);
void *operator new[]( std::size_t _Count, const std::nothrow_t& ) throw( );
void *operator new[]( std::size_t _Count, void* _Ptr ) throw( );
其用法如下:
#include<new>
#include<iostream>
using namespace std;
class MyClass
{
public:
MyClass( )
{
cout << "Construction MyClass." << this << endl;
};
~MyClass( )
{
imember = 0; cout << "Destructing MyClass." << this << endl;
};
int imember;
};
int main( )
{
// The first form of new delete
MyClass* fPtr = new MyClass;
delete fPtr;
// The second form of new delete
MyClass* fPtr2 = new( nothrow ) MyClass;
delete fPtr2;
// The third form of new delete
char x[sizeof( MyClass )];
MyClass* fPtr3 = new( &x[0] ) MyClass;
fPtr3 -> ~MyClass();
cout << "The address of x[0] is : " << ( void* )&x[0] << endl;
}
Construction MyClass.000B3F30
Destructing MyClass.000B3F30
Construction MyClass.000B3F30
Destructing MyClass.000B3F30
Construction MyClass.0023FC60
Destructing MyClass.0023FC60
The address of x[0] is : 0023FC60
#include <new>
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass() {
cout << "Construction MyClass." << this << endl;
};
~MyClass() {
imember = 0; cout << "Destructing MyClass." << this << endl;
};
int imember;
};
int main() {
// The first form of new delete
MyClass* fPtr = new MyClass[2];
delete[ ] fPtr;
// The second form of new delete
char x[2 * sizeof( MyClass ) + sizeof(int)];
MyClass* fPtr2 = new( &x[0] ) MyClass[2];
fPtr2[1].~MyClass();
fPtr2[0].~MyClass();
cout << "The address of x[0] is : " << ( void* )&x[0] << endl;
// The third form of new delete
MyClass* fPtr3 = new( nothrow ) MyClass[2];
delete[ ] fPtr3;
}
Construction MyClass.00311AEC
Construction MyClass.00311AF0
Destructing MyClass.00311AF0
Destructing MyClass.00311AEC
Construction MyClass.0012FED4
Construction MyClass.0012FED8
Destructing MyClass.0012FED8
Destructing MyClass.0012FED4
The address of x[0] is : 0012FED0
Construction MyClass.00311AEC
Construction MyClass.00311AF0
Destructing MyClass.00311AF0
Destructing MyClass.00311AEC
探究第一種new形式實(shí)現(xiàn)
??第一種形式為常規(guī)new,MyClass* fPtr1 = new MyClass;
// new.cpp
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
_THROW_NCEE(_XSTD bad_alloc, );
}
return (p);
}
??從上面new函數(shù)實(shí)現(xiàn)可以看到是使用malloc函數(shù)進(jìn)行分配秽誊,如果失敗則調(diào)用_callnewh調(diào)用注冊(cè)過(guò)的“new操作符失敗回調(diào)”函數(shù)(注冊(cè)用_set_new_handler)鲸沮,如果原先沒(méi)注冊(cè)new失敗回調(diào),則拋出bad_alloc異常锅论,可見(jiàn)在默認(rèn)情況下讼溺,該while只會(huì)執(zhí)行1次,僅當(dāng)自定義new失敗回調(diào)函數(shù)返回true最易,才可能多次嘗試分配怒坯。
??該種形式delete實(shí)現(xiàn)方式,單步以后vs不能定位到源碼耘纱,不過(guò)我們可以換一種思路敬肚,既然知道一定執(zhí)行析構(gòu)函數(shù),那么就在析構(gòu)中下斷點(diǎn)束析,斷下后查看反匯編艳馒,并執(zhí)行到上一級(jí)調(diào)用即可找到delete實(shí)現(xiàn)方法,因此看匯編實(shí)現(xiàn)员寇,發(fā)現(xiàn)是一個(gè)名為“scalar deleting destructor”的內(nèi)部函數(shù):
//
00EB3470 push ebp
00EB3471 mov ebp,esp
00EB3473 sub esp,0CCh
00EB3479 push ebx
00EB347A push esi
00EB347B push edi
00EB347C push ecx
00EB347D lea edi,[ebp-0CCh]
00EB3483 mov ecx,33h
00EB3488 mov eax,0CCCCCCCCh
00EB348D rep stos dword ptr es:[edi]
00EB348F pop ecx //以上部分為debug版API常見(jiàn)頭弄慰,無(wú)需理會(huì)
00EB3490 mov dword ptr [this],ecx
00EB3493 mov ecx,dword ptr [this]
00EB3496 call MyClass::~MyClass (0EB1023h) //執(zhí)行析構(gòu)
00EB349B mov eax,dword ptr [ebp+8]
00EB349E and eax,1
00EB34A1 je MyClass::`scalar deleting destructor'+3Fh (0EB34AFh) //如果傳入?yún)?shù)允許釋放則進(jìn)行調(diào)用對(duì)應(yīng)delete函數(shù)(對(duì)于定位new對(duì)應(yīng)的delete該參數(shù)是設(shè)置為不允許的)
00EB34A3 mov eax,dword ptr [this]
00EB34A6 push eax
00EB34A7 call operator delete (0EB1154h)
00EB34AC add esp,4
00EB34AF mov eax,dword ptr [this] //以下是無(wú)關(guān)的收尾工作
00EB34B2 pop edi
00EB34B3 pop esi
00EB34B4 pop ebx
00EB34B5 add esp,0CCh
00EB34BB cmp ebp,esp
00EB34BD call __RTC_CheckEsp (0EB1352h)
00EB34C2 mov esp,ebp
00EB34C4 pop ebp
00EB34C5 ret 4
??執(zhí)行到call operator delete這行,步入之后轉(zhuǎn)到源碼蝶锋,可以看到使用的是dbgdel.cpp的delete函數(shù)陆爽。實(shí)現(xiàn)如下
// dbgdel.cpp
void operator delete( void *pUserData )
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* 阻塞其他線程*/
__TRY
/* 得到用于內(nèi)存塊信息頭指針*/
pHead = pHdr(pUserData);
/* 檢查區(qū)塊類型 */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );//調(diào)用free函數(shù)釋放內(nèi)存
__FINALLY
_munlock(_HEAP_LOCK); /* 解鎖其他線程*/
__END_TRY_FINALLY
return;
}
探究第二種new形式實(shí)現(xiàn)
??第二種方式為不拋出異常的new,MyClass* fPtr2 = new( nothrow ) MyClass;
扳缕,可見(jiàn)慌闭,這里用try塊捕獲了異常别威,因此不再拋出異常,余下的就是調(diào)用常規(guī)new函數(shù)而已驴剔。
// newopnt.cpp
void * __CRTDECL operator new(size_t count, const std::nothrow_t&) _THROW0()
{ // try to allocate count bytes
void *p;
_TRY_BEGIN
p = operator new(count);
_CATCH_ALL
p = 0;
_CATCH_END
return (p);
}
#define _TRY_BEGIN try {
#define _CATCH(x) } catch (x) {
#define _CATCH_ALL } catch (...) {
#define _CATCH_END }
探究第三種new形式實(shí)現(xiàn)
??第三種方式為布局new:
char x1[sizeof( MyClass )];MyClass* fPtr3 = new( &x1[0] ) MyClass;
??這里所謂的布局就是說(shuō)告訴new我們已經(jīng)有一個(gè)內(nèi)存位置了:
// new
inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0()
{ // construct array with placement at _Where
return (_Where);
}
??可以發(fā)現(xiàn)該new什么都沒(méi)做省古,那么為什么還要new呢?仔細(xì)想想可以知道丧失,編譯器對(duì)new的處理是調(diào)用new函數(shù)后豺妓,之后將該地址作為this指針進(jìn)行初始化操作(比如設(shè)置虛表),再調(diào)用構(gòu)造函數(shù)布讹,而構(gòu)造函數(shù)這玩意不能直接調(diào)用琳拭,不像析構(gòu)函數(shù)那樣,因?yàn)闃?gòu)造之前還沒(méi)有對(duì)象和指針呢描验,對(duì)象和指針是構(gòu)造以后才有的白嘁,而調(diào)用析構(gòu)函數(shù)的時(shí)候,是已經(jīng)有對(duì)象或指針的挠乳。所以這種定位new在我理解权薯,就是可以相當(dāng)于可以直接構(gòu)造了。
??從上面可以看到new操作符先調(diào)用合適的new函數(shù)分配空間睡扬,之后調(diào)用構(gòu)造函數(shù)構(gòu)造盟蚣,而delete函數(shù)剛好相對(duì),先進(jìn)行析構(gòu)之后調(diào)用析構(gòu)函數(shù)析構(gòu)卖怜;同時(shí)可以看到布局new操作符的好處是可以手動(dòng)指定構(gòu)造和析構(gòu)的時(shí)間屎开,對(duì)于new無(wú)論哪種形式,在調(diào)用new函數(shù)分配好內(nèi)存后都會(huì)調(diào)用構(gòu)造函數(shù)進(jìn)行構(gòu)造马靠,而定位new函數(shù)實(shí)則是直接返回奄抽,這就導(dǎo)致直接使用當(dāng)前地址進(jìn)行構(gòu)造,相當(dāng)于顯示調(diào)用構(gòu)造函數(shù),而析構(gòu)時(shí)由于沒(méi)有實(shí)際分配空間甩鳄,因此不能用delete逞度,而是顯示調(diào)用析構(gòu)函數(shù)進(jìn)行析構(gòu)。
??上面都是對(duì)于有構(gòu)造函數(shù)和析構(gòu)函數(shù)對(duì)象的情況妙啃,用delete時(shí)档泽,編譯器會(huì)為該類專門生成一個(gè)scalar deleting destructor函數(shù),該函數(shù)中先進(jìn)行析構(gòu)揖赴,之后調(diào)用operator delete函數(shù)代赁。當(dāng)然职恳,如果沒(méi)有析構(gòu)函數(shù),那么就不會(huì)有scalar deleting destructor函數(shù)了危虱,此時(shí)單步是可以看到delete源碼的鸳吸,即dbgdel.cpp中的void operator delete(void *pUserData)函數(shù)林束。這一點(diǎn)在delete用于基本類型時(shí)顯而易見(jiàn)。
探究第一種new[]形式實(shí)現(xiàn)
??第一種類型new,MyClass* fPtr4 = new MyClass[2]
// newaop.cpp
void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
{ // try to allocate count bytes for an array
return (operator new(count));
}
??而編譯器傳給該new[]函數(shù)的參數(shù)count是sizeof(MyClass[2])+sizeof(int)恃锉,該sizeof(int)用于內(nèi)存管理。new仍然調(diào)用了new();沒(méi)有本質(zhì)區(qū)別呕臂,即這么多對(duì)象占用的內(nèi)存是當(dāng)作整體分配的淡喜。再分配好之后,就需要對(duì)每個(gè)對(duì)象this指針處進(jìn)行初始化和構(gòu)造了诵闭。
??通過(guò)逆向分析可知先調(diào)用了new[],如果成功分配內(nèi)存澎嚣,則調(diào)用數(shù)組構(gòu)造迭代器vector_constructor_iterator對(duì)每個(gè)對(duì)象進(jìn)行構(gòu)造疏尿。void* base=new[](sizeof(int)+sizeof(MyClass[2]));
,起始4字節(jié)存儲(chǔ)要初始化的對(duì)象個(gè)數(shù)易桃,剩余空間為對(duì)象占用內(nèi)存
push 0Ch ; count
call j_??_U@YAPAXI@Z ; operator new[](uint)
add esp, 4
mov [ebp+var_1A0], eax
mov [ebp+var_4], 3
cmp [ebp+var_1A0], 0
jz short loc_41851B
mov eax, [ebp+var_1A0]
mov dword ptr [eax], 2
push offset j_??1MyClass@@QAE@XZ ; pDtor
push offset j_??0MyClass@@QAE@XZ ; pCtor
push 2 ; count
push 4 ; size
mov ecx, [ebp+var_1A0]
add ecx, 4
push ecx ; ptr
call j_??_L@YGXPAXIHP6EX0@Z1@Z ; `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))
; ---------------------------------------------------------------------------
mov edx, [ebp+var_1A0]
add edx, 4
mov [ebp+var_22C], edx
jmp short loc_418525
; ---------------------------------------------------------------------------
loc_41851B: ; CODE XREF: _main+1FF j
mov [ebp+var_22C], 0
loc_418525: ; CODE XREF: _main+239 j
mov eax, [ebp+var_22C]
mov [ebp+var_1AC], eax
mov [ebp+var_4], 0FFFFFFFFh
mov ecx, [ebp+var_1AC]
mov [ebp+fPtr4], ecx
if(base)
{
*(int*)base=2;//2個(gè)對(duì)象
vector_construtor_iterator((MyClass*)((char*)base+4),sizeof(MyClass[2]),2,&MyClass::MyClass,&MyClass::~MyClass);
}
vector_constructor_iterator對(duì)應(yīng)代碼為:
; void __stdcall `eh vector constructor iterator'(void *ptr, unsigned int size, int count, void (__thiscall *pCtor)(void *), void (__thiscall *pDtor)(void *))
??_L@YGXPAXIHP6EX0@Z1@Z proc near ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *)) j
success = dword ptr -20h
i = dword ptr -1Ch
ms_exc = CPPEH_RECORD ptr -18h
ptr = dword ptr 8
size = dword ptr 0Ch
count = dword ptr 10h
pCtor = dword ptr 14h
pDtor = dword ptr 18h
push ebp
mov ebp, esp
push 0FFFFFFFEh
push offset stru_41F9A0
push offset j___except_handler4
mov eax, large fs:0
push eax
add esp, 0FFFFFFF0h
push ebx
push esi
push edi
mov eax, ___security_cookie
xor [ebp+ms_exc.registration.ScopeTable], eax
xor eax, ebp
push eax
lea eax, [ebp+ms_exc.registration]
mov large fs:0, eax
mov [ebp+success], 0
mov [ebp+ms_exc.registration.TryLevel], 0
mov [ebp+i], 0
jmp short loc_415730
; ---------------------------------------------------------------------------
loc_415727: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+67 j
mov eax, [ebp+i]
add eax, 1
mov [ebp+i], eax
loc_415730: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+45 j
mov ecx, [ebp+i]
cmp ecx, [ebp+count]
jge short loc_415749
mov ecx, [ebp+ptr]
call [ebp+pCtor]
mov edx, [ebp+ptr]
add edx, [ebp+size]
mov [ebp+ptr], edx
jmp short loc_415727
; ---------------------------------------------------------------------------
loc_415749: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+56 j
mov [ebp+success], 1
mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
call $LN9 ; Finally handler 0 for function 4156E0
; ---------------------------------------------------------------------------
loc_41575C: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *)):$LN10 j
jmp short $LN12
; ---------------------------------------------------------------------------
$LN9: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+77 j
; DATA XREF: .rdata:stru_41F9A0 o
cmp [ebp+success], 0 ; Finally handler 0 for function 4156E0
jnz short $LN10
mov eax, [ebp+pDtor]
push eax ; pDtor
mov ecx, [ebp+i]
push ecx ; count
mov edx, [ebp+size]
push edx ; size
mov eax, [ebp+ptr]
push eax ; ptr
call j_?__ArrayUnwind@@YGXPAXIHP6EX0@Z@Z ; __ArrayUnwind(void *,uint,int,void (*)(void *))
$LN10: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *))+82 j
retn
; ---------------------------------------------------------------------------
$LN12: ; CODE XREF: `eh vector constructor iterator'(void *,uint,int,void (*)(void *),void (*)(void *)):loc_41575C j
mov ecx, [ebp+ms_exc.registration.Next]
mov large fs:0, ecx
pop ecx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
retn 14h
??_L@YGXPAXIHP6EX0@Z1@Z endp
逆向分析得到C++語(yǔ)法:
void __stdcall vector_constructor_iterator(MyClass *objs, unsigned int size, int count, void (__thiscall *pCtor)(void *), void (__thiscall *pDtor)(void *))
{
int i=0;
__try
{
for(;i<count;i++,objs++)
{
objs->pCtor();//用構(gòu)造函數(shù)構(gòu)造
}
}
__except(1)
{
__ArrayUnwind(objs,size,i,pDtor);//如果某個(gè)構(gòu)造函數(shù)產(chǎn)生異常褥琐,則進(jìn)行棧解退,用到析構(gòu)函數(shù)
}
}
??棧解退晤郑,這里引用C++ Primer Plus的解釋:“現(xiàn)在假設(shè)函數(shù)由于出現(xiàn)異常而終止(而不是由于返回)敌呈,則程序也將釋放棧中的內(nèi)存,但不會(huì)在釋放棧的第一個(gè)返回地址后停止造寝,而是繼續(xù)釋放棧磕洪,直到找到一個(gè)位于try塊中的返回地址,隨后控制權(quán)將轉(zhuǎn)到塊尾的異常處理程序诫龙,而不會(huì)函數(shù)調(diào)用后面的第一條語(yǔ)句析显。這個(gè)過(guò)程稱為棧解退,引發(fā)機(jī)制的一個(gè)非常重要的特性是签赃,和函數(shù)返回一樣谷异,對(duì)于棧中的自動(dòng)類對(duì)象,而throw語(yǔ)句則處理try塊和throw之間的整個(gè)函數(shù)調(diào)用徐麗放在棧中的對(duì)象锦聊。如果沒(méi)有棧解退這種特性歹嘹,則引發(fā)異常后,對(duì)于中間函數(shù)調(diào)用放在棧中的自動(dòng)類對(duì)象孔庭,其析構(gòu)函數(shù)將不會(huì)被調(diào)用尺上。”史飞。unwind就是解退的意思尖昏,現(xiàn)在來(lái)查看__ArrayUnwind的源碼,根據(jù)函數(shù)名可知該函數(shù)用于對(duì)象數(shù)組解退:
push ebp
mov ebp, esp
push 0FFFFFFFEh
push offset stru_41F9E0
push offset j___except_handler4
mov eax, large fs:0
push eax
sub esp, 8
push ebx
push esi
push edi
mov eax, ___security_cookie
xor [ebp+ms_exc.registration.ScopeTable], eax
xor eax, ebp
push eax
lea eax, [ebp+ms_exc.registration]
mov large fs:0, eax
mov [ebp+ms_exc.old_esp], esp
mov [ebp+ms_exc.registration.TryLevel], 0
loc_41591A: ; CODE XREF: __ArrayUnwind(void *,uint,int,void (*)(void *))+54 j
mov eax, [ebp+count]
sub eax, 1
mov [ebp+count], eax
js short loc_415936
mov ecx, [ebp+ptr]
sub ecx, [ebp+size]
mov [ebp+ptr], ecx
mov ecx, [ebp+ptr]
call [ebp+pDtor]
jmp short loc_41591A
; ---------------------------------------------------------------------------
loc_415936: ; CODE XREF: __ArrayUnwind(void *,uint,int,void (*)(void *))+43 j
mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
jmp short loc_415956
; ---------------------------------------------------------------------------
$LN7: ; DATA XREF: .rdata:stru_41F9E0 o
mov edx, [ebp+ms_exc.exc_ptr] ; Exception filter 0 for function 4158E0
push edx ; pExPtrs
call ArrayUnwindFilter
add esp, 4
$LN9_1:
retn
; ---------------------------------------------------------------------------
$LN8_0: ; DATA XREF: .rdata:stru_41F9E0 o
mov esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 4158E0
mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
loc_415956: ; CODE XREF: __ArrayUnwind(void *,uint,int,void (*)(void *))+5D j
mov ecx, [ebp+ms_exc.registration.Next]
mov large fs:0, ecx
pop ecx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
retn 10h
逆向分析得到C++代碼:
void __stdcall __ArrayUnwind(MyClass* objs,unsigned size,int count,void (__thiscall *pDtor)(void*))
{//第[count]對(duì)象由于沒(méi)有構(gòu)造成功构资,因此從[count-1]個(gè)對(duì)象開(kāi)始析構(gòu)
__try
{
while(count--)
{
objs--;
objs->pDtor();
}
}
__except(terminate(),0)//如果析構(gòu)發(fā)生異常抽诉,則終止程序
{
return;
}
}
探究第一種形式delete[]形式實(shí)現(xiàn)
mov eax, [ebp+fPtr4]
mov [ebp+var_188], eax
mov ecx, [ebp+var_188]
mov [ebp+var_194], ecx
cmp [ebp+var_194], 0
jz short loc_418574//如果之前new成功,則往下執(zhí)行
push 3 ; unsigned int
mov ecx, [ebp+var_194] ; this
call j_??_EMyClass@@QAEPAXI@Z ; MyClass::`vector deleting destructor'(uint)
mov [ebp+var_22C], eax
jmp short loc_41857E
; ---------------------------------------------------------------------------
loc_418574: ; CODE XREF: _main+27D j
mov [ebp+var_22C], 0
??可見(jiàn)vector_deleting_destructor是用來(lái)析構(gòu)對(duì)象數(shù)組的吐绵,原型為void* __thiscall MyClass::vector_deleting_destructor(usigned int flag);
迹淌,該函數(shù)是編譯器內(nèi)部為MyClass類加的成員函數(shù)
flag含義未知河绽,所以需要分析該函數(shù)源碼:
push ebp
mov ebp, esp
sub esp, 0CCh
push ebx
push esi
push edi
push ecx
lea edi, [ebp+var_CC]
mov ecx, 33h
mov eax, 0CCCCCCCCh
rep stosd
pop ecx
mov [ebp+this], ecx
mov eax, [ebp+arg_0]
and eax, 2
jz short loc_413431
push offset j_??1MyClass@@QAE@XZ ; pDtor
mov eax, [ebp+this]
mov ecx, [eax-4]
push ecx ; count
push 4 ; size
mov edx, [ebp+this]
push edx ; ptr
call j_??_M@YGXPAXIHP6EX0@Z@Z ; `eh vector destructor iterator'(void *,uint,int,void (*)(void *))
; ---------------------------------------------------------------------------
mov eax, [ebp+arg_0]
and eax, 1
jz short loc_413429
mov eax, [ebp+this]
sub eax, 4
push eax ; void *
call j_??_V@YAXPAX@Z_0 ; operator delete[](void *)
add esp, 4
loc_413429: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+48 j
mov eax, [ebp+this]
sub eax, 4
jmp short loc_413450
; ---------------------------------------------------------------------------
loc_413431: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+29 j
mov ecx, [ebp+this] ; this
call j_??1MyClass@@QAE@XZ ; MyClass::~MyClass(void)
mov eax, [ebp+arg_0]
and eax, 1
jz short loc_41344D
mov eax, [ebp+this]
push eax ; void *
call j_??3@YAXPAX@Z_0 ; operator delete(void *)
add esp, 4
loc_41344D: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+6F j
mov eax, [ebp+this]
loc_413450: ; CODE XREF: MyClass::`vector deleting destructor'(uint)+5F j
pop edi
pop esi
pop ebx
add esp, 0CCh
cmp ebp, esp
call j___RTC_CheckEsp
mov esp, ebp
pop ebp
retn 4
經(jīng)過(guò)逆向分析得到C++代碼:
void* __thiscall MyClass::vector_deleting_destructor(usigned int flag)
{
if(flag&2)//由于push的是3,因此這里成立
{
vector_destructor_iterator(this,sizeof(MyClass),*(int*)((char*)this-4),MyClass::~MyClass);
if(flag&1))//由于push的是3唉窃,因此這里成立
{
delete[]((char*)this-4);
}
}
else
{
this->~MyClass();
if(flag&1)
{
delete(this);
}
}
}
僅從以上代碼可以分析出以下幾點(diǎn):
- 1.this-4這個(gè)地址為之前new成功分配所返回值耙饰,可以將其看成sizeof(int)+sizeof(MyClass[2])大小的結(jié)構(gòu)體,第一個(gè)成員為對(duì)象個(gè)數(shù)纹份。
- 2.該函數(shù)對(duì)數(shù)組和非數(shù)組進(jìn)行了分別處理苟跪,可以分析出第2個(gè)二進(jìn)制位為1時(shí),是析構(gòu)對(duì)象數(shù)組蔓涧,為0時(shí)是析構(gòu)普通對(duì)象件已。而第1個(gè)二進(jìn)制位是規(guī)定是否釋放內(nèi)存,可以想象如果這里是定位new元暴,那么這里是不應(yīng)該釋放的篷扩。
- 3.vector_destructor_iterator起實(shí)際析構(gòu)作用原型
void __stdcall vector_destructor_iterator(MyClass *objs, unsigned int size, int count, void (__thiscall *pDtor)(void *));
,下面來(lái)看該函數(shù)
push ebp
mov ebp, esp
push 0FFFFFFFEh
push offset stru_41F9C0
push offset j___except_handler4
mov eax, large fs:0
push eax
add esp, 0FFFFFFF4h
push ebx
push esi
push edi
mov eax, ___security_cookie
xor [ebp+ms_exc.registration.ScopeTable], eax
xor eax, ebp
push eax
lea eax, [ebp+ms_exc.registration]
mov large fs:0, eax
mov [ebp+success], 0
mov eax, [ebp+size]
imul eax, [ebp+count]
add eax, [ebp+ptr]
mov [ebp+ptr], eax
mov [ebp+ms_exc.registration.TryLevel], 0
loc_41580B: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+65 j
mov ecx, [ebp+count]
sub ecx, 1
mov [ebp+count], ecx
js short loc_415827
mov edx, [ebp+ptr]
sub edx, [ebp+size]
mov [ebp+ptr], edx
mov ecx, [ebp+ptr]
call [ebp+pDtor]
jmp short loc_41580B
; ---------------------------------------------------------------------------
loc_415827: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+54 j
mov [ebp+success], 1
mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
call $LN8 ; Finally handler 0 for function 4157C0
; ---------------------------------------------------------------------------
loc_41583A: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *)):$LN9_0 j
jmp short $LN11
; ---------------------------------------------------------------------------
$LN8: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+75 j
; DATA XREF: .rdata:stru_41F9C0 o
cmp [ebp+success], 0 ; Finally handler 0 for function 4157C0
jnz short $LN9_0
mov eax, [ebp+pDtor]
push eax ; pDtor
mov ecx, [ebp+count]
push ecx ; count
mov edx, [ebp+size]
push edx ; size
mov eax, [ebp+ptr]
push eax ; ptr
call j_?__ArrayUnwind@@YGXPAXIHP6EX0@Z@Z ; __ArrayUnwind(void *,uint,int,void (*)(void *))
$LN9_0: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *))+80 j
retn
; ---------------------------------------------------------------------------
$LN11: ; CODE XREF: `eh vector destructor iterator'(void *,uint,int,void (*)(void *)):loc_41583A j
mov ecx, [ebp+ms_exc.registration.Next]
mov large fs:0, ecx
pop ecx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
retn 10h
經(jīng)過(guò)逆向分析得到C++代碼:
void __stdcall vector_destructor_iterator(MyClass *objs, unsigned int size, int count, void (__thiscall *pDtor)(void *))
{
int i=0;
__try
{
MyClass* last=objs+size-1;//從最后一個(gè)對(duì)象開(kāi)始析構(gòu)
while(count--)
{
last->pDtor();
last--;
}
}
__except(1)
{
__ArrayUnwind(objs,size,count,pDtor);//如果某個(gè)析構(gòu)函數(shù)產(chǎn)生異常茉盏,則跳過(guò)該對(duì)象鉴未,繼續(xù)析構(gòu)之前的對(duì)象
}
}
??鑒于__ArrayUnwind前面已經(jīng)介紹過(guò),這里就不分析了鸠姨。如果仔細(xì)分析上一節(jié)開(kāi)頭給出main匯編代碼铜秆,會(huì)發(fā)現(xiàn)只要new成功了,delete都會(huì)去執(zhí)行析構(gòu)讶迁,即使出現(xiàn)對(duì)象數(shù)組中某個(gè)對(duì)象構(gòu)造失敗導(dǎo)致已經(jīng)進(jìn)行析構(gòu)羽峰,delete時(shí)所有元素仍會(huì)析構(gòu)一次。
探究第二種情況new[]形式實(shí)現(xiàn)
MyClass* fPtr5 = new( nothrow ) MyClass[2];
// newaopnt.cpp
void * __CRTDECL operator new[](::size_t count, const std::nothrow_t& x)
_THROW0()
{ // try to allocate count bytes for an array
return (operator new(count, x));
}
可見(jiàn)調(diào)用了單對(duì)象的第二種new形式添瓷,與非數(shù)組形式類似梅屉,不再贅述
探究第三種情況new[]形式實(shí)現(xiàn)
char x2[2*sizeof( MyClass ) + sizeof(int)];
MyClass* fPtr6 = new ( &x2[0] ) MyClass[2];
inline void *__CRTDECL operator new[](size_t, void *_Where) _THROW0()
{ // construct array with placement at _Where
return (_Where);
}
??可見(jiàn)等同于對(duì)象第三種new形式,不再贅述鳞贷。下面我們來(lái)看看其他內(nèi)存分配函數(shù)
malloca
void* _malloca(size_t size);
??MSDN里是這么描述的:在棧上分配內(nèi)存坯汤,是_alloca的安全性增強(qiáng)版本。返回指針是根據(jù)對(duì)象大小對(duì)齊搀愧,如果size是0則返回長(zhǎng)度0的合法指針惰聂。如果地址空間無(wú)法分配會(huì)拋出一個(gè)棧溢出異常,該異常不是C++異常咱筛,需要使用SEH搓幌。_malloca和_alloca的區(qū)別在于_alloc無(wú)論大小總是在棧上分配,且無(wú)需free釋放內(nèi)存迅箩。而_malloca需要使用_freea釋放內(nèi)存溉愁,在調(diào)試模式下,_malloca總是在堆上分配。在異常處理時(shí)顯式調(diào)用_malloca有一些限制饲趋,x86架構(gòu)處理器異常處理例程會(huì)自動(dòng)控制函數(shù)棧幀拐揭,在執(zhí)行操作時(shí)并不基于當(dāng)前閉合函數(shù)棧幀撤蟆,這一點(diǎn)在Windows NT SEH和C++異常處理的catch語(yǔ)句中很常見(jiàn)。因此在以下情況顯示調(diào)用_malloca堂污,在執(zhí)行異常處理例程后會(huì)產(chǎn)生程序崩潰家肯。
??Windows NT SEH異常過(guò)濾表達(dá)式:__except(_malloca())
??Windows NT SEH最終執(zhí)行表達(dá)式:__finally(_malloca())
??C++ 異常處理 catch語(yǔ)句
??然而_malloca可以從異常處理例程中除上述情況以外的情況下直接調(diào)用,或在異常處理所觸發(fā)的回調(diào)函數(shù)中調(diào)用也是允許的盟猖。先來(lái)看一個(gè)例子:
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
int main()
{
int size;
int numberRead = 0;
int errcode = 0;
void *p = NULL;
void *pMarker = NULL;
while (numberRead == 0)
{
printf_s("Enter the number of bytes to allocate "
"using _malloca: ");
numberRead = scanf_s("%d", &size);
}
// Do not use try/catch for _malloca,
// use __try/__except, since _malloca throws
// Structured Exceptions, not C++ exceptions.
__try
{
if (size > 0)
{
p = _malloca( size );
}
else
{
printf_s("Size must be a positive number.");
}
_freea( p );
}
// Catch any exceptions that may occur.
__except( GetExceptionCode() == STATUS_STACK_OVERFLOW )
{
printf_s("_malloca failed!\n");
// If the stack overflows, use this function to restore.
errcode = _resetstkoflw();
if (errcode)
{
printf("Could not reset the stack!");
_exit(1);
}
};
}
// malloc.h
#define _ALLOCA_S_THRESHOLD 1024
#define _ALLOCA_S_STACK_MARKER 0xCCCC
#define _ALLOCA_S_HEAP_MARKER 0xDDDD
#if defined(_M_IX86)
#define _ALLOCA_S_MARKER_SIZE 8
#elif defined(_M_X64)
#define _ALLOCA_S_MARKER_SIZE 16
#elif defined(_M_ARM)
#define _ALLOCA_S_MARKER_SIZE 8
#elif !defined (RC_INVOKED)
#error Unsupported target platform.
#endif
......
#if !defined(__midl) && !defined(RC_INVOKED)
#pragma warning(push)
#pragma warning(disable:6540)
__inline void *_MarkAllocaS(_Out_opt_ __crt_typefix(unsigned int*) void *_Ptr, unsigned int _Marker)
{
if (_Ptr)
{
*((unsigned int*)_Ptr) = _Marker;
_Ptr = (char*)_Ptr + _ALLOCA_S_MARKER_SIZE;
}
return _Ptr;
}
#pragma warning(pop)
#endif
#if defined(_DEBUG)
#if !defined(_CRTDBG_MAP_ALLOC)
#undef _malloca
#define _malloca(size) \
__pragma(warning(suppress: 6255)) \
_MarkAllocaS(malloc((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_HEAP_MARKER)
#endif
#else
#undef _malloca
#define _malloca(size) \
__pragma(warning(suppress: 6255)) \
((((size) + _ALLOCA_S_MARKER_SIZE) <= _ALLOCA_S_THRESHOLD) ? \
_MarkAllocaS(_alloca((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_STACK_MARKER) : \
_MarkAllocaS(malloc((size) + _ALLOCA_S_MARKER_SIZE), _ALLOCA_S_HEAP_MARKER))
#endif
??可以分析出DEBUG版下讨衣,宏調(diào)用了malloc進(jìn)行分配,之后使用_MarkAllocaS對(duì)分配內(nèi)存進(jìn)行一些處理(后面討論)式镐,而RELEASE版下值依,宏先判斷要分配的內(nèi)存是否過(guò)大,該門限為_(kāi)ALLOCA_S_THRESHOLD-_ALLOCA_S_MARKER_SIZE=1016碟案,如果超過(guò)該值則調(diào)用malloc,否則調(diào)用_alloca颇蜡。從字面意思上可以知道_ALLOCA_S_HEAP_MARKER這個(gè)標(biāo)志位說(shuō)明該內(nèi)存區(qū)是在堆上分配的价说,而_ALLOCA_S_STACK_MARKER標(biāo)志是在棧上分配的。在malloc或_alloca分配成功后风秤,總會(huì)調(diào)用_MarkAllocaS進(jìn)行調(diào)整鳖目。結(jié)合字面意思和5行C語(yǔ)言代碼可知,在執(zhí)行過(guò)內(nèi)存分配后缤弦,返回的指針前sizeof(unigned int*)字節(jié)為分配內(nèi)存類型標(biāo)志领迈,之后指針調(diào)整到空閑位置丟給用戶操作。那么所有的問(wèn)題都落在_alloca和malloc的源碼上碍沐,下面會(huì)進(jìn)行分析狸捅。
??_alloca(我第一次見(jiàn)棧上分配內(nèi)存是在逆向一個(gè)易語(yǔ)言程序時(shí),用的是sub esp累提,而微軟這個(gè)函數(shù)是第一次見(jiàn))void* _alloca(size_t size);
尘喝,該函數(shù)只在程序棧中分配字節(jié),而函數(shù)退出時(shí)該空間會(huì)自動(dòng)釋放斋陪,因此無(wú)需手動(dòng)釋放朽褪。用此函數(shù)的限制和_malloca相同。
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
int main()
{
int size = 1000;
int errcode = 0;
void *pData = NULL;
// 注意:不要使用try/catch无虚,而要使用__try/__except缔赠,因?yàn)開(kāi)alloca拋出SEH而不是C++異常
__try {
// 使用_alloca分配太大的空間很容易崩潰,推薦1024字節(jié)以下的空間
if (size > 0 && size < 1024)
{
pData = _alloca( size );
printf_s( "Allocated %d bytes of stack at 0x%p",
size, pData);
}
else
{
printf_s("Tried to allocate too many bytes.\n");
}
}
// 如果溢出
__except( GetExceptionCode() == STATUS_STACK_OVERFLOW )
{
printf_s("_alloca failed!\n");
// 使用下面的函數(shù)恢復(fù)函數(shù)棧
errcode = _resetstkoflw();
if (errcode)
{
printf_s("Could not reset the stack!\n");
_exit(1);
}
};
}
來(lái)看反匯編
; int __cdecl main()
_main proc near ; CODE XREF: j__main j
pAllocaBase = dword ptr -120h
cbSize = dword ptr -11Ch
var_114 = dword ptr -114h
allocaList = dword ptr -48h
pData = dword ptr -3Ch
errcode = dword ptr -30h
size = dword ptr -24h
var_1C = dword ptr -1Ch
ms_exc = CPPEH_RECORD ptr -18h
push ebp
mov ebp, esp
push 0FFFFFFFEh
push offset stru_416F80
push offset j___except_handler4
mov eax, large fs:0
push eax
add esp, 0FFFFFEF0h
push ebx
push esi
push edi
lea edi, [ebp+pAllocaBase]
mov ecx, 42h
mov eax, 0CCCCCCCCh
rep stosd
mov eax, ___security_cookie
xor [ebp+ms_exc.registration.ScopeTable], eax
xor eax, ebp
mov [ebp+var_1C], eax
push eax
lea eax, [ebp+ms_exc.registration]
mov large fs:0, eax
mov [ebp+ms_exc.old_esp], esp
mov [ebp+allocaList], 0
mov [ebp+size], 3E8h
mov [ebp+errcode], 0
mov [ebp+pData], 0
mov [ebp+ms_exc.registration.TryLevel], 0
cmp [ebp+size], 0
jle short loc_4114D3
cmp [ebp+size], 400h
jge short loc_4114D3
mov eax, [ebp+size]
add eax, 24h
mov [ebp+cbSize], eax
mov eax, [ebp+cbSize]
call j___alloca_probe_16
mov [ebp+pAllocaBase], esp
mov [ebp+ms_exc.old_esp], esp
lea ecx, [ebp+allocaList]
push ecx ; pAllocaInfoList
mov edx, [ebp+cbSize] ; cbSize
mov ecx, [ebp+pAllocaBase] ; pAllocaBase
call j_@_RTC_AllocaHelper@12 ; _RTC_AllocaHelper(x,x,x)
add [ebp+pAllocaBase], 20h
mov edx, [ebp+pAllocaBase]
mov [ebp+pData], edx
mov esi, esp
mov eax, [ebp+pData]
push eax
mov ecx, [ebp+size]
push ecx
push offset Format ; "Allocated %d bytes of stack at 0x%p"
call ds:__imp__printf_s
add esp, 0Ch
cmp esi, esp
call j___RTC_CheckEsp
jmp short loc_4114EA
; ---------------------------------------------------------------------------
loc_4114D3: ; CODE XREF: _main+72 j
; _main+7B j
mov esi, esp
push offset aTriedToAllocat ; "Tried to allocate too many bytes.\n"
call ds:__imp__printf_s
add esp, 4
cmp esi, esp
call j___RTC_CheckEsp
loc_4114EA: ; CODE XREF: _main+E1 j
mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
jmp loc_41158D
; ---------------------------------------------------------------------------
$LN10: ; DATA XREF: .rdata:stru_416F80 o
mov eax, [ebp+ms_exc.exc_ptr] ; Exception filter 0 for function 4113F0
mov ecx, [eax]
mov edx, [ecx]
mov [ebp+var_114], edx
cmp [ebp+var_114], 0C00000FDh
jnz short loc_41151B
mov [ebp+cbSize], 1
jmp short loc_411525
; ---------------------------------------------------------------------------
loc_41151B: ; CODE XREF: _main+11D j
mov [ebp+cbSize], 0
loc_411525: ; CODE XREF: _main+129 j
mov eax, [ebp+cbSize]
$LN12:
retn
; ---------------------------------------------------------------------------
$LN11: ; DATA XREF: .rdata:stru_416F80 o
mov esp, [ebp+ms_exc.old_esp] ; Exception handler 0 for function 4113F0
mov esi, esp
push offset a_allocaFailed ; "_alloca failed!\n"
call ds:__imp__printf_s
add esp, 4
cmp esi, esp
call j___RTC_CheckEsp
mov esi, esp
call ds:__imp___resetstkoflw
cmp esi, esp
call j___RTC_CheckEsp
mov [ebp+errcode], eax
cmp [ebp+errcode], 0
jz short loc_411586
mov esi, esp
push offset aCouldNotResetT ; "Could not reset the stack!\n"
call ds:__imp__printf_s
add esp, 4
cmp esi, esp
call j___RTC_CheckEsp
mov esi, esp
push 1 ; Code
call ds:__imp___exit
; ---------------------------------------------------------------------------
cmp esi, esp
call j___RTC_CheckEsp
loc_411586: ; CODE XREF: _main+16C j
mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
loc_41158D: ; CODE XREF: _main+101 j
jmp short loc_411591
; ---------------------------------------------------------------------------
jmp short loc_411593
; ---------------------------------------------------------------------------
loc_411591: ; CODE XREF: _main:loc_41158D j
xor eax, eax
loc_411593: ; CODE XREF: _main+19F j
push edx
mov ecx, ebp ; frame
push eax
lea edx, v ; v
push [ebp+allocaList] ; allocaList
call j_@_RTC_CheckStackVars2@12 ; _RTC_CheckStackVars2(x,x,x)
pop eax
pop edx
lea esp, [ebp-130h]
mov ecx, [ebp+ms_exc.registration.Next]
mov large fs:0, ecx
pop ecx
pop edi
pop esi
pop ebx
mov ecx, [ebp+var_1C]
xor ecx, ebp ; cookie
call j_@__security_check_cookie@4 ; __security_check_cookie(x)
mov esp, ebp
pop ebp
retn
mov eax, [ebp+size]
add eax, 24h
mov [ebp+cbSize], eax
mov eax, [ebp+cbSize]
call j___alloca_probe_16
mov [ebp+pAllocaBase], esp
mov [ebp+ms_exc.old_esp], esp
lea ecx, [ebp+allocaList]
push ecx ; pAllocaInfoList
mov edx, [ebp+cbSize] ; cbSize
mov ecx, [ebp+pAllocaBase] ; pAllocaBase
call j_@_RTC_AllocaHelper@12 ; _RTC_AllocaHelper(x,x,x)
add [ebp+pAllocaBase], 20h
mov edx, [ebp+pAllocaBase]
mov [ebp+pData], edx
??很疑惑地友题,在__alloca_probe_16調(diào)用之前嗤堰,發(fā)生了add eax,24h和mov eax,[ebp+cbSize],而在之后發(fā)生了mov [ebp+pAllocaBase], esp度宦,那么大膽做出猜測(cè):
- 1.add eax,24h梁棠,說(shuō)明這24h字節(jié)用來(lái)實(shí)現(xiàn)內(nèi)存管理或字節(jié)對(duì)齊之類功能
- 2.__alloca_probe_16為接受一個(gè)參數(shù)的函數(shù)置森,該參數(shù)通過(guò)eax傳遞,進(jìn)行的操作是修改esp符糊,因此esp可以看做執(zhí)行結(jié)果
- 3.另一個(gè)函數(shù)原型為
void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
凫海,反匯編得到
; void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
@_RTC_AllocaHelper@12 proc near ; CODE XREF: _RTC_AllocaHelper(x,x,x) j
pAllocaInfoList = dword ptr 8
pAllocaBase = ecx
cbSize = edx
push ebp
mov ebp, esp
push ebx
push esi
mov esi, pAllocaBase
mov ebx, cbSize
test esi, esi
jz short loc_4116CC
test ebx, ebx
jz short loc_4116CC
mov cbSize, [ebp+pAllocaInfoList]
test cbSize, cbSize
jz short loc_4116CC
push edi
mov al, 0CCh
mov edi, esi
mov pAllocaBase, ebx
rep stosb
mov eax, [cbSize]
mov [esi+4], eax
mov [esi+0Ch], ebx
mov [cbSize], esi
pop edi
loc_4116CC: ; CODE XREF: _RTC_AllocaHelper(x,x,x)+B j
; _RTC_AllocaHelper(x,x,x)+F j ...
pop esi
pop ebx
pop ebp
retn 4
@_RTC_AllocaHelper@12 endp
逆向分析得道C++代碼:
void main()
{
......
int size=1024,cbsize=size+sizeof(_RTC_ALLOCA_NODE)+4;
_RTC_ALLOCA_NODE* pAllocaBase=__alloca_probe_16(cbsize);
_RTC_AllocaHelper(pAllocaBase,cbsize,NULL);
void* pData=(void*)(pAllocaBase+1);
......
}
//這個(gè)結(jié)構(gòu)體來(lái)自于reactos
#pragma pack(push,1)
typedef struct _RTC_ALLOCA_NODE
{
__int32 guard1;
struct _RTC_ALLOCA_NODE *next;
#if (defined(_X86_) && !defined(__x86_64))
__int32 dummypad;
#endif
size_t allocaSize;
#if (defined(_X86_) && !defined(__x86_64))
__int32 dummypad2;
#endif
__int32 guard2[3];
}_RTC_ALLOCA_NODE;
#pragma pack(pop)
void __fastcall _RTC_AllocaHelper(_RTC_ALLOCA_NODE *pAllocaBase, unsigned int cbSize, _RTC_ALLOCA_NODE **pAllocaInfoList)
{//初始化已分配空間,可以用于維護(hù)調(diào)試版函數(shù)棧
if(pAllocaBase && cbSize && pAllocaInfoList)//由于最后一個(gè)參數(shù)在本例中為0男娄,這個(gè)函數(shù)實(shí)際相當(dāng)于沒(méi)有執(zhí)行
{
memset(pAllocaBase,0xCC,cbSize);//經(jīng)常在調(diào)試版程序椥刑埃空間看到0xCC "燙燙燙燙燙" 對(duì)吧,就是這樣的模闲。建瘫。。
pAllocaBase->next=*pAllocaInfoList;//鏈接到前一個(gè)結(jié)構(gòu)尸折;
pAllocaBase->allocaSize=cbSize;
*pAllocaInfoList=pAllocaBase;//自此可知啰脚,上述結(jié)構(gòu)形成鏈表,pAllocaInfoList指向當(dāng)前結(jié)構(gòu)
}
}
//__alloca_probe_16代碼下面會(huì)進(jìn)行分析
??以上是debug版的情況实夹,如果嘗試用release版查看反匯編代碼橄浓,會(huì)發(fā)現(xiàn)只有push和call __alloca_probe_16部分,可知add eax,24和AllocaHelper只是調(diào)試版本用于內(nèi)存管理的亮航。所以重點(diǎn)落在該函數(shù)的解析上荸实。進(jìn)入源代碼查看,__alloca_probe_16用來(lái)按16字節(jié)對(duì)齊內(nèi)存缴淋,而chkstk子例程進(jìn)行實(shí)際分配操作:
// alloca16.asm
; _alloca_probe_16, _alloca_probe_8 - 按8/16字節(jié)對(duì)齊例程
;輸入:EAX = 棧幀大小
;輸出:調(diào)整EAX准给,修改esp.
public _alloca_probe_8
_alloca_probe_16 proc ; 16 byte aligned alloca
push ecx
lea ecx, [esp] + 8 ; 父函數(shù)棧頂(call _alloca_probe_16和push ecx)
sub ecx, eax ;
and ecx, (16 - 1) ; 計(jì)算地址低4位未對(duì)齊偏移
add eax, ecx ; 增加cbSize使其對(duì)齊
sbb ecx, ecx ; 如果cbSize溢出,ecx = 0xFFFFFFFF重抖,否則ecx = 0
or eax, ecx ; 如果溢出露氮,則eax = 0xFFFFFFFF
pop ecx ; 還原ecx
jmp _chkstk ; eax存儲(chǔ)修正cbSize,并交給_chkstk處理
_alloca_probe_16 endp
end
public _alloca_probe
_chkstk proc
_alloca_probe = _chkstk
push ecx
lea ecx, [esp] + 8 - 4 ; 考慮到之后的ret指令對(duì)未來(lái)esp的修改
sub ecx, eax ; 分配椫优妫空間沦辙,ecx存儲(chǔ)更新后的棧位置
sbb eax, eax ; 如果申請(qǐng)空間過(guò)大,eax = 0xFFFFFFFF讹剔,否則eax = 0
not eax ;
and ecx, eax ; ecx = 0 | ecx = ecx
mov eax, esp ;
and eax, not ( _PAGESIZE_ - 1) ; 得到當(dāng)前棧位置所處頁(yè)面地址
cs10:
cmp ecx, eax ;
jb short cs20 ; 如果新的棧位置小于頁(yè)面地址
mov eax, ecx ;
pop ecx
xchg esp, eax ; 更新esp油讯,原始esp存儲(chǔ)在eax中
mov eax, dword ptr [eax] ; 當(dāng)前esp指向返回地址
mov dword ptr [esp], eax ; 修正函數(shù)棧幀,使其可以正確返回
ret
cs20:
sub eax, _PAGESIZE_ ; 獲取上一個(gè)頁(yè)面
test dword ptr [eax],eax ; 探測(cè)頁(yè)面權(quán)限
jmp short cs10 ; 如果沒(méi)有產(chǎn)生異常則跳轉(zhuǎn)延欠,如果出現(xiàn)異常陌兑,則直接進(jìn)入父函數(shù)的異常處理中
_chkstk endp
end
calloc
void* calloc(size_t num,size_t size);
??calloc用來(lái)分配數(shù)組空間,同樣返回指針是根據(jù)對(duì)象類型對(duì)齊的由捎,每個(gè)對(duì)象都被初始化為0兔综,如果待分配內(nèi)存超過(guò)_HEAP_MAXREQ或分配失敗則設(shè)置errno為ENOMEM,calloc內(nèi)部調(diào)用了malloc函數(shù)使用_set_new_mode函數(shù)設(shè)置回調(diào)模式,該回調(diào)用于處理分配失敗情況软驰,默認(rèn)情況下涧窒,分配失敗后malloc不會(huì)調(diào)用新回調(diào)分配內(nèi)存,然后我們可以通過(guò)提前調(diào)用_set_new_mode(1)或者鏈接NEWMODE.OBJ修改這種默認(rèn)行為.calloc用法如下:
#include <stdio.h>
#include <malloc.h>
int main( void )
{
long *buffer;
buffer = (long *)calloc( 40, sizeof( long ) );
if( buffer != NULL )
printf( "Allocated 40 long integers\n" );
else
printf( "Can't allocate memory\n" );
free( buffer );
}
Calloc源碼:
// calloc.c和calloc_impl.c
void * __cdecl _calloc_base (size_t num, size_t size)
{
int errno_tmp = 0;
void * pv = _calloc_impl(num, size, &errno_tmp);
if ( pv == NULL && errno_tmp != 0 && _errno())
{
errno = errno_tmp; // recall, #define errno *_errno()
}
return pv;
}
void * __cdecl _calloc_impl (size_t num, size_t size, int * errno_tmp)
{
size_t size_orig;
void * pvReturn;
/* ensure that (size * num) does not overflow */
if (num > 0)
{
_VALIDATE_RETURN_NOEXC((_HEAP_MAXREQ / num) >= size, ENOMEM, NULL);
}
size_orig = size = size * num;
/* force nonzero size */
if (size == 0)
size = 1;
for (;;)
{
pvReturn = NULL;
if (size <= _HEAP_MAXREQ)
{
if (pvReturn == NULL)
pvReturn = HeapAlloc(_crtheap, HEAP_ZERO_MEMORY, size);
}
if (pvReturn || _newmode == 0)
{
RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, size_orig, 0));
if (pvReturn == NULL)
{
if ( errno_tmp )
*errno_tmp = ENOMEM;
}
return pvReturn;
}
/* call installed new handler */
if (!_callnewh(size))
{
if ( errno_tmp )
*errno_tmp = ENOMEM;
return NULL;
}
/* new handler was successful -- try to allocate again */
}
}
??現(xiàn)在來(lái)分析_calloc_impl執(zhí)行流程:
- 1.先檢查申請(qǐng)大小是否超出門限锭亏,若申請(qǐng)大小為0則強(qiáng)制為1
- 2.使用HeapAlloc分配內(nèi)存并清零纠吴。如果成功則返回,否則執(zhí)行_callnewh慧瘤,即定義的失敗處理函數(shù)戴已,如果該回調(diào)函數(shù)返回0則原函數(shù)返回0退出,如果該回調(diào)函數(shù)返回非0锅减,則原函數(shù)重復(fù)執(zhí)行2直到成功糖儡。(_callnewh最終調(diào)用了NtQueryInformationProcess 0x24)
??可見(jiàn)calloc并沒(méi)有像MSDN說(shuō)的那樣調(diào)用了malloc。怔匣。握联。另外,沒(méi)看到有異常處理機(jī)制每瞒。
_expand
??用于擴(kuò)展或縮小已分配內(nèi)存金闽,用于改變已分配內(nèi)存區(qū)大小。void* _expand(void* memblock,size_t newsize);
??該函數(shù)會(huì)檢測(cè)地址的內(nèi)存權(quán)限独泞,如果不通過(guò)移動(dòng)內(nèi)存無(wú)法得到足夠的空間,該函數(shù)會(huì)返回空苔埋,該函數(shù)不會(huì)分配小于請(qǐng)求大小的內(nèi)存區(qū)懦砂。該函數(shù)不通過(guò)移動(dòng)內(nèi)存塊的方式增縮已分配堆內(nèi)存,在64位下該函數(shù)不會(huì)縮小內(nèi)存區(qū)组橄,大小小于16k的內(nèi)存塊都是在低碎片堆中分配的荞膘,在這種情況下_expand不會(huì)對(duì)內(nèi)存塊做任何變動(dòng)直接返回memblock。同樣該函數(shù)會(huì)檢測(cè)參數(shù)合法性玉工,且size不能超過(guò)門限值_HEAP_MAXREQ羽资。
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
int main( void )
{
char *bufchar;
printf( "Allocate a 512 element buffer\n" );
if( (bufchar = (char *)calloc( 512, sizeof( char ) )) == NULL )
exit( 1 );
printf( "Allocated %d bytes at %Fp\n",
_msize( bufchar ), (void *)bufchar );
if( (bufchar = (char *)_expand( bufchar, 1024 )) == NULL )
printf( "Can't expand" );
else
printf( "Expanded block to %d bytes at %Fp\n",
_msize( bufchar ), (void *)bufchar );
// Free memory
free( bufchar );
exit( 0 );
}
expand源碼為:
void * __cdecl _expand_base (void * pBlock, size_t newsize)
{
void * pvReturn;
size_t oldsize;
/* validation section */
_VALIDATE_RETURN(pBlock != NULL, EINVAL, NULL);
if (newsize > _HEAP_MAXREQ) {
errno = ENOMEM;
return NULL;
}
if (newsize == 0)
{
newsize = 1;
}
oldsize = (size_t)HeapSize(_crtheap, 0, pBlock);
pvReturn = HeapReAlloc(_crtheap, HEAP_REALLOC_IN_PLACE_ONLY, pBlock, newsize);
if (pvReturn == NULL)
{
/* 如果使用了低碎片堆則返回原指針. */
if (oldsize <= 0x4000 /* 低碎片堆最多申請(qǐng)16KB內(nèi)存 */
&& newsize <= oldsize && _is_LFH_enabled())
pvReturn = pBlock;
else
errno = _get_errno_from_oserr(GetLastError());
}
if (pvReturn)
{
RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, newsize, 0));
}
return pvReturn;
}
??可見(jiàn)_expand函數(shù)最終調(diào)用了HeapReAlloc函數(shù)。
??一個(gè)小插曲:其實(shí)這些內(nèi)存管理的庫(kù)函數(shù)普遍使用debug和release兩個(gè)版本遵班,debug源碼在dbg*.c(pp)中可以找到屠升,同時(shí)調(diào)試的時(shí)候是可以直接定位進(jìn)去的,而release版函數(shù)通常在函數(shù)名所在文件狭郑,比如delete在delete.cpp中腹暖;而debug版源碼內(nèi)存管理函數(shù)通常會(huì)有一種_CrtMemBlockHeader的結(jié)構(gòu)體,這些都需要自己摸索翰萨。
realloc
源碼:
void * __cdecl _realloc_base (void * pBlock, size_t newsize)
{
void * pvReturn;
size_t origSize = newsize;
// if ptr is NULL, call malloc
if (pBlock == NULL)
return(_malloc_base(newsize));
// if ptr is nonNULL and size is zero, call free and return NULL
if (newsize == 0)
{
_free_base(pBlock);
return(NULL);
}
for (;;) {
pvReturn = NULL;
if (newsize <= _HEAP_MAXREQ)
{
if (newsize == 0)
newsize = 1;
pvReturn = HeapReAlloc(_crtheap, 0, pBlock, newsize);
}
else
{
_callnewh(newsize);
errno = ENOMEM;
return NULL;
}
if ( pvReturn || _newmode == 0)
{
if (pvReturn)
{
RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
RTCCALLBACK(_RTC_Allocate_hook, (pvReturn, newsize, 0));
}
else
{
errno = _get_errno_from_oserr(GetLastError());
}
return pvReturn;
}
// call installed new handler
if (!_callnewh(newsize))
{
errno = _get_errno_from_oserr(GetLastError());
return NULL;
}
// new handler was successful -- try to allocate again
}
}
得到realloc->HeapReAlloc
malloc
源碼:
void * __cdecl _malloc_base (size_t size)
{
void *res = NULL;
// validate size
if (size <= _HEAP_MAXREQ) {
for (;;) {
// allocate memory block
res = _heap_alloc(size);
// if successful allocation, return pointer to memory
// if new handling turned off altogether, return NULL
if (res != NULL)
{
break;
}
if (_newmode == 0)
{
errno = ENOMEM;
break;
}
// call installed new handler
if (!_callnewh(size))
break;
// new handler was successful -- try to allocate again
}
} else {
_callnewh(size);
errno = ENOMEM;
return NULL;
}
RTCCALLBACK(_RTC_Allocate_hook, (res, size, 0));
if (res == NULL)
{
errno = ENOMEM;
}
return res;
}
__forceinline void * __cdecl _heap_alloc (size_t size)
{
if (_crtheap == 0) {
_FF_MSGBANNER(); /* write run-time error banner */
_NMSG_WRITE(_RT_CRT_NOTINIT); /* write message */
__crtExitProcess(255); /* normally _exit(255) */
}
return HeapAlloc(_crtheap, 0, size ? size : 1);
}
得到malloc->_heap_alloc->HeapAlloc
free
源碼:
void __cdecl _free_base (void * pBlock)
{
int retval = 0;
if (pBlock == NULL)
return;
RTCCALLBACK(_RTC_Free_hook, (pBlock, 0));
retval = HeapFree(_crtheap, 0, pBlock);
if (retval == 0)
{
errno = _get_errno_from_oserr(GetLastError());
}
}
得到free->HeapFree
現(xiàn)在所有問(wèn)題都集中在了HeapAlloc HeapFree HeapReAlloc上