|

Introduction
OK, So you want a memory leak detector and don't want to pay thru the nose
for it! You've gone ahead and read all the articles on Memory Leak Detection
(whew!!) and are totally confused and frustrated with all the technical details
on how to hook memory, walk a stack, display symbols and still get the
performance you need to run your application. You've Looked at lots of code and
found that it's kind of a big mess to add your own memory leak detection. Well I
hope that I can help out and clear the air. I have managed to create a single
class that you can add to your code and find those pesky memory leaks. You can
do this in debug mode or final release mode and customize your own memory leak
detection.
Background
Ok, I worked for a big corporation and demanded they buy me all those
expensive development toys :-) such as memory leak detection tools that I
needed. Who cares how much it costs, I said! It's worth every penny! You don't
want your application crashing on your customer premises do you? You don't want
to consume our clients computing resources do ya? How embarrassing that would be
being a top software development company? Well that was all good and said until
I left that big corporation and started my own company! Wow those development
tools I needed are sure expensive! But the fact is I must have a memory leak
detector to make my applications bullet proof. To give me the confidence that my
application is working at it's best! Not that I write leaky code or anything,
ooooh Nooooo not me :-(. I point the finger at all the other people that I link
into my code. From poorly or undocumented API's to complex source code I'd
rather not look at. It's their fault for not telling me to free this or free
that or delete this or delete that. hmmm. But it's my fault if I don't even
check my application before I distribute it to others and it's eats all their
memory!
There's no excuse to have leaky apps, your reputation is on the line, you
need to test your applications and make sure they are not leaking and consuming
unnecessary resources.
Before you Use the Code
Make sure that the dbghelp.dll file is an up to date version 6.1.17.2
or later. You can distribute this file with your application if you wish. If you
don't know where to get this file you can download it from the Microsoft site http://www.microsoft.com/whdc/ddk/debugging/
Before you install the "Debugging Tools for Windows" rename the current
dbghelp.dll to dbghelp_OLD.dll first. I have found that this file will not
update if there is one already installed.
Using the code
CMemLeakDetect() class is the only class you need to worry
about. In your code just include the "MemLeakDetect.h" file and then create a
global instance of the class with CMemLeadDetect memLeakDetect;
This has the effect of catching any memory leaks that are present in your
application before "theApp" starts execution and after it has exited. It will
also work for non-MFC apps, Win32 apps, Console Apps. It's too easy! Well isn't
that what it's all about! Making your life easier?
#include "stdafx.h"
#include "MFCLeakerTest.h"
#include "MFCLeakerTestDlg.h"
#include "MemLeakDetect.h"
BEGIN_MESSAGE_MAP(CMFCLeakerTestApp, CWinApp)
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
CMFCLeakerTestApp::CMFCLeakerTestApp()
{
CLeakMemory* pMem;
pMem = new CLeakMemory();
}
#ifdef _DEBUG
CMemLeakDetect memLeakDetect;
#endif
CMFCLeakerTestApp theApp;
BOOL CMFCLeakerTestApp::InitInstance()
{
InitCommonControls();
CWinApp::InitInstance();
AfxEnableControlContainer();
...
}
Understanding the Code
This class really looks pretty straight forward. The public methods are very
simple to understand. Init() initializes the CMemLeadDetect class.
End() does the memory report of unfreed memory when the destructor
method is called. AddMemoryTrace() - is when an alloc memory orcurrs,
RemoveMemoryTrace() - is a free memory event, RedoMemoryTrace() -
is a re-alloc memory event. I must point out that the fileName is limited to
MLD_MAX_NAME_LENGTH(256) characters. If you anticipate more than
this as a file name then please increase this limit. Also the same for traceinfo
MLD_MAX_TRACEINFO(256) . This means that if you expect that there
will be more than 256 functions deep when a memory allocation occurs then by all
means increase this limit also. Personally I've seen it go to 230 entries but it
was really unusual but not impossible. I would say that 256 entries is kind of
ordinary but 512 entries is very large. Somewhere in between maybe good but it's
up to you.
The AllocBlockInfo() class is a sub-class of
CMemLeakDetect() because this is what keeps track of all of the
allocations and free's. It's indexed by the request number that allocates,
reallocates or frees memory in a CMap list which is a hash list.
Because it's a hash list it's important to initialize it so that all the
allocations in the hash list are done before the application initializes. This
will keep your application from being sluggish with the
CMemLeakDetect. But don't just stick any number in the hash list
because you can seriously degrade a hash list performance if it starts dividing
even slots which in turn are called collisions. To avoid these collisions (as
many as we can) it's best to initialize it with a prime number such as
AllocatedMemoryList.InitHastTable(10211, TRUE) as I have done. When
your application ends and the memory report dumps all the unfreed memory and a
summary you will see a total allocations that occurred in your application. If
you have more than a total of 10211 allocations you should bump up the prime
number to the one that is more than your maximum. If you want more entries find
a bigger prime number. This pre-allocation of hash table entries will help your
performance tremendously during runtime. The STACKFRAMEENTRY is a
structure that is an array of traceinfo starting at location [0] which is always
the CMemLeakDetect() methods and it walks the stack to all the
callers that exist on the stack [1], [2].... and so on. The last traceinfo entry
is [n] = 0;
#define MLD_MAX_NAME_LENGTH 256
#define MLD_MAX_TRACEINFO 256
typedef struct _STACKFRAMEENTRY
{
ADDRESS AddrPC;
ADDRESS AddrFrame;
} STACKFRAMEENTRY;
class CMemLeakDetect
{
public:
class AllocBlockInfo
{
public:
inline AllocBlockInfo() {};
inline ~AllocBlockInfo() {};
inline AllocBlockInfo(AllocBlockInfo& abi)
{
address = abi.address;
size = abi.size;
lineNumber = abi.lineNumber;
occurance = abi.occurance;
memcpy(&traceinfo[0], &abi.traceinfo[0], sizeof(traceinfo));
memcpy(fileName, abi.fileName, sizeof(fileName));
};
void* address;
DWORD size;
char fileName[MLD_MAX_NAME_LENGTH];
DWORD lineNumber;
DWORD occurance;
STACKFRAMEENTRY traceinfo[MLD_MAX_TRACEINFO];
};
public:
CMemLeakDetect();
~CMemLeakDetect();
void Init();
void End();
void addMemoryTrace(void* addr, DWORD asize, char *fname, DWORD lnum);
void redoMemoryTrace(void* addr, void* oldaddr,
DWORD asize, char *fname, DWORD lnum);
void removeMemoryTrace(void* addr);
void cleanupMemoryTrace();
void dumpMemoryTrace();
CMap< LPVOID, LPVOID, AllocBlockInfo, AllocBlockInfo > m_AllocatedMemoryList;
DWORD memoccurance;
bool isLocked;
private:
BOOL initSymInfo(char* lpUserPath);
BOOL cleanupSymInfo();
void symbolPaths( char* lpszSymbolPaths);
void symStackTrace(STACKFRAMEENTRY* pStacktrace);
BOOL symFunctionInfoFromAddresses(ULONG fnAddress,
ULONG stackAddress, char* lpszSymbol);
BOOL symSourceInfoFromAddress(UINT address, char* lpszSourceInfo);
BOOL symModuleNameFromAddress(UINT address, char* lpszModule);
HANDLE m_hProcess;
PIMAGEHLP_SYMBOL m_pSymbol;
DWORD m_dwsymBufSize;
};
The private members are more interesting because that is where a lot of the
tricky work has been done! My goal was the keep all the sym* routines as simple
as possible and I tried not to do too many system calls.
symStackTrace() is the function that captures the stack at the time
of any memory operation.
Points of Interest
catchMemoryAllocHook() this is a memory hook routine which is
the at the heart of this CMemDetect class. It's where the most
complex and convoluted part of the code is. Documentation on this area is sparse
and mostly undocumented by our friends at Microsoft. I'm not even sure I have
caught all the situations. If there's going to be a major bug it's most likely
going to show up here. Also I'm only using a simple locking mechanism because I
didn't want to do any system calls for a Critical Section, Semaphores, or
CSimpleLock. All the documentation I read says that only one thread
can access this memory hook at a time and it reentrant safe! Based on that
premise I just used the simplest mechanism.
Credits
I must give credit to Code Glow http://www.codework.com/glowcode/order.html
for the inspiration because I didn't want to pay for it at $299.00 US (a fair
price though), Compuware Corporation http://www.compuware.com/products/devpartner/bounds.htm
again too expensive for my cheap budget! :-) so I wrote my own. Thanks to Erwin
Andreasen & Henner Zeller for LeakTracer http://www.andreasen.org/LeakTracer/
Johan Lindh for MemWatch http://www.linkdata.se/sourcecode.html
R. Walker http://www.codeproject.com/script/Submit/memleackcheckarticle.asp
and Jochen Kalmbach http://www.codeproject.com/tools/leakfinder.asp
in which I found inspiration to provide my own. I did a lot of research and
found an overwhelming amount of information on the subject but nothing was done
in a comprehensive and clear manner. So finally I give credit to myself :-) for
all the late nights and hard work getting this class all debugged and cleaned up
for this article. :-)
History
- Initial Version July 30, 2004, by David A. Jones
- Added Non MFC Version October 6, 2004, by David A. Jones
- Fixed User Issues October 11, 2004 by David A. Jones
- fixed buffer overruns now using
MLD_MAX_NAMELEN
- add symStackTrace2 that is an alternative to
StackWalker64(), in
non-MFC version & untested
- fixed up some lint issues in both versions
- Update October 18, 2004 by David A. Jones
- added an alternative stackwalker using
MLD_CUSTOMSTACKWALK to select which stackwalker to compile
- consolidated the MFC version and the non MFC version. They are both the same code now
- fixed more UNICODE issues, should be UNICODE compliant
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 93 (Total in Forum: 93) (Refresh) | FirstPrevNext |
|
|
 |
