1. Note

This page includes too much code. We could merge our stuff into a set of sources files and only put the documentations here.

What do you think about that Markus?

FrancoisDenisGonthier


Sounds like a good idea... Lets see if we can figure out the details on IRC

MarkusB


I've put all code into one header + one source file (+ mtrack_on/off.h, which remain unchanged). You can check them out at [http://www.newq.net/stuff/ldh.h ldh.h] and [http://www.newq.net/stuff/ldh.cpp ldh.cpp]. The code could use some cleaning up, but it should compile without errors/warnings.

MarkusB


2. Light Debug Helpers

3. About LDH

Light Debug Helpers for Win32 v1 2003 by François-Denis Gonthier

version 1: I just realised that this set of macros is very useful for debuging Win32 programs. That's why I release it.

4. Note

Not all API calls use SetLastError to transmit error values. ODBC for examples, relies on specific function in order to diagnose errors.

5. How to use LDH

6. Options

Define those variables before including err.h.

Output methods:

Prefix for output:

7. Macros

You can use those anywhere in your program.

8. ERR_PREPARE_CONSOLE

You need to call this macro at the begining of your program if you use ERR_USE_STDIO and if your target is an MFC application, a Win32 Application or anything that doesn't open a console by default. Once called, you will be able to call printf and related functions anywhere in your program.

9. ERR_CHECK

This macro wrap a call to any function with a check of GetLastError. The result will be output using the default output method or the one you defined.

Parameters:

Example: Writing

ERR_CHECK(printf("hello, world"));

will output:

*** DEBUG -- printf("hello, world") Err = 503;

in the unlikely occurance of the Win32 error no 503 after the call to printf. "*** DEBUG --" is the value of ERR_PREFIX.

10. ERR_BLOCK

This functions wraps a block of code with a check for GetLastError.

Parameters:

Example: Writing

ERR_BLOCK("String output", {
                   printf("hello");
                   printf("world");
               });

will output:

*** DEBUG -- String output: OK

if there is no GetLastError after the 2 printfs.

11. The code

Just copy this code in a file named err.h or whatever name you want and include it in your programs.

#pragma once
#ifdef _DEBUG

#ifndef ERR_PREFIX
#define ERR_PREFIX "*** DEBUG --"
#endif // ERR_PREFIX

#define ERR_OUTPUT_FUNC(X) OutputDebugString(X)

#ifdef ERR_USE_STDIO
#include <stdio.h>
#define ERR_OUTPUT_FUNC(X) fprintf(stderr, X)
#endif // ERR_USE_STDIO

#ifdef ERR_USE_ODS || ERR_USE_OUTPUTDEBUGSTRING
#include <windows.h>
#define ERR_OUTPUT_FUNC(X) OutputDebugString(X)
#endif // ERR_USE_ODS

#define ERR_PREPARE_CONSOLE _Err_Prepare_Console
#define ERR_CHECK(X) { SetLastError(0); X; _Err_Check(#X); }
#define ERR_BLOCK(T, X) { SetLastError(0); X; _Err_Block(T); }

BOOL _Err_Check(LPCSTR sLocation)
{
        CHAR sDbgFormat[] = ERR_PREFIX " %s Err = %d\n";
        CHAR sDbgText[1024];
        int nErr = GetLastError();

        if (nErr != 0) {
                wsprintf(sDbgText, sDbgFormat, sLocation, nErr);
                ERR_OUTPUT_FUNC(sDbgText);

                return TRUE;
        }
        else return FALSE;
}

VOID _Err_Block(LPSTR sLocation)
{
        CHAR sDbgOK[] = ERR_PREFIX " %s OK\n";
        CHAR sDbgText[1024];

        if (GetLastError() == 0) {
                wsprintf(sDbgText, sDbgOK, sLocation);
                ERR_OUTPUT_FUNC(sDbgText);
        } else
                _Err_Check(sLocation);
}

