| |||||||||||
Table of contents
What's new?9th March, 2006: The first beta version of the next release of Visual Leak Detector has been posted. Though this is still considered a beta, it should be very stable. This version introduces an all-new leak detection engine that does not have any of the restrictions of the 1.0 version. The most prominent improvement is that the new design is not limited to only detecting leaks from Using this new version is slightly different from using the older versions, but is still very easy to do, so if you decide to give the beta a try, be sure to do a quick read of the installation and use instructions in the README.html file included in the ZIP download. Note that the article below describes the inner workings of the 1.0 version. Because the beta version might change significantly before the next official release, the article will not be updated to reflect the design of the beta version until the official release is complete. IntroductionVisual C++ provides built-in memory leak detection, but its capabilities are minimal at best. This memory leak detector was created as a free alternative to the built-in memory leak detector provided with Visual C++. Here are some of Visual Leak Detector's features, none of which exist in the built-in detector:
Other after-market leak detectors for Visual C++ are already available. But most of the really popular ones, like Purify and BoundsChecker, are very expensive. A few free alternatives exist, but they're often too intrusive, restrictive, or unreliable. Here are some key advantages that Visual Leak Detector has over many other free alternatives:
Visual Leak Detector is licensed free of charge as a service to the Windows developer community. Using Visual Leak DetectorThis section briefly describes the basics of using Visual Leak Detector (VLD). For a more in-depth discussion of the configuration options, runtime APIs, and a discussion of the more advanced usage scenarios (such as using VLD with DLLs), please see the full documentation included in the downloadable Zip files. To use VLD with your project, follow these simple steps:
VLD will detect memory leaks in your program whenever you run the debug version under the Visual C++ debugger. A report of all the memory leaks detected will be displayed in the debugger's output window when your program exits. Double-clicking on a source file's line number in the memory leak report will take you to that file and line in the editor window, allowing easy navigation of the code path leading up to the allocation that resulted in a memory leak. Note: When you build release versions of your program, VLD will not be linked to the executable. So it is safe to leave vld.h included in your source files when doing release builds. Doing so will not result in any performance degradation or any other undesirable overhead. Making a memory leak detectorThe goal of Visual Leak Detector was to build a better replacement for the memory leak detector built-in to Visual C++. With that in mind, I set out to use the same method used by the built-in detector, namely the CRT Debug Heap. But this new detector would provide enhancements - primarily full stack traces, which can be extremely helpful for finding and fixing leaks. The built-in detectorThe built-in detector is pretty simple really. When a program is exiting, the CRT runs a bunch of cleanup code after the Note that the built-in detector detects leaks without doing any monitoring of the allocations or frees. It simply takes a snapshot of the heap just before the process terminates and determines if there are any leaks based on that snapshot. A snapshot of the heap only tells us if there are leaks; it does not tell us how they were leaked. Clearly, to determine the "how" we also need to obtain a stack trace. But to obtain a stack trace, we need to be able to monitor every allocation on-the-fly at runtime. This is what will distinguish our leak detector from the built-in one. Allocation hookingLuckily for us, Microsoft has provided an easy way to monitor every allocation made from the debug heap: allocation hooks. An allocation hook is simply a user-supplied callback function that will be called just before each allocation is made from the debug heap. Microsoft has provided a function, Walking the stackNow that we have a way to be notified every time a block is allocated, as well as a way to uniquely identify each allocation, all that's left to do is to record the call stack each time an allocation occurs. We could conceivably attempt to unwind the stack ourselves using inline assembly. But stack frames can be organized in different ways, depending on compiler optimizations and calling conventions, so it could become complicated to do it that way. Once again, Microsoft has provided us with a tool to help us out. This time it is a function that we can call iteratively to walk the stack, frame by frame. That function is Initializing the memory leak detectorWe now have the beginnings of a better memory leak detector. We can monitor every allocation, and for each allocation monitored, we can obtain and record a stack trace. The only challenge that remains is to ensure that the allocation hook function is registered with the debug heap as soon as the program starts executing. This can be very simply solved by creating a global instance of a C++ class object. The constructor will run when the program is initialized. From the constructor, we can call Detecting memory leaksBecause global objects are destroyed in the inverse order they are constructed, our global object will be destroyed after any user objects. We can then examine the heap, just like the built-in detector does. If we find a block on the heap that has not been freed, it is a leak and we can look up its call stack using the unique ID number recorded by our allocation hook function. An STL map would work fine here for mapping the ID number to the call stacks. I didn't use an STL map because I wanted my library to be compatible with both new and old versions of Visual C++. The STL from older versions is incompatible with the newer versions, so I couldn't use STL components. But the good news is that this gave me the opportunity to create a data structure similar in concept to the STL map, but with specific optimizations for use with my memory leak detector. Do you remember that the built-in leak detector peers inside the memory block to get the name of the file and the line number where the block was allocated? Well, all we have for our call stack is a bunch of program addresses. Dumping all those hex numbers to the debugger wouldn't be of much use. To make those addresses more meaningful, we need to translate them to human readable information: files and line numbers (and function names too). Once again, Microsoft comes through with the tools that will help us do our job: the symbol handler APIs. Like Key parts of the source codeIn case you got bored with the above section and skipped ahead, I'll summarize it here. In a nutshell, this memory leak detector works like this:
Step 1: Registering the allocation hookHere is the
Collapse
// 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 () { // Initialize private data. m_mallocmap = new BlockMap; m_process = GetCurrentProcess(); m_selftestfile = __FILE__; m_status = 0x0; m_thread = GetCurrentThread(); m_tlsindex = TlsAlloc(); if (_VLD_configflags & VLD_CONFIG_SELF_TEST) { // Self-test mode has been enabled. // Intentionally leak a small amount of // memory so that memory leak self-checking can be verified. strncpy(new char [21], "Memory Leak Self-Test", 21); m_selftestline = __LINE__; } 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"); reportconfig(); if (_VLD_configflags & VLD_CONFIG_START_DISABLED) { // Memory leak detection will initially be disabled. m_status |= VLD_STATUS_NEVER_ENABLED; } m_status |= VLD_STATUS_INSTALLED; return; } report("Visual Leak Detector is NOT installed!/n"); } Step 2: Walking the stackHere is the function responsible for obtaining call stacks. This is perhaps the trickiest part of the entire program. Setting up for the first call to Obtaining the address of the current frame is easy: it's stored in a CPU register ( In the following code,
Collapse
// getstacktrace - Traces the stack, starting from this function, as far // back as possible. // // - callstack (OUT): Pointer to an empty CallStack to be populated with // entries from the stack trace. // // Return Value: // // None. // 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(); // Get the frame pointer (aka base pointer) __asm mov [framepointer], BPREG #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); } } And here is the function that retrieves the
Collapse
// getprogramcounterx86x64 - Helper function // that retrieves the program counter // for getstacktrace() on Intel x86 or x64 architectures. // // 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; // 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) Step 3: Generating a better memory leak reportFinally, here is the function that converts the program addresses obtained while walking the stack into useful symbol names. Note that the address-to-symbol conversion code is only run if memory leaks are detected. This avoids having to do symbol lookups on-the-fly while the program is running, which would add considerable additional overhead. Not to mention that it just doesn't make sense to store (large) symbol names for later retrieval when you can store (small) addresses instead. The CRT doesn't expose any documented method for gaining access to its internal linked-list of allocated memory blocks. This linked list is what is used by the built-in detector for taking a "snapshot" of the heap to determine if there are any memory leaks. I've come up with a very simple trick to gain access to the list. Any time a new memory block is allocated, it happens to be placed at the beginning of the linked-list. So, to get a pointer to the head of the list, I just allocate a temporary memory block. That block's address can be converted to the address of the containing In the following code, This function is quite long. To cut down on clutter, I've removed all of the uninteresting and trivial parts. To see this function in its entirety, please download the source ZIP file:
Collapse
// reportleaks - Generates a memory leak report when the program terminates if // leaks were detected. The report is displayed in the debug output window. // // Return Value: // // None. // void VisualLeakDetector::reportleaks () { ... // Initialize the symbol handler. We use it for obtaining source file/line // number information and function names for the memory leak report. symbolpath = buildsymbolsearchpath(); pSymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME); if (!pSymInitialize(m_process, symbolpath, TRUE)) { report("WARNING: Visual Leak Detector: The symbol handler" " failed to initialize (error=%lu)./n" " Stack traces will probably not be available" " for leaked blocks./n", GetLastError()); } ... #ifdef _MT _mlock(_HEAP_LOCK); #endif // _MT pheap = new char; pheader = pHdr(pheap)->pBlockHeaderNext; delete pheap; while (pheader) { ... callstack = m_mallocmap->find(pheader->lRequest); if (callstack) { ... // 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 (pSymGetLineFromAddr64(m_process, (*callstack)[frame], &displacement, &sourceinfo)) { ... } // 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)"; } ... } ... } pheader = pheader->pBlockHeaderNext; } #ifdef _MT _munlock(_HEAP_LOCK); #endif // _MT ... } Known bugs and restrictionsThere are currently no known bugs in the latest release, but there are some known restrictions:
Credits
ReferencesHere are some links to related articles and resources:
LicenseVisual Leak Detector is distributed under the terms of the GNU Lesser General Public License. See the documentation included in the downloadable ZIP files for detailed licensing information. HistoryThis list gives a brief overview of the changes made from release to release. For a detailed description of the changes made in each release, please see the change log, CHANGES.txt, in the downloadable ZIP files.
About Dan Moulding
|