|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWith this utility you can simply find memory leaks in your program (CRT and COM-Leaks!). Each leak is displayed with the callstack (including the source line) of the allocation. So, you can easily find leaks, while using the STL. It will also write a file with the callstack if your application crashes (it can also handle stack-overflows!). It almost has no runtime-overhead (runtime-cost). And the best: it is free (GNU Lesser General Public License). Finding memory leaksIt is easy to implement this in your existing VC code:
All the leaks will be listed in the file YouAppName.exe.mem.log in the application directory (only in debug builds; it is deactivated for release builds). This will also activate exception-handling by default (release and debug builds). Only use exception-handlingIf you only want to use exception handing, you need to do the following:
If an exception occurs, it will write a file with the callstack in the application directory with the name YouAppName.exe.exp.log. ExampleA simple example is given below: #include <windows.h> #include "Stackwalker.h" void main() { // Uncomment the following if you only // need the UnhandledException-Filter // (to log unhandled exceptions) // then you can remove the "(De)InitAllocCheck" lines //OnlyInstallUnhandeldExceptionFilter(); InitAllocCheck(); // This shows how the mem-leak function works char *pTest1 = new char[100]; // This shows a COM-Leak CoTaskMemAlloc(120); // This shows the exception handling // and log-file writing for an exception: // If you want to try it, please comment it out... //char *p = NULL; //*p = 'A'; // BANG! DeInitAllocCheck(); } If you execute this example, you will get a file Appication-Name.exe.mem.log with the following content: ##### Memory Report ########################################
11/07/02 09:43:56
##### Leaks: ###############################################
RequestID: 42, Removed: 0, Size: 100
1: 11/07/02 09:43:56
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(359)
+30 bytes (_heap_alloc_dbg)
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(260)
+21 bytes (_nh_malloc_dbg)
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(139) +21 bytes (malloc)
1: f:\vs70builds\9466\vc\crtbld\crt\src\newop.cpp(12) +9 bytes (operator new)
1: d:\privat\memory_and_exception_trace\
memory_and_exception_trace\main.cpp(9) +7 bytes (main)
1: f:\vs70builds\9466\vc\crtbld\crt\src\crt0.c(259)
+25 bytes (mainCRTStartup)
**** Number of leaks: 1
##### COM-Leaks: ###############################################
(shortened)
**** Number of leaks: 1
ExplanationNow, I will explain the Memory-Report-File: RequestID: 42, Removed: 0, Size: 100
This line is the beginning of one leak. If you have more than one leak, then each leak will start with a
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(359)
+30 bytes (_heap_alloc_dbg)
This is an actual stack entry. The stack is shown from the last function on the top going through each callee until the end of the stack is reached.
More options by calling InitAllocCheck
Log-output with more infoYou can also get an output with more info about each stack entry. For this you have to call #include <windows.h> #include "Stackwalker.h" void main() { InitAllocCheck(ACOutput_Advanced); // This shows how the mem-leak function works char *pTest1 = new char[100]; DeInitAllocCheck(); } And here is the (shortened) output: ##### Memory Report ########################################
11/04/02 09:04:04
##### Leaks: ###############################################
RequestID: 45, Removed: 0, Size: 100
1: 11/04/02 09:04:04
// ...
1: 5 main +49 bytes
1: Decl: main
1: Line: d:\privat\memory_and_exception_trace\main.cpp(27) +7 bytes
1: Mod: Memory_and_Exception_Trace, base: 00400000h
1: 6 mainCRTStartup +363 bytes
1: Decl: mainCRTStartup
1: Line: f:\vs70builds\9466\vc\crtbld\crt\src\crt0.c(259) +25 bytes
1: Mod: Memory_and_Exception_Trace, base: 00400000h
1: 7 _BaseProcessStart@4 +35 bytes
1: Decl: _BaseProcessStart@4
1: Mod: kernel32, base: 77e40000h
**** Number of leaks: 1
// ...
ExplanationHere, I will explain the Memory-Report-File: RequestID: 45, Removed: 0, Size: 100
This line is the same as above: 1: 5 main +49 bytes
1: Decl: main
1: Line: d:\privat\memory_and_exception_trace\main.cpp(27) +7 bytes
1: Mod: Memory_and_Exception_Trace, base: 00400000h
XML outputIf you set the first parameter to <MEMREPORT date="11/08/02" time="10:43:47">
<LEAK requestID="47" size="100">
<!-- shortened -->
<STACKENTRY decl="main" decl_offset="+100"
srcfile="d:\...\main.cpp" line="16"
line_offset="+7" module="Memory_and_Exception_Trace" base="00400000"/>
<STACKENTRY decl="mainCRTStartup" decl_offset="+363"
srcfile="f:\...\crt0.c" line="259"
line_offset="+25" module="Memory_and_Exception_Trace" base="00400000"/>
</LEAK>
</MEMREPORT>
It is pretty self explaining if you take a look at the "advanced log output". Mem-leak-analyse toolIf you are using the XML-output format then you can use my MemLeakTool to display the leaks in a sorted order (sorted by callstack). Just select the "xml-leak"-File and press "Read". The callstack will be displayed in a TreeView. If you select a node, the source code will be shown in the right part (if it could be found). Information: This program requires .NET Framework 1.0!
A word on leaksYou should be aware, that some leaks might be the result of other leaks. For example the following code throws two leaks, but if you remove the "originator" of the leaks, the other leak will also disappear. For example: #include <windows.h> #include <stdlib.h> #include "stackwalker.h" class MyTest { public: MyTest(const char *szName) { // The following is the second resulting leak m_pszName = strdup(szName); } ~MyTest() { if (m_pszName != NULL) free(m_pszName); m_pszName = NULL; } protected: char *m_pszName; }; void main() { InitAllocCheck(); // This is the "main" leak MyTest *pTest = new MyTest("This is an example"); DeInitAllocCheck(); } How it works (CRT)The basis of the memory leak logger is a Hashtable with information about all the allocated memory (including callstack). Basically If the application calls In detailHashtableThe Hashtable contains by default 1024 entries. You can change this value if you are doing many allocations and want to reduce the collisions. Just change the As hash-key, the For hashing, a very simple and fast hash-function is used: static inline ULONG AllocHashFunction(long lRequestID) { return lRequestID % ALLOC_HASH_ENTRIES; } // AllocHashFunction Insert an allocation into the HashtableIf a new allocation should be inserted into the Hashtable, first a thread context for the actual thread is made by calling Actually, I only need the current At the moment, I just try to read 0x500 bytes by calling the If the callstack is not 0x500 bytes deep, then the Having the callstack I can simply insert the entry into the Hashtable. If the given hash-entry is already occupied, I make a linked list and append this entry to the end. Building the leak-listIf you call Ignoring allocationsAllocations/frees for How it works (COM)To track COM-memory-leaks, you have to provide an The storage of the callstack is done in the same way as for CRT- A word on COM-leaksActually, there is nothing to say, but... If you are using MSXML 3 or 4 implementation, you have to be aware of the fact that this parser uses a "smart" pseudo-garbage collector. This means that they allocate memory and will not free it after it is used! So, you may see some leaks which are only "cached memory". If you first call For more info see: Understanding the MSXML Garbage Collection Mechanism. MFC usageThe problem with MFC is that the derived You also have to add stackwalk.cpp and stackwalk.h to your project. You also need to add the static struct _test { _test() { InitAllocCheck(); } ~_test() { DeInitAllocCheck(); } } _myLeakFinder; Temporarily disable logging (only CRT)When you don't want to log a special allocation of your application (for whatever reason; MFC does this often), then you can simply deactivate it by disabling the CRT flag #include "Stackwalker.h" #include <crtdbg.h> bool EnableMemoryTracking(bool bTrack) { int nOldState = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); if (bTrack) _CrtSetDbgFlag(nOldState | _CRTDBG_ALLOC_MEM_DF); else _CrtSetDbgFlag(nOldState & ~_CRTDBG_ALLOC_MEM_DF); return nOldState & _CRTDBG_ALLOC_MEM_DF; } void main() { InitAllocCheck(); // The following will be logged char *pTest1 = new char[100]; EnableMemoryTracking(false); // disable logging // The following will NOT be logged char *pTest2 = new char[200]; EnableMemoryTracking(true); // enable logging // The following will be logged char *pTest3 = new char[300]; DeInitAllocCheck(); } Unhandled exceptionsThere are three ways to use this tool for unhandled exceptions. Simple usingIf you just call Second simple usingIf you don't want the int main() { OnlyInstallUnhandeldExceptionFilter(); // do your main code here... } Advanced usingYou can write your own exception filter and just call static LONG __stdcall MyUnhandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs) { LONG lRet; lRet = StackwalkFilter(pExPtrs, EXCEPTION_EXECUTE_HANDLER, _T("\\exception.log")); TCHAR lString[500]; _stprintf(lString, _T("*** Unhandled Exception!\n") _T(" ExpCode: 0x%8.8X\n") _T(" ExpFlags: %d\n") _T(" ExpAddress: 0x%8.8X\n") _T(" Please report!"), pExPtrs->ExceptionRecord->ExceptionCode, pExPtrs->ExceptionRecord->ExceptionFlags, pExPtrs->ExceptionRecord->ExceptionAddress); FatalAppExit(-1,lString); return lRet; } int main() { InitAllocCheck(ACOutput_Advanced, FALSE); SetUnhandledExceptionFilter(MyUnhandlerExceptionFilter); // do some stuff... DeInitAlloocCheck(); } Common mistakesOne of the most common mistakes while using this tool is that you statically instantiate classes in your #include <windows.h> #include "Stackwalker.h" #include <string> void main() { InitAllocCheck(); std::string szTemp; szTemp = "This is a really long string"; DeInitAllocCheck(); } There are two solutions for this. You can start a block after the call to #include <windows.h> #include "Stackwalker.h" #include <string> void main() { InitAllocCheck(); { std::string szTemp; szTemp = "This is a really long string"; } DeInitAllocCheck(); } The second solution is to use the same technique that is used for MFC applications (see above). Visual Studio 7 and Win2K / NTI found a problem with the executables built with VS7 and run on Win2K or NT. The problem is due to an old version of dbghelp.dll. The PDB files generated from VS7 are in a newer format (DIA). It appears that the VS installations do not update dbghelp.dll on Win2K. So the original version (5.0.*) is still on the system and will be used. But with this version it is not possible to read the new PDB format. So, no callstack can be displayed. To get it to work you have to do the followingDownload the latest Debugging Tools for Windows (which includes dbghelp.dll). You have to install it to get the files. But you only need the dbghelp.dll! Now we have another problem. The installer does not replace the original dbghelp.dll. So we need to copy the dbghelp.dll in our EXE dir. Now to make sure the right version is loaded you have to put a file with the name appname.local in your EXE dir (please replace appname with the EXE name (without extension)). Now it should also work on WinNT/2K. Known issues
References
History
| ||||||||||||||||||||||||||||