VOID _Err_Prepare_Console() {
int nStdOut; FILE * fStdOut;
int nStdIn;  FILE * fStdIn;
int nStdErr; FILE * fStdErr;

        if (!GetStdHandle(STD_OUTPUT_HANDLE)) {
                AllocConsole();

#pragma warning(disable: 4311)
                nStdOut = _open_osfhandle((long) GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT);
                nStdIn  = _open_osfhandle((long) GetStdHandle(STD_INPUT_HANDLE), _O_TEXT);
                nStdErr = _open_osfhandle((long) GetStdHandle(STD_ERROR_HANDLE), _O_TEXT);
#pragma warning(default: 4311)

                fStdOut = _fdopen(nStdOut, "w");
                fStdIn  = _fdopen(nStdIn, "r");
                fStdErr = _fdopen(nStdErr, "w");

                * stdout = * fStdOut;
                * stdin  = * fStdIn;
                * stderr = * fStdErr;
        }
}

#else
#define ERR_PREPARE_CONSOLE()
#define ERR_CHECK(X) X
#define ERR_BLOCK(T, X) X
#endif // _DEBUG

12. Memory Allocation Tracker

13. About

By MarkusB, with inspiration from the article "A Drop-in Debug Memory Manager" by Peter Dalton found in GameProgrammingGems 2.

The memory allocation tracker will help you to track down memory leaks and "memory trashing", i.e. writing out of bounds. It's designed for easy integration into your existing C++ applications. C is currently unsupported, but it is very easy to add C support :)

14. Usage

To turn on memory tracking, simply include the "mtrack_on.h" header file; the "mtrack_off.h" header file can disable memory tracking again. Some libraries, the STL for instance, are incompatible with the memory tracking (because they override the new and delete operators) - this is no big problem, as those libraries shouldn't have memory leaks anyway. Also make sure that "debug.cpp" is included in your project.
Note: memory tracking is only enabled if _DEBUG is defined. This disables memory tracking automatically in release builds in Visual C++.

15. Macros & Functions

I use a custom ASSERT macro; it allows the user to specify a message to be displayed in case of failure:

#define ASSERT( exp, msg )    (void)( (exp) || (_assert_func( #exp, msg, __FILE__, __LINE__ ), 0) )

Note: __FILE__ and __LINE__ are automatically defined in VC++, if you use another compiler, check the doc's for their equivalents.
_dbg_bytes_alloc() returns, in bytes, how much memory currently is allocated.
_dbg_blocks_alloc() returns the number of "blocks" allocated. A block is considered the memory allocated by a single new or new[] call.
_dbg_first_block() returns the first block in the allocation list. See the _dbg_memrec structure below.
_dbg_output_stats() outputs current statistics to the FILE* specified. Statistics include number of bytes & blocks allocated, as well as a complete list on blocks allocated, including information where in the code the memory was allocated.

16. Sample Output

Memory Tracking on:

*** 1100 bytes allocated in 2 blocks:
    Block at 0x  780e40: 100 bytes, allocated at 10 in E:\Projects\memtrack\test.cpp
    Block at 0x  7709a4: 1000 bytes, allocated at 11 in E:\Projects\memtrack\test.cpp

Memory Tracking off...

*** 1100 bytes allocated in 2 blocks:
    Block at 0x  780e40: 100 bytes, allocated at 0 in (not specified)
    Block at 0x  7709a4: 1000 bytes, allocated at 0 in (not specified)

17. The Code

18. debug_mem.h

#ifndef _DEBUG_MEM_H
#define _DEBUG_MEM_H

#ifdef new
#    undef new
#endif

#include <new.h>
#include <stdio.h>

#ifdef _DEBUG
#    define ASSERT( exp, msg )          (void)( (exp) || (_assert_fnc( #exp, msg, __FILE__, __LINE__ ), 0) )

#    define _dbg_bytes_alloc()          _dbg_BytesAlloc()
#    define _dbg_blocks_alloc()         _dbg_BlocksAlloc()
#    define _dbg_first_block()          _dbg_FirstBlock()
#    define _dbg_output_stats( fl )     _dbg_OutputStats( fl )

