Click here to Skip to main content
15,886,872 members
Articles / Programming Languages / C++

Trapping Bugs with BlackBox

Rate me:
Please Sign up or sign in to vote.
3.72/5 (27 votes)
25 May 2003BSD6 min read 263.6K   2.1K   130  
A brief article explaining how to use BlackBox in your programs to trap errors
/*----------------------------------------------------------------------
"Debugging Applications" (Microsoft Press)
Copyright (c) 1997-2000 John Robbins -- All rights reserved.
----------------------------------------------------------------------*/

// Get everything included.
#include "stdafx.h"
#include "BugslayerUtil.h"
#include "MemStressConstants.h"
#include "CRTDBG_Internals.h"

////////////////////////////////////////////////////////////////////////
// Internal Data Structures
////////////////////////////////////////////////////////////////////////
// The struct that holds a single file marked for failure and the line
// in the file allocations are supposed to fail.  If the line is -1,
// then any allocations from the file are supposed to fail.
// BIG NOTE: Notice that even in UNICODE builds, this structure only has
// character buffers.  Since __FILE__ is always defined in terms of
// chars, no matter what so everything is converted when it is read in.
typedef struct tag_FAILFILE
{
    char szFile[ MAX_PATH + 1 ] ;
    long lLine                  ;
} FAILFILE , * LPFAILFILE ;

// The struct that holds all of the options as they come out of the INI
// file.
typedef struct tag_FAILUREINFO
{
    // The CRT options that should be set for the user.
    BOOL        bCRTCheckMemory     ;
    BOOL        bCRTDelayMemFrees   ;
    BOOL        bCRTLeakChecking    ;
    // The general options for the hook.  If any of the UINT types are
    // zero, then they are not active.
    BOOL        bGENFailAllAllocs   ;
    UINT        uiGENFailEveryN     ;
    UINT        uiGENFailAfterX     ;
    UINT        uiGENFailOverY      ;
    BOOL        bGENAskOnEach       ;
    // The total file count.
    UINT        uiFileCount         ;
    // The array of files to fail.
    LPFAILFILE  pFailFiles          ;
} FAILUREINFO , * LPFAILUREINFO ;

////////////////////////////////////////////////////////////////////////
// Unfortunately, the CRT hooks do not get told if the memory being
// reallocated is being done in place or will cause a free first.
// Thus, it is impossible to know for sure exactly how much memory is
// outstanding in the system.  The statistics keeps are only for calls
// to allocation functions (new and malloc) and deallocation functions
// (delete and free).
// Memory statistics are recorded before any processing for failures
// takes place.
typedef struct tag_MEMSTATS
{
    // The total highwater mark of allocations so far.
    long lMaxAllocated      ;
    // The total number of calls to allocation functions.
    long lTotalAllocCalls   ;
    // The total number of calls to realloc functions.
    long lTotalReAllocCalls ;
    // The total number of calls to release functions.
    long lTotalReleaseCalls ;
    // The total amount of memory that is currently in use.
    long lCurrentlyInUse    ;
} MEMSTATS , * LPMEMSTATS ;

////////////////////////////////////////////////////////////////////////
// Static Variable Declarations
////////////////////////////////////////////////////////////////////////
// The critical section that everything will be protected with.
static CRITICAL_SECTION g_stCritSec ;
// The thread-local storage for the thread recursion count. This is to
// protect against re-entry when the code in this library calls
// something that would cause an allocation in the thread.  The perfect
// example is when the user wants to be asked on each allocation if they
// want the allocation to succeed.  I will have to pop up a message box
// in the middle of AllocationHook.  Since MFC does all of the window
// subclassing to paint the gray background, the internal MFC windows
// procedure will get called.  In that windows procedure, MFC does it's
// handle table mapping and allocates memory.  Thus a happy case of
// recursion in a single thread.  To get around the recursion, I keep a
// per-thread value that tells me the recursion count.  In
// AllocationHook, if the the recursion variable is 1, I just return
// TRUE and leave.
static DWORD g_tlsRecursionIndex = TLS_OUT_OF_INDEXES ;
// The one global flag that indicates that the library was properly
// initialized and ready for use.
static BOOL g_bLibIsInit = FALSE ;
// The private heap that will hold all of the allocations for this
// module.  Keep in mind that this library cannot do any normal CRT
// memory allocations because it could get into nasty recursive
// situations.
static HANDLE g_hHeap = NULL ;
// The failure information for this run of the library.
static LPFAILUREINFO g_pFailInfo = NULL ;
// The internal statistics.
static MEMSTATS g_stMemStats ;
// The previously installed hook.
static _CRT_ALLOC_HOOK pfnPrevHook ;

