//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $Workfile: $ // $NoKeywords: $ //===========================================================================// #include "pch_tier0.h" #include "tier0/stacktools.h" #include "tier0/threadtools.h" #include "tier0/icommandline.h" #include "tier0/valve_off.h" #if defined( PLATFORM_WINDOWS_PC ) #define WIN32_LEAN_AND_MEAN #include #include #endif #if defined( PLATFORM_X360 ) #include #include "xbox/xbox_console.h" #include "xbox/xbox_vxconsole.h" #include #include #endif #include "tier0/valve_on.h" #include "tier0/memdbgon.h" #if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) //disable the whole toolset int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) { return 0; } int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) { return 0; } //where we'll find our PDB's for win32. Translation will not work until this has been called once (even if with NULL) void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) { } void StackToolsNotify_LoadedLibrary( const char *szLibName ) { } int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) { if( iOutBufferSize > 0 ) *szOutput = '\0'; return 0; } void PreloadStackInformation( const void **pAddresses, int iAddressCount ) { } bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) { if( iMaxFileNameLength > 0 ) *pFileNameOut = '\0'; return false; } bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) { if( iMaxSymbolNameLength > 0 ) *pSymbolNameOut = '\0'; return false; } bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) { if( iMaxModuleNameLength > 0 ) *pModuleNameOut = '\0'; return false; } #else //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) //=============================================================================================================== // Shared Windows/X360 code //=============================================================================================================== CTHREADLOCALPTR( CStackTop_Base ) g_StackTop; class CStackTop_FriendFuncs : public CStackTop_Base { public: friend int AppendParentStackTrace( void **pReturnAddressesOut, int iArrayCount, int iAlreadyFilled ); friend int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ); }; inline int AppendParentStackTrace( void **pReturnAddressesOut, int iArrayCount, int iAlreadyFilled ) { CStackTop_FriendFuncs *pTop = (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; if( pTop != NULL ) { if( pTop->m_pReplaceAddress != NULL ) { for( int i = iAlreadyFilled; --i >= 0; ) { if( pReturnAddressesOut[i] == pTop->m_pReplaceAddress ) { iAlreadyFilled = i; break; } } } if( pTop->m_iParentStackTraceLength != 0 ) { int iCopy = MIN( iArrayCount - iAlreadyFilled, pTop->m_iParentStackTraceLength ); memcpy( pReturnAddressesOut + iAlreadyFilled, pTop->m_pParentStackTrace, iCopy * sizeof( void * ) ); iAlreadyFilled += iCopy; } } return iAlreadyFilled; } inline bool ValidStackAddress( void *pAddress, const void *pNoLessThan, const void *pNoGreaterThan ) { if( ( int )pAddress & 3 ) return false; if( pAddress < pNoLessThan ) //frame pointer traversal should always increase the pointer return false; if( pAddress > pNoGreaterThan ) //never traverse outside the stack (Oh 0xCCCCCCCC, how I hate you) return false; #if defined( WIN32 ) && !defined( _X360 ) && 1 if( IsBadReadPtr( pAddress, (sizeof( void * ) * 2) ) ) //safety net, but also throws an exception (handled internally) to stop bad access return false; #endif return true; } #pragma auto_inline( off ) int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) { //Only tested in windows. This function won't work with frame pointer omission enabled. "vpc /nofpo" all projects #if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ (defined( WIN32 ) && !defined( _X360 ) && !defined(_M_X64)) void *pStackCrawlEBP; __asm { mov [pStackCrawlEBP], ebp; } /* With frame pointer omission disabled, this should be the pattern all the way up the stack [ebp+00] Old ebp value [ebp+04] Return address */ void *pNoLessThan = pStackCrawlEBP; //impossible for a valid stack to traverse before this address int i; CStackTop_FriendFuncs *pTop = (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; if( pTop != NULL ) //we can do fewer error checks if we have a valid reference point for the top of the stack { void *pNoGreaterThan = pTop->m_pStackBase; //skips for( i = 0; i != iSkipCount; ++i ) { if( (pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan) ) return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); pNoLessThan = pStackCrawlEBP; pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value } //store for( i = 0; i != iArrayCount; ++i ) { if( (pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan) ) break; pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); pNoLessThan = pStackCrawlEBP; pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value } return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); } else { void *pNoGreaterThan = ((unsigned char *)pNoLessThan) + (1024 * 1024); //standard stack is 1MB. TODO: Get actual stack end address if available since this check isn't foolproof //skips for( i = 0; i != iSkipCount; ++i ) { if( !ValidStackAddress( pStackCrawlEBP, pNoLessThan, pNoGreaterThan ) ) return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); pNoLessThan = pStackCrawlEBP; pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value } //store for( i = 0; i != iArrayCount; ++i ) { if( !ValidStackAddress( pStackCrawlEBP, pNoLessThan, pNoGreaterThan ) ) break; pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); pNoLessThan = pStackCrawlEBP; pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value } return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); } #endif return 0; } #pragma auto_inline( on ) #if defined( WIN32 ) && !defined( _X360 ) //=============================================================================================================== // Windows version of the toolset //=============================================================================================================== #if defined( TIER0_FPO_DISABLED ) //# define USE_CAPTURESTACKBACKTRACE //faster than StackWalk64, but only works on XP or newer and only with Frame Pointer Omission optimization disabled(/Oy-) for every function it traces through #endif #if defined(_M_IX86) || defined(_M_X64) # define USE_STACKWALK64 # if defined(_M_IX86) # define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_I386 # else # define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_AMD64 # endif #endif typedef DWORD (WINAPI *PFN_SymGetOptions)( VOID ); typedef DWORD (WINAPI *PFN_SymSetOptions)( IN DWORD SymOptions ); typedef BOOL (WINAPI *PFN_SymSetSearchPath)( IN HANDLE hProcess, IN PSTR SearchPath ); typedef BOOL (WINAPI *PFN_SymInitialize)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ); typedef BOOL (WINAPI *PFN_SymCleanup)( IN HANDLE hProcess ); typedef BOOL (WINAPI *PFN_SymEnumerateModules64)( IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, IN PVOID UserContext ); typedef BOOL (WINAPI *PFN_EnumerateLoadedModules64)( IN HANDLE hProcess, IN PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, IN PVOID UserContext ); typedef DWORD64 (WINAPI *PFN_SymLoadModule64)( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); typedef BOOL (WINAPI *PFN_SymUnloadModule64)( IN HANDLE hProcess, IN DWORD64 BaseOfDll ); typedef BOOL (WINAPI *PFN_SymFromAddr)( IN HANDLE hProcess, IN DWORD64 Address, OUT PDWORD64 Displacement, IN OUT PSYMBOL_INFO Symbol ); typedef BOOL (WINAPI *PFN_SymGetLineFromAddr64)( IN HANDLE hProcess, IN DWORD64 qwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line64 ); typedef BOOL (WINAPI *PFN_SymGetModuleInfo64)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo ); typedef BOOL (WINAPI *PFN_StackWalk64)( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); typedef USHORT (WINAPI *PFN_CaptureStackBackTrace)( IN ULONG FramesToSkip, IN ULONG FramesToCapture, OUT PVOID *BackTrace, OUT OPTIONAL PULONG BackTraceHash ); DWORD WINAPI SymGetOptions_DummyFn( VOID ) { return 0; } DWORD WINAPI SymSetOptions_DummyFn( IN DWORD SymOptions ) { return 0; } BOOL WINAPI SymSetSearchPath_DummyFn( IN HANDLE hProcess, IN PSTR SearchPath ) { return FALSE; } BOOL WINAPI SymInitialize_DummyFn( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ) { return FALSE; } BOOL WINAPI SymCleanup_DummyFn( IN HANDLE hProcess ) { return TRUE; } BOOL WINAPI SymEnumerateModules64_DummyFn( IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, IN PVOID UserContext ) { return FALSE; } BOOL WINAPI EnumerateLoadedModules64_DummyFn( IN HANDLE hProcess, IN PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, IN PVOID UserContext ) { return FALSE; } DWORD64 WINAPI SymLoadModule64_DummyFn( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ) { return 0; } BOOL WINAPI SymUnloadModule64_DummyFn( IN HANDLE hProcess, IN DWORD64 BaseOfDll ) { return FALSE; } BOOL WINAPI SymFromAddr_DummyFn( IN HANDLE hProcess, IN DWORD64 Address, OUT PDWORD64 Displacement, IN OUT PSYMBOL_INFO Symbol ) { return FALSE; } BOOL WINAPI SymGetLineFromAddr64_DummyFn( IN HANDLE hProcess, IN DWORD64 qwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line64 ) { return FALSE; } BOOL WINAPI SymGetModuleInfo64_DummyFn( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo ) { return FALSE; } BOOL WINAPI StackWalk64_DummyFn( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ) { return FALSE; } USHORT WINAPI CaptureStackBackTrace_DummyFn( IN ULONG FramesToSkip, IN ULONG FramesToCapture, OUT PVOID *BackTrace, OUT OPTIONAL PULONG BackTraceHash ) { return 0; } class CHelperFunctionsLoader { public: CHelperFunctionsLoader( void ) { m_bIsInitialized = false; m_bShouldReloadSymbols = false; m_hDbgHelpDll = NULL; m_szPDBSearchPath = NULL; m_pSymInitialize = SymInitialize_DummyFn; m_pSymCleanup = SymCleanup_DummyFn; m_pSymSetOptions = SymSetOptions_DummyFn; m_pSymGetOptions = SymGetOptions_DummyFn; m_pSymSetSearchPath = SymSetSearchPath_DummyFn; m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; m_pSymLoadModule64 = SymLoadModule64_DummyFn; m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; m_pSymFromAddr = SymFromAddr_DummyFn; m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; #if defined( USE_STACKWALK64 ) m_pStackWalk64 = StackWalk64_DummyFn; #endif #if defined( USE_CAPTURESTACKBACKTRACE ) m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; m_hNTDllDll = NULL; #endif } ~CHelperFunctionsLoader( void ) { m_pSymCleanup( m_hProcess ); if( m_hDbgHelpDll != NULL ) ::FreeLibrary( m_hDbgHelpDll ); #if defined( USE_CAPTURESTACKBACKTRACE ) if( m_hNTDllDll != NULL ) ::FreeLibrary( m_hNTDllDll ); #endif if( m_szPDBSearchPath != NULL ) delete []m_szPDBSearchPath; } static BOOL CALLBACK UnloadSymbolsCallback( PSTR ModuleName, DWORD64 BaseOfDll, PVOID UserContext ) { const CHelperFunctionsLoader *pThis = ((CHelperFunctionsLoader *)UserContext); pThis->m_pSymUnloadModule64( pThis->m_hProcess, BaseOfDll ); return TRUE; } #if _MSC_VER >= 1600 static BOOL CALLBACK LoadSymbolsCallback( PCSTR ModuleName, DWORD64 ModuleBase, ULONG ModuleSize, PVOID UserContext ) #else static BOOL CALLBACK LoadSymbolsCallback( PSTR ModuleName, DWORD64 ModuleBase, ULONG ModuleSize, PVOID UserContext ) #endif { const CHelperFunctionsLoader *pThis = ((CHelperFunctionsLoader *)UserContext); //SymLoadModule64( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); pThis->m_pSymLoadModule64( pThis->m_hProcess, NULL, (PSTR)ModuleName, (PSTR)ModuleName, ModuleBase, ModuleSize ); return TRUE; } void TryLoadingNewSymbols( void ) { AUTO_LOCK_FM( m_Mutex ); if( m_bIsInitialized ) { //m_pSymEnumerateModules64( m_hProcess, UnloadSymbolsCallback, this ); //unloaded modules we've already loaded m_pEnumerateLoadedModules64( m_hProcess, LoadSymbolsCallback, this ); //load everything m_bShouldReloadSymbols = false; } } void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) { AUTO_LOCK_FM( m_Mutex ); if( m_szPDBSearchPath != NULL ) delete []m_szPDBSearchPath; if( szSemicolonSeparatedList == NULL ) { m_szPDBSearchPath = NULL; return; } int iLength = (int)strlen( szSemicolonSeparatedList ) + 1; char *pNewPath = new char [iLength]; memcpy( pNewPath, szSemicolonSeparatedList, iLength ); m_szPDBSearchPath = pNewPath; //re-init search paths. Or if we haven't yet loaded dbghelp.dll, this will go to the dummy function and do nothing m_pSymSetSearchPath( m_hProcess, m_szPDBSearchPath ); //TryLoadingNewSymbols(); m_bShouldReloadSymbols = true; } bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) { if( pAddress == NULL ) return false; AUTO_LOCK_FM( m_Mutex ); unsigned char genericbuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME*sizeof(TCHAR)]; ((PSYMBOL_INFO)genericbuffer)->SizeOfStruct = sizeof(SYMBOL_INFO); ((PSYMBOL_INFO)genericbuffer)->MaxNameLen = MAX_SYM_NAME; DWORD64 dwDisplacement; if( m_pSymFromAddr( m_hProcess, (DWORD64)pAddress, &dwDisplacement, (PSYMBOL_INFO)genericbuffer) ) { strncpy( pSymbolNameOut, ((PSYMBOL_INFO)genericbuffer)->Name, iMaxSymbolNameLength ); if( pDisplacementOut != NULL ) *pDisplacementOut = dwDisplacement; return true; } return false; } bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) { if( pAddress == NULL ) return false; AUTO_LOCK_FM( m_Mutex ); tchar szBuffer[1024]; szBuffer[0] = _T('\0'); IMAGEHLP_LINE64 imageHelpLine64; imageHelpLine64.SizeOfStruct = sizeof(IMAGEHLP_LINE64); imageHelpLine64.FileName = szBuffer; DWORD dwDisplacement; if( m_pSymGetLineFromAddr64( m_hProcess, (DWORD64)pAddress, &dwDisplacement, &imageHelpLine64 ) ) { strncpy( pFileNameOut, imageHelpLine64.FileName, iMaxFileNameLength ); iLineNumberOut = imageHelpLine64.LineNumber; if( pDisplacementOut != NULL ) *pDisplacementOut = dwDisplacement; return true; } return false; } bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) { AUTO_LOCK_FM( m_Mutex ); IMAGEHLP_MODULE64 moduleInfo; moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); if ( m_pSymGetModuleInfo64( m_hProcess, (DWORD64)pAddress, &moduleInfo ) ) { strncpy( pModuleNameOut, moduleInfo.ModuleName, iMaxModuleNameLength ); return true; } return false; } //only returns false if we ran out of buffer space. bool TranslatePointer( const void * const pAddress, tchar *pTranslationOut, int iTranslationBufferLength, TranslateStackInfo_StyleFlags_t style ) { //AUTO_LOCK( m_Mutex ); if( pTranslationOut == NULL ) return false; if( iTranslationBufferLength <= 0 ) return false; //sample desired output // valid translation - "tier0.dll!CHelperFunctionsLoader::TranslatePointer - u:\Dev\L4D\src\tier0\stacktools.cpp(162) + 4 bytes" // fallback translation - "tier0.dll!0x01234567" tchar *pWrite = pTranslationOut; *pWrite = '\0'; int iLength; if( style & TSISTYLEFLAG_MODULENAME ) { if( !this->GetModuleNameFromAddress( pAddress, pWrite, iTranslationBufferLength ) ) strncpy( pWrite, "unknown_module", iTranslationBufferLength ); iLength = (int)strlen( pWrite ); pWrite += iLength; iTranslationBufferLength -= iLength; if( iTranslationBufferLength < 2 ) return false; //need more buffer if( style & TSISTYLEFLAG_SYMBOLNAME ) { *pWrite = '!'; ++pWrite; --iTranslationBufferLength; *pWrite = '\0'; } } //use symbol name to test if the rest is going to work. So grab it whether they want it or not if( !this->GetSymbolNameFromAddress( pAddress, pWrite, iTranslationBufferLength, NULL ) ) { int nBytesWritten = _snprintf( pWrite, iTranslationBufferLength, "0x%p", pAddress ); if ( nBytesWritten < 0 ) { *pWrite = '\0'; // if we can't write all of the line/lineandoffset, don't write any at all return false; } return true; } else if( style & TSISTYLEFLAG_SYMBOLNAME ) { iLength = (int)strlen( pWrite ); pWrite += iLength; iTranslationBufferLength -= iLength; } else { *pWrite = '\0'; //symbol name lookup worked, but unwanted, discard } if( style & (TSISTYLEFLAG_FULLPATH | TSISTYLEFLAG_SHORTPATH | TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET) ) { if( pWrite != pTranslationOut ) //if we've written anything yet, separate the printed data from the file name and line { if( iTranslationBufferLength < 6 ) return false; //need more buffer pWrite[0] = ' '; //append " - " pWrite[1] = '-'; pWrite[2] = ' '; pWrite[3] = '\0'; pWrite += 3; iTranslationBufferLength -= 3; } uint32 iLine; uint32 iDisplacement; char szFileName[MAX_PATH]; if( this->GetFileAndLineFromAddress( pAddress, szFileName, MAX_PATH, iLine, &iDisplacement ) ) { if( style & TSISTYLEFLAG_FULLPATH ) { iLength = (int)strlen( szFileName ); if ( iTranslationBufferLength < iLength + 1 ) return false; memcpy( pWrite, szFileName, iLength + 1 ); pWrite += iLength; iTranslationBufferLength -= iLength; } else if( style & TSISTYLEFLAG_SHORTPATH ) { //shorten the path and copy iLength = (int)strlen( szFileName ); char *pShortened = szFileName + iLength; int iSlashesAllowed = 3; while( pShortened > szFileName ) { if( (*pShortened == '\\') || (*pShortened == '/') ) { --iSlashesAllowed; if( iSlashesAllowed == 0 ) break; } --pShortened; } iLength = (int)strlen( pShortened ); if( iTranslationBufferLength < iLength + 1 ) { //Remove the " - " that we can't append to pWrite -= 3; iTranslationBufferLength += 3; *pWrite = '\0'; return false; } memcpy( pWrite, szFileName, iLength + 1 ); pWrite += iLength; iTranslationBufferLength -= iLength; } if( style & (TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET) ) { int nBytesWritten = _snprintf( pWrite, iTranslationBufferLength, ((style & TSISTYLEFLAG_LINEANDOFFSET) && (iDisplacement != 0)) ? "(%d) + %d bytes" : "(%d)", iLine, iDisplacement ); if ( nBytesWritten < 0 ) { *pWrite = '\0'; // if we can't write all of the line/lineandoffset, don't write any at all return false; } pWrite += nBytesWritten; iTranslationBufferLength -= nBytesWritten; } } else { //Remove the " - " that we didn't append to pWrite -= 3; iTranslationBufferLength += 3; *pWrite = '\0'; } } return true; } //about to actually use the functions, load if necessary void EnsureReady( void ) { if( m_bIsInitialized ) { if( m_bShouldReloadSymbols ) TryLoadingNewSymbols(); return; } AUTO_LOCK_FM( m_Mutex ); //Only enabled for P4 and Steam Beta builds if( (CommandLine()->FindParm( "-steam" ) != 0) && //is steam (CommandLine()->FindParm( "-internalbuild" ) == 0) ) //is not steam beta { //disable the toolset by falsifying initialized state m_bIsInitialized = true; return; } m_hProcess = GetCurrentProcess(); if( m_hProcess == NULL ) return; m_bIsInitialized = true; // get the function pointer directly so that we don't have to include the .lib, and that // we can easily change it to using our own dll when this code is used on win98/ME/2K machines m_hDbgHelpDll = ::LoadLibrary( "DbgHelp.dll" ); if ( !m_hDbgHelpDll ) { //it's possible it's just way too early to initialize (as shown with attempts at using these tools in the memory allocator) if( m_szPDBSearchPath == NULL ) //not a rock solid check, but pretty good compromise between endless failing initialization and general failure due to trying too early m_bIsInitialized = false; return; } m_pSymInitialize = (PFN_SymInitialize) ::GetProcAddress( m_hDbgHelpDll, "SymInitialize" ); if( m_pSymInitialize == NULL ) { //very bad ::FreeLibrary( m_hDbgHelpDll ); m_hDbgHelpDll = NULL; m_pSymInitialize = SymInitialize_DummyFn; return; } m_pSymCleanup = (PFN_SymCleanup) ::GetProcAddress( m_hDbgHelpDll, "SymCleanup" ); if( m_pSymCleanup == NULL ) m_pSymCleanup = SymCleanup_DummyFn; m_pSymGetOptions = (PFN_SymGetOptions) ::GetProcAddress( m_hDbgHelpDll, "SymGetOptions" ); if( m_pSymGetOptions == NULL ) m_pSymGetOptions = SymGetOptions_DummyFn; m_pSymSetOptions = (PFN_SymSetOptions) ::GetProcAddress( m_hDbgHelpDll, "SymSetOptions" ); if( m_pSymSetOptions == NULL ) m_pSymSetOptions = SymSetOptions_DummyFn; m_pSymSetSearchPath = (PFN_SymSetSearchPath) ::GetProcAddress( m_hDbgHelpDll, "SymSetSearchPath" ); if( m_pSymSetSearchPath == NULL ) m_pSymSetSearchPath = SymSetSearchPath_DummyFn; m_pSymEnumerateModules64 = (PFN_SymEnumerateModules64) ::GetProcAddress( m_hDbgHelpDll, "SymEnumerateModules64" ); if( m_pSymEnumerateModules64 == NULL ) m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; m_pEnumerateLoadedModules64 = (PFN_EnumerateLoadedModules64) ::GetProcAddress( m_hDbgHelpDll, "EnumerateLoadedModules64" ); if( m_pEnumerateLoadedModules64 == NULL ) m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; m_pSymLoadModule64 = (PFN_SymLoadModule64) ::GetProcAddress( m_hDbgHelpDll, "SymLoadModule64" ); if( m_pSymLoadModule64 == NULL ) m_pSymLoadModule64 = SymLoadModule64_DummyFn; m_pSymUnloadModule64 = (PFN_SymUnloadModule64) ::GetProcAddress( m_hDbgHelpDll, "SymUnloadModule64" ); if( m_pSymUnloadModule64 == NULL ) m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; m_pSymFromAddr = (PFN_SymFromAddr) ::GetProcAddress( m_hDbgHelpDll, "SymFromAddr" ); if( m_pSymFromAddr == NULL ) m_pSymFromAddr = SymFromAddr_DummyFn; m_pSymGetLineFromAddr64 = (PFN_SymGetLineFromAddr64) ::GetProcAddress( m_hDbgHelpDll, "SymGetLineFromAddr64" ); if( m_pSymGetLineFromAddr64 == NULL ) m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; m_pSymGetModuleInfo64 = (PFN_SymGetModuleInfo64) ::GetProcAddress( m_hDbgHelpDll, "SymGetModuleInfo64" ); if( m_pSymGetModuleInfo64 == NULL ) m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; #if defined( USE_STACKWALK64 ) m_pStackWalk64 = (PFN_StackWalk64) ::GetProcAddress( m_hDbgHelpDll, "StackWalk64" ); if( m_pStackWalk64 == NULL ) m_pStackWalk64 = StackWalk64_DummyFn; #endif #if defined( USE_CAPTURESTACKBACKTRACE ) m_hNTDllDll = ::LoadLibrary( "ntdll.dll" ); m_pCaptureStackBackTrace = (PFN_CaptureStackBackTrace) ::GetProcAddress( m_hNTDllDll, "RtlCaptureStackBackTrace" ); if( m_pCaptureStackBackTrace == NULL ) m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; #endif m_pSymSetOptions( m_pSymGetOptions() | SYMOPT_DEFERRED_LOADS | //load on demand SYMOPT_EXACT_SYMBOLS | //don't load the wrong file SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_NO_PROMPTS | //don't prompt ever SYMOPT_LOAD_LINES ); //load line info m_pSymInitialize( m_hProcess, m_szPDBSearchPath, FALSE ); TryLoadingNewSymbols(); } bool m_bIsInitialized; bool m_bShouldReloadSymbols; HANDLE m_hProcess; HMODULE m_hDbgHelpDll; char *m_szPDBSearchPath; CThreadFastMutex m_Mutex; //DbgHelp functions are all single threaded. PFN_SymInitialize m_pSymInitialize; PFN_SymCleanup m_pSymCleanup; PFN_SymGetOptions m_pSymGetOptions; PFN_SymSetOptions m_pSymSetOptions; PFN_SymSetSearchPath m_pSymSetSearchPath; PFN_SymEnumerateModules64 m_pSymEnumerateModules64; PFN_EnumerateLoadedModules64 m_pEnumerateLoadedModules64; PFN_SymLoadModule64 m_pSymLoadModule64; PFN_SymUnloadModule64 m_pSymUnloadModule64; PFN_SymFromAddr m_pSymFromAddr; PFN_SymGetLineFromAddr64 m_pSymGetLineFromAddr64; PFN_SymGetModuleInfo64 m_pSymGetModuleInfo64; #if defined( USE_STACKWALK64 ) PFN_StackWalk64 m_pStackWalk64; #endif #if defined( USE_CAPTURESTACKBACKTRACE ) HMODULE m_hNTDllDll; PFN_CaptureStackBackTrace m_pCaptureStackBackTrace; #endif }; static CHelperFunctionsLoader s_HelperFunctions; #if defined( USE_STACKWALK64 ) //most reliable method thanks to boatloads of windows helper functions. Also the slowest. int CrawlStack_StackWalk64( CONTEXT *pExceptionContext, void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) { s_HelperFunctions.EnsureReady(); AUTO_LOCK_FM( s_HelperFunctions.m_Mutex ); CONTEXT currentContext; memcpy( ¤tContext, pExceptionContext, sizeof( CONTEXT ) ); STACKFRAME64 sfFrame = { 0 }; //memset(&sfFrame, 0x0, sizeof(sfFrame)); sfFrame.AddrPC.Mode = sfFrame.AddrFrame.Mode = AddrModeFlat; #ifdef _M_X64 sfFrame.AddrPC.Offset = currentContext.Rip; sfFrame.AddrFrame.Offset = currentContext.Rbp; // ???? #else sfFrame.AddrPC.Offset = currentContext.Eip; sfFrame.AddrFrame.Offset = currentContext.Ebp; #endif HANDLE hThread = GetCurrentThread(); int i; for( i = 0; i != iSkipCount; ++i ) //skip entries that the requesting function thinks are uninformative { if(!s_HelperFunctions.m_pStackWalk64( STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, &sfFrame, ¤tContext, NULL, NULL, NULL, NULL ) || (sfFrame.AddrFrame.Offset == 0) ) { return 0; } } for( i = 0; i != iArrayCount; ++i ) { if(!s_HelperFunctions.m_pStackWalk64( STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, &sfFrame, ¤tContext, NULL, NULL, NULL, NULL ) || (sfFrame.AddrFrame.Offset == 0) ) { break; } pReturnAddressesOut[i] = (void *)sfFrame.AddrPC.Offset; } return i; } void GetCallStackReturnAddresses_Exception( void **CallStackReturnAddresses, int *pRetCount, int iSkipCount, _EXCEPTION_POINTERS * pExceptionInfo ) { int iCount = CrawlStack_StackWalk64( pExceptionInfo->ContextRecord, CallStackReturnAddresses, *pRetCount, iSkipCount + 1 ); //skipping RaiseException() *pRetCount = iCount; } #endif //#if defined( USE_STACKWALK64 ) int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) { s_HelperFunctions.EnsureReady(); ++iSkipCount; //skip this function #if defined( USE_CAPTURESTACKBACKTRACE ) if( s_HelperFunctions.m_pCaptureStackBackTrace != CaptureStackBackTrace_DummyFn ) { //docs state a total limit of 63 back traces between skipped and stored int iRetVal = s_HelperFunctions.m_pCaptureStackBackTrace( iSkipCount, MIN( iArrayCount, 63 - iSkipCount ), pReturnAddressesOut, NULL ); return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, iRetVal ); } #endif #if defined( USE_STACKWALK64 ) if( s_HelperFunctions.m_pStackWalk64 != StackWalk64_DummyFn ) { int iInOutArrayCount = iArrayCount; //array count becomes both input and output with exception handler version __try { ::RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, NULL ); } __except ( GetCallStackReturnAddresses_Exception( pReturnAddressesOut, &iInOutArrayCount, iSkipCount, GetExceptionInformation() ), EXCEPTION_EXECUTE_HANDLER ) { return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, iInOutArrayCount ); } } #endif return GetCallStack_Fast( pReturnAddressesOut, iArrayCount, iSkipCount ); } void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) { s_HelperFunctions.SetStackTranslationSymbolSearchPath( szSemicolonSeparatedList ); } void StackToolsNotify_LoadedLibrary( const char *szLibName ) { s_HelperFunctions.m_bShouldReloadSymbols = true; } int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) { s_HelperFunctions.EnsureReady(); tchar *szStartOutput = szOutput; if( szEntrySeparator == NULL ) szEntrySeparator = _T(""); int iSeparatorSize = (int)strlen( szEntrySeparator ); for( int i = 0; i < iCallStackCount; ++i ) { if( !s_HelperFunctions.TranslatePointer( pCallStack[i], szOutput, iOutBufferSize, style ) ) { return i; } int iLength = (int)strlen( szOutput ); szOutput += iLength; iOutBufferSize -= iLength; if( iOutBufferSize > iSeparatorSize ) { memcpy( szOutput, szEntrySeparator, iSeparatorSize * sizeof( tchar ) ); szOutput += iSeparatorSize; iOutBufferSize -= iSeparatorSize; } *szOutput = '\0'; } szOutput -= iSeparatorSize; if( szOutput >= szStartOutput ) *szOutput = '\0'; return iCallStackCount; } void PreloadStackInformation( void * const *pAddresses, int iAddressCount ) { //nop on anything but 360 } bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) { s_HelperFunctions.EnsureReady(); return s_HelperFunctions.GetFileAndLineFromAddress( pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, pDisplacementOut ); } bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) { s_HelperFunctions.EnsureReady(); return s_HelperFunctions.GetSymbolNameFromAddress( pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut ); } bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) { s_HelperFunctions.EnsureReady(); return s_HelperFunctions.GetModuleNameFromAddress( pAddress, pModuleNameOut, iMaxModuleNameLength ); } #else //#if defined( WIN32 ) && !defined( _X360 ) //=============================================================================================================== // X360 version of the toolset //=============================================================================================================== class C360StackTranslationHelper { public: C360StackTranslationHelper( void ) { m_bInitialized = true; } ~C360StackTranslationHelper( void ) { StringSet_t::const_iterator iter; //module names { iter = m_ModuleNameSet.begin(); while(iter != m_ModuleNameSet.end()) { char *pModuleName = (char*)(*iter); delete []pModuleName; iter++; } m_ModuleNameSet.clear(); } //file names { iter = m_FileNameSet.begin(); while(iter != m_FileNameSet.end()) { char *pFileName = (char*)(*iter); delete []pFileName; iter++; } m_FileNameSet.clear(); } //symbol names { iter = m_SymbolNameSet.begin(); while(iter != m_SymbolNameSet.end()) { char *pSymbolName = (char*)(*iter); delete []pSymbolName; iter++; } m_SymbolNameSet.clear(); } m_bInitialized = false; } private: struct StackAddressInfo_t; public: inline StackAddressInfo_t *CreateEntry( const FullStackInfo_t &info ) { std::pair retval = m_AddressInfoMap.insert( AddressInfoMapEntry_t( info.pAddress, StackAddressInfo_t() ) ); if( retval.first->second.szModule != NULL ) return &retval.first->second; //already initialized retval.first->second.iLine = info.iLine; //share strings //module { const char *pModuleName; StringSet_t::const_iterator iter = m_ModuleNameSet.find( info.szModuleName ); if ( iter == m_ModuleNameSet.end() ) { int nLen = strlen(info.szModuleName) + 1; pModuleName = new char [nLen]; memcpy( (char *)pModuleName, info.szModuleName, nLen ); m_ModuleNameSet.insert( pModuleName ); } else { pModuleName = (char *)(*iter); } retval.first->second.szModule = pModuleName; } //file { const char *pFileName; StringSet_t::const_iterator iter = m_FileNameSet.find( info.szFileName ); if ( iter == m_FileNameSet.end() ) { int nLen = strlen(info.szFileName) + 1; pFileName = new char [nLen]; memcpy( (char *)pFileName, info.szFileName, nLen ); m_FileNameSet.insert( pFileName ); } else { pFileName = (char *)(*iter); } retval.first->second.szFileName = pFileName; } //symbol { const char *pSymbolName; StringSet_t::const_iterator iter = m_SymbolNameSet.find( info.szSymbol ); if ( iter == m_SymbolNameSet.end() ) { int nLen = strlen(info.szSymbol) + 1; pSymbolName = new char [nLen]; memcpy( (char *)pSymbolName, info.szSymbol, nLen ); m_SymbolNameSet.insert( pSymbolName ); } else { pSymbolName = (char *)(*iter); } retval.first->second.szSymbol = pSymbolName; } return &retval.first->second; } inline StackAddressInfo_t *FindInfoEntry( const void *pAddress ) { AddressInfoMapIter_t Iter = m_AddressInfoMap.find( pAddress ); if( Iter != m_AddressInfoMap.end() ) return &Iter->second; return NULL; } inline int RetrieveStackInfo( const void * const *pAddresses, FullStackInfo_t *pReturnedStructs, int iAddressCount ) { int ReturnedTranslatedCount = -1; //construct the message // Header Finished Count(out) Input Count Input Array Returned data write address int iMessageSize = 2 + sizeof( int * ) + sizeof( uint32 ) + (sizeof( void * ) * iAddressCount) + sizeof( FullStackInfo_t * ); uint8 *pMessage = (uint8 *)stackalloc( iMessageSize ); uint8 *pMessageWrite = pMessage; pMessageWrite[0] = XBX_DBG_BNH_STACKTRANSLATOR; //have this message handled by stack translator handler pMessageWrite[1] = ST_BHC_GETTRANSLATIONINFO; pMessageWrite += 2; *(int **)pMessageWrite = (int *)BigDWord( (DWORD)&ReturnedTranslatedCount ); pMessageWrite += sizeof( int * ); *(uint32 *)pMessageWrite = (uint32)BigDWord( (DWORD)iAddressCount ); pMessageWrite += sizeof( uint32 ); memcpy( pMessageWrite, pAddresses, iAddressCount * sizeof( void * ) ); pMessageWrite += iAddressCount * sizeof( void * ); *(FullStackInfo_t **)pMessageWrite = (FullStackInfo_t *)BigDWord( (DWORD)pReturnedStructs ); bool bSuccess = XBX_SendBinaryData( pMessage, iMessageSize, false, 30000 ); ReturnedTranslatedCount = BigDWord( ReturnedTranslatedCount ); if( bSuccess && (ReturnedTranslatedCount > 0) ) { return ReturnedTranslatedCount; } return 0; } inline StackAddressInfo_t *CreateEntry( const void *pAddress ) { //ask VXConsole for information about the addresses we're clueless about FullStackInfo_t ReturnedData; ReturnedData.pAddress = pAddress; ReturnedData.szFileName[0] = '\0'; //strncpy( ReturnedData.szFileName, "FileUninitialized", sizeof( ReturnedData.szFileName ) ); ReturnedData.szModuleName[0] = '\0'; //strncpy( ReturnedData.szModuleName, "ModuleUninitialized", sizeof( ReturnedData.szModuleName ) ); ReturnedData.szSymbol[0] = '\0'; //strncpy( ReturnedData.szSymbol, "SymbolUninitialized", sizeof( ReturnedData.szSymbol ) ); ReturnedData.iLine = 0; ReturnedData.iSymbolOffset = 0; int iTranslated = RetrieveStackInfo( &pAddress, &ReturnedData, 1 ); if( iTranslated == 1 ) { //store return CreateEntry( ReturnedData ); } return FindInfoEntry( pAddress ); //probably won't work, but last ditch. } inline StackAddressInfo_t *FindOrCreateEntry( const void *pAddress ) { StackAddressInfo_t *pReturn = FindInfoEntry( pAddress ); if( pReturn == NULL ) { pReturn = CreateEntry( pAddress ); } return pReturn; } inline void LoadStackInformation( void * const *pAddresses, int iAddressCount ) { Assert( (iAddressCount > 0) && (pAddresses != NULL) ); int iNeedLoading = 0; void **pNeedLoading = (void **)stackalloc( sizeof( const void * ) * iAddressCount ); //addresses we need to ask VXConsole about for( int i = 0; i != iAddressCount; ++i ) { if( FindInfoEntry( pAddresses[i] ) == NULL ) { //need to load this address pNeedLoading[iNeedLoading] = pAddresses[i]; ++iNeedLoading; } } if( iNeedLoading != 0 ) { //ask VXConsole for information about the addresses we're clueless about FullStackInfo_t *pReturnedStructs = (FullStackInfo_t *)stackalloc( sizeof( FullStackInfo_t ) * iNeedLoading ); for( int i = 0; i < iNeedLoading; ++i ) { pReturnedStructs[i].pAddress = 0; pReturnedStructs[i].szFileName[0] = '\0'; //strncpy( pReturnedStructs[i].szFileName, "FileUninitialized", sizeof( pReturnedStructs[i].szFileName ) ); pReturnedStructs[i].szModuleName[0] = '\0'; //strncpy( pReturnedStructs[i].szModuleName, "ModuleUninitialized", sizeof( pReturnedStructs[i].szModuleName ) ); pReturnedStructs[i].szSymbol[0] = '\0'; //strncpy( pReturnedStructs[i].szSymbol, "SymbolUninitialized", sizeof( pReturnedStructs[i].szSymbol ) ); pReturnedStructs[i].iLine = 0; pReturnedStructs[i].iSymbolOffset = 0; } int iTranslated = RetrieveStackInfo( pNeedLoading, pReturnedStructs, iNeedLoading ); if( iTranslated == iNeedLoading ) { //store for( int i = 0; i < iTranslated; ++i ) { CreateEntry( pReturnedStructs[i] ); } } } } inline bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) { StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); if( pInfo && (pInfo->szFileName[0] != '\0') ) { strncpy( pFileNameOut, pInfo->szFileName, iMaxFileNameLength ); iLineNumberOut = pInfo->iLine; if( pDisplacementOut ) *pDisplacementOut = 0; //can't get line displacement on 360 return true; } return false; } inline bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) { StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); if( pInfo && (pInfo->szSymbol[0] != '\0') ) { strncpy( pSymbolNameOut, pInfo->szSymbol, iMaxSymbolNameLength ); if( pDisplacementOut ) *pDisplacementOut = pInfo->iSymbolOffset; return true; } return false; } inline bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) { StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); if( pInfo && (pInfo->szModule[0] != '\0') ) { strncpy( pModuleNameOut, pInfo->szModule, iMaxModuleNameLength ); return true; } return false; } CThreadFastMutex m_hMutex; private: #pragma pack(push) #pragma pack(1) struct StackAddressInfo_t { StackAddressInfo_t( void ) : szModule(NULL), szFileName(NULL), szSymbol(NULL), iLine(0), iSymbolOffset(0) {} const char *szModule; const char *szFileName; const char *szSymbol; uint32 iLine; uint32 iSymbolOffset; }; #pragma pack(pop) typedef std::map< const void *, StackAddressInfo_t, std::less> AddressInfoMap_t; typedef AddressInfoMap_t::iterator AddressInfoMapIter_t; typedef AddressInfoMap_t::value_type AddressInfoMapEntry_t; class CStringLess { public: bool operator()(const char *pszLeft, const char *pszRight ) const { return ( V_tier0_stricmp( pszLeft, pszRight ) < 0 ); } }; typedef std::set StringSet_t; AddressInfoMap_t m_AddressInfoMap; //TODO: retire old entries? StringSet_t m_ModuleNameSet; StringSet_t m_FileNameSet; StringSet_t m_SymbolNameSet; bool m_bInitialized; }; static C360StackTranslationHelper s_360StackTranslator; int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) { ++iSkipCount; //skip this function //DmCaptureStackBackTrace() has no skip functionality, so we need to grab everything and skip within that list void **pAllResults = (void **)stackalloc( sizeof( void * ) * (iArrayCount + iSkipCount) ); if( DmCaptureStackBackTrace( iArrayCount + iSkipCount, pAllResults ) == XBDM_NOERR ) { for( int i = 0; i != iSkipCount; ++i ) { if( *pAllResults == NULL ) //DmCaptureStackBackTrace() NULL terminates the list instead of telling us how many were returned return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); ++pAllResults; //move the pointer forward so the second loop indices match up } for( int i = 0; i != iArrayCount; ++i ) { if( pAllResults[i] == NULL ) //DmCaptureStackBackTrace() NULL terminates the list instead of telling us how many were returned return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); pReturnAddressesOut[i] = pAllResults[i]; } return iArrayCount; //no room to append parent } return GetCallStack_Fast( pReturnAddressesOut, iArrayCount, iSkipCount ); } void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) { //nop on 360 } void StackToolsNotify_LoadedLibrary( const char *szLibName ) { //send off the notice to VXConsole uint8 message[2]; message[0] = XBX_DBG_BNH_STACKTRANSLATOR; //have this message handled by stack translator handler message[1] = ST_BHC_LOADEDLIBARY; //loaded a library notification XBX_SendBinaryData( message, 2 ); } int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) { if( iCallStackCount == 0 ) { if( iOutBufferSize > 1 ) *szOutput = '\0'; return 0; } if( szEntrySeparator == NULL ) szEntrySeparator = ""; int iSeparatorLength = strlen( szEntrySeparator ) + 1; int iDataSize = (sizeof( void * ) * iCallStackCount) + (iSeparatorLength * sizeof( tchar )) + 1; //1 for style flags //360 is incapable of translation on it's own. Encode the stack for translation in VXConsole //Encoded section is as such ":CSDECODE[encoded binary]" int iEncodedSize = -EncodeBinaryToString( NULL, iDataSize, NULL, 0 ); //get needed buffer size static const tchar cControlPrefix[] = XBX_CALLSTACKDECODEPREFIX; const size_t cControlLength = (sizeof( cControlPrefix )/sizeof(tchar)) - 1; //-1 to remove null terminator if( iOutBufferSize > (iEncodedSize + (int)cControlLength + 2) ) //+2 for ']' and null term { COMPILE_TIME_ASSERT( TSISTYLEFLAG_LAST < (1<<8) ); //need to update the encoder/decoder to use more than a byte for style flags uint8 *pData = (uint8 *)stackalloc( iDataSize ); pData[0] = (uint8)style; memcpy( pData + 1, szEntrySeparator, iSeparatorLength * sizeof( tchar ) ); memcpy( pData + 1 + (iSeparatorLength * sizeof( tchar )), pCallStack, iCallStackCount * sizeof( void * ) ); memcpy( szOutput, XBX_CALLSTACKDECODEPREFIX, cControlLength * sizeof( tchar ) ); int iLength = cControlLength + EncodeBinaryToString( pData, iDataSize, &szOutput[cControlLength], (iOutBufferSize - cControlLength) ); szOutput[iLength] = ']'; szOutput[iLength + 1] = '\0'; } return iCallStackCount; } void PreloadStackInformation( void * const *pAddresses, int iAddressCount ) { AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); s_360StackTranslator.LoadStackInformation( pAddresses, iAddressCount ); } bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) { AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); return s_360StackTranslator.GetFileAndLineFromAddress( pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, pDisplacementOut ); } bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) { AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); return s_360StackTranslator.GetSymbolNameFromAddress( pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut ); } bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) { AUTO_LOCK_FM( s_360StackTranslator.m_hMutex ); return s_360StackTranslator.GetModuleNameFromAddress( pAddress, pModuleNameOut, iMaxModuleNameLength ); } #endif //#else //#if defined( WIN32 ) && !defined( _X360 ) #endif //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) CCallStackStorage::CCallStackStorage( FN_GetCallStack GetStackFunction, uint32 iSkipCalls ) { iValidEntries = GetStackFunction( pStack, ARRAYSIZE( pStack ), iSkipCalls + 1 ); } CStackTop_CopyParentStack::CStackTop_CopyParentStack( void * const *pParentStackTrace, int iParentStackTraceLength ) { #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) //miniature version of GetCallStack_Fast() #if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ (defined( WIN32 ) && !defined( _X360 ) && !defined(_M_X64)) void *pStackCrawlEBP; __asm { mov [pStackCrawlEBP], ebp; } pStackCrawlEBP = *(void **)pStackCrawlEBP; m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); #else m_pReplaceAddress = NULL; m_pStackBase = this; #endif m_pParentStackTrace = NULL; if( (pParentStackTrace != NULL) && (iParentStackTraceLength > 0) ) { while( (iParentStackTraceLength > 0) && (pParentStackTrace[iParentStackTraceLength - 1] == NULL) ) { --iParentStackTraceLength; } if( iParentStackTraceLength > 0 ) { m_pParentStackTrace = new void * [iParentStackTraceLength]; memcpy( (void **)m_pParentStackTrace, pParentStackTrace, sizeof( void * ) * iParentStackTraceLength ); } } m_iParentStackTraceLength = iParentStackTraceLength; m_pPrevTop = g_StackTop; g_StackTop = this; Assert( (CStackTop_Base *)g_StackTop == this ); #endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) } CStackTop_CopyParentStack::~CStackTop_CopyParentStack( void ) { #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) Assert( (CStackTop_Base *)g_StackTop == this ); g_StackTop = m_pPrevTop; if( m_pParentStackTrace != NULL ) { delete []m_pParentStackTrace; } #endif } CStackTop_ReferenceParentStack::CStackTop_ReferenceParentStack( void * const *pParentStackTrace, int iParentStackTraceLength ) { #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) //miniature version of GetCallStack_Fast() #if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ (defined( WIN32 ) && !defined( _X360 ) && !defined(_M_X64)) void *pStackCrawlEBP; __asm { mov [pStackCrawlEBP], ebp; } pStackCrawlEBP = *(void **)pStackCrawlEBP; m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); #else m_pReplaceAddress = NULL; m_pStackBase = this; #endif m_pParentStackTrace = pParentStackTrace; if( (pParentStackTrace != NULL) && (iParentStackTraceLength > 0) ) { while( (iParentStackTraceLength > 0) && (pParentStackTrace[iParentStackTraceLength - 1] == NULL) ) { --iParentStackTraceLength; } } m_iParentStackTraceLength = iParentStackTraceLength; m_pPrevTop = g_StackTop; g_StackTop = this; Assert( (CStackTop_Base *)g_StackTop == this ); #endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) } CStackTop_ReferenceParentStack::~CStackTop_ReferenceParentStack( void ) { #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) Assert( (CStackTop_Base *)g_StackTop == this ); g_StackTop = m_pPrevTop; ReleaseParentStackReferences(); #endif } void CStackTop_ReferenceParentStack::ReleaseParentStackReferences( void ) { #if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) m_pParentStackTrace = NULL; m_iParentStackTraceLength = 0; #endif } //Encodes data so that every byte's most significant bit is a 1. Ensuring no null terminators. //This puts the encoded data in the 128-255 value range. Leaving all standard ascii characters for control. //Returns string length (not including the written null terminator as is standard). //Or if the buffer is too small. Returns negative of necessary buffer size (including room needed for null terminator) int EncodeBinaryToString( const void *pToEncode, int iDataLength, char *pEncodeOut, int iEncodeBufferSize ) { int iEncodedSize = iDataLength; iEncodedSize += (iEncodedSize + 6) / 7; //Have 1 control byte for every 7 actual bytes iEncodedSize += sizeof( uint32 ) + 1; //data size at the beginning of the blob and null terminator at the end if( (iEncodedSize > iEncodeBufferSize) || (pEncodeOut == NULL) || (pToEncode == NULL) ) return -iEncodedSize; //not enough room uint8 *pEncodeWrite = (uint8 *)pEncodeOut; //first encode the data size. Encodes lowest 28 bits and discards the high 4 pEncodeWrite[0] = ((iDataLength >> 21) & 0xFF) | 0x80; pEncodeWrite[1] = ((iDataLength >> 14) & 0xFF) | 0x80; pEncodeWrite[2] = ((iDataLength >> 7) & 0xFF) | 0x80; pEncodeWrite[3] = ((iDataLength >> 0) & 0xFF) | 0x80; pEncodeWrite += 4; const uint8 *pEncodeRead = (const uint8 *)pToEncode; const uint8 *pEncodeStop = pEncodeRead + iDataLength; uint8 *pEncodeWriteLastControlByte = pEncodeWrite; int iControl = 0; //Encode the data while( pEncodeRead < pEncodeStop ) { if( iControl == 0 ) { pEncodeWriteLastControlByte = pEncodeWrite; *pEncodeWriteLastControlByte = 0x80; } else { *pEncodeWrite = *pEncodeRead | 0x80; //encoded data always has the MSB bit set (cheap avoidance of null terminators) *pEncodeWriteLastControlByte |= (((*pEncodeRead) & 0x80) ^ 0x80) >> iControl; //We use the control byte to XOR the MSB back to original values on decode ++pEncodeRead; } ++pEncodeWrite; ++iControl; iControl &= 7; //8->0 } *pEncodeWrite = '\0'; return iEncodedSize - 1; } //Decodes a string produced by EncodeBinaryToString(). Safe to decode in place if you don't mind trashing your string, binary byte count always less than string byte count. //Returns: // >= 0 is the decoded data size // INT_MIN (most negative value possible) indicates an improperly formatted string (not our data) // all other negative values are the negative of how much dest buffer size is necessary. int DecodeBinaryFromString( const char *pString, void *pDestBuffer, int iDestBufferSize, char **ppParseFinishOut ) { const uint8 *pDecodeRead = (const uint8 *)pString; if( (pDecodeRead[0] < 0x80) || (pDecodeRead[1] < 0x80) || (pDecodeRead[2] < 0x80) || (pDecodeRead[3] < 0x80) ) { if( ppParseFinishOut != NULL ) *ppParseFinishOut = (char *)pString; return INT_MIN; //Don't know what the string is, but it's not our format } int iDecodedSize = 0; iDecodedSize |= (pDecodeRead[0] & 0x7F) << 21; iDecodedSize |= (pDecodeRead[1] & 0x7F) << 14; iDecodedSize |= (pDecodeRead[2] & 0x7F) << 7; iDecodedSize |= (pDecodeRead[3] & 0x7F) << 0; pDecodeRead += 4; int iTextLength = iDecodedSize; iTextLength += (iTextLength + 6) / 7; //Have 1 control byte for every 7 actual bytes //make sure it's formatted properly for( int i = 0; i != iTextLength; ++i ) { if( pDecodeRead[i] < 0x80 ) //encoded data always has MSB set { if( ppParseFinishOut != NULL ) *ppParseFinishOut = (char *)pString; return INT_MIN; //either not our data, or part of the string is missing } } if( iDestBufferSize < iDecodedSize ) { if( ppParseFinishOut != NULL ) *ppParseFinishOut = (char *)pDecodeRead; return -iDecodedSize; //dest buffer not big enough to hold the data } const uint8 *pStopDecoding = pDecodeRead + iTextLength; uint8 *pDecodeWrite = (uint8 *)pDestBuffer; int iControl = 0; int iLSBXOR = 0; while( pDecodeRead < pStopDecoding ) { if( iControl == 0 ) { iLSBXOR = *pDecodeRead; } else { *pDecodeWrite = *pDecodeRead ^ ((iLSBXOR << iControl) & 0x80); ++pDecodeWrite; } ++pDecodeRead; ++iControl; iControl &= 7; //8->0 } if( ppParseFinishOut != NULL ) *ppParseFinishOut = (char *)pDecodeRead; return iDecodedSize; }