locked
Stack trace memory leak: dbghelp and Constructors RRS feed

  • Question

  • I'm using StackWalk64() and SymGetSymFromAddr64() in dbghelp.lib v. 6.6.7.5 to do stack traces in VS2003 SP1.

    When I do the stack trace in the constructor of an object, I get a memory leak. If I do it outside the constructor, I don't. The leak exhibits itself as increasing memory usage as the program runs. The SymGetSymFromAddr64() call seems to be the one doing the leak; if I comment it out, then the leak doesn't occur.

    I included sample code below. The function Sub3() has two very small test segments, one that leaks, and one that doesn't. The first causes the stack trace to be called inside the constructor which causes a leak, and the second calls the same stack trace function right after the constructor which seems to avoid the leak.

    I tried to strip out everything not related to the leak, so the stack trace function and symbol lookup function aren't very useful since they don't return any results. They just make the calls and basically ignore the results.

    Any ideas?

    Thanks,
    Derek

    Code Snippet


    // TestLibDebug.cpp : Defines the entry point for the console application.

    #include "stdafx.h"

    #include <stdlib.h>
    #include <malloc.h>
    #include <psapi.h>

    #include "dbghelp.h"

    #define STACK_TRACE_BUFFER_SIZE (1024)
    #define DEBUG_SYMBOL_LENGTH_MAX (512)

    #define TEST_COUNT_MAX 100000


    // Defines a simple exception for managing errors. Includes stack trace capability.
    class SimpleException
    {
    public:

    // Default constructor.
    SimpleException();

    // Constructor that optionally does a stack trace.
    SimpleException( bool a_bSkipStackTrace );

    // Returns a pointer to the member stack trace buffer.
    LPTSTR GetStackTrace();

    // Do and save a stack trace.
    void SetStackTrace();

    private:

    // Holds the stack trace results in string form.
    TCHAR m_pszStackTraceBuffer[ STACK_TRACE_BUFFER_SIZE ];

    };


    // Wraps access to dbghelp lib calls.
    CRITICAL_SECTION g_csProtectDbgHelp;

    // If a_pszFilename is a string, returns a pointer to the substring that represents just the filename.
    LPTSTR StripFilenamePath( LPTSTR a_pszFilename );

    // Sets up the dbghelp library for access.
    void InitializeDbgHelp();

    // Brings down the dbghelp library.
    void DeinitializeDbgHelp();


    // Look up symbol info on the specified address.
    void GetAddressDescription( DWORD64 a_dw64Address );


    // Execute a stack trace.
    void GetStackTrace( int a_nFrameDepthEnd );

    // Simple pass-through functions for testing.
    void Sub1();
    void Sub2();
    void Sub3();


    // Application entry point.
    int _tmain(int argc, _TCHAR* argv[])
    {
    InitializeDbgHelp();

    for( int l_nCount = 0; l_nCount < TEST_COUNT_MAX; l_nCount++ )
    {
    printf( "Count: %6d\n", l_nCount );
    Sub1();
    }

    DeinitializeDbgHelp();

    return 0;
    }


    void Sub1()
    {
    Sub2();
    }


    void Sub2()
    {
    Sub3();
    }


    void Sub3()
    {

    // Test 1: Memory leak. SetStackTrace() called in constructor.
    SimpleException l_Exception1(true);

    //// Test 2: No memory leak. SetStackTrace() called after constructor.
    //SimpleException l_Exception2(false);
    //l_Exception2.SetStackTrace();

    }


    // Execute a stack trace.
    void GetStackTrace( int a_nFrameDepthEnd )
    {
    // All "dbghelp" library functions must be accessed synchronously.
    EnterCriticalSection( &g_csProtectDbgHelp );

    // Set up structures for StackWalk64().

    CONTEXT l_ContextRecord;
    memset(&l_ContextRecord, 0, sizeof(l_ContextRecord ));

    STACKFRAME64 l_StackFrame64;
    memset(&l_StackFrame64, 0, sizeof(l_StackFrame64));

    l_ContextRecord.ContextFlags = CONTEXT_FULL;

    __asm
    {
    call NextInstruction
    NextInstruction: pop eax
    mov l_ContextRecord.Eip, eax // Program counter.
    mov l_ContextRecord.Ebp, ebp // Frame base pointer.
    mov l_ContextRecord.Esp, esp // Stack pointer.

    } // end __asm

    l_StackFrame64.AddrPC.Offset = l_ContextRecord.Eip; // Program counter.
    l_StackFrame64.AddrPC.Mode = AddrModeFlat; // Virtual address (flat addressing).
    l_StackFrame64.AddrFrame.Offset = l_ContextRecord.Ebp; // Frame base pointer.
    l_StackFrame64.AddrFrame.Mode = AddrModeFlat; // Virtual address (flat addressing).
    l_StackFrame64.AddrStack.Offset = l_ContextRecord.Esp; // Stack pointer.
    l_StackFrame64.AddrStack.Mode = AddrModeFlat; // Virtual address (flat addressing).

    // Step through the stack frames and dump what we can until we hit the top of the stack
    // or we're prevented from going any further.
    for( int l_nFrameCount = 0; l_nFrameCount < a_nFrameDepthEnd + 1; l_nFrameCount++ )
    {
    // Get information about the current stack frame and advance to the next frame in the stack.
    BOOL l_bSuccess = StackWalk64(
    IMAGE_FILE_MACHINE_I386,
    GetCurrentProcess(),
    GetCurrentThread(),
    &l_StackFrame64,
    &l_ContextRecord,
    NULL,
    SymFunctionTableAccess64,
    SymGetModuleBase64,
    NULL );

    // Check if we advanced to a valid frame.
    if( (!l_bSuccess) || (0 == l_StackFrame64.AddrPC.Offset) || (0 == l_StackFrame64.AddrReturn.Offset) )
    {
    // We've traced to the end of the stack or into outside memory, which are normal trace
    // termination conditions, or StackWalk64() failed in some other way. Either way we're
    // done with the trace.
    break;
    }

    // Look up the symbol info for the frame address.
    GetAddressDescription( l_StackFrame64.AddrPC.Offset );

    } // for

    LeaveCriticalSection( &g_csProtectDbgHelp );
    }



    // Look up symbol info for the specified address.
    void GetAddressDescription( DWORD64 a_dw64Address )
    {
    // All "dbghelp" library functions must be accessed synchronously.
    EnterCriticalSection( &g_csProtectDbgHelp );

    // Make sure the address is valid.
    if( 0 != a_dw64Address )
    {
    // l_pSymbol64 is used by SymGetSymFromAddr64() to return the lookup result to us.
    // It needs an additional string buffer grafted onto it.
    IMAGEHLP_SYMBOL64 * l_pSymbol64 = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + DEBUG_SYMBOL_LENGTH_MAX);
    memset(l_pSymbol64, 0, sizeof(IMAGEHLP_SYMBOL64) + DEBUG_SYMBOL_LENGTH_MAX);
    l_pSymbol64->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
    l_pSymbol64->MaxNameLength = DEBUG_SYMBOL_LENGTH_MAX - 1;

    // Do the lookup.
    DWORD64 l_dw64Displacement;
    BOOL l_bSuccess = SymGetSymFromAddr64(
    GetCurrentProcess(),
    a_dw64Address,
    &l_dw64Displacement,
    l_pSymbol64 );

    // Free the symbol info structure.
    free(l_pSymbol64);
    l_pSymbol64 = NULL;
    }

    LeaveCriticalSection( &g_csProtectDbgHelp );
    }


    ///////////////////////////////////////////////////////////////////////////
    //
    // StripFilenamePath()
    //
    // If a_pszFilename represents a file name with or without a prepending path,
    // then return a pointer to the start of the filename.
    //
    LPTSTR StripFilenamePath( LPTSTR a_pszFilename )
    {
    // Walk backwards through the string until a path separation
    // character or the beginning of the string is found.
    size_t l_nSize = _tcslen(a_pszFilename);
    size_t l_nCharIdx = l_nSize - 1;
    while( (l_nCharIdx >= 0) && (l_nCharIdx < l_nSize) )
    {
    if( '\\' == a_pszFilename[l_nCharIdx] )
    break;
    l_nCharIdx--;
    }

    // Return a pointer to the character just after the path separator,
    // or the beginning of the string.
    return &(a_pszFilename[l_nCharIdx + 1]);
    }


    void InitializeDbgHelp()
    {
    // Use a critical section to synchronize access to all "dbghelp" library functions.
    InitializeCriticalSection( &g_csProtectDbgHelp );

    // All dbghelp functions must be accessed synchronously.
    EnterCriticalSection( &g_csProtectDbgHelp );

    //// Set debug symbols to load on-demand.
    //SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);

    // Set all symbols to load up front. Load symbols without compiler decorations.
    SymSetOptions(SYMOPT_UNDNAME);

    // Get the path of the currently running executable.
    TCHAR l_pszExecutablePath[MAX_PATH];
    l_pszExecutablePath[0] = (TCHAR) 0;
    DWORD l_dwSuccess = GetModuleFileNameEx(
    GetCurrentProcess(),
    NULL,
    l_pszExecutablePath,
    MAX_PATH );

    if( l_dwSuccess )
    {
    // Get just the path of the executable. Null-terminate the path string
    // where the filename begins.
    LPTSTR l_pszExecutableFileName = StripFilenamePath( l_pszExecutablePath );
    l_pszExecutableFileName[0] = (TCHAR) 0;

    // Search for the symbol file in the executable's path. Note this does a
    // recursive directory search, so be careful the executable's path doesn't
    // sit at the top of a very very large directory tree.
    SymInitialize(GetCurrentProcess(), l_pszExecutablePath, TRUE);
    }
    else
    {
    // Couldn't get the executable's path. Look in the system default locations for
    // the symbol file.
    SymInitialize(GetCurrentProcess(), NULL, TRUE);
    }

    // Done accessing dbghelp functions.
    LeaveCriticalSection( &g_csProtectDbgHelp );

    }

    ///////////////////////////////////////////////////////////////////////
    //
    // Clean up the dbghelp library support.
    //
    void DeinitializeDbgHelp()
    {
    // All "dbghelp" library functions must be accessed synchronously.
    EnterCriticalSection( &g_csProtectDbgHelp );

    // Release debug resources.
    SymCleanup(GetCurrentProcess);

    // Done accessing dbghelp functions.
    LeaveCriticalSection( &g_csProtectDbgHelp );

    // Presumably we're coming down and there will be no more access to the
    // dbghelp library from anywhere.
    DeleteCriticalSection( &g_csProtectDbgHelp );
    }


    // Default constructor.
    SimpleException::SimpleException()
    {
    m_pszStackTraceBuffer[0] = (TCHAR) 0;
    }


    // Constructor that optionally does a stack trace.
    SimpleException::SimpleException( bool a_bSaveStackTrace )
    {
    m_pszStackTraceBuffer[0] = (TCHAR) 0;

    if( a_bSaveStackTrace )
    {
    SetStackTrace();
    }
    }


    // Return a pointer to the member stack trace buffer.
    LPTSTR SimpleException::GetStackTrace()
    {
    return m_pszStackTraceBuffer;
    }


    // Execute a stack trace and save the results in the member stack trace buffer.
    void SimpleException::SetStackTrace()
    {
    ::GetStackTrace( 200 );
    }








    Monday, May 21, 2007 3:21 PM