//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #ifdef ASSERT_WITH_STACK #ifndef _WIN64 #include "AssertWithStack.h" // //--- Macros ------------------------------------------------------------------ // #define COUNT_OF(x) (sizeof(x) / sizeof(x[0])) // // Types and Constants -------------------------------------------------------- // struct __SYMBOL_INFO { DWORD dwOffset; char achModule[cchMaxAssertModuleLen]; char achSymbol[cchMaxAssertSymbolLen]; }; //--- Function Pointers to APIs in IMAGEHLP.DLL. Loaded dynamically. --------- typedef LPAPI_VERSION (__stdcall *pfnImgHlp_ImagehlpApiVersionEx)( LPAPI_VERSION AppVersion ); typedef BOOL (__stdcall *pfnImgHlp_StackWalk)( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME StackFrame, LPVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE TranslateAddress ); typedef BOOL (__stdcall *pfnImgHlp_SymGetModuleInfo)( IN HANDLE hProcess, IN DWORD dwAddr, OUT PIMAGEHLP_MODULE ModuleInfo ); typedef LPVOID (__stdcall *pfnImgHlp_SymFunctionTableAccess)( HANDLE hProcess, DWORD AddrBase ); typedef BOOL (__stdcall *pfnImgHlp_SymGetSymFromAddr)( IN HANDLE hProcess, IN DWORD dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_SYMBOL Symbol ); typedef BOOL (__stdcall *pfnImgHlp_SymInitialize)( IN HANDLE hProcess, IN LPSTR UserSearchPath, IN BOOL fInvadeProcess ); typedef BOOL (__stdcall *pfnImgHlp_SymUnDName)( IN PIMAGEHLP_SYMBOL sym, // Symbol to undecorate OUT LPSTR UnDecName, // Buffer to store undecorated name in IN DWORD UnDecNameLength // Size of the buffer ); typedef BOOL (__stdcall *pfnImgHlp_SymLoadModule)( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD BaseOfDll, IN DWORD SizeOfDll ); struct IMGHLPFN_LOAD { LPSTR pszFnName; LPVOID * ppvfn; }; // // Globals -------------------------------------------------------------------- // static BOOL g_fLoadedImageHlp = FALSE; // set to true on success static BOOL g_fLoadedImageHlpFailed = FALSE; // set to true on failure static HINSTANCE g_hinstImageHlp = NULL; static HANDLE g_hProcess = NULL; pfnImgHlp_ImagehlpApiVersionEx _ImagehlpApiVersionEx; pfnImgHlp_StackWalk _StackWalk; pfnImgHlp_SymGetModuleInfo _SymGetModuleInfo; pfnImgHlp_SymFunctionTableAccess _SymFunctionTableAccess; pfnImgHlp_SymGetSymFromAddr _SymGetSymFromAddr; pfnImgHlp_SymInitialize _SymInitialize; pfnImgHlp_SymUnDName _SymUnDName; pfnImgHlp_SymLoadModule _SymLoadModule; IMGHLPFN_LOAD ailFuncList[] = { { "ImagehlpApiVersionEx", (LPVOID*)&_ImagehlpApiVersionEx }, { "StackWalk", (LPVOID*)&_StackWalk }, { "SymGetModuleInfo", (LPVOID*)&_SymGetModuleInfo }, { "SymFunctionTableAccess", (LPVOID*)&_SymFunctionTableAccess }, { "SymGetSymFromAddr", (LPVOID*)&_SymGetSymFromAddr }, { "SymInitialize", (LPVOID*)&_SymInitialize }, { "SymUnDName", (LPVOID*)&_SymUnDName }, { "SymLoadModule", (LPVOID*)&_SymLoadModule }, }; // //--- Forward declarations ---------------------------------------------------- // static int Dummy1(); static int Dummy2(); /**************************************************************************** * Dummy1 * *--------* * Description: * A placeholder function used to determine if addresses being retrieved * are for functions in this compilation unit or not. * * WARNING!! This function must be the first function in this * compilation unit ****************************************************************************/ static int Dummy1() { return 1; } /**************************************************************************** * IsWin95 * *---------* * Description: * Are we running on Win95 or not. Some of the logic contained here * differs on Windows 9x. * * Return: * TRUE - If we're running on a Win 9x platform * FALSE - If we're running on a non-Win 9x platform ****************************************************************************/ static BOOL IsWin95() { return GetVersion() & 0x80000000; } /**************************************************************************** * MagicInit * *-----------* * Description: * Initializes the symbol loading code. Currently called (if necessary) * at the beginning of each method that might need ImageHelp to be * loaded. ****************************************************************************/ void MagicInit() { if (g_fLoadedImageHlp || g_fLoadedImageHlpFailed) { return; } g_hProcess = GetCurrentProcess(); // // Try to load imagehlp.dll // g_hinstImageHlp = LoadLibraryA("imagehlp.dll"); _ASSERT(g_hinstImageHlp); if (NULL == g_hinstImageHlp) { g_fLoadedImageHlpFailed = TRUE; return; } // // Try to get the API entrypoints in imagehlp.dll // for (int i = 0; i < COUNT_OF(ailFuncList); i++) { *(ailFuncList[i].ppvfn) = GetProcAddress( g_hinstImageHlp, ailFuncList[i].pszFnName); _ASSERT(*(ailFuncList[i].ppvfn)); if (!*(ailFuncList[i].ppvfn)) { g_fLoadedImageHlpFailed = TRUE; return; } } API_VERSION AppVersion = { 4, 0, API_VERSION_NUMBER, 0 }; LPAPI_VERSION papiver = _ImagehlpApiVersionEx(&AppVersion); // // We assume any version 4 or greater is OK. // _ASSERT(papiver->Revision >= 4); if (papiver->Revision < 4) { g_fLoadedImageHlpFailed = TRUE; return; } g_fLoadedImageHlp = TRUE; // // Initialize imagehlp.dll // _SymInitialize(g_hProcess, NULL, FALSE); return; } /**************************************************************************** * FillSymbolInfo * *----------------* * Description: * Fills in a __SYMBOL_INFO structure ****************************************************************************/ void FillSymbolInfo ( __SYMBOL_INFO *psi, DWORD dwAddr ) { if (!g_fLoadedImageHlp) { return; } _ASSERT(psi); memset(psi, 0, sizeof(__SYMBOL_INFO)); IMAGEHLP_MODULE mi; mi.SizeOfStruct = sizeof(mi); if (!_SymGetModuleInfo(g_hProcess, dwAddr, &mi)) { strncpy(psi->achModule, "", sizeof(psi->achModule)-1); } else { strncpy(psi->achModule, mi.ModuleName, sizeof(psi->achModule)-1); strupr(psi->achModule); } CHAR rgchUndec[256]; CHAR * pszSymbol = NULL; // Name field of IMAGEHLP_SYMBOL is dynamically sized. // Pad with space for 255 characters. union { CHAR rgchSymbol[sizeof(IMAGEHLP_SYMBOL) + 255]; IMAGEHLP_SYMBOL sym; }; __try { sym.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); sym.Address = dwAddr; sym.MaxNameLength = 255; if (_SymGetSymFromAddr(g_hProcess, dwAddr, &psi->dwOffset, &sym)) { pszSymbol = sym.Name; if (_SymUnDName(&sym, rgchUndec, COUNT_OF(rgchUndec)-1)) { pszSymbol = rgchUndec; } } else { pszSymbol = ""; } } __except (EXCEPTION_EXECUTE_HANDLER) { pszSymbol = ""; psi->dwOffset = dwAddr - mi.BaseOfImage; } strncpy(psi->achSymbol, pszSymbol, COUNT_OF(psi->achSymbol)-1); } /**************************************************************************** * FunctionTableAccess * *---------------------* * Description: * Helper for imagehlp's StackWalk API. ****************************************************************************/ LPVOID __stdcall FunctionTableAccess ( HANDLE hProcess, DWORD dwPCAddr ) { return _SymFunctionTableAccess( hProcess, dwPCAddr ); } /**************************************************************************** * GetModuleBase * *---------------* * Description: * Helper for imagehlp's StackWalk API. Retrieves the base address of * the module containing the giving virtual address. * * NOTE: If the module information for the given module hasnot yet been * loaded, then it is loaded on this call. * * Return: * Base virtual address where the module containing ReturnAddress is * loaded, or 0 if the address cannot be determined. ****************************************************************************/ DWORD __stdcall GetModuleBase ( HANDLE hProcess, DWORD dwAddr ) { IMAGEHLP_MODULE ModuleInfo; ModuleInfo.SizeOfStruct = sizeof(ModuleInfo); if (_SymGetModuleInfo(hProcess, dwAddr, &ModuleInfo)) { return ModuleInfo.BaseOfImage; } else { MEMORY_BASIC_INFORMATION mbi; if (VirtualQueryEx(hProcess, (LPVOID)dwAddr, &mbi, sizeof(mbi))) { if (IsWin95() || (mbi.Type & MEM_IMAGE)) { char achFile[MAX_PATH] = {0}; DWORD cch; cch = GetModuleFileNameA( (HINSTANCE)mbi.AllocationBase, achFile, MAX_PATH); // Ignore the return code since we can't do anything with it. _SymLoadModule( hProcess, NULL, ((cch) ? achFile : NULL), NULL, (DWORD)mbi.AllocationBase, 0); return (DWORD)mbi.AllocationBase; } } } return 0; } /**************************************************************************** * GetStackBacktrace * *-------------------* * Description: * Gets a stacktrace of the current stack, including symbols. * * Return: * The number of elements actually retrieved. ****************************************************************************/ UINT GetStackBacktrace ( UINT ifrStart, // How many stack elements to skip before starting. UINT cfrTotal, // How many elements to trace after starting. DWORD *pdwEip, // Array to be filled with stack addresses. __SYMBOL_INFO *psiSymbols // This array is filled with symbol information. // It should be big enough to hold cfrTotal elts. // If NULL, no symbol information is stored. ) { DWORD * pdw = pdwEip; __SYMBOL_INFO * psi = psiSymbols; MagicInit(); memset(pdwEip, 0, cfrTotal * sizeof(DWORD)); if (psiSymbols) { memset(psiSymbols, 0, cfrTotal * sizeof(__SYMBOL_INFO)); } if (!g_fLoadedImageHlp) { return 0; } HANDLE hThread; hThread = GetCurrentThread(); CONTEXT context; context.ContextFlags = CONTEXT_FULL; if (GetThreadContext(hThread, &context)) { STACKFRAME stkfrm; memset(&stkfrm, 0, sizeof(STACKFRAME)); stkfrm.AddrPC.Mode = AddrModeFlat; DWORD dwMachType; #if defined(_M_IX86) dwMachType = IMAGE_FILE_MACHINE_I386; stkfrm.AddrPC.Offset = context.Eip; // Program Counter stkfrm.AddrStack.Offset = context.Esp; // Stack Pointer stkfrm.AddrStack.Mode = AddrModeFlat; stkfrm.AddrFrame.Offset = context.Ebp; // Frame Pointer stkfrm.AddrFrame.Mode = AddrModeFlat; #elif defined(_M_MRX000) dwMachType = IMAGE_FILE_MACHINE_R4000; stkfrm.AddrPC.Offset = context.Fir; // Program Counter #elif defined(_M_ALPHA) dwMachType = IMAGE_FILE_MACHINE_ALPHA; stkfrm.AddrPC.Offset = (unsigned long) context.Fir; // Program Counter #elif defined(_M_PPC) dwMachType = IMAGE_FILE_MACHINE_POWERPC; stkfrm.AddrPC.Offset = context.Iar; // Program Counter #elif #error("Unknown Target Machine"); #endif // Ignore this function (GetStackBackTrace) ifrStart += 1; for (UINT i = 0; i < ifrStart + cfrTotal; i++) { if (!_StackWalk(dwMachType, g_hProcess, hThread, &stkfrm, &context, NULL, FunctionTableAccess, GetModuleBase, NULL)) { break; } if (i >= ifrStart && ((void*)stkfrm.AddrPC.Offset < (void*)Dummy1 || (void*)stkfrm.AddrPC.Offset > (void*)Dummy2)) { *pdw++ = stkfrm.AddrPC.Offset; if (psi) { FillSymbolInfo(psi++, stkfrm.AddrPC.Offset); } } } } return pdw - pdwEip; } /**************************************************************************** * GetStringFromSymbolInfo * *-------------------------* * Description: * Actually prints the info into the string for the symbol. ****************************************************************************/ void GetStringFromSymbolInfo ( DWORD dwAddr, __SYMBOL_INFO *psi, // @parm Pointer to __SYMBOL_INFO. Can be NULL. CHAR *pszString // @parm Place to put string. ) { _ASSERT(pszString); // ! + 0x 0x\n if (psi) { wsprintfA(pszString, "%s! %s + 0x%X (0x%08X)", (psi->achModule[0]) ? psi->achModule : "", (psi->achSymbol[0]) ? psi->achSymbol : "", psi->dwOffset, dwAddr); } else { wsprintfA(pszString, " (0x%08X)", dwAddr); } _ASSERT(strlen(pszString) < cchMaxAssertStackLevelStringLen); } /**************************************************************************** * GetStringFromStackLevels * *--------------------------* * Description: * Retrieves a string from the stack frame. If more than one frame, they * are separated by newlines ****************************************************************************/ void GetStringFromStackLevels ( UINT ifrStart, // @parm How many stack elements to skip before starting. UINT cfrTotal, // @parm How many elements to trace after starting. // Can't be more than cfrMaxAssertStackLevels. CHAR *pszString // @parm Place to put string. // Max size will be cchMaxAssertStackLevelStringLen * cfrTotal. ) { _ASSERT(pszString); _ASSERT(cfrTotal < cfrMaxAssertStackLevels); *pszString = '\0'; if (cfrTotal == 0) { return; } DWORD rgdwStackAddrs[cfrMaxAssertStackLevels]; __SYMBOL_INFO rgsi[cfrMaxAssertStackLevels]; // Ignore this function (GetStringFromStackLevels) ifrStart += 1; UINT uiRetrieved = GetStackBacktrace(ifrStart, cfrTotal, rgdwStackAddrs, rgsi); // First level CHAR aszLevel[cchMaxAssertStackLevelStringLen]; GetStringFromSymbolInfo(rgdwStackAddrs[0], &rgsi[0], aszLevel); strcpy(pszString, aszLevel); // Additional levels for (UINT i = 1; i < uiRetrieved; ++i) { strcat(pszString, "\n"); GetStringFromSymbolInfo(rgdwStackAddrs[i], &rgsi[i], aszLevel); strcat(pszString, aszLevel); } _ASSERT(strlen(pszString) <= cchMaxAssertStackLevelStringLen * cfrTotal); } /**************************************************************************** * GetAddrFromStackLevel * *-----------------------* * Description: * Retrieves the address of the next instruction to be executed on a * particular stack frame. * * Return: * The address of the next instruction, * 0 if there's an error. ****************************************************************************/ DWORD GetAddrFromStackLevel ( UINT ifrStart // How many stack elements to skip before starting. ) { MagicInit(); if (!g_fLoadedImageHlp) { return 0; } HANDLE hThread; hThread = GetCurrentThread(); CONTEXT context; context.ContextFlags = CONTEXT_FULL; if (GetThreadContext(hThread, &context)) { STACKFRAME stkfrm; memset(&stkfrm, 0, sizeof(STACKFRAME)); stkfrm.AddrPC.Mode = AddrModeFlat; DWORD dwMachType; #if defined(_M_IX86) dwMachType = IMAGE_FILE_MACHINE_I386; stkfrm.AddrPC.Offset = context.Eip; // Program Counter stkfrm.AddrStack.Offset = context.Esp; // Stack Pointer stkfrm.AddrStack.Mode = AddrModeFlat; stkfrm.AddrFrame.Offset = context.Ebp; // Frame Pointer stkfrm.AddrFrame.Mode = AddrModeFlat; #elif defined(_M_MRX000) dwMachType = IMAGE_FILE_MACHINE_R4000; stkfrm.AddrPC.Offset = context.Fir; // Program Counter #elif defined(_M_ALPHA) dwMachType = IMAGE_FILE_MACHINE_ALPHA; stkfrm.AddrPC.Offset = (unsigned long) context.Fir; // Program Counter #elif defined(_M_PPC) dwMachType = IMAGE_FILE_MACHINE_POWERPC; stkfrm.AddrPC.Offset = context.Iar; // Program Counter #elif #error("Unknown Target Machine"); #endif // Ignore this function (GetStackBackTrace) and the one below ifrStart += 2; for (UINT i = 0; i < ifrStart; i++) { if (!_StackWalk(dwMachType, g_hProcess, hThread, &stkfrm, &context, NULL, FunctionTableAccess, GetModuleBase, NULL)) { break; } } return stkfrm.AddrPC.Offset; } return 0; } /**************************************************************************** * GetStringFromAddr * *-------------------* * Description: * Returns a string from an address. ****************************************************************************/ void GetStringFromAddr ( DWORD dwAddr, CHAR *szString // Place to put string. // Buffer must hold at least cchMaxAssertStackLevelStringLen. ) { _ASSERT(szString); __SYMBOL_INFO si; FillSymbolInfo(&si, dwAddr); wsprintfA(szString, "%s! %s + 0x%X (0x%08X)", (si.achModule[0]) ? si.achModule : "", (si.achSymbol[0]) ? si.achSymbol : "", si.dwOffset, dwAddr); } /**************************************************************************** * MagicDeinit * *-------------* * Description: * Cleans up for the symbol loading code. Should be called before exit * to free the dynamically loaded imagehlp.dll. ****************************************************************************/ void MagicDeinit(void) { if (g_hinstImageHlp) { FreeLibrary(g_hinstImageHlp); g_hinstImageHlp = NULL; g_fLoadedImageHlp = FALSE; } } static int Dummy2() { return 2; } #endif // _WIN64 #endif // ASSERT_WITH_STACK