NirMH
NirMH

Reputation: 4939

Detecting memory leaks during d-tor of objects

My application is a dll based implementation and probably leaks memory during unload. I have noticed it during unload/reload cycles (when hosting process is not killed). the hosting process's virtual memory is increasing.

i have went through a code inspection to try and find the leaking code but didn't find any.

i'm looking for other techniques to detect memory leaks during unload (where objects are being destroyed).

EDIT: I am using win32 (XP) platform.

do you have experience in such tools/procedures? Thanks

Upvotes: 2

Views: 1163

Answers (4)

DXM
DXM

Reputation: 4543

you didn't specify which platform you are looking for. I'm a windows developer so I can only recommend windows solutions. If that's what you are working with, there's a number of commercial and free tools available. Few that I personally worked with: Purify, BoundsChecker, UMDH, LeakDiag, DebugDiag.

Out of these, I typically prefer UMDH. It is free and comes as part of Debugging Tools for Windows (DTW) installation. I found it to actually be more reliable and less resource intensive than most other, including professional tools. It is very simply to use and documentation can be found in a .chm file which comes with DTW install. In the end, I personally find that UMDH has very high signal to noise ratio compared to many other tools.

DebugDiag is another good alternative. As far as I can tell, it uses almost same APIs as UMDH but is slightly more cumbersome to use because it's UI-based rather than command prompt so to get things done usually requires more clicks, but for someone new, I would recommend it over UMDH.

UPDATE:

It's interesting that majority preference was to insert custom hooks into malloc/free and then add even more code for custom hooks into operators new/delete.

I would strongly suggest you take a look at UMDH and learn how it works even if you don't find in necessary in this specific case. At the core of all memory allocations are windows functions, HeapAlloc/HeapFree. Microsoft, anticipating the need for leak detection methods, already provided hooks that we can use at that root level.

These are other advantages of using UMDH over custom allocator hooks:

  • You get full stack trace of each allocation instead of just what's provided by __FILE__ and __LINE__
  • It already has full reporting and aggregation of statistics which is something you'd have to write on top of just intercepting malloc/free. You get # of allocations for each trace, # of bytes allocated by each trace and a list of allocated memory buffers so you can actually analyze what kind of data was leaked.
  • Detecting malloc/free leaks which occurred in someone else's code, not specifically the DLL under your control
  • Detecting leaks from other memory allocation functions such as CoTaskMemAlloc or SysStringAlloc
  • Detecting leaks of COM objects which are not properly released
  • Detecting logic errors in your code when you call into third party API that returns a buffer which you forgot to free.
  • You can use UMDH instantly with any code base without having to add custom code over and over again.
  • The method you accepted only works in debug environment. UMDH can be used just as effectively without any code changes on production systems.

Pretty much, any time there's an upward trend in memory usage, the tool will tell you where it's coming from. Most of the time, I can find a leak within 10 minutes if its reproducible on the development machine (it takes a bit longer when debugging production code because symbol files must be matched, sometimes manually).

So if you get all this completely free with DTW installations (which btw has other awesome debugging features), why do people prefer to roll their own leak detection code?

Upvotes: 2

Mike Nakis
Mike Nakis

Reputation: 62045

What I used to do a long time ago is this:

I wrote my own mymalloc, myrealloc and myfree (and overloaded new and delete so that they would call my functions.) Then I wrote malloc and realloc macros which invoked mymalloc and myrealloc, passing them __FILE__ and __LINE__. What mymalloc did is this: it invoked the malloc function of the standard library, allocating a slightly larger block, and it inserted __FILE__ and __LINE__ in that block. It also kept all allocated blocks in a linked list, so as to be able to traverse them later.

Upon program exit, I would traverse the list of blocks that had not been freed, and I would print out the file and line that were responsible for the memory leaks.

Nowadays I would assume that there would be ready-made tools that you could get to do that kind of stuff for you.

Upvotes: 4

In addition to Mike's answer (overriding Malloc) you can also overide the new and delete operators in Visual Studio.

Disclaimer: I found this code on the net back in 2004 and included it in a C++ project. I don't know the original source

Below is a code sample (which I included as a header file, memleak.h). This is pretty old code so it may have errors compiling! It does illustrate however how to override new and delete. It also dumps unfreed memory to a file. This code becomes effective if the define _DEBUG is included in your code.

Best regards,

#include <iostream>
#include <list>
using namespace std;

//void DumpUnfreed();
//void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum);
//void RemoveTrack(DWORD addr);

typedef struct 
{
    DWORD   address;
    DWORD   size;
    char    file[64];
    DWORD   line;
} ALLOC_INFO;      