////////////////////////////////////////////////////////////////////////
// Internal Function Declarations
////////////////////////////////////////////////////////////////////////
// The function that actually does the INI reading.
static LPFAILUREINFO ProcessINIFileA ( LPCSTR szProgram ) ;
// The actual allocation hook.
static int AllocationHook ( int                   nAllocType ,
                            void *                pvData     ,
                            size_t                nSize      ,
                            int                   nBlockUse  ,
                            long                  lRequest   ,
                            const unsigned char * szFileName ,
                            int                   nLine       ) ;

////////////////////////////////////////////////////////////////////////
// Function Implementation Starts Here
////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG
// The initialization function.
BOOL  __stdcall
    MemStressInitializeA ( const char * szProgName )
{
    // The return value.
    BOOL bRet = TRUE ;
    __try
    {
        __try
        {
            // Check all of the basic assumptions.
            ASSERT ( FALSE == g_bLibIsInit ) ;
            ASSERT ( NULL == g_hHeap ) ;
            ASSERT ( NULL == g_pFailInfo ) ;
            ASSERT ( NULL != szProgName ) ;

            // Initialize the few items that all routines in here will
            // have to share.
            InitializeCriticalSection ( &g_stCritSec ) ;
            // Now immediately, try and grab it.
            EnterCriticalSection ( &g_stCritSec ) ;

            FillMemory ( &g_stMemStats , sizeof ( MEMSTATS ), NULL ) ;

            // Create the private heap for this library.
            g_hHeap = HeapCreate ( HEAP_GENERATE_EXCEPTIONS , 0 , 0 ) ;

            // Get the failure information for the file.
            g_pFailInfo = ProcessINIFileA ( szProgName ) ;

            // Set the main CRT flags.
            int iFlags = _CrtSetDbgFlag ( _CRTDBG_REPORT_FLAG ) ;
            if ( TRUE == g_pFailInfo->bCRTCheckMemory )
            {
                iFlags |= _CRTDBG_CHECK_ALWAYS_DF ;
            }
            if ( TRUE == g_pFailInfo->bCRTDelayMemFrees )
            {
                iFlags |= _CRTDBG_DELAY_FREE_MEM_DF ;
            }
            if ( TRUE == g_pFailInfo->bCRTLeakChecking )
            {
                iFlags |= _CRTDBG_LEAK_CHECK_DF ;
            }
            _CrtSetDbgFlag ( iFlags ) ;

            // Set the allocation hook.
            pfnPrevHook =
                  _CrtSetAllocHook ( (_CRT_ALLOC_HOOK)AllocationHook ) ;

            g_bLibIsInit = TRUE ;

        }
        __except ( EXCEPTION_EXECUTE_HANDLER )
        {
            ASSERT ( !"Caught a crash in MemStressInitializeA!" ) ;
            // The library was unable to initialize.
            g_bLibIsInit = FALSE ;
            bRet = FALSE ;
        }
    }
    __finally
    {
        // Make sure that we always execute this!
        LeaveCriticalSection ( &g_stCritSec ) ;
    }
    return ( bRet ) ;
}
#else
BOOL  __stdcall
    MemStressInitializeA ( const char * )
{
    return ( TRUE ) ;
}
#endif  // _DEBUG

