草庐IT

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

木三百川 2024-02-03 原文

说明

使用 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 行,而且代码注释写得很详细,推荐有兴趣的仔细阅读源码。以下资料可能对理解其检测原理有帮助:

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 只能检测由 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);
}

有关【Visual Leak Detector】核心源码剖析(VLD 1.0)的更多相关文章

  1. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  2. elasticsearch源码关于TransportSearchAction【阶段三】 - 2

    1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>

  3. (附源码)vue3.0+.NET6实现聊天室(实时聊天SignalR) - 2

    参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍  介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。  内容有:    ①:Hub模型的方法介绍    ②:服务器端代码介绍    ③:前端vue3安装并调用后端方法    ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke()  去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on

  4. ruby - 如何让 ruby​​-prof 忽略 Ruby 核心/标准库/gem 方法? - 2

    我是Ruby分析的新手,看起来像ruby-prof是一个受欢迎的选择。我刚刚安装了gem并调用了我的程序:ruby-prof./my-prog.rb但是,输出非常冗长,因为包含所有Ruby核心和标准库方法以及其他gem的分析数据。例如,前三行是:8.790.0110.0100.0000.0013343*String#%7.280.0780.0090.0000.0692068*Array#each4.930.0380.0060.0000.0321098*Array#map这对我来说不是什么有用的信息,因为我已经知道我的程序经常处理字符串和数组,并且大概已经对这些类进行了优化。我只关心我代

  5. Cesium源码解析一(terrain文件的加载、解析与渲染全过程梳理) - 2

    快速导航(持续更新中…)Cesium源码解析一(terrain文件的加载、解析与渲染全过程梳理)Cesium源码解析二(metadataAvailability的含义)Cesium源码解析三(metadata元数据拓展中行列号的分块规则解析)Cesium源码解析四(Quantized-Mesh(.terrain)格式文件在CesiumJS和UE中加载情况的对比)目录1.前言2.本篇的由来3.terrain文件的加载3.1更新环境3.2更新和执行渲染命令3.3数据优化3.4结束当前帧4.总结1.前言  目前市场上三维比较火的实现方案主要有两种,b/s的方案主要是Cesium,c/s的方案主要是u

  6. [面试直通版]操作系统核心之进程、线程与协程(下) - 2

    点击->操作系统复习的文章集目录操作系统线程线程是什么进程与线程的关系用户态/内核态操作系统资源管理内核态用户态内核态/用户态切换程序运行类型分析计算密集型IO密集型结合进程,线程来理解程序运行类型分析协程基础上下文切换协程协程为什么叫协作式线程?协程的优缺点操作系统线程典型问题:简述进程和线程的区别以下内容带您一步步了解线程是什么比进程更小的独立运行的基本单位-线程(Threads)线程的提出主要是为了提高系统内程序并发执行的程度,从而进一步提升系统的吞吐量,充分发挥多核CPU的优越性而设计的引入进程是为了操作系统更加方便地管理程序,使得多个程序能并发管理和执行而线程则是为了减少程序在并发执

  7. 设计一个亿级高并发系统架构 - 12306火车票核心场景DDD领域建模 - 2

    “架设一个亿级高并发系统,是多数程序员、架构师的工作目标。许多的技术从业人员甚至有时会降薪去寻找这样的机会。但并不是所有人都有机会主导,甚至参与这样一个系统。今天我们用12306火车票购票这样一个业务场景来做DDD领域建模。”开篇要实现软件设计、软件开发在一个统一的思想、统一的节奏下进行,就应该有一个轻量级的框架对开发过程与代码编写做一定的约束。虽然DDD是一个软件开发的方法,而不是具体的技术或框架,但拥有一个轻量级的框架仍然是必要的,为了开发一个支持DDD的框架,首先需要理解DDD的基本概念和核心的组件。一.什么是领域驱动设计(DDD)首先要知道DDD是一种开发理念,核心是维护一个反应领域概

  8. 停车系统源码-基于springboot+uniapp开源项目 - 2

    Iparking停车收费管理系统-可商用介绍Iparking是一款基于springBoot的停车收费管理系统,支持封闭车场和路边车场,支持微信支付宝多种支付渠道,支持多种硬件,涵盖了停车场管理系统的所有基础功能。技术栈Springboot,MybatisPlus,Beetl,Mysql,Redis,RabbitMQ,UniApp功能云端功能序号模块功能描述1系统管理菜单管理配置系统菜单2系统管理组织管理管理组织机构3系统管理角色管理配置系统角色,包含数据权限和功能权限配置4系统管理用户管理管理后台用户5系统管理租户管理多租户管理6系统管理公众号配置租户公众号配置7系统管理操作日志审计日志8系统

  9. 打通源码,高效定位代码问题|云效工程师指北 - 2

    大家好,我叫胡飞虎,花名虎仔,目前负责云效旗下产品Codeup代码托管的设计与开发。代码作为企业最核心的数据资产,除了被构建、部署之外还有更大的价值。为了帮助企业和团队挖掘更多源代码价值以赋能日常代码研发、运维等工作,云效代码团队在大数据和智能化方向进行了一系列的探索和实践(例如代码搜索与推荐),本文主要介绍我们如何通过直接打通源代码来提高研发与运维效率。随着微服务架构的流行,一个业务流程需要多个微服务共同完成。一旦出现问题,运维人员在面对数量多、调用链路复杂的情况下,很难快速锁定导致问题发生的罪魁祸首:代码。为了提高排查效率,目前常见的解决方案是:链路跟踪+日志分析工具相结合。即通过链路跟踪

  10. Android Studio开发之使用内容组件Content获取通讯信息讲解及实战(附源码 包括添加手机联系人和发短信) - 2

    运行有问题或需要源码请点赞关注收藏后评论区留言一、利用ContentResolver读写联系人在实际开发中,普通App很少会开放数据接口给其他应用访问。内容组件能够派上用场的情况往往是App想要访问系统应用的通讯数据,比如查看联系人,短信,通话记录等等,以及对这些通讯数据及逆行增删改查。首先要给AndroidMaifest.xml中添加响应的权限配置 下面是往手机通讯录添加联系人信息的例子效果如下分成三个步骤先查出联系人的基本信息,然后查询联系人号码,再查询联系人邮箱代码 ContactAddActivity类packagecom.example.chapter07;importandroid

随机推荐