typedef list<ALLOC_INFO*> AllocList;   

AllocList *allocList; 

void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum)
{
    ALLOC_INFO *info;         
    if(!allocList) 
    {
        allocList = new(AllocList);
    }         
    info = new(ALLOC_INFO);
    info->address = addr;
    strncpy(info->file, fname, 63);
    info->line = lnum;
    info->size = asize;
    allocList->insert(allocList->begin(), info);
};      

void RemoveTrack(DWORD addr)
{
    AllocList::iterator i;        
    if(!allocList)
        return;
    for(i = allocList->begin(); i != allocList->end(); i++)
    {
        if((*i)->address == addr)
        {
            allocList->remove((*i));
            break;
        }
    }
};

void DumpUnfreed()
{
    AllocList::iterator i;
    DWORD totalSize = 0;
    char buf[1024];   
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
    OutputDebugString("DSP.DLL: Detecting unfreed memory...\n");
    if(!allocList)
    {
        OutputDebugString("No memory allocations were tracked!\n");
        return;       
    }
    for(i = allocList->begin(); i != allocList->end(); i++) 
    {
        sprintf(buf, "%-50s:\t\tLINE %d,\t\tADDRESS %d\t%d unfreed\n",
            (*i)->file, (*i)->line, (*i)->address, (*i)->size);
        OutputDebugString(buf);
        totalSize += (*i)->size;
    }
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
    sprintf(buf, "DSP.DLL Total Unfreed: %d bytes\n", totalSize);
    OutputDebugString(buf);
    sprintf(buf, "-----------------------------------------------------------\n");
    OutputDebugString(buf);
};

#ifdef _DEBUG
inline void * __cdecl operator new(unsigned int size,
                                         const char *file, int line)
{
    void *ptr = (void *)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);
};
inline void __cdecl operator delete(void *p)
{
    RemoveTrack((DWORD)p);
    free(p);
};
inline void * __cdecl operator new[ ] (unsigned int size, const char *file, int line)
{  
    void *ptr = (void *)malloc(size);
    AddTrack((DWORD)ptr, size, file, line);
    return(ptr);                            
};
inline void __cdecl operator delete[ ] (void *p)
{
    RemoveTrack((DWORD)p);
    free(p);
};
#endif

#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW

Upvotes: 1

johnsyweb
johnsyweb

Reputation: 141850

If you can compile your code on a supported platform, you should certainly give Valgrind's Memcheck tool a try. Read The Valgrind Quick Start Guide to see how.

Here's a quick example...

Source:

$ cat -n leaky.cpp 
     1  struct leaky
     2  {
     3      leaky()
     4          :bytes(new char[256])
     5      {
     6      }
     7  
     8      char* bytes;
     9  };
    10  
    11  int main()
    12  {
    13      leaky sieve;
    14      return sizeof sieve;
    15  }
    16  

Build:

$ make leaky
g++ -Wall -Wextra -Wshadow -pedantic -Wno-long-long -Wfloat-equal -Wcast-qual -g -I/opt/local/include -Weffc++ -Wall -I /opt/local/include -L/opt/local/lib  leaky.cpp   -o leaky

Check:

$ valgrind --leak-check=full ./leaky
==85800== Memcheck, a memory error detector
==85800== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==85800== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==85800== Command: ./leaky
==85800== 
==85800== 
==85800== HEAP SUMMARY:
==85800==     in use at exit: 2,367 bytes in 33 blocks
==85800==   total heap usage: 33 allocs, 0 frees, 2,367 bytes allocated
==85800== 
==85800== 256 bytes in 1 blocks are definitely lost in loss record 6 of 9
==85800==    at 0xB823: malloc (vg_replace_malloc.c:266)
==85800==    by 0x5768D: operator new(unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800==    by 0x576DA: operator new[](unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800==    by 0x100000EE7: leaky::leaky() (leaky.cpp:4)
==85800==    by 0x100000EB3: main (leaky.cpp:13)
==85800== 
==85800== LEAK SUMMARY:
==85800==    definitely lost: 256 bytes in 1 blocks
==85800==    indirectly lost: 0 bytes in 0 blocks
==85800==      possibly lost: 0 bytes in 0 blocks
==85800==    still reachable: 2,111 bytes in 32 blocks
==85800==         suppressed: 0 bytes in 0 blocks
==85800== Reachable blocks (those to which a pointer was found) are not shown.
==85800== To see them, rerun with: --leak-check=full --show-reachable=yes
==85800== 
==85800== For counts of detected and suppressed errors, rerun with: -v
==85800== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 1 from 1)

Upvotes: 1

Related Questions