#ifndef _DEBUG
BOOL  __stdcall
    MemStressInitializeW ( const wchar_t * szProgName )
{
    ASSERT ( NULL != szProgName ) ;
    char szBuff[ MAX_PATH + 1 ] ;
    int  iRet ;

    iRet = WideCharToMultiByte ( CP_ACP             ,
                                 0                  ,
                                 szProgName         ,
                                 -1                 ,
                                 szBuff             ,
                                 sizeof ( szBuff )  ,
                                 NULL               ,
                                 NULL                ) ;
    ASSERT ( 0 != iRet ) ;
    return ( MemStressInitializeA ( szBuff ) ) ;
}
#else
BOOL  __stdcall
    MemStressInitializeW ( const wchar_t * )
{
    return ( TRUE ) ;
}
#endif  // _DEBUG

#ifdef _DEBUG
BOOL  __stdcall
    MemStressTerminate ( void )
{
    // The return value.
    BOOL bRet = TRUE ;

    __try
    {
        __try
        {
            ASSERT ( TRUE == g_bLibIsInit ) ;
            ASSERT ( NULL != g_pFailInfo ) ;

            // Now immediately, try and grab it.
            EnterCriticalSection ( &g_stCritSec ) ;

            // Notice that I do not explicitly free any of the memory
            // allocated out of the private heap.  Since the whole
            // block gets destroyed in a single call, there is no
            // reason to do each subblock.
            BOOL bHRet = HeapDestroy ( g_hHeap ) ;
            ASSERT ( FALSE != bHRet ) ;
            g_hHeap = NULL ;
            g_pFailInfo = NULL ;
            g_bLibIsInit = FALSE ;

            // Set the allocation hook back to what it was.
            _CrtSetAllocHook ( (_CRT_ALLOC_HOOK)pfnPrevHook ) ;

        }
        __except ( EXCEPTION_EXECUTE_HANDLER )
        {
            ASSERT ( !"Caught a crash in MemStressTerminate!" ) ;
            bRet = FALSE ;
        }
    }
    __finally
    {
        LeaveCriticalSection ( &g_stCritSec ) ;
        // Just get rid of the critical section.
        DeleteCriticalSection ( &g_stCritSec ) ;
    }
    return ( bRet ) ;
}
#else
BOOL  __stdcall
    MemStressTerminate ( void )
{
    return ( TRUE ) ;
}
#endif  // _DEBUG

/*//////////////////////////////////////////////////////////////////////
       The following functions are active only in _DEBUG builds.
//////////////////////////////////////////////////////////////////////*/
#ifdef _DEBUG

