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?
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:
- ERR_USE_STDIO: Uses standard fprintf calls to output debugging messages on the console.
ERR_USE_ODS: Uses OutputDebugString to output debugging messages. This is the default.
- ERR_USE_OUTPUTDEBUGSTRING: Same as ERR_USE_ODS.
Prefix for output:
- ERR_PREFIX: String that will prefix every debug messages. Default is "*** DEBUG --"
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:
- X = The function call
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:
- T = The informational text
- X = The code block
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.cppMemory 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 */
