/**************************************************************************\ * * Copyright (c) 1998-1999 Microsoft Corporation * * Abstract: * * Debugging routines * * Revision History: * * 09/07/1999 agodfrey * Created it. * \**************************************************************************/ #include "precomp.hpp" namespace Globals { DebugEventProc UserDebugEventProc = NULL; }; #if DBG // GpDebugLevel is used to control the amount/severity of debugging messages // that are actually output. INT GpDebugLevel = DBG_TERSE; /**************************************************************************\ * * Function Description: * * Removes the path portion of a pathname * * Arguments: * * [IN] str - pathname to strip * * Return Value: * * A pointer to the filename portion of the pathname * * History: * * 09/07/1999 agodfrey * Moved from Entry\Initialize.cpp * \**************************************************************************/ const CHAR* StripDirPrefix( const CHAR* str ) { const CHAR* p; p = strrchr(str, '\\'); return p ? p+1 : str; } const int maxInputStringSize = 1024; /**************************************************************************\ * * Function Description: * * Outputs to the debugger * * Arguments: * * [IN] format - printf-like format string and variable arguments * * Return Value: * * Zero. This is to conform to NTDLL's definition of DbgPrint. * * Notes: * * There will be no output if a debugger is not connected. * * History: * * 09/07/1999 agodfrey * Moved from Entry\Initialize.cpp * \**************************************************************************/ ULONG _cdecl DbgPrint( CHAR* format, ... ) { va_list arglist; va_start(arglist, format); char buf[maxInputStringSize]; _vsnprintf(buf, maxInputStringSize, format, arglist); buf[maxInputStringSize-1]=0; OutputDebugStringA(buf); va_end(arglist); return 0; } // If we can't allocate memory for the debug string, we'll use this buffer // in desperation. It's not thread-safe. I *did* say 'desperation'. static CHAR desperationBuffer[maxInputStringSize]; /**************************************************************************\ * * Function Description: * * Creates a new string, and sprintf's to it. * * Arguments: * * [IN] format - printf-like format string and variable arguments * * Return Value: * * The probably-newly-allocated string result. * * Notes: * * This function is not intended for general use. It guards against memory * failure by using a global buffer. So, while the caller is responsible * for freeing the memory, the caller must also check for that buffer. * i.e. we only want DbgEmitMessage to call this. * * It's also only mostly thread-safe, because if we run out of memory, * we'll use that global buffer in a non-protected way. * * This is the only solution I could find so that I could move most of the * implementation details out of the header file. The root cause is that * macros don't handle multiple arguments natively, so we have to pass * the printf arguments as a single macro argument (in parentheses). * Which means, the function that consumes those arguments can have no * other arguments. * * History: * * 02/01/2000 agodfrey * Created it. Finally, I've found a way to get debug implementation * details out of the headers. * \**************************************************************************/ CHAR * _cdecl GpParseDebugString( CHAR* format, ... ) { va_list arglist; va_start(arglist, format); // Don't use GpMalloc here so that we can use ASSERT and WARNING in // our memory allocation routines. char *newBuf = static_cast(LocalAlloc(LMEM_FIXED, maxInputStringSize)); if (!newBuf) { newBuf = desperationBuffer; } _vsnprintf(newBuf, maxInputStringSize, format, arglist); // Nuke the last byte, because MSDN isn't clear on what _vsnprintf does // in that case. newBuf[maxInputStringSize-1]=0; va_end(arglist); return newBuf; } /**************************************************************************\ * * Function Description: * * Processes a debug event. Frees the message string. * * Arguments: * * level - The debug level of the event * file - Should be __FILE__ * line - Should be __LINE__ * message - The debug message. * * Notes: * * You don't want to call this directly. That would be error-prone. * Use ASSERT, WARNING, etc. * * Depending on the debug level, an identifying prefix will be output. * * If the debug level is DBG_RIP, will suspend execution (e.g. by * hitting a breakpoint.) * * Note on the "debug event" callback: * * We optionally pass WARNINGs and ASSERTs to a reporting function * provided by the user, instead of to the debugger. * (e.g. their function may pop up a dialog or throw an exception). * Lesser events will still be sent to the debugger. * * History: * * 02/01/2000 agodfrey * Created it. * \**************************************************************************/ VOID _cdecl GpLogDebugEvent( INT level, CHAR *file, UINT line, CHAR *message ) { // We may want to add things to the passed-in message. So we need // a temporary buffer const int maxOutputStringSize = maxInputStringSize + 100; CHAR tempBuffer[maxOutputStringSize+1]; // MSDN's _vsnprintf doc isn't clear on this, so just in case: tempBuffer[maxOutputStringSize] = 0; INT callbackEventType = -1; CHAR *prefix = ""; if (GpDebugLevel <= (level)) { switch (level) { case DBG_WARNING: prefix = "WRN "; if (Globals::UserDebugEventProc) { callbackEventType = DebugEventLevelWarning; } break; case DBG_RIP: prefix = "RIP "; if (Globals::UserDebugEventProc) { callbackEventType = DebugEventLevelFatal; } break; } // The convention is that we append the trailing \n, not the caller. // Two reasons: // 1) Callers tend to forget it. // 2) More importantly, it encourages the caller to think of each // call as a separate event. This is important in some cases - e.g. // when the user's "debug event" callback produces a popup for // each event. _snprintf( tempBuffer, maxOutputStringSize, "%s%s(%d): %s\n", prefix, StripDirPrefix(file), line, message ); if (callbackEventType >= 0) { // Wrap the following call in an exception handler in case the // initial component who set the debug event proc uninitializes // on another thread, resulting in UserDebugEventProc being set // to NULL. __try { Globals::UserDebugEventProc((DebugEventLevel) callbackEventType, tempBuffer); } __except(EXCEPTION_EXECUTE_HANDLER) { OutputDebugStringA(tempBuffer); } } else { OutputDebugStringA(tempBuffer); } } // Free the message buffer if (message != desperationBuffer) { LocalFree(message); } // Force a breakpoint, if it's warranted. if ((GpDebugLevel <= DBG_RIP) && (level == DBG_RIP) && (callbackEventType < 0)) { DebugBreak(); } } #endif // DBG