内存泄漏工具VLD1.0_要点分析

0X01 关闭FPO优化

// Frame pointer omission (FPO) optimization should be turned off for this

// entire file. The release VLD libs don't include FPO debug information, so FPO

// optimization will interfere with stack walking.

#pragma optimize ("y", off)

Release版本的VLD库不包含FPO调试信息,进而影响栈回溯,所以文件开头关闭FPO优化;

 

0X02 内存泄漏检测器全局对象

// 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;

对于同一个文件来说,全局变量的构造顺序是按照全局变量的声明顺序来构造的。但对于不同文件来说,不同文件间的全局变量构造顺序是不确定的。在C++标准中,只要求“处于同一编译但与的全局对象按其声明顺序初始化并倒序析构”,但并没有对处于不同编译单元的全局对象的初始化顺序做出要求。

通过init_seg预处理器指令中,:

#pragma init_seg(compiler)

#pragma init_seg(lib)

#pragma init_seg(user)

#pragma init_seg("user_defined_segment_name")

编译器、库、用户和用户定义段,按顺序依次初始化(但据实测,用户和用户定义段的优先级相同);

注:一个源文件中只能出现一次init_seg指令;且编译器段是微软保留给C/C++运行时库用的,一般情况下我们不应该使用它。

VLD为了保证尽可能的全面诊断内存泄漏,势必需要在其他全局变量构造前完成初始化。VLD就利用init_seg了处理器指令,让VLD的VisualLeakDetector在绝大多数情况下早于其他全局变量进行构造和初始化,即上面的实现。

#pragma warning (disable:4074):用于屏蔽C4074编译器警告,否则编译时会提示:warning C4074: initializers put in compiler reserved initialization area

 

0X03 设置内存开辟钩子

VisualLeakDetector::VisualLeakDetector ()

{

。。。 。。。

    else if (linkdebughelplibrary()) { // 显示加载dbghelp.dll中必要的API

        // Register our allocation hook function with the debug heap.

        m_poldhook = _CrtSetAllocHook(allochook);

        report("Visual Leak Detector Version "VLD_VERSION" installed ("VLD_LIBTYPE").\n");

        reportconfig();

。。。 。。。

}

}

 

 

 

0x04 钩子函数

// 该函数不用考虑线程安全问题,CRT有调试堆有锁定

int VisualLeakDetector::allochook (int type, void *pdata, size_t size, int use, long request, const unsigned char *file, int line)

{

    static bool inallochook = false;

    int         status = true;

 

// 1. 防止递归;2.不处理CRT内部使用的内存块

    if (inallochook || (use == _CRT_BLOCK)) {

        if (visualleakdetector.m_poldhook) {

            status = visualleakdetector.m_poldhook(type, pdata, size, use, request, file, line);

        }

        return status;

    }

    inallochook = true;

 

    // 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;

    }

 

    if (visualleakdetector.m_poldhook) {

        status = visualleakdetector.m_poldhook(type, pdata, size, use, request, file, line);

    }

    inallochook = false;

    return status;

}

 

 

0X05 栈回溯

#if defined(_M_IX86) || defined(_M_X64)

#pragma auto_inline(off)

DWORD_PTR VisualLeakDetector::getprogramcounterx86x64 ()

{

DWORD_PTR programcounter;

 

// Get the return address out of the current stack frame

__asm mov AXREG, [BPREG + SIZEOFPTR]

 

// Put the return address into the variable we'll return

    __asm mov [programcounter], AXREG

 

    return programcounter;

}

#pragma auto_inline(on)

#endif // defined(_M_IX86) || defined(_M_X64)

 

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);

    }

}

StackWalk64进行栈回溯需要精确的知道栈从哪里开始。这需要提供当前的栈帧地址和当前的EIP(RIP)寄存器值,而通过GetThreadContext获取的当前线程上下文对于一个正在运行的线程是不可靠的。所以这里通过了内联汇编代码实现,即getprogramcounterx86x64()函数的作用,其返回函数的返回地址,当函数返回时即当前的EIP(RIP)寄存器值。

 

posted @ 2015-11-09 22:37  Through  阅读(417)  评论(0编辑  收藏  举报