CLR內(nèi)核調(diào)試之:Malloc函數(shù)實(shí)現(xiàn)
自從可以動(dòng)態(tài)調(diào)試SSCLI之后,發(fā)現(xiàn)這個(gè)玩意還真是個(gè)寶山,越玩越有意思,就像捅開(kāi)一扇門(mén),發(fā)現(xiàn)門(mén)后面還有一座寶山……不光CLR的內(nèi)部實(shí)現(xiàn)細(xì)節(jié),可以象是放電影一樣呈現(xiàn)在眼前,Visual Studio里面的一些底層的技術(shù),還有OS的底層技術(shù),查看起來(lái)那是相當(dāng)?shù)谋憬荨?/span>
這里就說(shuō)說(shuō)malloc函數(shù)是具體如何實(shí)現(xiàn)的,能夠F10,F11動(dòng)態(tài)的在VS里面跟蹤其一步一步的實(shí)現(xiàn)的源碼,真是一件痛快的事情。
為嘛要說(shuō)malloc的實(shí)現(xiàn)呢?因?yàn)樵?/span>main開(kāi)始以后,要為傳遞進(jìn)來(lái)的參數(shù)分配內(nèi)存地址。而這個(gè)時(shí)候我不小心按了F11,不小心看到了malloc是具體如何運(yùn)作的:
Clix.cpp的int __cdecl main(int argc, char **argv)之后,
首先得到參數(shù)行,然后分配一個(gè)空間把這個(gè)參數(shù)行保存起來(lái):
pwzCmdLine = ::GetCommandLineW();
// Allocate for the worst case storage requirement.
WCHAR *pAlloc = (WCHAR*)malloc((wcslen(pwzCmdLine) + 1) * sizeof(WCHAR));
首先獲得到命令行,然后使用malloc分配ygie空間給存起來(lái)。sizeof(WCHAR)的含義ms是在后面加上一個(gè)終止的標(biāo)記。
Malloc函數(shù),首先跳轉(zhuǎn)到了d:\Rotor\sscli20\pal\win32\win32pal.c下:
One:
PALIMPORT
void *
__cdecl
PAL_malloc(size_t bytes)
{
//mark how much bytes had successful allocated.
void *Ret;
LOGAPI("malloc(bytes=%p)\n", bytes);
Ret = malloc(bytes);
LOGAPI("malloc returns void* %p\n", Ret);
return Ret;
}
這是一個(gè)PAL_malloc,是sscli里面針對(duì)特定的操作系統(tǒng)的PAL層的實(shí)現(xiàn),因?yàn)槲矣玫氖遣僮飨到y(tǒng)是Windows XP En,這里就開(kāi)始調(diào)用操作系統(tǒng)里面的malloc的實(shí)現(xiàn)了,在看malloc之前,先看看LOGAPI是干嘛的:
Two:
void
__cdecl
PalLogApi(const char *fmt,...)
{
va_list list;
// Assert that the PAL APIs are only being called when the PAL
// is properly initialized.
// This assert is disabled because of gcc startup code calls Win32 functions before calling PAL initialize.
// PALASSERT(PalReferenceCount != 0);
if (LogFileHandle == INVALID_HANDLE_VALUE) {
// Logging isn't enabled
return;
}
va_start(list, fmt);
PalLogApiCore(fmt, list);
va_end(list);
}
首先,我很奇怪C++里面的…是個(gè)怎么樣子的參數(shù)傳遞方法….
打開(kāi)一看,咱就知道了,這個(gè)是和SSCLI的調(diào)試功能緊密結(jié)合在一起的。下面截圖了個(gè):