/*----------------------------------------------------------------------
Function        :   AllocationHook
Discussion      :
    The memory allocation hook that is installed to handle the memory
failures.
Parameters      :
    nAllocType - The type of allocation operation, _HOOK_ALLOC,
                 _HOOK_REALLOC, or _HOOK_FREE.
    pvData     - The pointer to the data if nAllocType is _HOOK_FREE,
                 otherwise NULL.
    nSize      - The size of the memory block.
    nBlockUse  - The type of memory block, _CRT_BLOCK, etc.
    lRequest   - The block request number.
    szFileName - The file that had the memory operation.
    nLine      - The line where the memory operation took place.
Returns         :
    TRUE  - The allocation function should succeed.
    FALSE - The allocation function should fail.
----------------------------------------------------------------------*/
static int AllocationHook ( int                   nAllocType    ,
                            void *                pvData        ,
                            size_t                nSize         ,
                            int                   nBlockUse     ,
                            long                  /*lRequest*/  ,
                            const unsigned char * szFileName    ,
                            int                   nLine          )
{
    BOOL bRet = TRUE ;

    // Before I do anything else, double check that thread executing
    // is not recursing in because of my call to MessageBox below.
    DWORD dwRecursion = (DWORD)TlsGetValue ( g_tlsRecursionIndex ) ;
    if ( dwRecursion > 0 )
    {
        // Re-entering because of the message loop!  Get out of here.
        return ( bRet ) ;
    }
    dwRecursion++ ;
    TlsSetValue ( g_tlsRecursionIndex , (LPVOID)dwRecursion ) ;

    __try
    {
        __try
        {
            // Grab the critical section.
            EnterCriticalSection ( &g_stCritSec ) ;

            // The CRT blocks must be ignored or we will cause many
            // problems.
            if ( _CRT_BLOCK == nBlockUse )
            {
                __leave ;
            }

            // Skip all the processing for _HOOK_FREE type calls.
            if ( _HOOK_FREE == nAllocType )
            {
                _leave ;
            }

            // Now go through the litany of possible failure cases.
            if ( TRUE == g_pFailInfo->bGENFailAllAllocs )
            {
                bRet = FALSE ;
                __leave ;
            }
            if ( TRUE == g_pFailInfo->bGENAskOnEach )
            {
                char szBuff [ 1024 ] ;
                if ( NULL == szFileName )
                {
                    wsprintfA ( szBuff , "Unknown File & Line!" ) ;
                }
                else
                {
                    wsprintfA ( szBuff       ,
                                k_MSGFMT     ,
                                szFileName   ,
                                nLine         ) ;
                }


                if ( IDYES == MessageBoxA ( GetActiveWindow ( ) ,
                                            szBuff              ,
                                            k_MSGTITLE          ,
                                            MB_YESNO          ) )
                {
                    bRet = FALSE ;
                    __leave ;
                }
            }
            if ( 0 != g_pFailInfo->uiGENFailEveryN )
            {
                if ( 0 == g_stMemStats.lTotalAllocCalls %
                          g_pFailInfo->uiGENFailEveryN    )
                {
                    bRet = FALSE ;
                    __leave ;
                }
            }
            if ( 0 != g_pFailInfo->uiGENFailAfterX )
            {
                if ( g_pFailInfo->uiGENFailAfterX     <=
                     (UINT)g_stMemStats.lMaxAllocated    )
                {
                    bRet = FALSE ;
                    _leave ;
                }
            }
            if ( 0 != g_pFailInfo->uiGENFailOverY )
            {
                if ( nSize > g_pFailInfo->uiGENFailOverY )
                {
                    bRet = FALSE ;
                    _leave ;
                }
            }
            if ( 0 != g_pFailInfo->uiFileCount )
            {
                // If no filename is passed in, there is not much I can
                // do here.
                if ( NULL != szFileName )
                {

                    // Because the value for the __FILE__ macro seems to
                    // be randomly generated, I need to strip off any
                    // path stuff that may, or may not, be there.
                    char szJustFilename[ MAX_PATH ] ;
                    char szExt[ MAX_PATH ] ;

                    _splitpath ( (LPCSTR)szFileName   ,
                                  NULL                ,
                                  NULL                ,
                                  szJustFilename      ,
                                  szExt                ) ;

                    strcat ( szJustFilename , szExt ) ;

                    for ( UINT i = 0                   ;
                          i < g_pFailInfo->uiFileCount ;
                          i++                           )
                    {
                        if ( 0 ==
                            stricmp ( g_pFailInfo->pFailFiles[i].szFile,
                                      (LPCSTR)szJustFilename          ))
                        {
                            if((-1 ==g_pFailInfo->pFailFiles[i].lLine)||
                               ( nLine ==
                                      g_pFailInfo->pFailFiles[i].lLine))
                            {
                                bRet = FALSE ;
                                __leave ;
                            }
                        }
                    }
                }
            }
        }
        __except ( EXCEPTION_EXECUTE_HANDLER )
        {
            ASSERT ( !"Caught a crash in AllocationHook!\n" ) ;
        }
    }
    __finally
    {
        // Now that the allocation success or failure is known, do the
        // statistics upkeep.
        if ( _HOOK_FREE == nAllocType )
        {
            // Since the size is always zero when this is
            // called for a free function, get the
            // information ourselves.
            _CrtMemBlockHeader * pHead = pHdr(pvData) ;

            g_stMemStats.lTotalReleaseCalls++ ;
            g_stMemStats.lCurrentlyInUse -= pHead->nDataSize ;
            // Since the user can easily start and stop the hook
            // whenever they want to, we might see extra calls to frees
            // here.  If that is the case, don't let the currently
            // used go to less than zero.
            if ( g_stMemStats.lCurrentlyInUse <= 0 )
            {
                g_stMemStats.lCurrentlyInUse = 0 ;
            }
        }
        else if ( TRUE == bRet )
        {
            g_stMemStats.lMaxAllocated += nSize ;
            g_stMemStats.lCurrentlyInUse += nSize ;
            switch ( nAllocType )
            {
                case _HOOK_ALLOC    :
                    g_stMemStats.lTotalAllocCalls++ ;
                    break ;
                case _HOOK_REALLOC  :
                    g_stMemStats.lTotalReAllocCalls++ ;
                    break ;
                default     :
                    ASSERT ( FALSE ) ;
                    break ;
            }
        }

        // Make sure that we always execute this!
        LeaveCriticalSection ( &g_stCritSec ) ;

        // This thread is done, drop the recursion flag.
        TlsSetValue ( g_tlsRecursionIndex , 0 ) ;
    }
    return ( bRet ) ;
}


