Implemented stacktrace collection for Windows.
This commit is contained in:
parent
a1d7a63aba
commit
6090d5fc74
@ -5,14 +5,22 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include "../detect.hpp"
|
#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
|
#define MIJIN_USE_LIBBACKTRACE 1
|
||||||
#else
|
#else
|
||||||
#define MIJIN_USE_LIBBACKTRACE 0
|
#define MIJIN_USE_LIBBACKTRACE 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if MIJIN_USE_LIBBACKTRACE
|
#if MIJIN_USE_LIBBACKTRACE
|
||||||
#include <backtrace.h>
|
#include <backtrace.h>
|
||||||
|
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <mutex>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <DbgHelp.h>
|
||||||
|
#include "../util/winundef.hpp"
|
||||||
|
#pragma comment(lib, "dbghelp")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -32,11 +40,15 @@ namespace
|
|||||||
// internal types
|
// internal types
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if MIJIN_USE_LIBBACKTRACE
|
||||||
struct BacktraceData
|
struct BacktraceData
|
||||||
{
|
{
|
||||||
std::optional<std::string> error;
|
std::optional<std::string> error;
|
||||||
std::vector<Stackframe> stackframes;
|
std::vector<Stackframe> stackframes;
|
||||||
};
|
};
|
||||||
|
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||||
|
HANDLE gProcessHandle = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
// internal variables
|
// internal variables
|
||||||
@ -44,6 +56,11 @@ struct BacktraceData
|
|||||||
|
|
||||||
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
|
thread_local Optional<Stacktrace> gCurrentExceptionStackTrace;
|
||||||
|
|
||||||
|
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||||
|
std::mutex gDbgHelpMutex;
|
||||||
|
bool gDbgHelpInitCalled = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
// internal functions
|
// internal functions
|
||||||
//
|
//
|
||||||
@ -68,6 +85,49 @@ void backtraceErrorCallback(void* data, const char* msg, int /* errnum */)
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_local backtrace_state* gBacktraceState = nullptr;
|
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
|
#endif // MIJIN_USE_LIBBACKTRACE
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -98,7 +158,85 @@ Result<Stacktrace> captureStacktrace(unsigned skipFrames) MIJIN_NOEXCEPT
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Stacktrace(std::move(btData.stackframes));
|
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<std::byte, SYMBOL_BUFFER_SIZE> symbolBuffer alignas(SYMBOL_INFO);
|
||||||
|
SYMBOL_INFO& symbolInfo = *std::bit_cast<SYMBOL_INFO*>(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<Stackframe> 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<void*>(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<int>(line64.LineNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stacktrace(std::move(stackframes));
|
||||||
|
#else // MIJIN_USE_LIBBACKTRACE || (MIJIN_TARGET_OS == MIJIN_OS_WINDOWS)
|
||||||
(void) skipFrames;
|
(void) skipFrames;
|
||||||
return ResultError("not implemented");
|
return ResultError("not implemented");
|
||||||
#endif // MIJIN_USE_LIBBACKTRACE
|
#endif // MIJIN_USE_LIBBACKTRACE
|
||||||
|
Loading…
x
Reference in New Issue
Block a user