使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇對 VLD 1.0 源碼做記憶體泄漏檢測的思路進行剖析。 ...
說明
使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇對 VLD 1.0 源碼做記憶體泄漏檢測的思路進行剖析。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄
目錄1. 源碼獲取
version 1.0
及之前版本都使用舊的檢測思路,可以在網站 CodeProject-Visual-Leak-Detector 中下載 version 1.0
的源碼(國內網路資源:百度網盤-vld-1.0 源碼包),同時在該網站中可以看到庫作者 Dan Moulding
對舊檢測原理的介紹。這個網站中有下圖這段文字,但經過我一番查找,還是未找到 Dan Moulding
對後續新檢測原理的介紹文章,本篇文章主要對 version 1.0
的源碼進行剖析。
version 1.0
的源碼算上註釋一共不到 3000
行,而且代碼註釋寫得很詳細,推薦有興趣的仔細閱讀源碼。以下資料可能對理解其檢測原理有幫助:
- CodeProject-Visual-Leak-Detector。
- 博客園-vs 2010 下使用 VLD 工具。
- 博客園-關於記憶體泄漏。
- Github-dbgint.h。這個文件的第 310~335 行有結構體
_CrtMemBlockHeader
與pHdr()
的定義。
2. 源碼文件概覽
version 1.0
源碼包中一共有 11
個文件,目錄結構如下:
vld-10-src
CHANGES.txt
COPYING.txt
README.html
vld.cpp
vld.dsp
vld.h
vldapi.cpp
vldapi.h
vldint.h
vldutil.cpp
vldutil.h
其中 3
個 .cpp
文件,4
個 .h
文件,2
個 .txt
文件,1
個 .dsp
文件,1
個 .html
文件,各文件用途簡述如下:
-
文件
README.html
為網頁版的幫助文檔,裡面介紹了VLD
的功能、使用方法、配置選項、編譯方法、功能限制等。從這個幫助文檔中可以得知:這個版本的VLD
只能檢測由new
或malloc
導致的記憶體泄漏;若需要檢測多個DLL
庫,則要確保載入這些庫前,已經包含了vld.h
頭文件。 -
文件
CHANGES.txt
為版本迭代日誌,記錄了各版本的更新概要; -
文件
COPYING.txt
為LGPL 2.1
開源協議; -
文件
vld.dsp
為Visual C++
的項目文件,全稱Microsoft Developer Studio Project File
; -
文件
vldapi.h
為使用VLD
庫時需包含的頭文件之一,裡面聲明瞭兩個介面:VLDEnable()
與VLDDisable()
; -
文件
vldapi.cpp
裡面是介面VLDEnable()
與VLDDisable()
的函數定義; -
文件
vldint.h
裡面定義了dbghelp.dll
中一些函數的別名,並聲明瞭VisualLeakDetector
類; -
文件
vldutil.h
裡面定義了一些VLD
內部使用的巨集,重載了內部的new/delete
運算符,並聲明瞭CallStack
類與BlockMap
類,這兩個類是VLD
自定義的數據結構,用來存儲泄漏信息,CallStack
類似於STL vector
,BlockMap
類似於STL map
; -
文件
vldutil.cpp
為CallStack
與BlockMap
的類方法實現; -
文件
vld.h
為使用VLD
庫時需包含的頭文件之一,裡面是一些配置選項的巨集定義,用戶可使用這些巨集來定製VLD
的功能。特別地,這個文件里有以下一行代碼,用來強制引用VLD
庫中的全局對象visualleakdetector
,使其鏈接到當前程式(資料參考 MSDN-pragma-comment、MSDN-Linker-options、MSDN-/INCLUDE)。// Force a symbolic reference to the global VisualLeakDetector class object from // the library. This enusres that the object is linked with the program, even // though nobody directly references it outside of the library. #pragma comment(linker, "/include:?visualleakdetector@@3VVisualLeakDetector@@A")
-
文件
vld.cpp
為VisualLeakDetector
的類方法實現,主要功能的代碼都在這個文件里;
3. 源碼剖析
3.1 註冊自定義 AllocHook 函數
使用 #pragma init_seg (compiler)
指令構造一個全局對象 visualleakdetector
,來確保這個對象的構造函數最先被調用(詳見 vld.cpp
第 49~55 行)。
// The one and only VisualLeakDetector object instance. This is placed in the
// "compiler" initialization area, so that it gets constructed during C runtime
// initialization and before any user global objects are constructed. Also,
// disable the warning about us using the "compiler" initialization area.
#pragma warning (disable:4074)
#pragma init_seg (compiler)
VisualLeakDetector visualleakdetector;
在全局對象 visualleakdetector
的構造函數中調用 _CrtSetAllocHook 介面註冊自定義 AllocHook
函數,使程式能捕捉之後的記憶體操作(記憶體分配/記憶體釋放)事件(詳見 vld.cpp
第 57~95 行)。
// Constructor - Dynamically links with the Debug Help Library and installs the
// allocation hook function so that the C runtime's debug heap manager will
// call the hook function for every heap request.
//
VisualLeakDetector::VisualLeakDetector ()
{
...
if (m_tlsindex == TLS_OUT_OF_INDEXES) {
report("ERROR: Visual Leak Detector: Couldn't allocate thread local storage.\n");
}
else if (linkdebughelplibrary()) {
// Register our allocation hook function with the debug heap.
m_poldhook = _CrtSetAllocHook(allochook);
report("Visual Leak Detector Version "VLD_VERSION" installed ("VLD_LIBTYPE").\n");
...
}
report("Visual Leak Detector is NOT installed!\n");
}
此外,在 visualleakdetector
的構造函數中,還做了以下工作:
- 初始化
VLD
的配置信息,詳見vld.cpp
第 71~75 行、第 84~90 行、以及reportconfig()
函數,詳見vld.cpp
第 768~800 行。 - 動態載入
dbghelp.dll
庫,用於後續獲取調用堆棧信息,詳見linkdebughelplibrary()
函數,vld.cpp
第 662~741 行,所使用的dbghelp.dll
庫版本為6.3.17.0
。
3.2 使用 StackWalk64 獲取調用堆棧信息
全局對象 visualleakdetector
有一個成員變數 m_mallocmap
,用來存儲堆記憶體分配時的調用堆棧信息,這是一種基於紅黑樹的自定義 Map
容器(類似於 STL
的 map
),這個容器的聲明及定義可見 vldutil.h
和 vldutil.cpp
文件 。
////////////////////////////////////////////////////////////////////////////////
//
// The BlockMap Class
//
// This data structure is similar in concept to a STL map, but is specifically
// tailored for use by VLD, making it more efficient than a standard STL map.
//
// The purpose of the BlockMap is to map allocated memory blocks (via their
// unique allocation request numbers) to the call stacks that allocated them.
// One of the primary concerns of the BlockMap is to be able to quickly insert
// search and delete. For this reason, the underlying data structure is
// a red-black tree (a type of balanced binary tree).
//
// The red-black tree is overlayed on top of larger "chunks" of pre-allocated
// storage. These chunks, which are arranged in a linked list, make it possible
// for the map to have reserve capacity, allowing it to grow dynamically
// without incurring a heap hit each time a new element is added to the map.
//
class BlockMap
{
...
};
每次進行記憶體操作(alloc/realloc/free
)時,都會自動執行前述自定義的 AllocHook
函數,其定義如下,詳見 vld.cpp
第 175~260 行。
// allochook - This is a hook function that is installed into Microsoft's
// CRT debug heap when the VisualLeakDetector object is constructed. Any time
// an allocation, reallocation, or free is made from/to the debug heap,
// the CRT will call into this hook function.
//
// Note: The debug heap serializes calls to this function (i.e. the debug heap
// is locked prior to calling this function). So we don't need to worry about
// thread safety -- it's already taken care of for us.
//
// - type (IN): Specifies the type of request (alloc, realloc, or free).
//
// - pdata (IN): On a free allocation request, contains a pointer to the
// user data section of the memory block being freed. On alloc requests,
// this pointer will be NULL because no block has actually been allocated
// yet.
//
// - size (IN): Specifies the size (either real or requested) of the user
// data section of the memory block being freed or requested. This function
// ignores this value.
//
// - use (IN): Specifies the "use" type of the block. This can indicate the
// purpose of the block being requested. It can be for internal use by
// the CRT, it can be an application defined "client" block, or it can
// simply be a normal block. Client blocks are just normal blocks that
// have been specifically tagged by the application so that the application
// can separately keep track of the tagged blocks for debugging purposes.
//
// - request (IN): Specifies the allocation request number. This is basically
// a sequence number that is incremented for each allocation request. It
// is used to uniquely identify each allocation.
//
// - filename (IN): String containing the filename of the source line that
// initiated this request. This function ignores this value.
//
// - line (IN): Line number within the source file that initiated this request.
// This function ignores this value.
//
// Return Value:
//
// Always returns true, unless another allocation hook function was already
// installed before our hook function was called, in which case we'll return
// whatever value the other hook function returns. Returning false will
// cause the debug heap to deny the pending allocation request (this can be
// useful for simulating out of memory conditions, but Visual Leak Detector
// has no need to make use of this capability).
//
int VisualLeakDetector::allochook (int type, void *pdata, size_t size, int use, long request, const unsigned char *file, int line)
{
...
// Call the appropriate handler for the type of operation.
switch (type) {
case _HOOK_ALLOC:
visualleakdetector.hookmalloc(request);
break;
case _HOOK_FREE:
visualleakdetector.hookfree(pdata);
break;
case _HOOK_REALLOC:
visualleakdetector.hookrealloc(pdata, request);
break;
default:
visualleakdetector.report("WARNING: Visual Leak Detector: in allochook(): Unhandled allocation type (%d).\n", type);
break;
}
...
}
這個函數的輸入參數中,有一個 request
值,這個值被用來做為所分配記憶體塊的唯一標識符,即 m_mallocmap
的 key
值。函數體中,會根據記憶體操作事件的類型做對應的處理,hookmalloc()
、hookfree()
與 hookrealloc()
的定義詳見 vld.cpp
第 594~660 行。
void VisualLeakDetector::hookfree (const void *pdata)
{
long request = pHdr(pdata)->lRequest;
m_mallocmap->erase(request);
}
void VisualLeakDetector::hookmalloc (long request)
{
CallStack *callstack;
if (!enabled()) {
// Memory leak detection is disabled. Don't track allocations.
return;
}
callstack = m_mallocmap->insert(request);
getstacktrace(callstack);
}
void VisualLeakDetector::hookrealloc (const void *pdata, long request)
{
// Do a free, then do a malloc.
hookfree(pdata);
hookmalloc(request);
}
(1)若涉及到分配新記憶體,則使用內聯彙編技術獲取當前程式地址,然後將其作為參數初值,迴圈調用 StackWalk64
介面獲得完整的調用堆棧信息 CallStack
(調用堆棧中各指令的地址信息),詳見 getstacktrace()
函數,vld.cpp
第 530~592 行,接著與 request
值關聯一起插入到 m_mallocmap
中。如下所示,其中的 pStackWalk64
是一個函數指針,指向 dbghelp.dll
庫中的 StackWalk64
函數。
void VisualLeakDetector::getstacktrace (CallStack *callstack)
{
DWORD architecture;
CONTEXT context;
unsigned int count = 0;
STACKFRAME64 frame;
DWORD_PTR framepointer;
DWORD_PTR programcounter;
// Get the required values for initialization of the STACKFRAME64 structure
// to be passed to StackWalk64(). Required fields are AddrPC and AddrFrame.
#if defined(_M_IX86) || defined(_M_X64)
architecture = X86X64ARCHITECTURE;
programcounter = getprogramcounterx86x64();
__asm mov [framepointer], BPREG // Get the frame pointer (aka base pointer)
#else
// If you want to retarget Visual Leak Detector to another processor
// architecture then you'll need to provide architecture-specific code to
// retrieve the current frame pointer and program counter in order to initialize
// the STACKFRAME64 structure below.
#error "Visual Leak Detector is not supported on this architecture."
#endif // defined(_M_IX86) || defined(_M_X64)
// Initialize the STACKFRAME64 structure.
memset(&frame, 0x0, sizeof(frame));
frame.AddrPC.Offset = programcounter;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = framepointer;
frame.AddrFrame.Mode = AddrModeFlat;
// Walk the stack.
while (count < _VLD_maxtraceframes) {
count++;
if (!pStackWalk64(architecture, m_process, m_thread, &frame, &context,
NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) {
// Couldn't trace back through any more frames.
break;
}
if (frame.AddrFrame.Offset == 0) {
// End of stack.
break;
}
// Push this frame's program counter onto the provided CallStack.
callstack->push_back((DWORD_PTR)frame.AddrPC.Offset);
}
}
通過內聯彙編獲取當前程式地址的代碼詳見 getprogramcounterx86x64()
函數,vld.cpp
第 501~528 行,如下,通過 return
這個函數的返回地址得到。
// getprogramcounterx86x64 - Helper function that retrieves the program counter
// (aka the EIP (x86) or RIP (x64) register) for getstacktrace() on Intel x86
// or x64 architectures (x64 supports both AMD64 and Intel EM64T). There is no
// way for software to directly read the EIP/RIP register. But it's value can
// be obtained by calling into a function (in our case, this function) and
// then retrieving the return address, which will be the program counter from
// where the function was called.
//
// Note: Inlining of this function must be disabled. The whole purpose of this
// function's existence depends upon it being a *called* function.
//
// Return Value:
//
// Returns the caller's program address.
//
#if defined(_M_IX86) || defined(_M_X64)
#pragma auto_inline(off)
DWORD_PTR VisualLeakDetector::getprogramcounterx86x64 ()
{
DWORD_PTR programcounter;
__asm mov AXREG, [BPREG + SIZEOFPTR] // Get the return address out of the current stack frame
__asm mov [programcounter], AXREG // Put the return address into the variable we'll return
return programcounter;
}
#pragma auto_inline(on)
#endif // defined(_M_IX86) || defined(_M_X64)
(2)若涉及到釋放舊記憶體,則從 m_mallocmap
中去除這個記憶體塊對應的 request
值及 CallStack
信息,詳見 hookfree()
函數。
3.3 遍歷雙向鏈表生成泄漏檢測報告
程式結束時,全局對象 visualleakdetector
的析構函數最後被調用(因為構造順序與析構順序相反)。在它的析構函數中(詳見 vld.cpp
第 97~173 行),主要做了以下幾件事:
(1)註銷自定義 AllocHook
函數。
// Unregister the hook function.
pprevhook = _CrtSetAllocHook(m_poldhook);
if (pprevhook != allochook) {
// WTF? Somebody replaced our hook before we were done. Put theirs
// back, but notify the human about the situation.
_CrtSetAllocHook(pprevhook);
report("WARNING: Visual Leak Detector: The CRT allocation hook function was unhooked prematurely!\n"
" There's a good possibility that any potential leaks have gone undetected!\n");
}
(2)生成泄漏檢測報告。詳見 reportleaks()
函數,vld.cpp
第 802~962 行。報告生成思路如下:
-
在
Debug
模式下,每次分配記憶體時,系統都會給分配的數據塊加上一個記憶體管理頭_CrtMemBlockHeader
,如下所示,這個結構體有pBlockHeaderNext
及pBlockHeaderPrev
兩個成員變數,通過它們可以訪問到其他已分配的記憶體塊,全部的記憶體管理頭組合在一起形成了一個雙向鏈表結構,而新加入的記憶體管理頭會被放置在該鏈表的頭部。當釋放記憶體時,對應的節點會在鏈表中被剔除。typedef struct _CrtMemBlockHeader { struct _CrtMemBlockHeader* pBlockHeaderNext; struct _CrtMemBlockHeader* pBlockHeaderPrev; char* szFileName; int nLine; #ifdef _WIN64 /* These items are reversed on Win64 to eliminate gaps in the struct * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is * maintained in the debug heap. */ int nBlockUse; size_t nDataSize; #else /* _WIN64 */ size_t nDataSize; int nBlockUse; #endif /* _WIN64 */ long lRequest; unsigned char gap[nNoMansLandSize]; /* followed by: * unsigned char data[nDataSize]; * unsigned char anotherGap[nNoMansLandSize]; */ } _CrtMemBlockHeader;
因此只需要臨時
new
一塊記憶體,就可以根據這個臨時記憶體塊的地址推導出鏈表頭指針,如下代碼中的pheader
。然後遍歷這個鏈表,可以得到程式快結束時,仍未釋放的記憶體信息。pheap = new char; pheader = pHdr(pheap)->pBlockHeaderNext; delete pheap;
-
遍歷過程中,依據每個節點的
nBlockUse
值,可以分辨出當前記憶體塊的來由:用戶分配、CRT
分配、還是VLD
分配,據此可做一個篩選,例如只考慮來自用戶分配的記憶體。if (_BLOCK_TYPE(pheader->nBlockUse) == _CRT_BLOCK) { // Skip internally allocated blocks. pheader = pheader->pBlockHeaderNext; continue; }
-
遍歷過程中,在
m_mallocmap
中查找篩選後節點的lRequest
值,若存在,則表明有記憶體泄漏發生,由此可以獲得發生泄漏的調用堆棧信息CallStack
,這是一系列指令地址。接下來,將這些指令地址作為輸入參數,迴圈調用SymGetLineFromAddr64
獲得源文件名和行數,調用SymFromAddr
獲得函數名。將獲取的信息傳遞給report()
函數。callstack = m_mallocmap->find(pheader->lRequest); if (callstack) { // Found a block which is still in the allocated list, and which we // have an entry for in the allocated block map. We've identified a // memory leak. if (leaksfound == 0) { report("WARNING: Visual Leak Detector detected memory leaks!\n"); } leaksfound++; report("---------- Block %ld at "ADDRESSFORMAT": %u bytes ----------\n", pheader->lRequest, pbData(pheader), pheader->nDataSize); if (_VLD_configflags & VLD_CONFIG_AGGREGATE_DUPLICATES) { // Aggregate all other leaks which are duplicates of this one // under this same heading, to cut down on clutter. duplicates = eraseduplicates(pheader->pBlockHeaderNext, pheader->nDataSize, callstack); if (duplicates) { report("A total of %lu leaks match this size and call stack. Showing only the first one.\n", duplicates + 1); leaksfound += duplicates; } } report(" Call Stack:\n"); // Iterate through each frame in the call stack. for (frame = 0; frame < callstack->size(); frame++) { // Try to get the source file and line number associated with // this program counter address. if ((foundline = pSymGetLineFromAddr64(m_process, (*callstack)[frame], &displacement, &sourceinfo)) == TRUE) { // Unless the "show useless frames" option has been enabled, // don't show frames that are internal to the heap or Visual // Leak Detector. There is virtually no situation where they // would be useful for finding the source of the leak. if (!(_VLD_configflags & VLD_CONFIG_SHOW_USELESS_FRAMES)) { if (strstr(sourceinfo.FileName, "afxmem.cpp") || strstr(sourceinfo.FileName, "dbgheap.c") || strstr(sourceinfo.FileName, "new.cpp") || strstr(sourceinfo.FileName, "vld.cpp")) { continue; } } } // Try to get the name of the function containing this program // counter address. if (pSymFromAddr(m_process, (*callstack)[frame], &displacement64, pfunctioninfo)) { functionname = pfunctioninfo->Name; } else { functionname = "(Function name unavailable)"; } // Display the current stack frame's information. if (foundline) { report(" %s (%d): %s\n", sourceinfo.FileName, sourceinfo.LineNumber, functionname); } else { report(" "ADDRESSFORMAT" (File and line number not available): ", (*callstack)[frame]); report("%s\n", functionname); } } // Dump the data in the user data section of the memory block. if (_VLD_maxdatadump != 0) { dumpuserdatablock(pheader); } report("\n"); }
-
在
report()
函數中格式化後再使用OutputDebugString
輸出泄漏報告。這裡用到了C
語言中的變長參數,用法可參考 博客園-C++ 實現可變參數的三個方法。// report - Sends a printf-style formatted message to the debugger for display. // // - format (IN): Specifies a printf-compliant format string containing the // message to be sent to the debugger. // // - ... (IN): Arguments to be formatted using the specified format string. // // Return Value: // // None. // void VisualLeakDetector::report (const char *format, ...) { va_list args; #define MAXREPORTMESSAGESIZE 513 char message [MAXREPORTMESSAGESIZE]; va_start(args, format); _vsnprintf(message, MAXREPORTMESSAGESIZE, format, args); va_end(args); message[MAXREPORTMESSAGESIZE - 1] = '\0'; OutputDebugString(message); }
(3)卸載 dbghelp.dll
庫。
// Unload the Debug Help Library.
FreeLibrary(m_dbghelp);
(4)泄漏自檢。通過遍歷系統用於記憶體管理的雙向鏈表,判斷 VLD
自身是否發生記憶體泄漏,同樣是依據每個節點的 nBlockUse
值。
// Do a memory leak self-check.
pheap = new char;
pheader = pHdr(pheap)->pBlockHeaderNext;
delete pheap;
while (pheader) {
if (_BLOCK_SUBTYPE(pheader->nBlockUse) == VLDINTERNALBLOCK) {
// Doh! VLD still has an internally allocated block!
// This won't ever actually happen, right guys?... guys?
internalleaks++;
leakfile = pheader->szFileName;
leakline = pheader->nLine;
report("ERROR: Visual Leak Detector: Detected a memory leak internal to Visual Leak Detector!!\n");
report("---------- Block %ld at "ADDRESSFORMAT": %u bytes ----------\n", pheader->lRequest, pbData(pheader), pheader->nDataSize);
report("%s (%d): Full call stack not available.\n", leakfile, leakline);
dumpuserdatablock(pheader);
report("\n");
}
pheader = pheader->pBlockHeaderNext;
}
if (_VLD_configflags & VLD_CONFIG_SELF_TEST) {
if ((internalleaks == 1) && (strcmp(leakfile, m_selftestfile) == 0) && (leakline == m_selftestline)) {
report("Visual Leak Detector passed the memory leak self-test.\n");
}
else {
report("ERROR: Visual Leak Detector: Failed the memory leak self-test.\n");
}
}
(5)輸出卸載成功的提示信息。這一輸出發生在析構函數的結尾括弧 }
前。
report("Visual Leak Detector is now exiting.\n");
4. 其他問題
4.1 如何區分分配記憶體的來由
_CrtMemBlockHeader
結構體有個 nBlockUse
成員變數,用來標識分配用途,這個值是可以人為設置的,VLD
正是利用這一點,重載了 VLD
內部使用的記憶體分配函數,使得庫內部每次進行記憶體請求時,都會將這個 nBlockUse
設置為 VLD
分配標識,詳見 vldutil.h
第 49~153 行。
(1)分配時,核心代碼如下,第二個參數為設置的 nBlockUse
值:
void *pdata = _malloc_dbg(size, _CRT_BLOCK | (VLDINTERNALBLOCK << 16), file, line);
(2)使用 nBlockUse
來對分配用途做判斷時,核心代碼如下:
// 判斷是否由 CRT 或 VLD 分配
if (_BLOCK_TYPE(pheader->nBlockUse) == _CRT_BLOCK) {
...
}
// 判斷是否由 VLD 分配
if (_BLOCK_SUBTYPE(pheader->nBlockUse) == VLDINTERNALBLOCK) {
...
}
(3)這裡面涉及到的幾個巨集定義如下:
文件 crtdbg.h
中。
#define _BLOCK_TYPE(block) (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block) (block >> 16 & 0xFFFF)
// Memory block identification
#define _FREE_BLOCK 0
#define _NORMAL_BLOCK 1
#define _CRT_BLOCK 2
#define _IGNORE_BLOCK 3
#define _CLIENT_BLOCK 4
#define _MAX_BLOCKS 5
文件 vldutil.h
中。
#define VLDINTERNALBLOCK 0xbf42 // VLD internal memory block subtype
4.2 如何實現多線程檢測
使用線程本地存儲(Thread Local Storage),參考 MicroSoft-Using-Thread-Local-Storage。全局對象 visualleakdetector
有個成員變數 m_tlsindex
,詳見 vldint.h
第 146 行,如下:
DWORD m_tlsindex; // Index for thread-local storage of VLD data
這個變數被用來接收 TlsAlloc()
返回的索引值,在 visualleakdetector
的構造函數中被初始化,詳見 vld.cpp
第 69 行、77~79 行,如下:
m_tlsindex = TlsAlloc();
...
if (m_tlsindex == TLS_OUT_OF_INDEXES) {
report("ERROR: Visual Leak Detector: Couldn't allocate thread local storage.\n");
}
初始化成功後,當前進程的任何線程都可以使用這個索引值來存儲和訪問對應線程本地的值,不同線程間互不影響,訪問獲得的結果也與其他線程無關,因此可用它來存儲 VLD
在每個線程中的開關狀態。在分配新記憶體時,會觸發 hookmalloc()
函數,該函數會在分配行為所屬的線程中執行,詳見 vld.cpp
第 611~636 行:
void VisualLeakDetector::hookmalloc (long request)
{
CallStack *callstack;
if (!enabled()) {
// Memory leak detection is disabled. Don't track allocations.
return;
}
callstack = m_mallocmap->insert(request);
getstacktrace(callstack);
}
(1)判斷當前線程是否開啟了 VLD
。在 enabled()
函數中,會調用 TlsGetValue()
訪問所屬線程本地的值,根據此值判斷 VLD
記憶體檢測功能是否處於開啟狀態。若是第一次訪問(此時 TlsGetValue()
的返回值為 VLD_TLS_UNINITIALIZED
),則根據用戶配置,使用 TlsSetValue()
初始化對應線程本地的值。
// enabled - Determines if memory leak detection is enabled for the current
// thread.
//
// Return Value:
//
// Returns true if Visual Leak Detector is enabled for the current thread.
// Otherwise, returns false.
//
bool VisualLeakDetector::enabled ()
{
unsigned long status;
status = (unsigned long)TlsGetValue(m_tlsindex);
if (status == VLD_TLS_UNINITIALIZED) {
// TLS is uninitialized for the current thread. Use the initial state.
if (_VLD_configflags & VLD_CONFIG_START_DISABLED) {
status = VLD_TLS_DISABLED;
}
else {
status = VLD_TLS_ENABLED;
}
// Initialize TLS for this thread.
TlsSetValue(m_tlsindex, (LPVOID)status);
}
return (status & VLD_TLS_ENABLED) ? true : false;
}
(2)對當前線程設置 VLD
的開關狀態。這是兩個對外的介面函數,其定義如下,詳見 vldapi.cpp
第 31~57 行,使用 TlsSetValue()
設置對應值即可:
void VLDEnable ()
{
if (visualleakdetector.enabled()) {
// Already enabled for the current thread.
return;
}
// Enable memory leak detection for the current thread.
TlsSetValue(visualleakdetector.m_tlsindex, (LPVOID)VLD_TLS_ENABLED);
visualleakdetector.m_status &= ~VLD_STATUS_NEVER_ENABLED;
}
void VLDDisable ()
{
if (!visualleakdetector.enabled()) {
// Already disabled for the current thread.
return;
}
// Disable memory leak detection for the current thread.
TlsSetValue(visualleakdetector.m_tlsindex, (LPVOID)VLD_TLS_DISABLED);
}
本文作者:木三百川
本文鏈接:https://www.cnblogs.com/young520/p/17360523.html
版權聲明:本文系博主原創文章,著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請附上出處鏈接。遵循 署名-非商業性使用-相同方式共用 4.0 國際版 (CC BY-NC-SA 4.0) 版權協議。