Files
OCCT/src/FoundationClasses/TKernel/Standard/Standard_StackTrace.cxx
T
Pasukhin Dmitry c020fc2fad Coding - Clang-Tidy apply with refactoring (#965)
- Replacing empty constructor/destructor implementations with `= default`
- Removing redundant `virtual` keywords from override methods
- Replacing `NULL` and `0` with `nullptr`
- Replacing C headers with C++ equivalents (`<cstdio>`, `<cstring>`, etc.)
- Marking copy constructors/assignment operators as `= delete` for non-copyable classes
- Converting `void` parameter lists to empty parameter lists
- Replacing integer literals with appropriate boolean values
2025-12-29 11:55:04 +00:00

374 lines
12 KiB
C++

// 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 <Standard.hxx>
#include <Message.hxx>
#include <mutex>
#include <Standard_WarningDisableFunctionCast.hxx>
#if defined(__APPLE__)
#import <TargetConditionals.h>
#endif
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#elif defined(__ANDROID__)
// #include <unwind.h>
#elif defined(__QNX__)
// #include <backtrace.h> // requires linking to libbacktrace
#elif !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
#include <execinfo.h>
#elif defined(_WIN32) && !defined(OCCT_UWP)
#include <Standard_WarningsDisable.hxx>
#include <windows.h>
#include <dbghelp.h>
#include <Standard_WarningsRestore.hxx>
//! 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<std::mutex> 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
}