// The function that does the INI reading.
static LPFAILUREINFO ProcessINIFileA ( LPCSTR szProgram  )

{
    // Allocate the space for the buffer that will be returned.
    LPFAILUREINFO pFailInfo =
               (LPFAILUREINFO)HeapAlloc ( g_hHeap                    ,
                                          HEAP_GENERATE_EXCEPTIONS |
                                            HEAP_ZERO_MEMORY         ,
                                          sizeof ( FAILUREINFO )      );

    // Look to see if the section with the program name really exists.
    // If it does not the default section will be used.
    char    szBuff [ MAX_PATH + 1 ] ;
    DWORD   dwRet                   ;
    LPCSTR  lpAppName               ;

    dwRet = GetPrivateProfileSectionA ( szProgram         ,
                                        szBuff            ,
                                        sizeof ( szBuff ) ,
                                        k_INI_FILENAME     ) ;
    if ( 0 == dwRet )
    {
        // The section for this program does not exist so use the
        // default.
        lpAppName = k_INI_DEFAULTSECTION ;
    }
    else
    {
        lpAppName = szProgram ;
    }

    // Pound through and get all of the fields.
    pFailInfo->bCRTCheckMemory =
                           GetPrivateProfileIntA ( lpAppName         ,
                                                   k_INI_CRTCHECKMEM ,
                                                   1                 ,
                                                   k_INI_FILENAME     );
    pFailInfo->bCRTDelayMemFrees =
                           GetPrivateProfileIntA ( lpAppName         ,
                                                   k_INI_CRTDELAYMEM ,
                                                   1                 ,
                                                   k_INI_FILENAME     );
    pFailInfo->bCRTLeakChecking =
                           GetPrivateProfileIntA ( lpAppName           ,
                                                   k_INI_CRTCHECKLEAKS ,
                                                   1                   ,
                                                   k_INI_FILENAME     );

    pFailInfo->bGENFailAllAllocs =
                         GetPrivateProfileIntA ( lpAppName             ,
                                                 k_INI_GENFAILALLALLOCS,
                                                 0                     ,
                                                 k_INI_FILENAME       );
    pFailInfo->uiGENFailEveryN =
                      GetPrivateProfileIntA ( lpAppName                ,
                                             k_INI_GENFAILEVERYNALLOCS ,
                                             0                         ,
                                             k_INI_FILENAME           );
    pFailInfo->uiGENFailAfterX =
                       GetPrivateProfileIntA ( lpAppName               ,
                                              k_INI_GENFAILAFTERXBYTES ,
                                              0                        ,
                                              k_INI_FILENAME          );
    pFailInfo->uiGENFailOverY =
                        GetPrivateProfileIntA ( lpAppName              ,
                                               k_INI_GENFAILOVERYBYTES ,
                                               0                       ,
                                               k_INI_FILENAME         );
    pFailInfo->bGENAskOnEach =
                        GetPrivateProfileIntA ( lpAppName              ,
                                               k_INI_GENASKONEACHALLOC ,
                                               0                       ,
                                               k_INI_FILENAME         );

    // Now for the fun stuff, loop through and load the files to fail.
    pFailInfo->uiFileCount =
                       GetPrivateProfileIntA ( lpAppName               ,
                                              k_INI_GENNUMFILEFAILURES ,
                                              0                        ,
                                              k_INI_FILENAME          );
    if ( 0 != pFailInfo->uiFileCount )
    {
        // Allocate the space in the structure.
        pFailInfo->pFailFiles =
                    (LPFAILFILE)HeapAlloc ( g_hHeap                    ,
                                          HEAP_GENERATE_EXCEPTIONS |
                                              HEAP_ZERO_MEMORY         ,
                                          sizeof ( FAILFILE ) *
                                               pFailInfo->uiFileCount );
        char   szCurrFile [ 30 ] ;
        LPSTR  pEndOfConst       ;
        UINT   uiCurrIndex       ;
        UINT   uiCount           ;
        LPSTR  pTok1             ;
        LPSTR  pTok2             ;
        long   lData             ;

        lstrcpy ( szCurrFile , k_INI_GENFILEFAILPREFIX ) ;
        pEndOfConst = szCurrFile + lstrlen ( k_INI_GENFILEFAILPREFIX ) ;

        uiCurrIndex = 0 ;

        for ( uiCount = 1                       ;
              uiCount <= pFailInfo->uiFileCount ;
              uiCount++                          )
        {
            // Build up the key to read.
            itoa ( uiCount , pEndOfConst , 10 ) ;

            // Read the key.
            dwRet = GetPrivateProfileStringA ( lpAppName         ,
                                               szCurrFile        ,
                                               ""                ,
                                              szBuff             ,
                                              sizeof ( szBuff )  ,
                                              k_INI_FILENAME      ) ;

            // If nothing was read, then just go for the next one.
            if ( 0 == dwRet )
            {
                continue ;
            }

            // Extract the two parts of the string.
            pTok1 = strtok ( szBuff , k_SEP_FILEANDLINE ) ;
            pTok2 = strtok ( NULL , k_SEP_FILEANDLINE ) ;

            if ( ( NULL == pTok1 ) || ( NULL == pTok2 ) )
            {
                continue ;
            }

            // Convert the second string into a number.
            lData = BlackBox::atol ( pTok2 ) ;

            if ( 0 == lData )
            {
                continue ;
            }

            // Whew!  We can go ahead and add this one.
            pFailInfo->pFailFiles[ uiCurrIndex ].lLine = lData ;
            lstrcpy ( pFailInfo->pFailFiles[uiCurrIndex].szFile ,
                     pTok1                                      ) ;

            // Update the array counter.
            uiCurrIndex++ ;
        }

        // Set the count to the proper count since some entries might
        // have been thrown away.
        pFailInfo->uiFileCount = uiCurrIndex ;
    }
    return ( pFailInfo ) ;
}


BOOL InternalMemStressInitialize ( void )
{
    // This function is called during the DllMain DLL_PROCESS_ATTACH
    // processing.  All I need to do is to initialize the thread
    // local storage index.
    g_tlsRecursionIndex = TlsAlloc ( ) ;

    return ( TLS_OUT_OF_INDEXES != g_tlsRecursionIndex ) ;

}

BOOL InternalMemStressShutdown ( void )
{
    // Free up the thread-local storage index.
    BOOL bRet = TlsFree ( g_tlsRecursionIndex ) ;
    g_tlsRecursionIndex = TLS_OUT_OF_INDEXES ;
    return ( bRet ) ;
}

#endif  // _DEBUG

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer (Senior)
United States United States
Currently working on the Visual Component Framework, a really cool C++ framework. Currently the VCF has millions upon millions upon billions of Users. If I make anymore money from it I'll have to buy my own country.

Comments and Discussions