|
|
I also found stack problems. Starting my app in Debug Mode with MemLeakDetect, I got a Stack Overflow. Below is the top part of the Call Stack Window (Visual Studio 6). Another thing: The example code in the article "CLeakMemory* pMem; pMem = new CLeakMemory(); " did not compile: no such class is defined. I inspected the header file, and found a class CMemLeakDetect, so I coded: "CMemLeakDetect* pMem; pMem = new CMemLeakDetect(); ". It then compiled. Could this change lead to my issue, or is this just due to code changes after the article was written? Regards, Willem
Stack trace: catchMemoryAllocHook(int 1, void * 0x00000000, unsigned int 5412, int 1, long 1583, const unsigned char * 0x00000000, int 0) line 43 + 6 bytes catchMemoryAllocHook(int 1, void * 0x00000000, unsigned int 5412, int 1, long 1583, const unsigned char * 0x00000000, int 0) line 79 + 34 bytes catchMemoryAllocHook(int 1, void * 0x00000000, unsigned int 5412, int 1, long 1583, const unsigned char * 0x00000000, int 0) line 79 + 34 bytes catchMemoryAllocHook(int 1, void * 0x00000000, unsigned int 5412, int 1, long 1583, const unsigned char * 0x00000000, int 0) line 79 + 34 bytes catchMemoryAllocHook(int 1, void * 0x00000000, unsigned int 5412, int 1, long 1583, const unsigned char * 0x00000000, int 0) line 79 + 34 bytes catchMemoryAllocHook(int 1, void * 0x00000000, unsigned int 5412, int 1, long 1583, const unsigned char * 0x00000000, int 0) line 79 + 34 bytes catchMemoryAllocHook(int 1, void * 0x00000000, unsigned int 5412, int 1, long 1583, const unsigned char * 0x00000000, int 0) line 79 + 34 bytes
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
first of all... awesome work. this piece of code has helped me a lot!
I'm sorry if this issue has already been addressed before in this forum or if its a really stupid question. assume I allocated an array like so: arrayname = new int[10]; I found that when I deleted this array using 'delete arrayname;' instead of 'delete [] arrayname;', it did not detect the memory leak. is there really not a leak?
Julian.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The difference between 'delete p' and 'delete [] p' is, that the []-Version calls destructors for the array members. So in case of your int- or other simple-type arrays, there's no mem-leak, because the destructor doesn't have anything esle to free.
I tried this with VS 2005:
class Dummy { public: Dummy(): mData(new int(42)) {} ~Dummy() { delete mData; OutputDebugString("\n~Dummy()"); } int *mData; };
Dummy *arr=new Dummy [10]; delete arr;
The result was an assertion in the debug built. The release build did not assert, but the Destructor wasn't called. So each Dummy-Object will leave a leak for one int. Using delete [] arr; calls the destructor 10 times as expected.
tbrammer
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Before my question, just a general comment -- great job! I'm working on a reference counting, copy on write class hierarchy, and memory leaks are my #1 concern (since access violations are easy to detect :}). I wrote a console app as a test harness. Oops... No leak detection in MSVC without MFC, apparently. Your class was a great help.
There are many types of leaks that can take place in an application, and not all of them start with malloc. If you are writing a service or anything that has to have a long lifespan, these become critical. GDI objects, synchronization objects, files, pipes, etc. all can leak. Do you know of any tool or technique to monitor these types of leaks?
Chris
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
I was getting meaningless stack traces until I did the following:
1. downloaded this http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx 2. copied dbghelp.dll and dbgeng.dll from (1) to windows\system32 3. make SURE debugger "working directory" is set to the full debug folder, eg "C:\somepath\Debug".
(3) is very important
I can now use the class easily in VC6sp5 on XP using this:
#ifdef _DEBUG #include "MemLeakDetect.h" CMemLeakDetect memLeakDetect; #endif
brilliant and thanks so much to the author !!!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Am I missing something, or is the downloadable code outdated? From the revision log seems like you've fixed the unicode issues, but what I've downloaded is in no way unicode-compliant. Looks like great software, though. Thanks!
|
| Sign In·View Thread·PermaLink | 3.00/5 (2 votes) |
|
|
|
 |