#else
#    define ASSERT( exp, msg )          ((void)0)

#    define _dbg_bytes_alloc()          (0xFFFFFFFF)
#    define _dbg_blocks_alloc()         (0xFFFFFFFF)
#    define _dbg_first_block()          (0)

#    define _dbg_output_stats( fl )     ((void)0)
#endif  /* _DEBUG */

#ifdef _DEBUG

struct _dbg_memrec
{
    unsigned int        m_line;
    const char*         m_file;

    size_t                      m_blockSize;

    _dbg_memrec*        m_next;
    _dbg_memrec*        m_prev;

    unsigned int        m_mage;
};

void* operator new ( size_t size );
void* operator new[] ( size_t size );

void* operator new ( size_t size, const char* file, size_t line );
void* operator new[] ( size_t size, const char* file, size_t line );

void operator delete ( void* addr );
void operator delete[] ( void* addr );

void _assert_fnc( const char* exp, const char* msg, const char* file, unsigned int line ); 

void _dbg_OutputStats( FILE* fl );

unsigned int _dbg_BytesAlloc( void );
unsigned int _dbg_BlocksAlloc( void );
_dbg_memrec* _dbg_FirstBlock( void );

    // allocation / deallocation functions
void  _dbg_delete( void* addr );
void* _dbg_alloc( size_t size, unsigned int line, const char* file );

#endif /* _DEBUG */
#endif /* _DEBUG_MEM_H */

19. debug_mem.cpp

#include "debug_mem.h"
#ifdef _DEBUG

#ifdef WIN32
#    define WIN32_LEAN_AND_MEAN
#    include <windows.h>
#endif /* WIN32 */

#include <stdlib.h>
#include <memory.h>

static unsigned int _bytes = 0;
static unsigned int _blocks = 0;

static _dbg_memrec* _first_block = NULL;
static _dbg_memrec* _last_block = NULL;

void* operator new ( size_t size )
{
    return _dbg_alloc( size, 0, "(not specified)" );
}
void* operator new[] ( size_t size )
{
    return _dbg_alloc( size, 0, "(not specified)" );
}

void* operator new ( size_t size, const char* file, size_t line )
{
    return _dbg_alloc( size, line, file );
}
void* operator new[] ( size_t size, const char* file, size_t line )
{
    return _dbg_alloc( size, line, file );
}

void operator delete    ( void* addr )
{
    _dbg_delete( addr );
}
void operator delete[] ( void* addr )
{
    _dbg_delete( addr );
}

void* _dbg_alloc( size_t size, unsigned int line, const char* file )
{
    if( size == 0 )
        return NULL;

    // Alloc memory + additinal space for debug structure and mem-trash flag
    _dbg_memrec*        ptr = (_dbg_memrec*)::malloc( size + sizeof( _dbg_memrec ) + sizeof( unsigned int ) );

    ASSERT( ptr, "_dbg_alloc() : malloc() failed; unable to alloc requested memory" );

    // store information
    ptr->m_blockSize    = size;
    ptr->m_mage = 0xDEADBEEF;

    ptr->m_file = file;
    ptr->m_line  = line;

    ptr->m_next = NULL;
    ptr->m_prev = _last_block;

    if( _last_block )
        _last_block->m_next = ptr;

    _last_block = ptr;

    if( !_first_block )
        _first_block = ptr;

    // Add trailing memory trash flag
    *(unsigned int*)(((char*)ptr) + sizeof( _dbg_memrec ) + size)       = 0xDEADBEEF;

    // update global statistics
    _blocks++;
    _bytes += size;
    
    // return correct address
    return ((char*)ptr) + sizeof( _dbg_memrec );
}

