/*========================================================================== * * Copyright (C) 2001 Microsoft Corporation. All Rights Reserved. * * File: MiniStack.h * Content: Reduced-overhead call stack tracking class * * History: * Date By Reason * ==== == ====== * 03/27/01 RichGr Derived from CallStack.h ***************************************************************************/ #ifndef __MINISTACK_H__ #define __MINISTACK_H__ #define _IMAGEHLP_SOURCE_ #include #include //********************************************************************** // Constant definitions //********************************************************************** #define MINISTACK_DEPTH 5 // Increasing this beyond 5 definitely slows things down. #define SYM_CACHE_SIZE 100 // 100 seems big enough. Old entries are automatically replaced. //********************************************************************** // Macro definitions //********************************************************************** //********************************************************************** // Structure definitions //********************************************************************** //********************************************************************** // Variable definitions //********************************************************************** //********************************************************************** // Function prototypes //********************************************************************** // // prototypes for ImageHlp.dll functions we get from LoadLibrary(). // typedef DWORD (__stdcall * PIMAGEHELP_SYMGETOPTIONS)( void ); typedef DWORD (__stdcall * PIMAGEHELP_SYMSETOPTIONS)( DWORD SymOptions ); typedef BOOL (__stdcall * PIMAGEHELP_SYMINITIALIZE)( HANDLE hProcess, PSTR pUserSearchPath, BOOL fInvadeProcess ); typedef BOOL (__stdcall * PIMAGEHELP_SYMGETSYMFROMADDR)( HANDLE hProcess, DWORD dwAddress, PDWORD pdwDisplacement, PIMAGEHLP_SYMBOL pSymbol ); typedef BOOL (__stdcall * PIMAGEHELP_SYMGETSYMFROMADDR64)( HANDLE hProcess, DWORD_PTR dwAddress, PDWORD_PTR pdwDisplacement, PIMAGEHLP_SYMBOL64 pSymbol ); //********************************************************************** // Class definitions //********************************************************************** class CMiniStack { public: // Member functions CMiniStack(); ~CMiniStack(); void GetCallStackString(char *const pOutputString); private: // Member functions void Initialize(char *const pOutputString); void NoteCurrentCallStack(PVOID pCallStack[]); // Member variables HANDLE m_hPID; BOOL m_bNotInited; volatile LONG m_nGuardInit; HINSTANCE m_hImageHelp; PIMAGEHELP_SYMGETOPTIONS m_pSymGetOptions; PIMAGEHELP_SYMSETOPTIONS m_pSymSetOptions; PIMAGEHELP_SYMINITIALIZE m_pSymInitialize; #ifndef _WIN64 PIMAGEHELP_SYMGETSYMFROMADDR m_pSymGetSymFromAddr; #else PIMAGEHELP_SYMGETSYMFROMADDR64 m_pSymGetSymFromAddr; #endif // _WIN64 volatile LONG m_nGuardSymTable; int m_nNextCacheSlot; BOOL m_bCacheIsFull; struct { PVOID pAddress; char szName[64]; int nReadCount; } m_SymTable[SYM_CACHE_SIZE]; }; //********************************************************************** // Per-Process instantiations //********************************************************************** #ifdef PER_PROCESS_INSTANTIATIONS // Define this in just one source file. CMiniStack g_MiniStack; #else extern CMiniStack g_MiniStack; #endif #ifdef PER_PROCESS_INSTANTIATIONS // Define this in just one source file. //********************************************************************** // Class member function definitions //********************************************************************** // These should be moved to a new file MiniStack.cpp for DX9. // ------------------------------ // CMiniStack::CMiniStack - constructor // // Entry: Nothing // // Exit: Nothing // ------------------------------ CMiniStack::CMiniStack() { m_bNotInited = TRUE; m_nGuardInit = 0; m_hPID = GetCurrentProcess(); m_pSymGetOptions = NULL; m_pSymSetOptions = NULL; m_pSymInitialize = NULL; m_pSymGetSymFromAddr = NULL; m_hImageHelp = NULL; memset(&m_SymTable, 0, sizeof m_SymTable); m_nNextCacheSlot = 0; m_bCacheIsFull = FALSE; return; } // ------------------------------ // CMiniStack::Initialize - load ImageHlp.dll and get proc addresses. // // Entry: Nothing // // Exit: Nothing // ------------------------------ void CMiniStack::Initialize(char *const pOutputString) { // Attempt to load ImageHelp.dll if ( (m_hImageHelp = LoadLibrary( "ImageHlp.dll" )) == NULL) goto FailedImageHelpLoad; m_pSymGetOptions = (PIMAGEHELP_SYMGETOPTIONS)GetProcAddress( m_hImageHelp, "SymGetOptions" ); m_pSymSetOptions = (PIMAGEHELP_SYMSETOPTIONS)GetProcAddress( m_hImageHelp, "SymSetOptions" ); m_pSymInitialize = (PIMAGEHELP_SYMINITIALIZE)GetProcAddress( m_hImageHelp, "SymInitialize" ); #ifndef _WIN64 m_pSymGetSymFromAddr = (PIMAGEHELP_SYMGETSYMFROMADDR)GetProcAddress( m_hImageHelp, "SymGetSymFromAddr" ); #else // _WIN64 m_pSymGetSymFromAddr = (PIMAGEHELP_SYMGETSYMFROMADDR64)GetProcAddress( m_hImageHelp, "SymGetSymFromAddr64" ); #endif // _WIN64 if ( m_pSymGetOptions == NULL || m_pSymSetOptions == NULL || m_pSymInitialize == NULL || m_pSymGetSymFromAddr == NULL ) { goto FailedImageHelpLoad; } m_pSymSetOptions( SYMOPT_DEFERRED_LOADS | m_pSymGetOptions() ); if ( m_pSymInitialize( m_hPID, NULL, TRUE ) == FALSE || m_pSymInitialize( m_hPID, NULL, FALSE ) == FALSE ) { goto FailedImageHelpLoad; } m_bNotInited = FALSE; Exit: return; FailedImageHelpLoad: strcpy(pOutputString, "*** ImageHlp.dll could not be loaded ***\r\n"); if (m_hImageHelp) { FreeLibrary(m_hImageHelp); m_hImageHelp = NULL; } goto Exit; } //********************************************************************** // ------------------------------ // CMiniStack::CMiniStack - destructor // // Entry: Nothing // // Exit: Nothing // ------------------------------ CMiniStack::~CMiniStack() { if (m_hImageHelp) { FreeLibrary(m_hImageHelp); m_hImageHelp = NULL; } return; } //********************************************************************** // ------------------------------ // CMiniStack::NoteCurrentCallStack - get a call stack // // Entry: Nothing // // Exit: Nothing // ------------------------------ void CMiniStack::NoteCurrentCallStack(PVOID pCallStack[]) { PVOID *ppCallersEBP; PVOID pStackTop; PVOID pStackBottom; int i; ppCallersEBP = NULL; pStackTop = pStackBottom = NULL; #ifdef _X86_ _asm { mov eax,dword ptr fs:[4] mov pStackTop, eax mov eax,dword ptr fs:[8] mov pStackBottom, eax mov eax,[ebp] mov ppCallersEBP,eax } __try { // This code can generate exception if it steps back too far... // Skip the 1st. stack item because it's always PerfEnterCriticalSection() // and we don't need to report it. for (i = -1; i < MINISTACK_DEPTH; i++) { if ( ppCallersEBP < pStackBottom || ppCallersEBP >= pStackTop ) break; if (i >= 0) pCallStack[i] = ppCallersEBP[1]; ppCallersEBP = (PVOID*)*ppCallersEBP; // get callers callers ebp } } __except( 1 ) // went too far back on the stack, rest of array is filled with zeros { // Benign access violation creating return address stack. } #endif // _X86_ return; } //********************************************************************** // ------------------------------ // CMiniStack::GetCallStack - return pointer to call stack string // // Entry: Pointer to destination string // // Exit: Nothing // ------------------------------ void CMiniStack::GetCallStackString( char *const pOutputString ) { int i, j; PVOID pCallStack[MINISTACK_DEPTH] = {0}; char ImageBuffer[sizeof IMAGEHLP_SYMBOL + 64] = {0}; DWORD_PTR dwFunctionDisplacement = 0; BOOL b1stStringDone = FALSE; char szMsg[50] = {0}; #ifndef _WIN64 IMAGEHLP_SYMBOL *const pImageHelpSymbol = (IMAGEHLP_SYMBOL*)ImageBuffer; #else // _WIN64 IMAGEHLP_SYMBOL64 *const pImageHelpSymbol = (IMAGEHLP_SYMBOL64*)ImageBuffer; #endif // _WIN64 strcpy(pOutputString, "STACK: "); if (m_bNotInited) { // Make sure we are the only thread attempting to call Initialize() at one time. if (InterlockedIncrement(&m_nGuardInit) == 1) Initialize(pOutputString); InterlockedDecrement(&m_nGuardInit); // If we skipped the Initialize() step or the Initialize() failed, we can just exit. if (m_bNotInited) return; } NoteCurrentCallStack(pCallStack); pImageHelpSymbol->SizeOfStruct = sizeof( *pImageHelpSymbol ); pImageHelpSymbol->Flags = 0; pImageHelpSymbol->MaxNameLength = sizeof ImageBuffer - sizeof *pImageHelpSymbol - sizeof TCHAR; // Loop thru the call stack addresses and pick up the corresponding function names. for (i = 0; i < MINISTACK_DEPTH && pCallStack[i] != NULL; i++) { PVOID pAddr; char *psz; pAddr = pCallStack[i]; psz = NULL; // Check to see if the address is in our Symbol table cache. // For a full array of 100, this only takes an average of 5 usecs on a P550. for (j = 0; m_SymTable[j].pAddress != NULL && j < SYM_CACHE_SIZE; j++) { if (pAddr == m_SymTable[j].pAddress) { psz = &m_SymTable[j].szName[0]; m_SymTable[j].nReadCount++; // Don't worry about wraps. break; } } // It's not in the cache, so get the name from the symbol file(using ImageHlp.dll). if (psz == NULL) { pImageHelpSymbol->Address = (DWORD_PTR)pAddr; if ( m_pSymGetSymFromAddr( m_hPID, (DWORD_PTR)pCallStack[i], &dwFunctionDisplacement, pImageHelpSymbol) != FALSE ) { psz = pImageHelpSymbol->Name; // We've taken a lot of time to extract the name. Now save it in our Symbol table cache. // Make sure we are the only thread attempting to update the cache at one time. // If it doesn't get updated by a particular thread, it doesn't matter - it'll get updated // some other time. if (InterlockedIncrement(&m_nGuardSymTable) == 1) { int nLastSlotFilled; // Fill slot. m_SymTable[m_nNextCacheSlot].pAddress = pAddr; m_SymTable[m_nNextCacheSlot].nReadCount = 0; // Some symbol names are invalid, so omit them. if ( !_stricmp(pImageHelpSymbol->Name, "GetModuleHandleA")) { wsprintf(szMsg, "0x%p (no symbols)", pAddr); strcpy(m_SymTable[m_nNextCacheSlot].szName, szMsg); psz = m_SymTable[m_nNextCacheSlot].szName; } else strcpy(m_SymTable[m_nNextCacheSlot].szName, pImageHelpSymbol->Name); nLastSlotFilled = m_nNextCacheSlot; // Select the next slot if ( !m_bCacheIsFull) { // This will work fine until we fill the cache table, // then we have to change our strategy and look for the // Least-Used slot. m_nNextCacheSlot++; if (m_nNextCacheSlot >= SYM_CACHE_SIZE) m_bCacheIsFull = TRUE; } // The cache is full, so we'll find the Least-Used slot and select that. if (m_bCacheIsFull) { int nLowestReadCount = 0x7fffffff; int k = 0; for (j = 0; j < SYM_CACHE_SIZE; j++) { if (j != nLastSlotFilled && m_SymTable[j].nReadCount < nLowestReadCount) { nLowestReadCount = m_SymTable[j].nReadCount; k = j; if (nLowestReadCount == 0) break; } } m_nNextCacheSlot = k; } } InterlockedDecrement(&m_nGuardSymTable); } } if (psz) { if (b1stStringDone) strcat(pOutputString, ", "); strcat(pOutputString, psz); b1stStringDone = TRUE; } } return; } #endif //#ifdef PER_PROCESS_INSTANTIATIONS // Define this in just one source file. #endif // __MINISTACK_H__