這下看到了,LogFileHandle表示如果開(kāi)啟了SSCLI的log功能,就把這次malloc的情況給記錄下來(lái)。
然后這里沒(méi)有開(kāi)啟LOG功能,這個(gè)變量就是0xfffffff,然后就直接return了。繼續(xù)回到One里面的Ret = malloc(bytes);這個(gè)地方。這個(gè)方法的目的,是Allocate of block of memory of at least size bytes from the heap and return a pointer to it.下跳轉(zhuǎn)到了Malloc的本地實(shí)現(xiàn)了,到了文件:
Three:
C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\dbgmalloc.c
extern "C" _CRTIMP void * __cdecl malloc (size_t nSize)
{
void *res = _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);
RTCCALLBACK(_RTC_Allocate_hook, (res, nSize, 0));
return res;
}
然后繼續(xù)看調(diào)用的_nh_malloc_dbg來(lái)如何實(shí)現(xiàn)內(nèi)存的分配,這下來(lái)到了
C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\dbgheap.c,這個(gè)文件中:
Four:
extern "C" void * __cdecl _nh_malloc_dbg (
size_t nSize,
int nhFlag,
int nBlockUse,
const char * szFileName,
int nLine
)
{
int errno_tmp = 0;
void * pvBlk = _nh_malloc_dbg_impl(nSize, nhFlag, nBlockUse, szFileName, nLine, &errno_tmp);
if ( pvBlk == NULL && errno_tmp != 0 && _errno())
{
errno = errno_tmp; // recall, #define errno *_errno()
}
return pvBlk;
}
從名字上來(lái)看,是在Debug heap上面給分配的空間。這個(gè)方法的purpose,is Allocate of block of memory of at least size bytes from the debug heap and return a pointer to it. Assumes heap already locked.If no blocks available, call new handler.Allocates any type of supported memory block.
Five:
extern "C" static void * __cdecl _nh_malloc_dbg_impl (
size_t nSize,
int nhFlag,
int nBlockUse,
const char * szFileName,
int nLine,
int * errno_tmp
)
{
void * pvBlk;
for (;;)
{
/* do the allocation*/
pvBlk = _heap_alloc_dbg_impl(nSize, nBlockUse, szFileName, nLine, errno_tmp);
if (pvBlk)
{
return pvBlk;
}
if (nhFlag == 0)
{
*errno_tmp = ENOMEM;
return pvBlk;
}
/* call installed new handler */
if (!_callnewh(nSize))
{
*errno_tmp = ENOMEM;
return NULL;
}
/* new handler was successful -- try to allocate again */
}
}
_nh_malloc_dbg_impl()這個(gè)方法,主要是在Debug heap上面分配一段的memory,同時(shí)返回一個(gè)指向它的指針,同時(shí),假設(shè)這個(gè)heap已經(jīng)被lock了。這里,又跳轉(zhuǎn)到了_heap_alloc_dbg_impl,唉,我等的花都謝了,還是這個(gè)文件里面,終于,在這里看到了實(shí)際的內(nèi)存分配的過(guò)程:…
Six:
extern "C" static void * __cdecl _heap_alloc_dbg_impl(
size_t nSize,
int nBlockUse,
const char * szFileName,
int nLine,
int * errno_tmp
)
{
long lRequest;
size_t blockSize;
int fIgnore = FALSE;
_CrtMemBlockHeader * pHead;
void *retval=NULL;
/* lock the heap*/
_mlock(_HEAP_LOCK);
__try {
/* verify heap before allocation */
if (check_frequency > 0)//false
if (check_counter == (check_frequency - 1))
{
_ASSERTE(_CrtCheckMemory());
check_counter = 0;
}
else
check_counter++;
lRequest = _lRequestCurr;
/* break into debugger at specific memory allocation */
if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)//false
_CrtDbgBreak();
/* forced failure */
if ((_pfnAllocHook) && !(*_pfnAllocHook)(_HOOK_ALLOC, NULL, nSize,
nBlockUse, lRequest, (const unsigned char *)szFileName, nLine)) //false
{
if (szFileName)
_RPT2(_CRT_WARN, "Client hook allocation failure at file %hs line %d.\n",
szFileName, nLine);
else
_RPT0(_CRT_WARN, "Client hook allocation failure.\n");
}
else
{
/* cannot ignore CRT allocations */
if (_BLOCK_TYPE(nBlockUse) != _CRT_BLOCK &&
!(_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF)) //false
fIgnore = TRUE;
/* Diagnostic memory allocation from this point on */
if (nSize > (size_t)(_HEAP_MAXREQ - nNoMansLandSize - sizeof(_CrtMemBlockHeader))) //false
{
_RPT1(_CRT_ERROR, "Invalid allocation size: %Iu bytes.\n", nSize);
*errno_tmp = ENOMEM;
}
else
{
if (!_BLOCK_TYPE_IS_VALID(nBlockUse)) //false
{
_RPT0(_CRT_ERROR, "Error: memory allocation: bad memory block type.\n");
}
blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;
RTCCALLBACK(_RTC_FuncCheckSet_hook,(0));
//acture called:
//return HeapAlloc(_crtheap, 0, size ? size : 1);
pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);
if (pHead == NULL) //false
{
*errno_tmp = ENOMEM;
RTCCALLBACK(_RTC_FuncCheckSet_hook,(1));
}
else
{
/* commit allocation */
++_lRequestCurr;
if (fIgnore) //false
{
pHead->pBlockHeaderNext = NULL;
pHead->pBlockHeaderPrev = NULL;
pHead->szFileName = NULL;
pHead->nLine = IGNORE_LINE;
pHead->nDataSize = nSize;
pHead->nBlockUse = _IGNORE_BLOCK;
pHead->lRequest = IGNORE_REQ;
}
else {
/* keep track of total amount of memory allocated */
if (SIZE_MAX - _lTotalAlloc > nSize)
{
_lTotalAlloc += nSize;
}
else
{
_lTotalAlloc = SIZE_MAX;
}
_lCurAlloc += nSize;
if (_lCurAlloc > _lMaxAlloc)
_lMaxAlloc = _lCurAlloc;
if (_pFirstBlock) //false
_pFirstBlock->pBlockHeaderPrev = pHead;
else
_pLastBlock = pHead;
pHead->pBlockHeaderNext = _pFirstBlock;
pHead->pBlockHeaderPrev = NULL;
pHead->szFileName = (char *)szFileName;
pHead->nLine = nLine;
pHead->nDataSize = nSize;
pHead->nBlockUse = nBlockUse;
pHead->lRequest = lRequest;
//the type of pHead is _CrtMemBlockHeader *
/* link blocks together */
_pFirstBlock = pHead;
}
/* fill in gap before and after real block */
//memset founction is usually used to set a gap of mem to a certain char, usually used for initial memory.
memset((void *)pHead->gap, _bNoMansLandFill, nNoMansLandSize);
memset((void *)(pbData(pHead) + nSize), _bNoMansLandFill, nNoMansLandSize);
/* fill data with silly value (but non-zero) */
memset((void *)pbData(pHead), _bCleanLandFill, nSize);
retval=(void *)pbData(pHead);
}
}
}
}
__finally {
/* unlock the heap */
_munlock(_HEAP_LOCK);
}
return retval;
}
先看看_mlock(_HEAP_LOCK);是如何實(shí)現(xiàn)的:
void __cdecl _lock (
int locknum
)
{
/*
* Create/open the lock, if necessary
*/
if ( _locktable[locknum].lock == NULL ) {
if ( !_mtinitlocknum(locknum) )
_amsg_exit( _RT_LOCK );
}
/*
* Enter the critical section.
*/
EnterCriticalSection( _locktable[locknum].lock );
}
看到了傳說(shuō)中的locktable,截圖留念下:

也得到了Locktable是如何操作的方法,如上面所示…
在Six:中,我標(biāo)出了每個(gè)if判斷的時(shí)候所走的分支。做好了這一切之后,分配好了空間就一步一步返回了。最后提下memset方法,鏈接到了C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\INTEL\memset.asm中,一大段匯編代碼來(lái)移動(dòng)內(nèi)存。主要用來(lái)做內(nèi)存的初始化的時(shí)候用。
Lbq1221119@cnblogs first post at http://sscli.cnblogs.com
2008-11-12 10:35:22 PM
posted on 2008-11-13 20:52 lbq1221119 閱讀(5673) 評(píng)論(11) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)