void  _dbg_delete( void* addr )
{
    if( !addr )
    return;

    // Get Pointer to the debug structure
    _dbg_memrec*    ptr = (_dbg_memrec*)(((char*)addr) - sizeof( _dbg_memrec ));

    // Make sure the pointer is valid (WIN32)
#ifdef WIN32
    ASSERT( !IsBadReadPtr( ptr, sizeof( _dbg_memrec ) ), "Attempt to free invalid memory" );
    ASSERT( !IsBadReadPtr( ptr, ptr->m_blockSize ), "Attempt to free invalid memory" );
#endif /* WIN32 */

    // unlink structure from chain
    if( ptr == _first_block )
        _first_block = ptr->m_next;
    if( ptr == _last_block )
        _last_block = ptr->m_prev;

    if( ptr->m_prev )
        ptr->m_prev->m_next = ptr->m_next;

    if( ptr->m_next )
        ptr->m_next->m_prev = ptr->m_prev;

    // uupdate global statistics
    _bytes -= ptr->m_blockSize;
    _blocks--;

    // Make sure memory is not trashed
    ASSERT( ptr->m_mage == 0xDEADBEEF, "Memory integrity check failed: possible memory trashing" );
    ASSERT( *(unsigned int*)(((char*)ptr) + sizeof( _dbg_memrec ) + ptr->m_blockSize) == 0xDEADBEEF, "Memory integrity check failed: possible memory trashing" );

    // Free memory
    ::free( ptr );
}


void _dbg_OutputStats( FILE* fl )
{
    ASSERT( fl, "Invalid file specified" );
    fprintf( fl, "*** %d bytes allocated in %d blocks:\n", _bytes, _blocks );

    try
    {
        _dbg_memrec* ptr = _first_block;
        for( ; ptr != NULL; ptr = ptr->m_next )
        {
            if( ptr->m_mage != 0xDEADBEEF )
                fprintf( fl, "\t -- BLOCK at 0x%8X DAMAGED! --\n", ptr ); 
            else
                fprintf( fl, "\tBlock at 0x%8x: %d bytes, allocated at %d in %s\n", ptr, ptr->m_blockSize, ptr->m_line, ptr->m_file );
        }
    }
    catch( ... )
    {
        fprintf( fl, "# Fatal Error.\n" );
    }
}

unsigned int _dbg_BytesAlloc( void )
{
    return _bytes;
}
unsigned int _dbg_BlocksAlloc( void )
{
    return _blocks;
}
_dbg_memrec* _dbg_FirstBlock( void )
{
    return _first_block;
}

void _assert_fnc( const char* exp, const char* msg, const char* file, unsigned int line )
{
    if( exp && msg && file )
    {
        char buff[1024];

        _snprintf( buff, 1023, "Assertation failed!\n---------------------------\n\n - Expression:\n\t%s\n\n - Message:\n\t%s\n\n - Location:\n\t%d in %s\n\nPress Abort to exit, Retry to debug or Ignore to continue\n", exp, msg, line, file );
        int ret = MessageBox( HWND_DESKTOP, buff, "ASSERTATION FAULT", MB_ABORTRETRYIGNORE | MB_ICONWARNING | MB_TASKMODAL | MB_TOPMOST );

        switch( ret )
        {
            case IDABORT:
                abort();
                break;

            case IDIGNORE:
                break;

            case IDRETRY:
                _asm int 3;
                break;
        }
    }
}

#endif /* DEBUG */

20. mtrack_on.h

#ifdef _DEBUG
#pragma warning( disable:4291 )    // Weird warning about non-matching new/delete

extern void* operator new    ( size_t size );
extern void* operator new[]    ( size_t size );

extern void* operator new    ( size_t size, const char* file, size_t line );
extern void* operator new[]    ( size_t size, const char* file, size_t line );

#define    new    new( __FILE__, __LINE__ )

#endif /* _DEBUG */

21. mtrack_off.h

#ifdef _DEBUG
#undef  new     
#endif /* _DEBUG */

LightDebugHelpers (last edited 2008-07-09 05:47:56 by localhost)