Skip to content

Commit

Permalink
StProcess - try adding backtrace to the crash info
Browse files Browse the repository at this point in the history
  • Loading branch information
gkv311 committed Jan 13, 2025
1 parent 8f08f9b commit d9ba97b
Show file tree
Hide file tree
Showing 3 changed files with 337 additions and 0 deletions.
9 changes: 9 additions & 0 deletions StShared/StProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,15 @@ struct StSEHandler {
if (theCode == EXCEPTION_IN_PAGE_ERROR) {
aStr << "Underlying NTSTATUS code that resulted in the exception " << theExcPtr->ExceptionRecord->ExceptionInformation[2] << "\n";
}

const int aStackLength = 10;
const int aStackBufLen = stMax(aStackLength * 200, 2048);
char* aStackBuffer = aStackLength != 0 ? (char*)alloca(aStackBufLen) : NULL;
if (aStackBuffer != NULL) {
memset(aStackBuffer, 0, aStackBufLen);
StThread::addStackTrace(aStackBuffer, aStackBufLen, aStackLength, theExcPtr->ContextRecord);
aStr << aStackBuffer;
}
}

return aStr.str();
Expand Down
305 changes: 305 additions & 0 deletions StShared/StThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <StThreads/StThread.h>
#include <StStrings/StLogger.h>

#include <StThreads/StMutex.h>

#ifdef _WIN32
#include <windows.h>
#include <process.h>
Expand Down Expand Up @@ -296,3 +298,306 @@ int StThread::countLogicalProcessors() {
return (int )sysconf(_SC_NPROCESSORS_ONLN);
#endif
}

#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)

#ifdef _MSC_VER
#pragma warning(disable : 4091)
#endif

#include <dbghelp.h>
#ifdef _MSC_VER
#pragma warning(default : 4091)
#endif

/**
* 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 StDbgHelper {
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 StDbgHelper& getInstance() {
static StDbgHelper aDbgHelper;
return aDbgHelper;
}

/** Return global mutex. */
static StMutex& getMutex() {
static StMutex aMutex;
return aMutex;
}

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* getErrorMessage() const { return myError; }

private:

/** Main constructor. */
StDbgHelper()
: SymInitialize(NULL),
SymCleanup(NULL),
StackWalk64(NULL),
SymFunctionTableAccess64(NULL),
SymGetModuleBase64(NULL),
SymFromAddr(NULL),
myDbgHelpLib(LoadLibraryW(L"DbgHelp.dll")),
myError(NULL) {
if (myDbgHelpLib == NULL) {
myError = "Failed to load DbgHelp.dll";
return;
}

if ((SymInitialize = (SYMINITIALIZEPROC)GetProcAddress(myDbgHelpLib, "SymInitialize")) == NULL) {
myError = "Function not found in DbgHelp.dll: SymInitialize";
unload();
return;
}
if ((SymCleanup = (SYMCLEANUPPROC)GetProcAddress(myDbgHelpLib, "SymCleanup")) == NULL) {
myError = "Function not found in DbgHelp.dll: SymCleanup";
unload();
return;
}
if ((StackWalk64 = (STACKWALK64PROC)GetProcAddress(myDbgHelpLib, "StackWalk64")) == NULL) {
myError = "Function not found in DbgHelp.dll: StackWalk64";
unload();
return;
}
if ((SymFunctionTableAccess64 = (PFUNCTION_TABLE_ACCESS_ROUTINE64)GetProcAddress(myDbgHelpLib, "SymFunctionTableAccess64")) == NULL) {
myError = "Function not found in DbgHelp.dll: SymFunctionTableAccess64";
unload();
return;
}
if ((SymGetModuleBase64 = (PGET_MODULE_BASE_ROUTINE64)GetProcAddress(myDbgHelpLib, "SymGetModuleBase64")) == NULL) {
myError = "Function not found in DbgHelp.dll: SymGetModuleBase64";
unload();
return;
}
if ((SymFromAddr = (SYMFROMADDRPROC)GetProcAddress(myDbgHelpLib, "SymFromAddr")) == NULL) {
myError = "Function not found in DbgHelp.dll: SymFromAddr";
unload();
return;
}
}

/** Destructor. */
~StDbgHelper() {
// unloading library makes not sense for singletone
//unload();
}

/** Unload library. */
void unload() {
if (myDbgHelpLib != NULL) {
FreeLibrary(myDbgHelpLib);
myDbgHelpLib = NULL;
}
}

private:
StDbgHelper(const StDbgHelper&);
StDbgHelper& operator= (const StDbgHelper&);
private:
HMODULE myDbgHelpLib; //!< handle to DbgHelp
const char* myError; //!< loading error message
};

#endif

bool StThread::addStackTrace(char* theBuffer,
const int theBufferSize,
const int theNbTraces,
void* theContext,
const int theNbTopSkip) {
(void)theContext;
if (theBufferSize < 1
|| theNbTraces < 1
|| theBuffer == NULL
|| theNbTopSkip < 0) {
return false;
}

#if defined(__EMSCRIPTEN__)
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__)
// not implemented
return false;
#elif defined(__QNX__)
// bt_get_backtrace()
// not implemented
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
StMutexAuto aLock(StDbgHelper::getMutex());
StDbgHelper& aDbgHelp = StDbgHelper::getInstance();
if (!aDbgHelp.isLoaded()) {
strcat_s(theBuffer, theBufferSize, "\n==Backtrace==\n");
strcat_s(theBuffer, theBufferSize, aDbgHelp.getErrorMessage());
strcat_s(theBuffer, theBufferSize, "\n=============");
return false;
}

aDbgHelp.SymInitialize(anHProcess, NULL, TRUE);

STACKFRAME64 aStackFrame;
memset(&aStackFrame, 0, sizeof(aStackFrame));

DWORD 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
// 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 == NULL) {
return false;
}

aNbTraces = ::backtrace(aStackArr, aNbTraces);
if (aNbTraces <= 1) {
return false;
}

aNbTraces -= aTopSkip;
char** aStrings = ::backtrace_symbols(aStackArr + aTopSkip, aNbTraces);
if (aStrings == NULL) {
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
}
23 changes: 23 additions & 0 deletions include/StThreads/StThread.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,29 @@ class StThread {
*/
ST_CPPEXPORT static size_t getCurrentThreadId();

/**
* Appends backtrace to a message buffer.
* Stack information might be incomplete in case of stripped binaries.
*
* On non-Windows platform, this function is a wrapper to backtrace() system call.
* On Windows (Win32) platform, the function loads DbgHelp.dll dynamically,
* and no stack will be provided if this or companion libraries (SymSrv.dll, SrcSrv.dll, etc.) will not be found;
* .pdb symbols should be provided on Windows platform to retrieve a meaningful stack;
* only x86_64 CPU architecture is currently implemented.
* @param[in, out] theBuffer message buffer to extend
* @param[in] theBufferSize message buffer size
* @param[in] theNbTraces maximum number of stack traces
* @param[in] theContext optional platform-dependent frame context;
* in case of DbgHelp (Windows) should be a pointer to CONTEXT
* @param[in] theNbTopSkip number of traces on top of the stack to skip
* @return TRUE on success
*/
ST_CPPEXPORT static bool addStackTrace(char* theBuffer,
const int theBufferSize,
const int theNbTraces = 10,
void* theContext = NULL,
const int theNbTopSkip = 0);

/**
* The function cannot be used by one thread to create a handle that can be used by other threads to refer to the first thread!
* /
Expand Down

0 comments on commit d9ba97b

Please sign in to comment.