【Visual Leak Detector】核心源碼剖析(VLD 1.0)

来源:https://www.cnblogs.com/young520/archive/2023/04/27/17360523.html
-Advertisement-
Play Games

使用 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 的源碼進行剖析。

Oh Shit!-圖片走丟了-打個廣告-歡迎來博客園關註“木三百川”

version 1.0 的源碼算上註釋一共不到 3000 行,而且代碼註釋寫得很詳細,推薦有興趣的仔細閱讀源碼。以下資料可能對理解其檢測原理有幫助:

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

Oh Shit!-圖片走丟了-打個廣告-歡迎來博客園關註“木三百川”

其中 3.cpp 文件,4.h 文件,2.txt 文件,1.dsp 文件,1.html 文件,各文件用途簡述如下:

  • 文件 README.html 為網頁版的幫助文檔,裡面介紹了 VLD 的功能、使用方法、配置選項、編譯方法、功能限制等。從這個幫助文檔中可以得知:這個版本的 VLD 只能檢測由 newmalloc 導致的記憶體泄漏;若需要檢測多個 DLL 庫,則要確保載入這些庫前,已經包含了 vld.h 頭文件。

  • 文件 CHANGES.txt 為版本迭代日誌,記錄了各版本的更新概要;

  • 文件 COPYING.txtLGPL 2.1 開源協議;

  • 文件 vld.dspVisual 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 vectorBlockMap 類似於 STL map

  • 文件 vldutil.cppCallStackBlockMap 的類方法實現;

  • 文件 vld.h 為使用 VLD 庫時需包含的頭文件之一,裡面是一些配置選項的巨集定義,用戶可使用這些巨集來定製 VLD 的功能。特別地,這個文件里有以下一行代碼,用來強制引用 VLD 庫中的全局對象 visualleakdetector,使其鏈接到當前程式(資料參考 MSDN-pragma-commentMSDN-Linker-optionsMSDN-/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.cppVisualLeakDetector 的類方法實現,主要功能的代碼都在這個文件里;

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 容器(類似於 STLmap),這個容器的聲明及定義可見 vldutil.hvldutil.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_mallocmapkey 值。函數體中,會根據記憶體操作事件的類型做對應的處理,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,如下所示,這個結構體有 pBlockHeaderNextpBlockHeaderPrev 兩個成員變數,通過它們可以訪問到其他已分配的記憶體塊,全部的記憶體管理頭組合在一起形成了一個雙向鏈表結構,而新加入的記憶體管理頭會被放置在該鏈表的頭部。當釋放記憶體時,對應的節點會在鏈表中被剔除。

    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) 版權協議。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大家好,我是DOM哥。我用 ChatGPT 開發了一個 Vue 的資源導航網站。不管你是資深 Vue 用戶,還是剛入門想學習 Vue 的小白,這個網站都能幫助到你。網站地址:https://dombro.site/vue#/vue ...
  • 在業務中,有這麼一種場景,表格下的某一列 ID 值,文本超長了,正常而言會是這樣: 通常,這種情況都需要超長省略溢出打點,那麼,就會變成這樣: 但是,這種展示有個缺點,3 個 ID 看上去就完全一致了,因此,PM 希望能夠實現頭部省略打點,尾部完全展示,那麼,最終希望的效果就會是這樣的: OK,很有 ...
  • CORS(跨來源資源共用)是一種用於解決跨域問題的方案。 CORS(跨來源資源共用)是一種安全機制,用於在瀏覽器和伺服器之間傳遞數據時,限制來自不同功能變數名稱的請求。在前端開發中,當通過 XMLHttpRequest(XHR)或 Fetch API 發送跨域請求時,如果伺服器沒有正確配置 CORS,瀏覽器 ...
  • 唯一不變的就是變化本身。 我們經常講的系統、子系統、模塊、組件、類、函數就是從邏輯上將軟體一步步分解為更細微的部分,即邏輯單元, 分而治之, 複雜問題拆解為若幹簡單問題, 逐個解決。 邏輯單元內部、外部的交互會產生依賴,從而產生了內聚、耦合概念。內聚主要描述邏輯單元內部,耦合主要描述邏輯單元之間的關 ...
  • 微服務架構是將單個服務拆分成一系列小服務,且這些小服務都擁有獨立的進程,彼此獨立,很好地解決了傳統單體應用的上述問題,但是在微服務架構下如何保證事務的一致性呢? ...
  • 通過小demo的方式跟大家分享一下我對DDD中戰術層級的理解,算是拋磚引玉,該理解僅代表我個人在現階段的一個理解,也可能未來隨著業務經驗深入,還會有不同的理解。 ...
  • 簡介 單例模式(Singleton Pattern)屬於創建型設計模式,這種模式只創建一個單一的類,保證一個類只有一個實例,並提供一個訪問該實例的全局節點。 當您想控制實例數目,節省系統資源,並不想混用的時候,可以使用單例模式。單例有很多種實現方式,主要分為懶漢和餓漢模式,同時要通過加鎖來避免線程安 ...
  • 非常簡單的一題了,但還是交了兩三次,原因:對數組的理解不足;對數字和字元之間的轉換不夠敏感。這將在下文中細說。 Given a non-negative integer N, your task is to compute the sum of all the digits of N, and ou ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...