//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ // //===========================================================================// #include "tier0/platform.h" #if defined( PLATFORM_WINDOWS_PC ) #define WIN_32_LEAN_AND_MEAN #include // Currently needed for IsBadReadPtr and IsBadWritePtr #pragma comment(lib,"user32.lib") // For MessageBox #endif #include "tier0/minidump.h" #include "tier0/stacktools.h" #include "tier0/etwprof.h" #include #include #include #include #include #include "color.h" #include "tier0/dbg.h" #include "tier0/threadtools.h" #include "tier0/icommandline.h" #include "tier0/vprof.h" #include #if defined( _X360 ) #include "xbox/xbox_console.h" #endif #ifndef STEAM #define PvRealloc realloc #define PvAlloc malloc #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) #pragma optimize( "g", off ) //variable argument functions seem to screw up stack walking unless this optimization is disabled // Disable this warning: dbg.cpp(479): warning C4748: /GS can not protect parameters and local variables from local buffer overrun because optimizations are disabled in function #pragma warning( disable : 4748 ) #endif DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_LOADING, "LOADING" ); //----------------------------------------------------------------------------- // Stack attachment management //----------------------------------------------------------------------------- #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) static bool s_bCallStacksWithAllWarnings = false; //if true, attach a call stack to every SPEW_WARNING message. Warning()/DevWarning()/... static int s_iWarningMaxCallStackLength = 5; #define AutomaticWarningCallStackLength() (s_bCallStacksWithAllWarnings ? s_iWarningMaxCallStackLength : 0) void _Warning_AlwaysSpewCallStack_Enable( bool bEnable ) { s_bCallStacksWithAllWarnings = bEnable; } void _Warning_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) { s_iWarningMaxCallStackLength = iMaxCallStackLength; } static bool s_bCallStacksWithAllErrors = false; //if true, attach a call stack to every SPEW_ERROR message. Mostly just Error() static int s_iErrorMaxCallStackLength = 20; //default to higher output with an error since we're quitting anyways #define AutomaticErrorCallStackLength() (s_bCallStacksWithAllErrors ? s_iErrorMaxCallStackLength : 0) void _Error_AlwaysSpewCallStack_Enable( bool bEnable ) { s_bCallStacksWithAllErrors = bEnable; } void _Error_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) { s_iErrorMaxCallStackLength = iMaxCallStackLength; } #else //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) #define AutomaticWarningCallStackLength() 0 #define AutomaticErrorCallStackLength() 0 void _Warning_AlwaysSpewCallStack_Enable( bool bEnable ) { } void _Warning_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) { } void _Error_AlwaysSpewCallStack_Enable( bool bEnable ) { } void _Error_AlwaysSpewCallStack_Length( int iMaxCallStackLength ) { } #endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) // Skip forward past the directory static const char *SkipToFname( const tchar* pFile ) { if ( pFile == NULL ) return "unknown"; const tchar* pSlash = _tcsrchr( pFile, '\\' ); const tchar* pSlash2 = _tcsrchr( pFile, '/' ); if (pSlash < pSlash2) pSlash = pSlash2; return pSlash ? pSlash + 1: pFile; } void _ExitOnFatalAssert( const tchar* pFile, int line ) { Log_Msg( LOG_ASSERT, _T("Fatal assert failed: %s, line %d. Application exiting.\n"), pFile, line ); // only write out minidumps if we're not in the debugger if ( !Plat_IsInDebugSession() ) { WriteMiniDump(); } Log_Msg( LOG_DEVELOPER, _T("_ExitOnFatalAssert\n") ); Plat_ExitProcess( EXIT_FAILURE ); } //----------------------------------------------------------------------------- // Templates to assist in validating pointers: //----------------------------------------------------------------------------- PLATFORM_INTERFACE void _AssertValidReadPtr( void* ptr, int count/* = 1*/ ) { #if defined( _WIN32 ) && !defined( _X360 ) Assert( !IsBadReadPtr( ptr, count ) ); #else Assert( !count || ptr ); #endif } PLATFORM_INTERFACE void _AssertValidWritePtr( void* ptr, int count/* = 1*/ ) { #if defined( _WIN32 ) && !defined( _X360 ) Assert( !IsBadWritePtr( ptr, count ) ); #else Assert( !count || ptr ); #endif } PLATFORM_INTERFACE void _AssertValidReadWritePtr( void* ptr, int count/* = 1*/ ) { #if defined( _WIN32 ) && !defined( _X360 ) Assert(!( IsBadWritePtr(ptr, count) || IsBadReadPtr(ptr,count))); #else Assert( !count || ptr ); #endif } PLATFORM_INTERFACE void _AssertValidStringPtr( const tchar* ptr, int maxchar/* = 0xFFFFFF */ ) { #if defined( _WIN32 ) && !defined( _X360 ) #ifdef TCHAR_IS_CHAR Assert( !IsBadStringPtr( ptr, maxchar ) ); #else Assert( !IsBadStringPtrW( ptr, maxchar ) ); #endif #else Assert( ptr ); #endif } PLATFORM_INTERFACE void AssertValidWStringPtr( const wchar_t* ptr, int maxchar/* = 0xFFFFFF */ ) { #if defined( _WIN32 ) && !defined( _X360 ) Assert( !IsBadStringPtrW( ptr, maxchar ) ); #else Assert( ptr ); #endif } void AppendCallStackToLogMessage( tchar *formattedMessage, int iMessageLength, int iAppendCallStackLength ) { #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) # if defined( TCHAR_IS_CHAR ) //I'm horrible with unicode and I don't plan on testing this with wide characters just yet if( iAppendCallStackLength > 0 ) { int iExistingMessageLength = (int)strlen( formattedMessage ); //no V_strlen in tier 0, plus we're only compiling this for windows and 360. Seems safe formattedMessage += iExistingMessageLength; iMessageLength -= iExistingMessageLength; if( iMessageLength <= 32 ) return; //no room for anything useful //append directly to the spew message if( (iExistingMessageLength > 0) && (formattedMessage[-1] == '\n') ) { --formattedMessage; ++iMessageLength; } //append preface int iAppendedLength = _snprintf( formattedMessage, iMessageLength, _T("\nCall Stack:\n\t") ); void **CallStackBuffer = (void **)stackalloc( iAppendCallStackLength * sizeof( void * ) ); int iCount = GetCallStack( CallStackBuffer, iAppendCallStackLength, 2 ); if( TranslateStackInfo( CallStackBuffer, iCount, formattedMessage + iAppendedLength, iMessageLength - iAppendedLength, _T("\n\t") ) == 0 ) { //failure formattedMessage[0] = '\0'; //this is pointing at where we wrote "\nCall Stack:\n\t" } else { iAppendedLength += (int)strlen( formattedMessage + iAppendedLength ); //no V_strlen in tier 0, plus we're only compiling this for windows and 360. Seems safe if( iAppendedLength < iMessageLength ) { formattedMessage[iAppendedLength] = '\n'; //Add another newline. ++iAppendedLength; formattedMessage[iAppendedLength] = '\0'; } } } # else AssertMsg( false, "Fixme" ); # endif #endif } // Forward declare for internal use only. CLoggingSystem *GetGlobalLoggingSystem(); #define Log_LegacyHelperColor_Stack( Channel, Severity, Color, MessageFormat, AppendCallStackLength ) \ do \ { \ CLoggingSystem *pLoggingSystem = GetGlobalLoggingSystem(); \ if ( pLoggingSystem->IsChannelEnabled( Channel, Severity ) ) \ { \ tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH]; \ va_list args; \ va_start( args, MessageFormat ); \ Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, MessageFormat, args ); \ va_end( args ); \ AppendCallStackToLogMessage( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, AppendCallStackLength ); \ pLoggingSystem->LogDirect( Channel, Severity, Color, formattedMessage ); \ } \ } while( 0 ) #define Log_LegacyHelperColor( Channel, Severity, Color, MessageFormat ) Log_LegacyHelperColor_Stack( Channel, Severity, Color, MessageFormat, 0 ) #define Log_LegacyHelper_Stack( Channel, Severity, MessageFormat, AppendCallStackLength ) Log_LegacyHelperColor_Stack( Channel, Severity, pLoggingSystem->GetChannelColor( Channel ), MessageFormat, AppendCallStackLength ) #define Log_LegacyHelper( Channel, Severity, MessageFormat ) Log_LegacyHelperColor( Channel, Severity, pLoggingSystem->GetChannelColor( Channel ), MessageFormat ) #if !defined( DBGFLAG_STRINGS_STRIP ) void Msg( const tchar* pMsgFormat, ... ) { Log_LegacyHelper( LOG_GENERAL, LS_MESSAGE, pMsgFormat ); } void Warning( const tchar *pMsgFormat, ... ) { Log_LegacyHelper_Stack( LOG_GENERAL, LS_WARNING, pMsgFormat, AutomaticWarningCallStackLength() ); } void Warning_SpewCallStack( int iMaxCallStackLength, const tchar *pMsgFormat, ... ) { Log_LegacyHelper_Stack( LOG_GENERAL, LS_WARNING, pMsgFormat, iMaxCallStackLength ); } #endif // !DBGFLAG_STRINGS_STRIP void Error( const tchar *pMsgFormat, ... ) { #if !defined( DBGFLAG_STRINGS_STRIP ) Log_LegacyHelper_Stack( LOG_GENERAL, LS_ERROR, pMsgFormat, AutomaticErrorCallStackLength() ); // Many places that call Error assume that execution will not continue afterwards so it // is important to exit here. The function prototype promises that this will happen. Plat_ExitProcess( 100 ); #endif } void Error_SpewCallStack( int iMaxCallStackLength, const tchar *pMsgFormat, ... ) { #if !defined( DBGFLAG_STRINGS_STRIP ) Log_LegacyHelper_Stack( LOG_GENERAL, LS_ERROR, pMsgFormat, iMaxCallStackLength ); // Many places that call Error_SpewCallStack assume that execution will not continue afterwards so it // is important to exit here. The function prototype promises that this will happen. Plat_ExitProcess( 100 ); #endif } #if !defined( DBGFLAG_STRINGS_STRIP ) //----------------------------------------------------------------------------- // A couple of super-common dynamic spew messages, here for convenience // These looked at the "developer" group, print if it's level 1 or higher //----------------------------------------------------------------------------- void DevMsg( int level, const tchar* pMsgFormat, ... ) { LoggingChannelID_t channel = level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER; Log_LegacyHelper( channel, LS_MESSAGE, pMsgFormat ); } void DevWarning( int level, const tchar *pMsgFormat, ... ) { LoggingChannelID_t channel = level >= 2 ? LOG_DEVELOPER_VERBOSE : LOG_DEVELOPER; Log_LegacyHelper( channel, LS_WARNING, pMsgFormat ); } void DevMsg( const tchar *pMsgFormat, ... ) { Log_LegacyHelper( LOG_DEVELOPER, LS_MESSAGE, pMsgFormat ); } void DevWarning( const tchar *pMsgFormat, ... ) { Log_LegacyHelper( LOG_DEVELOPER, LS_WARNING, pMsgFormat ); } void ConColorMsg( const Color& clr, const tchar* pMsgFormat, ... ) { Log_LegacyHelperColor( LOG_CONSOLE, LS_MESSAGE, clr, pMsgFormat ); } void ConMsg( const tchar *pMsgFormat, ... ) { Log_LegacyHelper( LOG_CONSOLE, LS_MESSAGE, pMsgFormat ); } void ConDMsg( const tchar *pMsgFormat, ... ) { Log_LegacyHelper( LOG_DEVELOPER_CONSOLE, LS_MESSAGE, pMsgFormat ); } #endif // !DBGFLAG_STRINGS_STRIP // If we don't have a function from math.h, then it doesn't link certain floating-point // functions in and printfs with %f cause runtime errors in the C libraries. PLATFORM_INTERFACE float CrackSmokingCompiler( float a ) { return (float)fabs( a ); } void* Plat_SimpleLog( const tchar* file, int line ) { FILE* f = _tfopen( _T("simple.log"), _T("at+") ); _ftprintf( f, _T("%s:%i\n"), file, line ); fclose( f ); return NULL; } #if !defined( DBGFLAG_STRINGS_STRIP ) //----------------------------------------------------------------------------- // Purpose: For debugging startup times, etc. // Input : *fmt - // ... - //----------------------------------------------------------------------------- void COM_TimestampedLog( char const *fmt, ... ) { static float s_LastStamp = 0.0; static bool s_bShouldLog = false; static bool s_bShouldLogToConsole = false; static bool s_bShouldLogToETW = false; static bool s_bChecked = false; static bool s_bFirstWrite = false; if ( !s_bChecked ) { s_bShouldLog = ( CommandLine()->CheckParm( "-profile" ) ) ? true : false; s_bShouldLogToConsole = ( CommandLine()->ParmValue( "-profile", 0.0f ) != 0.0f ) ? true : false; s_bShouldLogToETW = ( CommandLine()->CheckParm( "-etwprofile" ) ) ? true : false; if ( s_bShouldLogToETW ) { s_bShouldLog = true; } s_bChecked = true; } if ( !s_bShouldLog ) { return; } char string[1024]; va_list argptr; va_start( argptr, fmt ); Tier0Internal_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); float curStamp = Plat_FloatTime(); #if defined( _X360 ) XBX_rTimeStampLog( curStamp, string ); #elif defined( _PS3 ) Log_Warning( LOG_LOADING, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string ); #endif if ( IsPC() ) { // If ETW profiling is enabled then do it only. if ( s_bShouldLogToETW ) { ETWMark( string ); } else { if ( !s_bFirstWrite ) { unlink( "timestamped.log" ); s_bFirstWrite = true; } FILE* fp = fopen( "timestamped.log", "at+" ); fprintf( fp, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string ); fclose( fp ); if ( s_bShouldLogToConsole ) { Msg( "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string ); } } } s_LastStamp = curStamp; } #endif // !DBGFLAG_STRINGS_STRIP static AssertFailedNotifyFunc_t s_AssertFailedNotifyFunc = NULL; //----------------------------------------------------------------------------- // Sets an assert failed notify handler //----------------------------------------------------------------------------- void SetAssertFailedNotifyFunc( AssertFailedNotifyFunc_t func ) { s_AssertFailedNotifyFunc = func; } //----------------------------------------------------------------------------- // Calls the assert failed notify handler if one has been set //----------------------------------------------------------------------------- void CallAssertFailedNotifyFunc( const char *pchFile, int nLine, const char *pchMessage ) { if ( s_AssertFailedNotifyFunc ) s_AssertFailedNotifyFunc( pchFile, nLine, pchMessage ); } #ifdef IS_WINDOWS_PC class CHardwareBreakPoint { public: enum EOpCode { BRK_SET = 0, BRK_UNSET, }; CHardwareBreakPoint() { m_eOperation = BRK_SET; m_pvAddress = 0; m_hThread = 0; m_hThreadEvent = 0; m_nRegister = 0; m_bSuccess = false; } const void *m_pvAddress; HANDLE m_hThread; EHardwareBreakpointType m_eType; EHardwareBreakpointSize m_eSize; HANDLE m_hThreadEvent; int m_nRegister; EOpCode m_eOperation; bool m_bSuccess; static void SetBits( DWORD_PTR& dw, int lowBit, int bits, int newValue ); static DWORD WINAPI ThreadProc( LPVOID lpParameter ); }; void CHardwareBreakPoint::SetBits( DWORD_PTR& dw, int lowBit, int bits, int newValue ) { DWORD_PTR mask = (1 << bits) - 1; dw = (dw & ~(mask << lowBit)) | (newValue << lowBit); } DWORD WINAPI CHardwareBreakPoint::ThreadProc( LPVOID lpParameter ) { CHardwareBreakPoint *h = reinterpret_cast< CHardwareBreakPoint * >( lpParameter ); SuspendThread( h->m_hThread ); // Get current context CONTEXT ct = {0}; ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; GetThreadContext(h->m_hThread,&ct); int FlagBit = 0; bool Dr0Busy = false; bool Dr1Busy = false; bool Dr2Busy = false; bool Dr3Busy = false; if (ct.Dr7 & 1) Dr0Busy = true; if (ct.Dr7 & 4) Dr1Busy = true; if (ct.Dr7 & 16) Dr2Busy = true; if (ct.Dr7 & 64) Dr3Busy = true; if ( h->m_eOperation == CHardwareBreakPoint::BRK_UNSET ) { // Remove if (h->m_nRegister == 0) { FlagBit = 0; ct.Dr0 = 0; Dr0Busy = false; } if (h->m_nRegister == 1) { FlagBit = 2; ct.Dr1 = 0; Dr1Busy = false; } if (h->m_nRegister == 2) { FlagBit = 4; ct.Dr2 = 0; Dr2Busy = false; } if (h->m_nRegister == 3) { FlagBit = 6; ct.Dr3 = 0; Dr3Busy = false; } ct.Dr7 &= ~(1 << FlagBit); } else { if (!Dr0Busy) { h->m_nRegister = 0; ct.Dr0 = (DWORD_PTR)h->m_pvAddress; Dr0Busy = true; } else if (!Dr1Busy) { h->m_nRegister = 1; ct.Dr1 = (DWORD_PTR)h->m_pvAddress; Dr1Busy = true; } else if (!Dr2Busy) { h->m_nRegister = 2; ct.Dr2 = (DWORD_PTR)h->m_pvAddress; Dr2Busy = true; } else if (!Dr3Busy) { h->m_nRegister = 3; ct.Dr3 = (DWORD_PTR)h->m_pvAddress; Dr3Busy = true; } else { h->m_bSuccess = false; ResumeThread(h->m_hThread); SetEvent(h->m_hThreadEvent); return 0; } ct.Dr6 = 0; int st = 0; if (h->m_eType == BREAKPOINT_EXECUTE) st = 0; if (h->m_eType == BREAKPOINT_READWRITE) st = 3; if (h->m_eType == BREAKPOINT_WRITE) st = 1; int le = 0; if (h->m_eSize == BREAKPOINT_SIZE_1) le = 0; if (h->m_eSize == BREAKPOINT_SIZE_2) le = 1; if (h->m_eSize == BREAKPOINT_SIZE_4) le = 3; if (h->m_eSize == BREAKPOINT_SIZE_8) le = 2; SetBits( ct.Dr7, 16 + h->m_nRegister*4, 2, st ); SetBits( ct.Dr7, 18 + h->m_nRegister*4, 2, le ); SetBits( ct.Dr7, h->m_nRegister*2,1,1); } ct.ContextFlags = CONTEXT_DEBUG_REGISTERS; SetThreadContext(h->m_hThread,&ct); ResumeThread( h->m_hThread ); h->m_bSuccess = true; SetEvent( h->m_hThreadEvent ); return 0; } HardwareBreakpointHandle_t SetHardwareBreakpoint( EHardwareBreakpointType eType, EHardwareBreakpointSize eSize, const void *pvLocation ) { CHardwareBreakPoint *h = new CHardwareBreakPoint(); h->m_pvAddress = pvLocation; h->m_eSize = eSize; h->m_eType = eType; HANDLE hThread = GetCurrentThread(); h->m_hThread = hThread; if ( hThread == GetCurrentThread() ) { DWORD nThreadId = GetCurrentThreadId(); h->m_hThread = OpenThread( THREAD_ALL_ACCESS, 0, nThreadId ); } h->m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); h->m_eOperation = CHardwareBreakPoint::BRK_SET; // Set Break CreateThread( 0, 0, CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0, 0 ); WaitForSingleObject( h->m_hThreadEvent,INFINITE ); CloseHandle( h->m_hThreadEvent ); h->m_hThreadEvent = 0; if ( hThread == GetCurrentThread() ) { CloseHandle( h->m_hThread ); } h->m_hThread = hThread; if ( !h->m_bSuccess ) { delete h; return (HardwareBreakpointHandle_t)0; } return (HardwareBreakpointHandle_t)h; } bool ClearHardwareBreakpoint( HardwareBreakpointHandle_t handle ) { CHardwareBreakPoint *h = reinterpret_cast< CHardwareBreakPoint* >( handle ); if ( !h ) { return false; } bool bOpened = false; if ( h->m_hThread == GetCurrentThread() ) { DWORD nThreadId = GetCurrentThreadId(); h->m_hThread = OpenThread( THREAD_ALL_ACCESS, 0, nThreadId ); bOpened = true; } h->m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); h->m_eOperation = CHardwareBreakPoint::BRK_UNSET; // Remove Break CreateThread( 0,0,CHardwareBreakPoint::ThreadProc, (LPVOID)h, 0,0 ); WaitForSingleObject( h->m_hThreadEvent, INFINITE ); CloseHandle( h->m_hThreadEvent ); h->m_hThreadEvent = 0; if ( bOpened ) { CloseHandle( h->m_hThread ); } delete h; return true; } #endif // IS_WINDOWS_PC