diff --git a/source/mijin/debug/stacktrace.cpp b/source/mijin/debug/stacktrace.cpp index d9a4d00..5cdee55 100644 --- a/source/mijin/debug/stacktrace.cpp +++ b/source/mijin/debug/stacktrace.cpp @@ -5,14 +5,22 @@ #include #include "../detect.hpp" -#if MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC +#if MIJIN_TARGET_OS != MIJIN_OS_WINDOWS && (MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC) #define MIJIN_USE_LIBBACKTRACE 1 #else #define MIJIN_USE_LIBBACKTRACE 0 #endif #if MIJIN_USE_LIBBACKTRACE -#include + #include +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS + #include + #include + #include + #include + #include + #include "../util/winundef.hpp" + #pragma comment(lib, "dbghelp") #endif @@ -32,11 +40,15 @@ namespace // internal types // +#if MIJIN_USE_LIBBACKTRACE struct BacktraceData { std::optional error; std::vector stackframes; }; +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS +HANDLE gProcessHandle = nullptr; +#endif // // internal variables @@ -44,6 +56,11 @@ struct BacktraceData thread_local Optional gCurrentExceptionStackTrace; +#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS +std::mutex gDbgHelpMutex; +bool gDbgHelpInitCalled = false; +#endif + // // internal functions // @@ -68,6 +85,49 @@ void backtraceErrorCallback(void* data, const char* msg, int /* errnum */) } thread_local backtrace_state* gBacktraceState = nullptr; +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS +void cleanupDbgHelp() MIJIN_NOEXCEPT +{ + if (!SymCleanup(gProcessHandle)) + { + [[maybe_unused]] const DWORD error = GetLastError(); + MIJIN_ERROR("Error cleaning up DbgHelp."); + } +} + +[[nodiscard]] +bool initDbgHelp() MIJIN_NOEXCEPT +{ + if (gDbgHelpInitCalled) + { + return gProcessHandle != nullptr; // if init was successful, process handle is not null + } + gDbgHelpInitCalled = true; + + SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); + + HANDLE hCurrentProcess = GetCurrentProcess(); + HANDLE hCopy = nullptr; + if (!DuplicateHandle(hCurrentProcess, hCurrentProcess, hCurrentProcess, &hCopy, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + [[maybe_unused]] const DWORD error = GetLastError(); + MIJIN_ERROR("Error duplicating process handle."); + return false; + } + if (!SymInitialize(hCopy, nullptr, true)) + { + [[maybe_unused]] const DWORD error = GetLastError(); + MIJIN_ERROR("Error initializing DbHelp."); + return false; + } + + const int result = std::atexit(&cleanupDbgHelp); + MIJIN_ASSERT(result == 0, "Error registering DbgHelp cleanup handler."); + + // only copy in the end so we can still figure out if initialization was successful + gProcessHandle = hCopy; + return true; +} #endif // MIJIN_USE_LIBBACKTRACE } // namespace @@ -98,7 +158,85 @@ Result captureStacktrace(unsigned skipFrames) MIJIN_NOEXCEPT } return Stacktrace(std::move(btData.stackframes)); -#else // MIJIN_USE_LIBBACKTRACE +#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS + if (!initDbgHelp()) + { + return ResultError("error initializing DbgHelp"); + } + + const HANDLE hThread = GetCurrentThread(); + + CONTEXT context; + RtlCaptureContext(&context); + + STACKFRAME64 stackFrame = { + .AddrPC = { + .Offset = context.Rip, + .Mode = AddrModeFlat + }, + .AddrFrame = { + .Offset = context.Rbp, + .Mode = AddrModeFlat + }, + .AddrStack = { + .Offset = context.Rsp, + .Mode = AddrModeFlat + } + }; + + ++skipFrames; // always skip the first frame (the current function) + + // for symbol info + DWORD64 displacement64 = 0; + static constexpr std::size_t SYMBOL_BUFFER_SIZE = sizeof(SYMBOL_INFO) + (MAX_SYM_NAME * sizeof(char)); + std::array symbolBuffer alignas(SYMBOL_INFO); + SYMBOL_INFO& symbolInfo = *std::bit_cast(symbolBuffer.data()); + symbolInfo.SizeOfStruct = sizeof(SYMBOL_BUFFER_SIZE); + symbolInfo.MaxNameLen = MAX_SYM_NAME; + + // for file and line info + DWORD displacement = 0; + IMAGEHLP_LINE64 line64; + line64.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + std::vector stackframes; + while (StackWalk64( + /* MachineType = */ IMAGE_FILE_MACHINE_AMD64, + /* hProcess = */ gProcessHandle, + /* hThread = */ hThread, + /* StackFrame = */ &stackFrame, + /* ContextRecord = */ &context, + /* ReadMemoryRoutine = */ nullptr, + /* FunctionTableAccessRoutine = */ &SymFunctionTableAccess64, + /* GetModuleBaseRoutine = */ &SymGetModuleBase64, + /* TranslateAddress = */ nullptr + )) + { + if (skipFrames > 0) + { + --skipFrames; + continue; + } + Stackframe& frame = stackframes.emplace_back(); + const DWORD64 baseAddress = SymGetModuleBase64(gProcessHandle, stackFrame.AddrPC.Offset); + const DWORD64 relativeAddress = stackFrame.AddrPC.Offset - baseAddress; + + frame.address = std::bit_cast(relativeAddress); + + if (SymFromAddr(gProcessHandle, stackFrame.AddrPC.Offset, &displacement64, &symbolInfo)) + { + frame.function = symbolInfo.Name; + } + + if (SymGetLineFromAddr64(gProcessHandle, stackFrame.AddrPC.Offset, &displacement, &line64)) + { + frame.filename = line64.FileName; + frame.lineNumber = static_cast(line64.LineNumber); + } + } + + return Stacktrace(std::move(stackframes)); +#else // MIJIN_USE_LIBBACKTRACE || (MIJIN_TARGET_OS == MIJIN_OS_WINDOWS) (void) skipFrames; return ResultError("not implemented"); #endif // MIJIN_USE_LIBBACKTRACE