// Created on: 2020-11-30 // Copyright (c) 2020 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #include #include #include #include #if defined(__APPLE__) #import #endif #if defined(__EMSCRIPTEN__) #include #elif defined(__ANDROID__) // #include #elif defined(__QNX__) // #include // requires linking to libbacktrace #elif !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) #include #elif defined(_WIN32) && !defined(OCCT_UWP) #include #include #include #include //! This is a wrapper of DbgHelp library loaded dynamically. //! DbgHelp is coming with Windows SDK, so that technically it is always available. //! However, it's usage requires extra steps: //! - .pdb files are necessary to resolve function names; //! Normal release DLLs without PDBs will show no much useful info. //! - DbgHelp.dll and friends (SymSrv.dll, SrcSrv.dll) should be packaged with application; //! DbgHelp.dll coming with system might be of other incompatible version //! (some applications load it dynamically to avoid packaging extra DLL, //! with a extra hacks determining library version) class Standard_DbgHelper { public: // dbgHelp.dll function types typedef BOOL(WINAPI* SYMINITIALIZEPROC)(HANDLE, PCSTR, BOOL); typedef BOOL(WINAPI* STACKWALK64PROC)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64); typedef BOOL(WINAPI* SYMCLEANUPPROC)(HANDLE); typedef BOOL(WINAPI* SYMFROMADDRPROC)(HANDLE, DWORD64, PDWORD64, PSYMBOL_INFO); public: //! Return global instance. static Standard_DbgHelper& GetDbgHelper() { static Standard_DbgHelper aDbgHelper; return aDbgHelper; } //! Return global mutex. static std::mutex& Mutex() { static std::mutex THE_MUTEX_LOCK; return THE_MUTEX_LOCK; } public: SYMINITIALIZEPROC SymInitialize; SYMCLEANUPPROC SymCleanup; STACKWALK64PROC StackWalk64; PFUNCTION_TABLE_ACCESS_ROUTINE64 SymFunctionTableAccess64; PGET_MODULE_BASE_ROUTINE64 SymGetModuleBase64; SYMFROMADDRPROC SymFromAddr; //! Return TRUE if library has been loaded. bool IsLoaded() const { return myDbgHelpLib != NULL; } //! Return loading error message. const char* ErrorMessage() const { return myError; } private: //! Main constructor. Standard_DbgHelper() : SymInitialize(NULL), SymCleanup(NULL), StackWalk64(NULL), SymFunctionTableAccess64(NULL), SymGetModuleBase64(NULL), SymFromAddr(NULL), myDbgHelpLib(LoadLibraryW(L"DbgHelp.dll")), myError(NULL) { if (myDbgHelpLib == NULL) { myError = "Standard_DbgHelper, Failed to load DbgHelp.dll"; return; } if ((SymInitialize = (SYMINITIALIZEPROC)GetProcAddress(myDbgHelpLib, "SymInitialize")) == NULL) { myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymInitialize"; unload(); return; } if ((SymCleanup = (SYMCLEANUPPROC)GetProcAddress(myDbgHelpLib, "SymCleanup")) == NULL) { myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymCleanup"; unload(); return; } if ((StackWalk64 = (STACKWALK64PROC)GetProcAddress(myDbgHelpLib, "StackWalk64")) == NULL) { myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: StackWalk64"; unload(); return; } if ((SymFunctionTableAccess64 = (PFUNCTION_TABLE_ACCESS_ROUTINE64)GetProcAddress(myDbgHelpLib, "SymFunctionTableAccess64")) == NULL) { myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymFunctionTableAccess64"; unload(); return; } if ((SymGetModuleBase64 = (PGET_MODULE_BASE_ROUTINE64)GetProcAddress(myDbgHelpLib, "SymGetModuleBase64")) == NULL) { myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymGetModuleBase64"; unload(); return; } if ((SymFromAddr = (SYMFROMADDRPROC)GetProcAddress(myDbgHelpLib, "SymFromAddr")) == NULL) { myError = "Standard_DbgHelper, Function not found in DbgHelp.dll: SymFromAddr"; unload(); return; } } //! Destructor. ~Standard_DbgHelper() { // we could unload library here, but don't do it as it is kept loaded // unload(); } //! Unload library. void unload() { if (myDbgHelpLib != NULL) { FreeLibrary(myDbgHelpLib); myDbgHelpLib = NULL; } } private: Standard_DbgHelper(const Standard_DbgHelper&); Standard_DbgHelper& operator=(const Standard_DbgHelper&); private: HMODULE myDbgHelpLib; //!< handle to DbgHelp const char* myError; //!< loading error message }; #endif //================================================================================================= bool Standard::StackTrace(char* theBuffer, const int theBufferSize, const int theNbTraces = 10, void* theContext, const int theNbTopSkip) { (void)theContext; if (theBufferSize < 1 || theNbTraces < 1 || theBuffer == nullptr || theNbTopSkip < 0) { return false; } #if defined(__EMSCRIPTEN__) // theNbTraces is ignored // EM_LOG_JS_STACK? return emscripten_get_callstack(EM_LOG_C_STACK | EM_LOG_DEMANGLE | EM_LOG_NO_PATHS | EM_LOG_FUNC_PARAMS, theBuffer, theBufferSize) > 0; #elif defined(__ANDROID__) Message::SendTrace("Standard::StackTrace() is not implemented for this platform"); return false; #elif defined(__QNX__) // bt_get_backtrace() Message::SendTrace("Standard::StackTrace() is not implemented for this platform"); return false; #elif defined(OCCT_UWP) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) Message::SendTrace("Standard::StackTrace() is not implemented for this platform"); return false; #elif defined(_WIN32) // Each CPU architecture requires manual stack frame setup, // and 32-bit version requires also additional hacks to retrieve current context; // this implementation currently covers only x86_64 architecture. #if defined(_M_X64) int aNbTraces = theNbTraces; const HANDLE anHProcess = GetCurrentProcess(); const HANDLE anHThread = GetCurrentThread(); CONTEXT aCtx; if (theContext != NULL) { memcpy(&aCtx, theContext, sizeof(aCtx)); } else { ++aNbTraces; memset(&aCtx, 0, sizeof(aCtx)); aCtx.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&aCtx); } // DbgHelp is not thread-safe library, hence global lock is used for serial access std::lock_guard aLock(Standard_DbgHelper::Mutex()); Standard_DbgHelper& aDbgHelp = Standard_DbgHelper::GetDbgHelper(); if (!aDbgHelp.IsLoaded()) { strcat_s(theBuffer, theBufferSize, "\n==Backtrace==\n"); strcat_s(theBuffer, theBufferSize, aDbgHelp.ErrorMessage()); strcat_s(theBuffer, theBufferSize, "\n============="); return false; } aDbgHelp.SymInitialize(anHProcess, NULL, TRUE); DWORD anImage = 0; STACKFRAME64 aStackFrame; memset(&aStackFrame, 0, sizeof(aStackFrame)); anImage = IMAGE_FILE_MACHINE_AMD64; aStackFrame.AddrPC.Offset = aCtx.Rip; aStackFrame.AddrPC.Mode = AddrModeFlat; aStackFrame.AddrFrame.Offset = aCtx.Rsp; aStackFrame.AddrFrame.Mode = AddrModeFlat; aStackFrame.AddrStack.Offset = aCtx.Rsp; aStackFrame.AddrStack.Mode = AddrModeFlat; char aModBuffer[MAX_PATH] = {}; char aSymBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(CHAR)]; SYMBOL_INFO* aSymbol = (SYMBOL_INFO*)aSymBuffer; aSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); aSymbol->MaxNameLen = MAX_SYM_NAME; int aTopSkip = theNbTopSkip + 1; // skip this function call and specified extra number strcat_s(theBuffer, theBufferSize, "\n==Backtrace=="); for (int aLineIter = 0; aLineIter < aNbTraces; ++aLineIter) { BOOL aRes = aDbgHelp.StackWalk64(anImage, anHProcess, anHThread, &aStackFrame, &aCtx, NULL, aDbgHelp.SymFunctionTableAccess64, aDbgHelp.SymGetModuleBase64, NULL); if (!aRes) { break; } if (theContext == NULL && aTopSkip > 0) { --aTopSkip; continue; } if (aStackFrame.AddrPC.Offset == 0) { break; } strcat_s(theBuffer, theBufferSize, "\n"); const DWORD64 aModuleBase = aDbgHelp.SymGetModuleBase64(anHProcess, aStackFrame.AddrPC.Offset); if (aModuleBase != 0 && GetModuleFileNameA((HINSTANCE)aModuleBase, aModBuffer, MAX_PATH)) { strcat_s(theBuffer, theBufferSize, aModBuffer); } DWORD64 aDisp = 0; strcat_s(theBuffer, theBufferSize, "("); if (aDbgHelp.SymFromAddr(anHProcess, aStackFrame.AddrPC.Offset, &aDisp, aSymbol)) { strcat_s(theBuffer, theBufferSize, aSymbol->Name); } else { strcat_s(theBuffer, theBufferSize, "???"); } strcat_s(theBuffer, theBufferSize, ")"); } strcat_s(theBuffer, theBufferSize, "\n============="); aDbgHelp.SymCleanup(anHProcess); return true; #else Message::SendTrace("Standard::StackTrace() is not implemented for this CPU architecture"); return false; #endif #else const int aTopSkip = theNbTopSkip + 1; // skip this function call and specified extra number int aNbTraces = theNbTraces + aTopSkip; void** aStackArr = (void**)alloca(sizeof(void*) * aNbTraces); if (aStackArr == nullptr) { return false; } aNbTraces = ::backtrace(aStackArr, aNbTraces); if (aNbTraces <= 1) { return false; } aNbTraces -= aTopSkip; char** aStrings = ::backtrace_symbols(aStackArr + aTopSkip, aNbTraces); if (aStrings == nullptr) { return false; } const size_t aLenInit = strlen(theBuffer); size_t aLimit = (size_t)theBufferSize - aLenInit - 1; if (aLimit > 14) { strcat(theBuffer, "\n==Backtrace=="); aLimit -= 14; } for (int aLineIter = 0; aLineIter < aNbTraces; ++aLineIter) { const size_t aLen = strlen(aStrings[aLineIter]); if (aLen + 1 >= aLimit) { break; } strcat(theBuffer, "\n"); strcat(theBuffer, aStrings[aLineIter]); aLimit -= aLen + 1; } free(aStrings); if (aLimit > 14) { strcat(theBuffer, "\n============="); } return true; #endif }