|
|
Code seems to date back to 2004?? Will you be posting an update? I agree with the comment - this is very useful stuff/
gjr
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Hello,
Can you be so kind and share the solution for VS2005?
When I include this tool in my project, I can run it only on computer with VS2005 installed. On computers without it, I get an "Application failed to initialize properly (0x0000034)" error.
Thanks in advance, Ziv
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Appears this has still not been updated with the release of the UNICODE version? Is the author still maintaining this code or have we moved on to something else??
gjr
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Worked OK in the DEBUG mode. But can not compile in the release mode. Here are the error messages:
C:\MemLeakDetect.cpp(32) : error C2065: '_CrtDbgReport' : undeclared identifier MemLeakDetect.cpp(53) : error C2065: '_crtDbgFlag' : undeclared identifier MemLeakDetect.cpp(95) : error C2065: 'pHdr' : undeclared identifier MemLeakDetect.cpp(95) : error C2440: '=' : cannot convert from 'int' to 'struct _CrtMemBlockHeader *' Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast MemLeakDetect.cpp(96) : error C2027: use of undefined type '_CrtMemBlockHeader' C:\Program Files\Microsoft Visual Studio\VC98\INCLUDE\crtdbg.h(139) : see declaration of '_CrtMemBlockHeader' MemLeakDetect.cpp(96) : error C2227: left of '->lRequest' must point to class/struct/union MemLeakDetect.cpp(98) : error C2027: use of undefined type '_CrtMemBlockHeader' C:\Program Files\Microsoft Visual Studio\VC98\INCLUDE\crtdbg.h(139) : see declaration of '_CrtMemBlockHeader' MemLeakDetect.cpp(98) : error C2227: left of '->nBlockUse' must point to class/struct/union MemLeakDetect.cpp(114) : error C2440: '=' : cannot convert from 'int' to 'struct _CrtMemBlockHeader *' Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast MemLeakDetect.cpp(115) : error C2027: use of undefined type '_CrtMemBlockHeader' C:\Program Files\Microsoft Visual Studio\VC98\INCLUDE\crtdbg.h(139) : see declaration of '_CrtMemBlockHeader' MemLeakDetect.cpp(115) : error C2227: left of '->lRequest' must point to class/struct/union MemLeakDetect.cpp(117) : error C2027: use of undefined type '_CrtMemBlockHeader' C:\Program Files\Microsoft Visual Studio\VC98\INCLUDE\crtdbg.h(139) : see declaration of '_CrtMemBlockHeader' MemLeakDetect.cpp(117) : error C2227: left of '->nBlockUse' must point to class/struct/union MemLeakDetect.cpp(276) : error C2440: '=' : cannot convert from 'void' to 'int (__cdecl *)(int,void *,unsigned int,int,long,const unsigned char *,int)' Expressions of type void cannot be converted to other types Generating Code... Error executing cl.exe.
14 error(s), 23 warning(s)
Any idea?
-- modified at 13:55 Friday 10th February, 2006
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
My advice: Don't use this class in release builds at all. Only in debug builds!
#ifdef it all out in release configurations.
I suppose there's a lot more of project settings to alter. + Optimization, + _NDEBUG / _DEBUG preprocessor settings (may affect linking to release/debug libs) + external release/debug libraries + ...
If you changed them all, you don't have a release build anymore 
Cheers tbrammer
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, At first time, big thank to you for this project.
I used your Demo project to see if it's work greatly before I use it in my project. I encounter a problem with the mapping of the malloc function to _malloc_dbg. It's causing a probleme beacause I don't get the source(filename and line) of the memory leak.
I did not found the source of the problem, but it's probably due to the amount of redeffinition of malloc function in _DEBUG mode. I found 5 redefinitions of malloc in this file : dbgheap.c(135), smalheap.c(85), crtdbg.h(416) it's the good one for debug, malloc.h (the true one), stdlib(296)
I use Visual Studio .Net 2003 (v7.1.3088) under windows XP SP1 Pro. Also, I use the version 6.5.3.8 of the dbghelp.dll file, that I found on the web site of MS : http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
In fact, I use your code in my project and it work greatly, thank a lot !
The result I have with your project is : Memory Leak(1)-------------------> Memory Leak <0x76> bytes(4) occurance(0) 0x0012DD88->0x7FFE0304() Memory Leak(2)-------------------> Memory Leak <0x78> bytes(33) occurance(1) 0x0012DD88->0x7FFE0304() Memory Leak(3)-------------------> Memory Leak <0x7A> bytes(4) occurance(2) 0x0012DD88->0x7FFE0304()
Patrik Dufresne
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hi, I think I have a similar problem.
I found a way to use _malloc_dbg: just use #define _CRTDBG_MAP_ALLOC which will cause the app to use the definition in crtdbg.h
But I have another problem: I doesn't work with "new"
When having _CRTDBG_MAP_ALLOC defined, an inlined "new" operator in crtdbg.h will be used, which always results in crtdbg.h as the source file of the alloc.
Redefining new doesn't work either, I get lots of errors like "too much actual arguments to the macro" etc.
Does anyone know a way to make "new" posting the correct files as source of alloc?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, I'm getting an unhandled exception when I used the MemLeakDetect.cpp while debugging. The debugger points to the following line: Line No 431 of MemLeakDetect.cpp
InstructionPtr = ((ADDR *)FramePtr)[1];
could someone help me out?
thanks and regards, Rajesh
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I also have the same problem with MemLeakDetect. I use VC6.0 sp4, installed the latest DebuggingTools from Microsoft.
You help will be very appreciated
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have the same problem too. Visual C++ .NET and the latest debugging utilities. I noticed that FramePtr was = to 4 so I changed line 429 to:
while (FramePtr && FramePtr!=4)
This seems to work, but why does it happen and how should it be fixed properly?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
i faced same problem with 42, 255, 250, 144... and i don't believe in increasing the number of filters. Additionally, on compilation i get about 378 warning about debug code truncated to 255 characters. I want to use this but can't.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just modify the line from: while (FramePtr) to: while (FramePtr && !IsBadReadPtr((LPVOID)FramePtr,sizeof(ADDR)*2))
And the access voilation will fixed. Actually the function "IsBadReadPtr" check if the pointer value is available for read. But ... no memory leak found in my program.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hai, i tried with u r memory trace..
its giving the following error. "ERROR!CMemLeakDetect::removeMemoryTrace() didnt find Address(0x00000056) to free"
what could be the reason
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
double free is the main reason.
Usually this happens when you have freed a memory address more than once or you are freeing a memory address that has been corrupted. Set a break point on this removeMemoryTrace() line and check the stack in your program.
Cheers
|
| Sign In·View Thread·PermaLink | 3.00/5 (2 votes) |
|
|
|
 |
|
|
Hi! I found that when using STL (e.g. std::stringstream class) there are allocated _CRT_BLOCK type blocks, but they are freed as _NORMAL_BLOCK (free() just calls _free_dbg(ptr, _NORMAL_BLOCK)). For _CRT_BLOCK we do not trace allocation, but when they are freed we try to found appropriate trace data.
I've made some good fix for it: At the beginig of catchMemoryAllocHook(), before checking the blockType, add following lines:
if (userData != 0) { _CrtMemBlockHeader * pHead; pHead = pHdr(userData); blockType = pHead->nBlockUse; }
Greatings,
-= Pawel Zarzycki =-
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Whenever I try to use MLD with a multithreaded app I get an Access Violation for line 430
InstructionPtr = ((ADDR *)FramePtr)[1];
The violation is somewhere rather close to NULL (~0x00000480). Is symStackTrace2() thread safe? I don't have any issues when using symStackTrace().
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|