From 5b9931456f595b0a2163fabb65dceac99e0e220f Mon Sep 17 00:00:00 2001 From: Anders Leino Date: Wed, 8 Jan 2025 08:30:18 +0200 Subject: Add backtraces to examples (#5973) * examples: Log stack trace on exceptions For now, this is only implemented on Windows. This helps to address #5520. * examples: Print log file if there is any * format code --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> Co-authored-by: Yong He --- examples/stacktrace-windows/common.cpp | 201 +++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 examples/stacktrace-windows/common.cpp (limited to 'examples/stacktrace-windows/common.cpp') diff --git a/examples/stacktrace-windows/common.cpp b/examples/stacktrace-windows/common.cpp new file mode 100644 index 000000000..b07f78d0a --- /dev/null +++ b/examples/stacktrace-windows/common.cpp @@ -0,0 +1,201 @@ +#include "common.h" + +#include +#include +#include +#include + +// dbghelp.h needs to be included after windows.h +#include + +#define SLANG_EXAMPLE_LOG_ERROR(...) \ + fprintf(file, "error: %s: %d: ", __FILE__, __LINE__); \ + print(file, __VA_ARGS__); \ + fprintf(file, "\n"); + +static void print(FILE* /* file */) {} +static void print(FILE* file, unsigned int n) +{ + fprintf(file, "%u", n); +} + + +static bool getModuleFileNameAtAddress(FILE* file, DWORD64 const address, std::string& fileName) +{ + HMODULE module = NULL; + { + BOOL result = GetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCTSTR)address, + &module); + if (result == 0) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + return false; + } + if (module == NULL) + { + SLANG_EXAMPLE_LOG_ERROR(); + return false; + } + } + + std::vector buffer(1U << 8U); + uint32_t constexpr maxBufferSize = 1U << 20; + while (buffer.size() < maxBufferSize) + { + DWORD result = GetModuleFileNameA(module, buffer.data(), buffer.size()); + if (result == 0) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + return false; + } + else if (result == ERROR_INSUFFICIENT_BUFFER) + { + buffer.resize(buffer.size() << 1U); + } + else + { + break; + } + } + if (buffer.size() == maxBufferSize) + { + SLANG_EXAMPLE_LOG_ERROR(); + return false; + } + + fileName = std::string(buffer.data(), buffer.data() + buffer.size()); + return true; +} + +// NOTE: This function is not thread-safe, due to usage of StackWalk64 and static buffers. +static bool printStack(FILE* file, HANDLE process, HANDLE thread, CONTEXT const& context) +{ +#if defined(_M_AMD64) + DWORD constexpr machineType = IMAGE_FILE_MACHINE_AMD64; +#else +#error Unsupported machine type +#endif + + static char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + + // StackWalk64 may modify the context record + CONTEXT contextCopy; + memcpy(&contextCopy, &context, sizeof(CONTEXT)); + + STACKFRAME64 frame = {}; + constexpr uint32_t maxFrameCount = 1U << 10; + uint32_t frameIndex = 0U; + while (frameIndex < maxFrameCount) + { + // Use the default routine + PREAD_PROCESS_MEMORY_ROUTINE64 readMemoryRoutine = NULL; + // Not sure what this is for, but documentation says most callers can pass NULL + PTRANSLATE_ADDRESS_ROUTINE64 translateAddressRoutine = NULL; + { + BOOL result = StackWalk64( + machineType, + process, + thread, + &frame, + &contextCopy, + readMemoryRoutine, + SymFunctionTableAccess64, + SymGetModuleBase64, + translateAddressRoutine); + if (result == FALSE) + break; + } + + PSYMBOL_INFO maybeSymbol = (PSYMBOL_INFO)symbolBuffer; + { + maybeSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + maybeSymbol->MaxNameLen = MAX_SYM_NAME; + DWORD64 address = frame.AddrPC.Offset; + // Not required, we want to look up the symbol exactly at the address + PDWORD64 displacement = NULL; + BOOL result = SymFromAddr(process, address, displacement, maybeSymbol); + if (result == FALSE) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + maybeSymbol = NULL; + } + } + + fprintf(file, "%u", frameIndex); + + std::string moduleFileName; + if (getModuleFileNameAtAddress(file, frame.AddrPC.Offset, moduleFileName)) + fprintf(file, ": %s", moduleFileName.c_str()); + + if (maybeSymbol) + { + PSYMBOL_INFO& symbol = maybeSymbol; + + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + DWORD displacement; + if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &displacement, &line)) + { + fprintf(file, ": %s: %s: %lu", symbol->Name, line.FileName, line.LineNumber); + } + else + { + fprintf(file, ": %s", symbol->Name); + } + + fprintf(file, ": 0x%.16" PRIXPTR, symbol->Address); + } + fprintf(file, "\n"); + + frameIndex++; + } + + return frameIndex < maxFrameCount; +} + +int exceptionFilter(FILE* logFile, _EXCEPTION_POINTERS* exception) +{ + FILE* file = logFile ? logFile : stdout; + fprintf( + file, + "error: Exception 0x%x occurred. Stack trace:\n", + exception->ExceptionRecord->ExceptionCode); + + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + bool symbolsLoaded = false; + { + // The default search paths should suffice + PCSTR symbolFileSearchPath = NULL; + BOOL loadSymbolsOfLoadedModules = TRUE; + BOOL result = SymInitialize(process, symbolFileSearchPath, loadSymbolsOfLoadedModules); + if (result == FALSE) + { + fprintf(file, "warning: Failed to load symbols\n"); + } + else + { + symbolsLoaded = true; + } + } + + if (!printStack(file, process, thread, *exception->ContextRecord)) + { + fprintf(file, "warning: Failed to print complete stack trace!\n"); + } + + if (symbolsLoaded) + { + BOOL result = SymCleanup(process); + if (result == FALSE) + { + SLANG_EXAMPLE_LOG_ERROR(GetLastError()); + } + } + + return EXCEPTION_EXECUTE_HANDLER; +} -- cgit v1.2.3