aboutsummaryrefslogtreecommitdiff
path: root/src/common/Debugging/Windows
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/Debugging/Windows')
-rw-r--r--src/common/Debugging/Windows/WheatyExceptionReport.cpp1909
-rw-r--r--src/common/Debugging/Windows/WheatyExceptionReport.h407
2 files changed, 2316 insertions, 0 deletions
diff --git a/src/common/Debugging/Windows/WheatyExceptionReport.cpp b/src/common/Debugging/Windows/WheatyExceptionReport.cpp
new file mode 100644
index 00000000000..cec3a27e002
--- /dev/null
+++ b/src/common/Debugging/Windows/WheatyExceptionReport.cpp
@@ -0,0 +1,1909 @@
+//==========================================
+// Matt Pietrek
+// MSDN Magazine, 2002
+// FILE: WheatyExceptionReport.CPP
+//==========================================
+#include "WheatyExceptionReport.h"
+#include "Errors.h"
+#include "GitRevision.h"
+#include <algorithm>
+
+#ifdef __clang__
+// clang-cl doesn't have these hardcoded types available, correct ehdata_forceinclude.h that relies on it
+#define _ThrowInfo ThrowInfo
+#endif
+
+#include <ehdata.h>
+#include <rttidata.h>
+#include <tlhelp32.h>
+#include <tchar.h>
+
+#include <comdef.h>
+#include <WbemIdl.h>
+
+#define CrashFolder _T("Crashes")
+#pragma comment(linker, "/DEFAULTLIB:dbghelp.lib")
+#pragma comment(linker, "/DEFAULTLIB:wbemuuid.lib")
+
+inline LPTSTR ErrorMessage(DWORD dw)
+{
+ LPVOID lpMsgBuf;
+ DWORD formatResult = FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ nullptr,
+ dw,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, nullptr);
+ if (formatResult != 0)
+ return (LPTSTR)lpMsgBuf;
+ else
+ {
+ LPTSTR msgBuf = (LPTSTR)LocalAlloc(LPTR, 30);
+ _sntprintf(msgBuf, 30, _T("Unknown error: %d"), (int)dw);
+ return msgBuf;
+ }
+
+}
+
+//============================== Global Variables =============================
+
+//
+// Declare the static variables of the WheatyExceptionReport class and force their initialization before any other static in the program
+//
+#pragma warning(push)
+#pragma warning(disable: 4073) // C4073: initializers put in library initialization area
+#pragma init_seg(lib)
+TCHAR WheatyExceptionReport::m_szLogFileName[MAX_PATH];
+TCHAR WheatyExceptionReport::m_szDumpFileName[MAX_PATH];
+LPTOP_LEVEL_EXCEPTION_FILTER WheatyExceptionReport::m_previousFilter;
+_invalid_parameter_handler WheatyExceptionReport::m_previousCrtHandler;
+FILE* WheatyExceptionReport::m_hReportFile;
+HANDLE WheatyExceptionReport::m_hDumpFile;
+HANDLE WheatyExceptionReport::m_hProcess;
+SymbolPairs WheatyExceptionReport::symbols;
+std::stack<SymbolDetail> WheatyExceptionReport::symbolDetails;
+bool WheatyExceptionReport::alreadyCrashed;
+std::mutex WheatyExceptionReport::alreadyCrashedLock;
+WheatyExceptionReport::pRtlGetVersion WheatyExceptionReport::RtlGetVersion;
+#pragma warning(pop)
+
+//============================== Class Methods =============================
+
+WheatyExceptionReport::WheatyExceptionReport() // Constructor
+{
+ // Install the unhandled exception filter function
+ m_previousFilter = SetUnhandledExceptionFilter(WheatyUnhandledExceptionFilter);
+ m_previousCrtHandler = _set_invalid_parameter_handler(WheatyCrtHandler);
+ m_hProcess = GetCurrentProcess();
+ alreadyCrashed = false;
+ RtlGetVersion = (pRtlGetVersion)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlGetVersion");
+ if (!IsDebuggerPresent())
+ {
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+ }
+}
+
+//============
+// Destructor
+//============
+WheatyExceptionReport::~WheatyExceptionReport()
+{
+ if (m_previousFilter)
+ SetUnhandledExceptionFilter(m_previousFilter);
+ if (m_previousCrtHandler)
+ _set_invalid_parameter_handler(m_previousCrtHandler);
+ ClearSymbols();
+}
+
+//===========================================================
+// Entry point where control comes on an unhandled exception
+//===========================================================
+LONG WINAPI WheatyExceptionReport::WheatyUnhandledExceptionFilter(
+PEXCEPTION_POINTERS pExceptionInfo)
+{
+ std::unique_lock<std::mutex> guard(alreadyCrashedLock);
+ // Handle only 1 exception in the whole process lifetime
+ if (alreadyCrashed)
+ return EXCEPTION_EXECUTE_HANDLER;
+
+ alreadyCrashed = true;
+
+ TCHAR module_folder_name[MAX_PATH];
+ GetModuleFileName(nullptr, module_folder_name, MAX_PATH);
+ TCHAR* pos = _tcsrchr(module_folder_name, '\\');
+ if (!pos)
+ return 0;
+ pos[0] = '\0';
+ ++pos;
+
+ TCHAR crash_folder_path[MAX_PATH];
+ _stprintf_s(crash_folder_path, _T("%s\\%s"), module_folder_name, CrashFolder);
+ if (!CreateDirectory(crash_folder_path, nullptr))
+ {
+ if (GetLastError() != ERROR_ALREADY_EXISTS)
+ return 0;
+ }
+
+#ifdef _UNICODE
+#define PRSTRc "S"
+#else
+#define PRSTRc "s"
+#endif
+
+ SYSTEMTIME systime;
+ GetLocalTime(&systime);
+ _stprintf_s(m_szDumpFileName, _T("%s\\%" PRSTRc "_%s_[%u-%u_%u-%u-%u].dmp"),
+ crash_folder_path, GitRevision::GetHash(), pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond);
+
+ _stprintf_s(m_szLogFileName, _T("%s\\%" PRSTRc "_%s_[%u-%u_%u-%u-%u].txt"),
+ crash_folder_path, GitRevision::GetHash(), pos, systime.wDay, systime.wMonth, systime.wHour, systime.wMinute, systime.wSecond);
+
+ m_hDumpFile = CreateFile(m_szDumpFileName,
+ GENERIC_WRITE,
+ 0,
+ nullptr,
+ OPEN_ALWAYS,
+ FILE_FLAG_WRITE_THROUGH,
+ nullptr);
+
+ if (m_hDumpFile)
+ {
+ MINIDUMP_EXCEPTION_INFORMATION info;
+ info.ClientPointers = FALSE;
+ info.ExceptionPointers = pExceptionInfo;
+ info.ThreadId = GetCurrentThreadId();
+
+ MINIDUMP_USER_STREAM additionalStream = {};
+ MINIDUMP_USER_STREAM_INFORMATION additionalStreamInfo = {};
+
+ if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ASSERTION_FAILURE && pExceptionInfo->ExceptionRecord->NumberParameters > 0)
+ {
+ additionalStream.Type = CommentStreamA;
+ additionalStream.Buffer = reinterpret_cast<PVOID>(pExceptionInfo->ExceptionRecord->ExceptionInformation[0]);
+ additionalStream.BufferSize = strlen(reinterpret_cast<char const*>(pExceptionInfo->ExceptionRecord->ExceptionInformation[0])) + 1;
+
+ additionalStreamInfo.UserStreamArray = &additionalStream;
+ additionalStreamInfo.UserStreamCount = 1;
+ }
+
+ MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
+ m_hDumpFile, MiniDumpWithIndirectlyReferencedMemory, &info, &additionalStreamInfo, nullptr);
+
+ CloseHandle(m_hDumpFile);
+ }
+
+ m_hReportFile = _tfopen(m_szLogFileName, _T("wb"));
+
+ if (m_hReportFile)
+ {
+ GenerateExceptionReport(pExceptionInfo);
+
+ fclose(m_hReportFile);
+ m_hReportFile = nullptr;
+ }
+
+ if (m_previousFilter)
+ return m_previousFilter(pExceptionInfo);
+ else
+ return EXCEPTION_EXECUTE_HANDLER/*EXCEPTION_CONTINUE_SEARCH*/;
+}
+
+void __cdecl WheatyExceptionReport::WheatyCrtHandler(wchar_t const* /*expression*/, wchar_t const* /*function*/, wchar_t const* /*file*/, unsigned int /*line*/, uintptr_t /*pReserved*/)
+{
+ RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, nullptr);
+}
+
+BOOL WheatyExceptionReport::_GetProcessorName(TCHAR* sProcessorName, DWORD maxcount)
+{
+ if (!sProcessorName)
+ return FALSE;
+
+ HKEY hKey;
+ LONG lRet;
+ lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"),
+ 0, KEY_QUERY_VALUE, &hKey);
+ if (lRet != ERROR_SUCCESS)
+ return FALSE;
+ TCHAR szTmp[2048];
+ DWORD cntBytes = sizeof(szTmp);
+ lRet = ::RegQueryValueEx(hKey, _T("ProcessorNameString"), nullptr, nullptr,
+ (LPBYTE)szTmp, &cntBytes);
+ if (lRet != ERROR_SUCCESS)
+ return FALSE;
+ ::RegCloseKey(hKey);
+ sProcessorName[0] = '\0';
+ // Skip spaces
+ TCHAR* psz = szTmp;
+ while (iswspace(*psz))
+ ++psz;
+ _tcsncpy(sProcessorName, psz, maxcount);
+ return TRUE;
+}
+
+template<size_t size>
+void ToTchar(wchar_t const* src, TCHAR (&dst)[size])
+{
+ if constexpr (std::is_same_v<TCHAR, char>)
+ ::wcstombs_s(nullptr, dst, src, _TRUNCATE);
+ else
+ ::wcscpy_s(dst, size, src);
+}
+
+BOOL WheatyExceptionReport::_GetWindowsVersion(TCHAR* szVersion, DWORD cntMax)
+{
+ *szVersion = _T('\0');
+
+ if (_GetWindowsVersionFromWMI(szVersion, cntMax))
+ return TRUE;
+
+ // Try calling GetVersionEx using the OSVERSIONINFOEX structure.
+ // If that fails, try using the OSVERSIONINFO structure.
+ RTL_OSVERSIONINFOEXW osvi = { };
+ osvi.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
+ NTSTATUS bVersionEx = RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi);
+ if (FAILED(bVersionEx))
+ {
+ osvi.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW);
+ if (!RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi))
+ return FALSE;
+ }
+
+ TCHAR szCSDVersion[256];
+ ToTchar(osvi.szCSDVersion, szCSDVersion);
+
+ TCHAR wszTmp[128];
+ switch (osvi.dwPlatformId)
+ {
+ // Windows NT product family.
+ case VER_PLATFORM_WIN32_NT:
+ {
+ #if WINVER < 0x0500
+ BYTE suiteMask = osvi.wReserved[0];
+ BYTE productType = osvi.wReserved[1];
+ #else
+ WORD suiteMask = osvi.wSuiteMask;
+ BYTE productType = osvi.wProductType;
+ #endif // WINVER < 0x0500
+
+ // Test for the specific product family.
+ if (osvi.dwMajorVersion == 10)
+ {
+ if (productType == VER_NT_WORKSTATION)
+ _tcsncat(szVersion, _T("Windows 10 "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Windows Server 2016 "), cntMax);
+ }
+ else if (osvi.dwMajorVersion == 6)
+ {
+ if (productType == VER_NT_WORKSTATION)
+ {
+ if (osvi.dwMinorVersion == 3)
+ _tcsncat(szVersion, _T("Windows 8.1 "), cntMax);
+ else if (osvi.dwMinorVersion == 2)
+ _tcsncat(szVersion, _T("Windows 8 "), cntMax);
+ else if (osvi.dwMinorVersion == 1)
+ _tcsncat(szVersion, _T("Windows 7 "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Windows Vista "), cntMax);
+ }
+ else if (osvi.dwMinorVersion == 3)
+ _tcsncat(szVersion, _T("Windows Server 2012 R2 "), cntMax);
+ else if (osvi.dwMinorVersion == 2)
+ _tcsncat(szVersion, _T("Windows Server 2012 "), cntMax);
+ else if (osvi.dwMinorVersion == 1)
+ _tcsncat(szVersion, _T("Windows Server 2008 R2 "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Windows Server 2008 "), cntMax);
+ }
+ else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
+ _tcsncat(szVersion, _T("Microsoft Windows Server 2003 "), cntMax);
+ else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
+ _tcsncat(szVersion, _T("Microsoft Windows XP "), cntMax);
+ else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
+ _tcsncat(szVersion, _T("Microsoft Windows 2000 "), cntMax);
+ else if (osvi.dwMajorVersion <= 4)
+ _tcsncat(szVersion, _T("Microsoft Windows NT "), cntMax);
+
+ // Test for specific product on Windows NT 4.0 SP6 and later.
+ if (bVersionEx >= 0)
+ {
+ // Test for the workstation type.
+ if (productType == VER_NT_WORKSTATION)
+ {
+ if (osvi.dwMajorVersion == 4)
+ _tcsncat(szVersion, _T("Workstation 4.0 "), cntMax);
+ else if (suiteMask & VER_SUITE_PERSONAL)
+ _tcsncat(szVersion, _T("Home Edition "), cntMax);
+ else if (suiteMask & VER_SUITE_EMBEDDEDNT)
+ _tcsncat(szVersion, _T("Embedded "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Professional "), cntMax);
+ }
+ // Test for the server type.
+ else if (productType == VER_NT_SERVER)
+ {
+ if (osvi.dwMajorVersion == 6 || osvi.dwMajorVersion == 10)
+ {
+ if (suiteMask & VER_SUITE_SMALLBUSINESS_RESTRICTED)
+ _tcsncat(szVersion, _T("Essentials "), cntMax);
+ else if (suiteMask & VER_SUITE_DATACENTER)
+ _tcsncat(szVersion, _T("Datacenter "), cntMax);
+ else if (suiteMask & VER_SUITE_ENTERPRISE)
+ _tcsncat(szVersion, _T("Enterprise "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Standard "), cntMax);
+ }
+ else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
+ {
+ if (suiteMask & VER_SUITE_DATACENTER)
+ _tcsncat(szVersion, _T("Datacenter Edition "), cntMax);
+ else if (suiteMask & VER_SUITE_ENTERPRISE)
+ _tcsncat(szVersion, _T("Enterprise Edition "), cntMax);
+ else if (suiteMask == VER_SUITE_BLADE)
+ _tcsncat(szVersion, _T("Web Edition "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Standard Edition "), cntMax);
+ }
+ else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
+ {
+ if (suiteMask & VER_SUITE_DATACENTER)
+ _tcsncat(szVersion, _T("Datacenter Server "), cntMax);
+ else if (suiteMask & VER_SUITE_ENTERPRISE)
+ _tcsncat(szVersion, _T("Advanced Server "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Server "), cntMax);
+ }
+ else // Windows NT 4.0
+ {
+ if (suiteMask & VER_SUITE_ENTERPRISE)
+ _tcsncat(szVersion, _T("Server 4.0, Enterprise Edition "), cntMax);
+ else
+ _tcsncat(szVersion, _T("Server 4.0 "), cntMax);
+ }
+ }
+ }
+
+ // Display service pack (if any) and build number.
+ if (osvi.dwMajorVersion == 4 && _tcsicmp(szCSDVersion, _T("Service Pack 6")) == 0)
+ {
+ HKEY hKey;
+ LONG lRet;
+
+ // Test for SP6 versus SP6a.
+ lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"), 0, KEY_QUERY_VALUE, &hKey);
+ if (lRet == ERROR_SUCCESS)
+ {
+ _stprintf_s(wszTmp, _T("Service Pack 6a (Version %d.%d, Build %d)"),
+ osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
+ _tcsncat(szVersion, wszTmp, cntMax);
+ }
+ else // Windows NT 4.0 prior to SP6a
+ {
+ _stprintf_s(wszTmp, _T("%s (Version %d.%d, Build %d)"),
+ szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
+ _tcsncat(szVersion, wszTmp, cntMax);
+ }
+ ::RegCloseKey(hKey);
+ }
+ else // Windows NT 3.51 and earlier or Windows 2000 and later
+ {
+ if (!_tcslen(szCSDVersion))
+ _stprintf_s(wszTmp, _T("(Version %d.%d, Build %d)"),
+ osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
+ else
+ _stprintf_s(wszTmp, _T("%s (Version %d.%d, Build %d)"),
+ szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
+ _tcsncat(szVersion, wszTmp, cntMax);
+ }
+ break;
+ }
+ default:
+ _stprintf_s(wszTmp, _T("%s (Version %d.%d, Build %d)"),
+ szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
+ _tcsncat(szVersion, wszTmp, cntMax);
+ break;
+ }
+
+ return TRUE;
+}
+
+BOOL WheatyExceptionReport::_GetWindowsVersionFromWMI(TCHAR* szVersion, DWORD cntMax)
+{
+ // Step 1: --------------------------------------------------
+ // Initialize COM. ------------------------------------------
+ HRESULT hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ if (FAILED(hres))
+ return FALSE;
+
+ std::shared_ptr<void> com(nullptr, [](void*)
+ {
+ CoUninitialize();
+ });
+
+ // Step 2: --------------------------------------------------
+ // Set general COM security levels --------------------------
+ hres = CoInitializeSecurity(
+ nullptr,
+ -1, // COM authentication
+ nullptr, // Authentication services
+ nullptr, // Reserved
+ RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
+ RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
+ nullptr, // Authentication info
+ EOAC_NONE, // Additional capabilities
+ nullptr // Reserved
+ );
+
+ if (FAILED(hres))
+ return FALSE;
+
+ // Step 3: ---------------------------------------------------
+ // Obtain the initial locator to WMI -------------------------
+ std::shared_ptr<IWbemLocator> loc = []() -> std::shared_ptr<IWbemLocator>
+ {
+ IWbemLocator* tmp = nullptr;
+ HRESULT hres = CoCreateInstance(
+ CLSID_WbemLocator,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IWbemLocator,
+ reinterpret_cast<LPVOID*>(&tmp));
+
+ if (FAILED(hres))
+ return nullptr;
+
+ return { tmp, [](IWbemLocator* ptr) { if (ptr) ptr->Release(); } };
+ }();
+
+ if (!loc)
+ return FALSE;
+
+ // Step 4: -----------------------------------------------------
+ // Connect to the root\cimv2 namespace with
+ // the current user and obtain pointer pSvc
+ // to make IWbemServices calls.
+ std::shared_ptr<IWbemServices> svc = [loc]() ->std::shared_ptr<IWbemServices>
+ {
+ IWbemServices* tmp = nullptr;
+ HRESULT hres = loc->ConnectServer(
+ bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
+ nullptr, // User name. NULL = current user
+ nullptr, // User password. NULL = current
+ nullptr, // Locale. NULL indicates current
+ WBEM_FLAG_CONNECT_USE_MAX_WAIT, // Security flags.
+ nullptr, // Authority (for example, Kerberos)
+ nullptr, // Context object
+ &tmp // pointer to IWbemServices proxy
+ );
+
+ if (FAILED(hres))
+ return nullptr;
+
+ return { tmp, [](IWbemServices* ptr) { if (ptr) ptr->Release(); } };
+ }();
+
+ if (!svc)
+ return FALSE;
+
+ // Step 5: --------------------------------------------------
+ // Set security levels on the proxy -------------------------
+ hres = CoSetProxyBlanket(
+ svc.get(), // Indicates the proxy to set
+ RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
+ RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
+ nullptr, // Server principal name
+ RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
+ RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
+ nullptr, // client identity
+ EOAC_NONE // proxy capabilities
+ );
+
+ if (FAILED(hres))
+ return FALSE;
+
+ // Step 6: --------------------------------------------------
+ // Use the IWbemServices pointer to make requests of WMI ----
+
+ // For example, get the name of the operating system
+ std::shared_ptr<IEnumWbemClassObject> queryResult = [svc]() -> std::shared_ptr<IEnumWbemClassObject>
+ {
+ IEnumWbemClassObject* tmp = nullptr;
+ HRESULT hres = svc->ExecQuery(
+ bstr_t("WQL"),
+ bstr_t("SELECT Caption, CSDVersion FROM Win32_OperatingSystem"),
+ WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
+ nullptr,
+ &tmp);
+
+ if (FAILED(hres))
+ return nullptr;
+
+ return { tmp, [](IEnumWbemClassObject* ptr) { if (ptr) ptr->Release(); } };
+ }();
+
+ BOOL result = FALSE;
+ // Step 7: -------------------------------------------------
+ // Get the data from the query in step 6 -------------------
+ if (queryResult)
+ {
+ do
+ {
+ IWbemClassObject* fields = nullptr;
+
+ ULONG rows = 0;
+ queryResult->Next(WBEM_INFINITE, 1, &fields, &rows);
+ if (!rows)
+ break;
+
+ VARIANT field;
+ VariantInit(&field);
+ fields->Get(L"Caption", 0, &field, nullptr, nullptr);
+ TCHAR buf[256] = { };
+ ToTchar(field.bstrVal, buf);
+ _tcsncat(szVersion, buf, cntMax);
+ VariantClear(&field);
+
+ fields->Get(L"CSDVersion", 0, &field, nullptr, nullptr);
+ if (field.vt == VT_BSTR)
+ {
+ _tcsncat(szVersion, _T(" "), cntMax);
+ memset(buf, 0, sizeof(buf));
+ ToTchar(field.bstrVal, buf);
+ if (_tcslen(buf))
+ _tcsncat(szVersion, buf, cntMax);
+ }
+ VariantClear(&field);
+
+ fields->Release();
+
+ result = TRUE;
+ } while (true);
+ }
+
+ return result;
+}
+
+void WheatyExceptionReport::PrintSystemInfo()
+{
+ SYSTEM_INFO SystemInfo;
+ ::GetSystemInfo(&SystemInfo);
+
+ MEMORYSTATUS MemoryStatus;
+ MemoryStatus.dwLength = sizeof (MEMORYSTATUS);
+ ::GlobalMemoryStatus(&MemoryStatus);
+ TCHAR sString[1024];
+ Log(_T("//=====================================================\r\n"));
+ if (_GetProcessorName(sString, std::size(sString)))
+ Log(_T("*** Hardware ***\r\nProcessor: %s\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"),
+ sString, SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400);
+ else
+ Log(_T("*** Hardware ***\r\nProcessor: <unknown>\r\nNumber Of Processors: %d\r\nPhysical Memory: %d KB (Available: %d KB)\r\nCommit Charge Limit: %d KB\r\n"),
+ SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400);
+
+ if (_GetWindowsVersion(sString, std::size(sString)))
+ Log(_T("\r\n*** Operation System ***\r\n%s\r\n"), sString);
+ else
+ Log(_T("\r\n*** Operation System:\r\n<unknown>\r\n"));
+}
+
+//===========================================================================
+void WheatyExceptionReport::printTracesForAllThreads(bool bWriteVariables)
+{
+ THREADENTRY32 te32;
+
+ DWORD dwOwnerPID = GetCurrentProcessId();
+ DWORD dwCurrentTID = GetCurrentThreadId();
+ m_hProcess = GetCurrentProcess();
+ // Take a snapshot of all running threads
+ HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ return;
+
+ // Fill in the size of the structure before using it.
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ // Retrieve information about the first thread,
+ // and exit if unsuccessful
+ if (!Thread32First(hThreadSnap, &te32))
+ {
+ CloseHandle(hThreadSnap); // Must clean up the
+ // snapshot object!
+ return;
+ }
+
+ // Now walk the thread list of the system,
+ // and display information about each thread
+ // associated with the specified process
+ do
+ {
+ if (te32.th32OwnerProcessID == dwOwnerPID && te32.th32ThreadID != dwCurrentTID)
+ {
+ CONTEXT context;
+ context.ContextFlags = 0xffffffff;
+ HANDLE threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, false, te32.th32ThreadID);
+ if (threadHandle)
+ {
+ if (GetThreadContext(threadHandle, &context))
+ WriteStackDetails(&context, bWriteVariables, threadHandle);
+ CloseHandle(threadHandle);
+ }
+ }
+ } while (Thread32Next(hThreadSnap, &te32));
+
+// Don't forget to clean up the snapshot object.
+ CloseHandle(hThreadSnap);
+}
+
+//===========================================================================
+// Open the report file, and write the desired information to it. Called by
+// WheatyUnhandledExceptionFilter
+//===========================================================================
+void WheatyExceptionReport::GenerateExceptionReport(
+PEXCEPTION_POINTERS pExceptionInfo)
+{
+ __try
+ {
+ SYSTEMTIME systime;
+ GetLocalTime(&systime);
+
+ // Start out with a banner
+ Log(_T("Revision: %s\r\n"), GitRevision::GetFullVersion());
+ Log(_T("Date %u:%u:%u. Time %u:%u \r\n"), systime.wDay, systime.wMonth, systime.wYear, systime.wHour, systime.wMinute);
+ PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;
+
+ PrintSystemInfo();
+ // First print information about the type of fault
+ Log(_T("\r\n//=====================================================\r\n"));
+ Log(_T("Exception code: %08X %s\r\n"),
+ pExceptionRecord->ExceptionCode,
+ GetExceptionString(pExceptionRecord->ExceptionCode));
+ if (pExceptionRecord->ExceptionCode == EXCEPTION_ASSERTION_FAILURE && pExceptionRecord->NumberParameters >= 2)
+ {
+ pExceptionRecord->ExceptionAddress = reinterpret_cast<PVOID>(pExceptionRecord->ExceptionInformation[1]);
+ Log(_T("Assertion message: %s\r\n"), pExceptionRecord->ExceptionInformation[0]);
+ }
+
+ // Now print information about where the fault occured
+ TCHAR szFaultingModule[MAX_PATH];
+ DWORD section;
+ DWORD_PTR offset;
+ GetLogicalAddress(pExceptionRecord->ExceptionAddress,
+ szFaultingModule,
+ sizeof(szFaultingModule),
+ section, offset);
+
+#if defined(_M_IX86) || defined(_M_ARM)
+ Log(_T("Fault address: %08X %02X:%08X %s\r\n"),
+ pExceptionRecord->ExceptionAddress,
+ section, offset, szFaultingModule);
+#endif
+#if defined(_M_X64) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined (_M_ARM64EC)
+ Log(_T("Fault address: %016I64X %02X:%016I64X %s\r\n"),
+ pExceptionRecord->ExceptionAddress,
+ section, offset, szFaultingModule);
+#endif
+
+ PCONTEXT pCtx = pExceptionInfo->ContextRecord;
+
+ // Show the registers
+#ifdef _M_IX86 // X86 Only!
+ Log(_T("\r\nRegisters:\r\n"));
+
+ Log(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n")
+ , pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx,
+ pCtx->Esi, pCtx->Edi);
+
+ Log(_T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip);
+ Log(_T("SS:ESP:%04X:%08X EBP:%08X\r\n"),
+ pCtx->SegSs, pCtx->Esp, pCtx->Ebp);
+ Log(_T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"),
+ pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs);
+ Log(_T("Flags:%08X\r\n"), pCtx->EFlags);
+#endif
+
+#ifdef _M_X64
+ Log(_T("\r\nRegisters:\r\n"));
+ Log(_T("RAX:%016I64X\r\nRBX:%016I64X\r\nRCX:%016I64X\r\nRDX:%016I64X\r\nRSI:%016I64X\r\nRDI:%016I64X\r\n")
+ _T("R8: %016I64X\r\nR9: %016I64X\r\nR10:%016I64X\r\nR11:%016I64X\r\nR12:%016I64X\r\nR13:%016I64X\r\nR14:%016I64X\r\nR15:%016I64X\r\n")
+ , pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx,
+ pCtx->Rsi, pCtx->Rdi, pCtx->R8, pCtx->R9, pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13, pCtx->R14, pCtx->R15);
+ Log(_T("CS:RIP:%04X:%016I64X\r\n"), pCtx->SegCs, pCtx->Rip);
+ Log(_T("SS:RSP:%04X:%016I64X RBP:%08X\r\n"),
+ pCtx->SegSs, pCtx->Rsp, pCtx->Rbp);
+ Log(_T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"),
+ pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs);
+ Log(_T("Flags:%08X\r\n"), pCtx->EFlags);
+#endif
+
+#ifdef _M_ARM64
+ Log(_T("\r\nRegisters:\r\n"));
+ Log(_T("X0:%016I64X\r\nX1:%016I64X\r\nX2:%016I64X\r\nX3:%016I64X\r\nX4:%016I64X\r\nX5:%016I64X\r\n"),
+ _T("X6:%016I64X\r\nX7:%016I64X\r\nX8:%016I64X\r\nX9:%016I64X\r\nX10:%016I64X\r\nX11:%016I64X\r\nX12:%016I64X\r\nX13:%016I64X\r\n"),
+ _T("X14:%016I64X\r\nX15:%016I64X\r\nX16:%016I64X\r\nX17:%016I64X\r\nX18:%016I64X\r\nX19:%016I64X\r\nX20:%016I64X\r\nX21:%016I64X\r\n"),
+ _T("X22:%016I64X\r\nX23:%016I64X\r\nX24:%016I64X\r\nX25:%016I64X\r\nX26:%016I64X\r\nX27:%016I64X\r\nX28:%016I64X\r\n"),
+ pCtx->X0, pCtx->X1, pCtx->X2, pCtx->X3, pCtx->X4, pCtx->X5,
+ pCtx->X6, pCtx->X7, pCtx->X8, pCtx->X9, pCtx->X10, pCtx->X11, pCtx->X12, pCtx->X13,
+ pCtx->X14, pCtx->X15, pCtx->X16, pCtx->X17, pCtx->X18, pCtx->X19, pCtx->X20, pCtx->X21,
+ pCtx->X22, pCtx->X23, pCtx->X24, pCtx->X25, pCtx->X26, pCtx->X27, pCtx->X28);
+ Log(_T("LR:%016I64X\r\n"), pCtx->Lr);
+ Log(_T("PC:%016I64X\r\n"), pCtx->Pc);
+ Log(_T("SP:%016I64X FP:%016I64X\r\n"), pCtx->Sp, pCtx->Fp);
+ Log(_T("Flags:%08X\r\n"), pCtx->Cpsr);
+#endif
+
+ SymSetOptions(SYMOPT_DEFERRED_LOADS);
+
+ // Initialize DbgHelp
+ if (!SymInitialize(GetCurrentProcess(), nullptr, TRUE))
+ {
+ Log(_T("\r\n"));
+ Log(_T("----\r\n"));
+ Log(_T("SYMBOL HANDLER ERROR (THIS IS NOT THE CRASH ERROR)\r\n\r\n"));
+ Log(_T("Couldn't initialize symbol handler for process when generating crash report\r\n"));
+ Log(_T("Error: %s\r\n"), ErrorMessage(GetLastError()));
+ Log(_T("THE BELOW CALL STACKS MIGHT HAVE MISSING OR INACCURATE FILE/FUNCTION NAMES\r\n\r\n"));
+ Log(_T("----\r\n"));
+ }
+
+ if (pExceptionRecord->ExceptionCode == 0xE06D7363 && pExceptionRecord->NumberParameters >= 2)
+ {
+ PVOID exceptionObject = reinterpret_cast<PVOID>(pExceptionRecord->ExceptionInformation[1]);
+ ThrowInfo const* throwInfo = reinterpret_cast<ThrowInfo const*>(pExceptionRecord->ExceptionInformation[2]);
+#if _EH_RELATIVE_TYPEINFO
+ // When _EH_RELATIVE_TYPEINFO is defined, the pointers need to be retrieved with some pointer math
+ auto resolveExceptionRVA = [pExceptionRecord](int32 rva) -> DWORD_PTR
+ {
+ return rva + (pExceptionRecord->NumberParameters >= 4 ? pExceptionRecord->ExceptionInformation[3] : 0);
+ };
+#else
+ // Otherwise the pointers are already there in the API types
+ auto resolveExceptionRVA = [](void const* input) -> void const* { return input; };
+#endif
+
+ CatchableTypeArray const* catchables = reinterpret_cast<CatchableTypeArray const*>(resolveExceptionRVA(throwInfo->pCatchableTypeArray));
+ CatchableType const* catchable = catchables->nCatchableTypes ? reinterpret_cast<CatchableType const*>(resolveExceptionRVA(catchables->arrayOfCatchableTypes[0])) : nullptr;
+ TypeDescriptor const* exceptionTypeinfo = catchable ? reinterpret_cast<TypeDescriptor const*>(resolveExceptionRVA(catchable->pType)) : nullptr;
+
+ if (exceptionTypeinfo)
+ {
+ void* stdExceptionTypeInfo = []() -> void*
+ {
+ try
+ {
+ std::exception fake;
+ return __RTtypeid(&fake);
+ }
+ catch (...)
+ {
+ return nullptr;
+ }
+ }();
+ std::exception const* exceptionPtr = [](void* object, TypeDescriptor const* typeInfo, void* stdExceptionTypeInfo) -> std::exception const*
+ {
+ try
+ {
+ // real_type descriptor is obtained by parsing throwinfo
+ // equivalent to expression like this
+ // std::exception* e = object;
+ // real_type* r = dynamic_cast<real_type*>(e);
+ // return r;
+ return reinterpret_cast<std::exception const*>(__RTDynamicCast(object, 0, stdExceptionTypeInfo, (void*)typeInfo, false));
+ }
+ catch (...)
+ {
+ return nullptr;
+ }
+ }(exceptionObject, exceptionTypeinfo, stdExceptionTypeInfo);
+
+ // dynamic_cast<type>(variable_that_already_has_that_type) is optimized away by compiler and attempting to call __RTDynamicCast fails for it
+ if (!exceptionPtr && exceptionTypeinfo == stdExceptionTypeInfo)
+ exceptionPtr = reinterpret_cast<std::exception*>(exceptionObject);
+
+ Log(_T("\r\nUncaught C++ exception info:"));
+ if (exceptionPtr)
+ Log(_T(" %s"), exceptionPtr->what());
+
+ Log(_T("\r\n"));
+
+ char undName[MAX_SYM_NAME] = { };
+ if (UnDecorateSymbolName(&exceptionTypeinfo->name[1], &undName[0], MAX_SYM_NAME, UNDNAME_32_BIT_DECODE | UNDNAME_NAME_ONLY | UNDNAME_NO_ARGUMENTS))
+ {
+ char buf[MAX_SYM_NAME + sizeof(SYMBOL_INFO)] = { };
+ PSYMBOL_INFO sym = (PSYMBOL_INFO)&buf[0];
+ sym->SizeOfStruct = sizeof(SYMBOL_INFO);
+ sym->MaxNameLen = MAX_SYM_NAME;
+ if (SymGetTypeFromName(m_hProcess, (ULONG64)GetModuleHandle(nullptr), undName, sym))
+ {
+ sym->Address = pExceptionRecord->ExceptionInformation[1];
+ sym->Flags = 0;
+ char const* variableName = "uncaught_exception";
+ memset(sym->Name, 0, MAX_SYM_NAME);
+ memcpy(sym->Name, variableName, strlen(variableName));
+ FormatSymbolValue(sym, nullptr);
+ }
+ }
+ }
+ }
+
+ CONTEXT trashableContext = *pCtx;
+
+ WriteStackDetails(&trashableContext, false, nullptr);
+ printTracesForAllThreads(false);
+
+ // #ifdef _M_IX86 // X86 Only!
+
+ Log(_T("========================\r\n"));
+ Log(_T("Local Variables And Parameters\r\n"));
+
+ trashableContext = *pCtx;
+ WriteStackDetails(&trashableContext, true, nullptr);
+ printTracesForAllThreads(true);
+
+ SymCleanup(GetCurrentProcess());
+
+ Log(_T("\r\n"));
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ Log(_T("Error writing the crash log\r\n"));
+ }
+}
+
+//======================================================================
+// Given an exception code, returns a pointer to a static string with a
+// description of the exception
+//======================================================================
+LPCTSTR WheatyExceptionReport::GetExceptionString(DWORD dwCode)
+{
+ #define EXCEPTION(x) case EXCEPTION_##x: return _T(#x);
+
+ switch (dwCode)
+ {
+ EXCEPTION(ACCESS_VIOLATION)
+ EXCEPTION(DATATYPE_MISALIGNMENT)
+ EXCEPTION(BREAKPOINT)
+ EXCEPTION(SINGLE_STEP)
+ EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
+ EXCEPTION(FLT_DENORMAL_OPERAND)
+ EXCEPTION(FLT_DIVIDE_BY_ZERO)
+ EXCEPTION(FLT_INEXACT_RESULT)
+ EXCEPTION(FLT_INVALID_OPERATION)
+ EXCEPTION(FLT_OVERFLOW)
+ EXCEPTION(FLT_STACK_CHECK)
+ EXCEPTION(FLT_UNDERFLOW)
+ EXCEPTION(INT_DIVIDE_BY_ZERO)
+ EXCEPTION(INT_OVERFLOW)
+ EXCEPTION(PRIV_INSTRUCTION)
+ EXCEPTION(IN_PAGE_ERROR)
+ EXCEPTION(ILLEGAL_INSTRUCTION)
+ EXCEPTION(NONCONTINUABLE_EXCEPTION)
+ EXCEPTION(STACK_OVERFLOW)
+ EXCEPTION(INVALID_DISPOSITION)
+ EXCEPTION(GUARD_PAGE)
+ EXCEPTION(INVALID_HANDLE)
+ case 0xE06D7363: return _T("Unhandled C++ exception");
+ }
+
+ // If not one of the "known" exceptions, try to get the string
+ // from NTDLL.DLL's message table.
+
+ static TCHAR szBuffer[512] = { 0 };
+
+ FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
+ GetModuleHandle(_T("NTDLL.DLL")),
+ dwCode, 0, szBuffer, sizeof(szBuffer), nullptr);
+
+ return szBuffer;
+}
+
+//=============================================================================
+// Given a linear address, locates the module, section, and offset containing
+// that address.
+//
+// Note: the szModule paramater buffer is an output buffer of length specified
+// by the len parameter (in characters!)
+//=============================================================================
+BOOL WheatyExceptionReport::GetLogicalAddress(
+PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD_PTR& offset)
+{
+ MEMORY_BASIC_INFORMATION mbi;
+
+ if (!VirtualQuery(addr, &mbi, sizeof(mbi)))
+ return FALSE;
+
+ DWORD_PTR hMod = (DWORD_PTR)mbi.AllocationBase;
+
+ if (!hMod)
+ return FALSE;
+
+ if (!GetModuleFileName((HMODULE)hMod, szModule, len))
+ return FALSE;
+
+ // Point to the DOS header in memory
+ PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;
+
+ // From the DOS header, find the NT (PE) header
+ PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + DWORD_PTR(pDosHdr->e_lfanew));
+
+ PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr);
+
+ DWORD_PTR rva = (DWORD_PTR)addr - hMod; // RVA is offset from module load address
+
+ // Iterate through the section table, looking for the one that encompasses
+ // the linear address.
+ for (unsigned i = 0;
+ i < pNtHdr->FileHeader.NumberOfSections;
+ i++, pSection++)
+ {
+ DWORD_PTR sectionStart = pSection->VirtualAddress;
+ DWORD_PTR sectionEnd = sectionStart
+ + DWORD_PTR(std::max(pSection->SizeOfRawData, pSection->Misc.VirtualSize));
+
+ // Is the address in this section???
+ if ((rva >= sectionStart) && (rva <= sectionEnd))
+ {
+ // Yes, address is in the section. Calculate section and offset,
+ // and store in the "section" & "offset" params, which were
+ // passed by reference.
+ section = i+1;
+ offset = rva - sectionStart;
+ return TRUE;
+ }
+ }
+
+ return FALSE; // Should never get here!
+}
+
+// It contains SYMBOL_INFO structure plus additional
+// space for the name of the symbol
+struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE
+{
+ CSymbolInfoPackage()
+ {
+ si.SizeOfStruct = sizeof(SYMBOL_INFO);
+ si.MaxNameLen = sizeof(name);
+ }
+};
+
+//============================================================
+// Walks the stack, and writes the results to the report file
+//============================================================
+void WheatyExceptionReport::WriteStackDetails(
+PCONTEXT pContext,
+bool bWriteVariables, HANDLE pThreadHandle) // true if local/params should be output
+{
+ Log(_T("\r\nCall stack:\r\n"));
+
+ Log(_T("Address Frame Function SourceFile\r\n"));
+
+ DWORD dwMachineType = 0;
+ // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag
+
+ STACKFRAME64 sf;
+ memset(&sf, 0, sizeof(sf));
+
+ // Initialize the STACKFRAME structure for the first call.
+ sf.AddrPC.Mode = AddrModeFlat;
+ sf.AddrStack.Mode = AddrModeFlat;
+ sf.AddrFrame.Mode = AddrModeFlat;
+
+#ifdef _M_IX86
+ sf.AddrPC.Offset = pContext->Eip;
+ sf.AddrStack.Offset = pContext->Esp;
+ sf.AddrFrame.Offset = pContext->Ebp;
+ dwMachineType = IMAGE_FILE_MACHINE_I386;
+#elif defined(_M_X64)
+ sf.AddrPC.Offset = pContext->Rip;
+ sf.AddrStack.Offset = pContext->Rsp;
+ sf.AddrFrame.Offset = pContext->Rbp;
+ dwMachineType = IMAGE_FILE_MACHINE_AMD64;
+#elif defined(_M_ARM64)
+ sf.AddrPC.Offset = pContext->Pc;
+ sf.AddrStack.Offset = pContext->Sp;
+ sf.AddrFrame.Offset = pContext->Fp;
+ dwMachineType = IMAGE_FILE_MACHINE_ARM64;
+#endif
+
+ for (;;)
+ {
+ // Get the next stack frame
+ if (! StackWalk64(dwMachineType,
+ m_hProcess,
+ pThreadHandle != nullptr ? pThreadHandle : GetCurrentThread(),
+ &sf,
+ pContext,
+ nullptr,
+ SymFunctionTableAccess64,
+ SymGetModuleBase64,
+ nullptr))
+ break;
+ if (0 == sf.AddrFrame.Offset) // Basic sanity check to make sure
+ break; // the frame is OK. Bail if not.
+#ifdef _M_IX86
+ Log(_T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset);
+#elif defined(_M_X64) || defined(_M_ARM64)
+ Log(_T("%016I64X %016I64X "), sf.AddrPC.Offset, sf.AddrFrame.Offset);
+#endif
+
+ DWORD64 symDisplacement = 0; // Displacement of the input address,
+ // relative to the start of the symbol
+
+ // Get the name of the function for this stack frame entry
+ CSymbolInfoPackage sip;
+ if (SymFromAddr(
+ m_hProcess, // Process handle of the current process
+ sf.AddrPC.Offset, // Symbol address
+ &symDisplacement, // Address of the variable that will receive the displacement
+ &sip.si)) // Address of the SYMBOL_INFO structure (inside "sip" object)
+ {
+ Log(_T("%hs+%I64X"), sip.si.Name, symDisplacement);
+
+ }
+ else // No symbol found. Print out the logical address instead.
+ {
+ TCHAR szModule[MAX_PATH] = _T("");
+ DWORD section = 0;
+ DWORD_PTR offset = 0;
+
+ GetLogicalAddress((PVOID)sf.AddrPC.Offset,
+ szModule, sizeof(szModule), section, offset);
+#ifdef _M_IX86
+ Log(_T("%04X:%08X %s"), section, offset, szModule);
+#elif defined(_M_X64) || defined(_M_ARM64)
+ Log(_T("%04X:%016I64X %s"), section, offset, szModule);
+#endif
+ }
+
+ // Get the source line for this stack frame entry
+ IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE64) };
+ DWORD dwLineDisplacement;
+ if (SymGetLineFromAddr64(m_hProcess, sf.AddrPC.Offset,
+ &dwLineDisplacement, &lineInfo))
+ {
+ Log(_T(" %s line %u"), lineInfo.FileName, lineInfo.LineNumber);
+ }
+
+ Log(_T("\r\n"));
+
+ // Write out the variables, if desired
+ if (bWriteVariables)
+ {
+ // Use SymSetContext to get just the locals/params for this frame
+ IMAGEHLP_STACK_FRAME imagehlpStackFrame;
+ imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset;
+ SymSetContext(m_hProcess, &imagehlpStackFrame, nullptr);
+
+ // Enumerate the locals/parameters
+ EnumerateSymbolsCallbackContext ctx;
+ ctx.sf = &sf;
+ ctx.context = pContext;
+ SymEnumSymbols(m_hProcess, 0, nullptr, EnumerateSymbolsCallback, &ctx);
+
+ Log(_T("\r\n"));
+ }
+ }
+
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// The function invoked by SymEnumSymbols
+//////////////////////////////////////////////////////////////////////////////
+
+BOOL CALLBACK
+WheatyExceptionReport::EnumerateSymbolsCallback(
+PSYMBOL_INFO pSymInfo,
+ULONG /*SymbolSize*/,
+PVOID UserContext)
+{
+ __try
+ {
+ ClearSymbols();
+ FormatSymbolValue(pSymInfo, (EnumerateSymbolsCallbackContext*)UserContext);
+
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ Log(_T("punting on symbol %s, partial output:\r\n"), pSymInfo->Name);
+ }
+
+ return TRUE;
+}
+
+Optional<DWORD_PTR> WheatyExceptionReport::GetIntegerRegisterValue(PCONTEXT context, ULONG registerId)
+{
+#define REG_L(x) ((BYTE)(((DWORD_PTR)(x)) & 0xff))
+#define REG_H(x) ((BYTE)((((DWORD_PTR)(x)) >> 8) & 0xff))
+#define REG_X(x) ((WORD)(((DWORD_PTR)(x)) & 0xffff))
+#define REG_E(x) ((DWORD)(((DWORD_PTR)(x)) & 0xffffffff))
+#define REG_R(x) ((DWORD64)(((DWORD_PTR)(x)) & 0xffffffffffffffff))
+#define CPU_REG(reg, field, part) case reg: return part(context->field);
+ switch (registerId)
+ {
+#ifdef _M_IX86
+ CPU_REG(CV_REG_AL, Eax, REG_L);
+ CPU_REG(CV_REG_CL, Ecx, REG_L);
+ CPU_REG(CV_REG_DL, Edx, REG_L);
+ CPU_REG(CV_REG_BL, Ebx, REG_L);
+ CPU_REG(CV_REG_AH, Eax, REG_H);
+ CPU_REG(CV_REG_CH, Ecx, REG_H);
+ CPU_REG(CV_REG_DH, Edx, REG_H);
+ CPU_REG(CV_REG_BH, Ebx, REG_H);
+ CPU_REG(CV_REG_AX, Eax, REG_X);
+ CPU_REG(CV_REG_CX, Ecx, REG_X);
+ CPU_REG(CV_REG_DX, Edx, REG_X);
+ CPU_REG(CV_REG_BX, Ebx, REG_X);
+ CPU_REG(CV_REG_SP, Esp, REG_X);
+ CPU_REG(CV_REG_BP, Ebp, REG_X);
+ CPU_REG(CV_REG_SI, Esi, REG_X);
+ CPU_REG(CV_REG_DI, Edi, REG_X);
+ CPU_REG(CV_REG_EAX, Eax, REG_E);
+ CPU_REG(CV_REG_ECX, Ecx, REG_E);
+ CPU_REG(CV_REG_EDX, Edx, REG_E);
+ CPU_REG(CV_REG_EBX, Ebx, REG_E);
+ CPU_REG(CV_REG_ESP, Esp, REG_E);
+ CPU_REG(CV_REG_EBP, Ebp, REG_E);
+ CPU_REG(CV_REG_ESI, Esi, REG_E);
+ CPU_REG(CV_REG_EDI, Edi, REG_E);
+ CPU_REG(CV_REG_EIP, Eip, REG_E);
+#elif defined (_M_X64)
+ CPU_REG(CV_AMD64_AL, Rax, REG_L);
+ CPU_REG(CV_AMD64_CL, Rcx, REG_L);
+ CPU_REG(CV_AMD64_DL, Rdx, REG_L);
+ CPU_REG(CV_AMD64_BL, Rbx, REG_L);
+ CPU_REG(CV_AMD64_SIL, Rsi, REG_L);
+ CPU_REG(CV_AMD64_DIL, Rdi, REG_L);
+ CPU_REG(CV_AMD64_BPL, Rbp, REG_L);
+ CPU_REG(CV_AMD64_SPL, Rsp, REG_L);
+ CPU_REG(CV_AMD64_R8B, R8, REG_L);
+ CPU_REG(CV_AMD64_R9B, R9, REG_L);
+ CPU_REG(CV_AMD64_R10B, R10, REG_L);
+ CPU_REG(CV_AMD64_R11B, R11, REG_L);
+ CPU_REG(CV_AMD64_R12B, R12, REG_L);
+ CPU_REG(CV_AMD64_R13B, R13, REG_L);
+ CPU_REG(CV_AMD64_R14B, R14, REG_L);
+ CPU_REG(CV_AMD64_R15B, R15, REG_L);
+ CPU_REG(CV_AMD64_AH, Rax, REG_H);
+ CPU_REG(CV_AMD64_CH, Rcx, REG_H);
+ CPU_REG(CV_AMD64_DH, Rdx, REG_H);
+ CPU_REG(CV_AMD64_BH, Rbx, REG_H);
+ CPU_REG(CV_AMD64_AX, Rax, REG_X);
+ CPU_REG(CV_AMD64_CX, Rcx, REG_X);
+ CPU_REG(CV_AMD64_DX, Rdx, REG_X);
+ CPU_REG(CV_AMD64_BX, Rbx, REG_X);
+ CPU_REG(CV_AMD64_SP, Rsp, REG_X);
+ CPU_REG(CV_AMD64_BP, Rbp, REG_X);
+ CPU_REG(CV_AMD64_SI, Rsi, REG_X);
+ CPU_REG(CV_AMD64_DI, Rdi, REG_X);
+ CPU_REG(CV_AMD64_R8W, R8, REG_X);
+ CPU_REG(CV_AMD64_R9W, R9, REG_X);
+ CPU_REG(CV_AMD64_R10W, R10, REG_X);
+ CPU_REG(CV_AMD64_R11W, R11, REG_X);
+ CPU_REG(CV_AMD64_R12W, R12, REG_X);
+ CPU_REG(CV_AMD64_R13W, R13, REG_X);
+ CPU_REG(CV_AMD64_R14W, R14, REG_X);
+ CPU_REG(CV_AMD64_R15W, R15, REG_X);
+ CPU_REG(CV_AMD64_EAX, Rax, REG_E);
+ CPU_REG(CV_AMD64_ECX, Rcx, REG_E);
+ CPU_REG(CV_AMD64_EDX, Rdx, REG_E);
+ CPU_REG(CV_AMD64_EBX, Rbx, REG_E);
+ CPU_REG(CV_AMD64_ESP, Rsp, REG_E);
+ CPU_REG(CV_AMD64_EBP, Rbp, REG_E);
+ CPU_REG(CV_AMD64_ESI, Rsi, REG_E);
+ CPU_REG(CV_AMD64_EDI, Rdi, REG_E);
+ CPU_REG(CV_AMD64_R8D, R8, REG_E);
+ CPU_REG(CV_AMD64_R9D, R9, REG_E);
+ CPU_REG(CV_AMD64_R10D, R10, REG_E);
+ CPU_REG(CV_AMD64_R11D, R11, REG_E);
+ CPU_REG(CV_AMD64_R12D, R12, REG_E);
+ CPU_REG(CV_AMD64_R13D, R13, REG_E);
+ CPU_REG(CV_AMD64_R14D, R14, REG_E);
+ CPU_REG(CV_AMD64_R15D, R15, REG_E);
+ CPU_REG(CV_AMD64_RIP, Rip, REG_R);
+ CPU_REG(CV_AMD64_RAX, Rax, REG_R);
+ CPU_REG(CV_AMD64_RBX, Rbx, REG_R);
+ CPU_REG(CV_AMD64_RCX, Rcx, REG_R);
+ CPU_REG(CV_AMD64_RDX, Rdx, REG_R);
+ CPU_REG(CV_AMD64_RSI, Rsi, REG_R);
+ CPU_REG(CV_AMD64_RDI, Rdi, REG_R);
+ CPU_REG(CV_AMD64_RBP, Rbp, REG_R);
+ CPU_REG(CV_AMD64_RSP, Rsp, REG_R);
+ CPU_REG(CV_AMD64_R8, R8, REG_R);
+ CPU_REG(CV_AMD64_R9, R9, REG_R);
+ CPU_REG(CV_AMD64_R10, R10, REG_R);
+ CPU_REG(CV_AMD64_R11, R11, REG_R);
+ CPU_REG(CV_AMD64_R12, R12, REG_R);
+ CPU_REG(CV_AMD64_R13, R13, REG_R);
+ CPU_REG(CV_AMD64_R14, R14, REG_R);
+ CPU_REG(CV_AMD64_R15, R15, REG_R);
+#elif defined(_M_ARM64)
+ CPU_REG(CV_ARM64_W0, X0, REG_E);
+ CPU_REG(CV_ARM64_W1, X1, REG_E);
+ CPU_REG(CV_ARM64_W2, X2, REG_E);
+ CPU_REG(CV_ARM64_W3, X3, REG_E);
+ CPU_REG(CV_ARM64_W4, X4, REG_E);
+ CPU_REG(CV_ARM64_W5, X5, REG_E);
+ CPU_REG(CV_ARM64_W6, X6, REG_E);
+ CPU_REG(CV_ARM64_W7, X7, REG_E);
+ CPU_REG(CV_ARM64_W8, X8, REG_E);
+ CPU_REG(CV_ARM64_W9, X9, REG_E);
+ CPU_REG(CV_ARM64_W10, X10, REG_E);
+ CPU_REG(CV_ARM64_W11, X11, REG_E);
+ CPU_REG(CV_ARM64_W12, X12, REG_E);
+ CPU_REG(CV_ARM64_W13, X13, REG_E);
+ CPU_REG(CV_ARM64_W14, X14, REG_E);
+ CPU_REG(CV_ARM64_W15, X15, REG_E);
+ CPU_REG(CV_ARM64_W16, X16, REG_E);
+ CPU_REG(CV_ARM64_W17, X17, REG_E);
+ CPU_REG(CV_ARM64_W18, X18, REG_E);
+ CPU_REG(CV_ARM64_W19, X19, REG_E);
+ CPU_REG(CV_ARM64_W20, X20, REG_E);
+ CPU_REG(CV_ARM64_W21, X21, REG_E);
+ CPU_REG(CV_ARM64_W22, X22, REG_E);
+ CPU_REG(CV_ARM64_W23, X23, REG_E);
+ CPU_REG(CV_ARM64_W24, X24, REG_E);
+ CPU_REG(CV_ARM64_W25, X25, REG_E);
+ CPU_REG(CV_ARM64_W26, X26, REG_E);
+ CPU_REG(CV_ARM64_W27, X27, REG_E);
+ CPU_REG(CV_ARM64_W28, X28, REG_E);
+ CPU_REG(CV_ARM64_W29, Fp, REG_E);
+ CPU_REG(CV_ARM64_W30, Lr, REG_E);
+ case CV_ARM64_WZR: return 0;
+ CPU_REG(CV_ARM64_X0, X0, REG_R);
+ CPU_REG(CV_ARM64_X1, X1, REG_R);
+ CPU_REG(CV_ARM64_X2, X2, REG_R);
+ CPU_REG(CV_ARM64_X3, X3, REG_R);
+ CPU_REG(CV_ARM64_X4, X4, REG_R);
+ CPU_REG(CV_ARM64_X5, X5, REG_R);
+ CPU_REG(CV_ARM64_X6, X6, REG_R);
+ CPU_REG(CV_ARM64_X7, X7, REG_R);
+ CPU_REG(CV_ARM64_X8, X8, REG_R);
+ CPU_REG(CV_ARM64_X9, X9, REG_R);
+ CPU_REG(CV_ARM64_X10, X10, REG_R);
+ CPU_REG(CV_ARM64_X11, X11, REG_R);
+ CPU_REG(CV_ARM64_X12, X12, REG_R);
+ CPU_REG(CV_ARM64_X13, X13, REG_R);
+ CPU_REG(CV_ARM64_X14, X14, REG_R);
+ CPU_REG(CV_ARM64_X15, X15, REG_R);
+ CPU_REG(CV_ARM64_IP0, X16, REG_R);
+ CPU_REG(CV_ARM64_IP1, X17, REG_R);
+ CPU_REG(CV_ARM64_X18, X18, REG_R);
+ CPU_REG(CV_ARM64_X19, X19, REG_R);
+ CPU_REG(CV_ARM64_X20, X20, REG_R);
+ CPU_REG(CV_ARM64_X21, X21, REG_R);
+ CPU_REG(CV_ARM64_X22, X22, REG_R);
+ CPU_REG(CV_ARM64_X23, X23, REG_R);
+ CPU_REG(CV_ARM64_X24, X24, REG_R);
+ CPU_REG(CV_ARM64_X25, X25, REG_R);
+ CPU_REG(CV_ARM64_X26, X26, REG_R);
+ CPU_REG(CV_ARM64_X27, X27, REG_R);
+ CPU_REG(CV_ARM64_X28, X28, REG_R);
+ CPU_REG(CV_ARM64_FP, Fp, REG_R);
+ CPU_REG(CV_ARM64_LR, Lr, REG_R);
+ CPU_REG(CV_ARM64_SP, Sp, REG_R);
+ case CV_ARM64_ZR: return 0;
+#endif
+ default:
+ break;
+ }
+ return {};
+#undef CPU_REG
+#undef REG_R
+#undef REG_E
+#undef REG_X
+#undef REG_H
+#undef REG_L
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Given a SYMBOL_INFO representing a particular variable, displays its
+// contents. If it's a user defined type, display the members and their
+// values.
+//////////////////////////////////////////////////////////////////////////////
+bool WheatyExceptionReport::FormatSymbolValue(
+PSYMBOL_INFO pSym,
+EnumerateSymbolsCallbackContext* pCtx)
+{
+ // If it's a function, don't do anything.
+ if (pSym->Tag == SymTagFunction) // SymTagFunction from CVCONST.H from the DIA SDK
+ return false;
+
+ DWORD_PTR pVariable = pSym->Address; // Will point to the variable's data in memory
+ Optional<DWORD_PTR> registerVariableStorage;
+
+ if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_FRAMERELATIVE
+ || (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE && pSym->Register == CV_ALLREG_VFRAME))
+ {
+ pVariable += pCtx->sf->AddrFrame.Offset;
+ }
+ else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE)
+ {
+ Optional<DWORD_PTR> registerValue = GetIntegerRegisterValue(pCtx->context, pSym->Register);
+ if (!registerValue)
+ return false;
+
+ pVariable += *registerValue;
+ }
+ else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER)
+ {
+ registerVariableStorage = GetIntegerRegisterValue(pCtx->context, pSym->Register);
+ if (!registerVariableStorage)
+ return false; // Don't try to report non-integer register variable
+
+ pVariable = reinterpret_cast<DWORD_PTR>(&*registerVariableStorage);
+ }
+ else
+ {
+ // It must be a global variable
+ }
+
+ PushSymbolDetail();
+
+ // Indicate if the variable is a local or parameter
+ if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER)
+ symbolDetails.top().Prefix = "Parameter ";
+ else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL)
+ symbolDetails.top().Prefix = "Local ";
+
+ // Determine if the variable is a user defined type (UDT). IF so, bHandled
+ // will return true.
+ bool bHandled;
+ DumpTypeIndex(pSym->ModBase, pSym->TypeIndex, pVariable, bHandled, pSym->Name, "", false, true);
+
+ if (!bHandled)
+ {
+ // The symbol wasn't a UDT, so do basic, stupid formatting of the
+ // variable. Based on the size, we're assuming it's a char, WORD, or
+ // DWORD.
+ BasicType basicType = GetBasicType(pSym->TypeIndex, pSym->ModBase);
+ if (symbolDetails.top().Type.empty())
+ symbolDetails.top().Type = rgBaseType[basicType];
+
+ // Emit the variable name
+ if (pSym->Name[0] != '\0')
+ symbolDetails.top().Name = pSym->Name;
+
+ char buffer[50];
+ FormatOutputValue(buffer, basicType, pSym->Size, (PVOID)pVariable, sizeof(buffer));
+ symbolDetails.top().Value = buffer;
+ }
+
+ PopSymbolDetail();
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// If it's a user defined type (UDT), recurse through its members until we're
+// at fundamental types. When he hit fundamental types, return
+// bHandled = false, so that FormatSymbolValue() will format them.
+//////////////////////////////////////////////////////////////////////////////
+void WheatyExceptionReport::DumpTypeIndex(
+DWORD64 modBase,
+DWORD dwTypeIndex,
+DWORD_PTR offset,
+bool & bHandled,
+char const* Name,
+char const* /*suffix*/,
+bool newSymbol,
+bool logChildren)
+{
+ bHandled = false;
+
+ if (newSymbol)
+ PushSymbolDetail();
+
+ DWORD typeTag;
+ if (!SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_SYMTAG, &typeTag))
+ return;
+
+ // Get the name of the symbol. This will either be a Type name (if a UDT),
+ // or the structure member name.
+ WCHAR * pwszTypeName;
+ if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_SYMNAME,
+ &pwszTypeName))
+ {
+ // handle special cases
+ if (wcscmp(pwszTypeName, L"std::basic_string<char,std::char_traits<char>,std::allocator<char> >") == 0)
+ {
+ LocalFree(pwszTypeName);
+ symbolDetails.top().Type = "std::string";
+ char buffer[50];
+ FormatOutputValue(buffer, btStdString, 0, (PVOID)offset, sizeof(buffer));
+ symbolDetails.top().Value = buffer;
+ if (Name != nullptr && Name[0] != '\0')
+ symbolDetails.top().Name = Name;
+ bHandled = true;
+ return;
+ }
+
+ char buffer[WER_SMALL_BUFFER_SIZE];
+ wcstombs(buffer, pwszTypeName, sizeof(buffer));
+ buffer[WER_SMALL_BUFFER_SIZE - 1] = '\0';
+ if (Name != nullptr && Name[0] != '\0')
+ {
+ symbolDetails.top().Type = buffer;
+ symbolDetails.top().Name = Name;
+ }
+ else if (buffer[0] != '\0')
+ symbolDetails.top().Name = buffer;
+
+ LocalFree(pwszTypeName);
+ }
+ else if (Name != nullptr && Name[0] != '\0')
+ symbolDetails.top().Name = Name;
+
+ if (!StoreSymbol(dwTypeIndex, offset))
+ {
+ // Skip printing address and base class if it has been printed already
+ if (typeTag == SymTagBaseClass)
+ bHandled = true;
+ return;
+ }
+
+ DWORD innerTypeID;
+ switch (typeTag)
+ {
+ case SymTagPointerType:
+ if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID))
+ {
+ if (Name != nullptr && Name[0] != '\0')
+ symbolDetails.top().Name = Name;
+
+ BOOL isReference;
+ SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_IS_REFERENCE, &isReference);
+
+ char addressStr[40];
+ memset(addressStr, 0, sizeof(addressStr));
+
+ if (isReference)
+ symbolDetails.top().Suffix += "&";
+ else
+ symbolDetails.top().Suffix += "*";
+
+ // Try to dereference the pointer in a try/except block since it might be invalid
+ DWORD_PTR address = DereferenceUnsafePointer(offset);
+
+ char buffer[WER_SMALL_BUFFER_SIZE];
+ FormatOutputValue(buffer, btVoid, sizeof(PVOID), (PVOID)offset, sizeof(buffer));
+ symbolDetails.top().Value = buffer;
+
+ if (symbolDetails.size() >= WER_MAX_NESTING_LEVEL)
+ logChildren = false;
+
+ // no need to log any children since the address is invalid anyway
+ if (address == 0 || address == DWORD_PTR(-1))
+ logChildren = false;
+
+ DumpTypeIndex(modBase, innerTypeID, address, bHandled, Name, addressStr, false, logChildren);
+
+ if (!bHandled)
+ {
+ BasicType basicType = GetBasicType(dwTypeIndex, modBase);
+ if (symbolDetails.top().Type.empty())
+ symbolDetails.top().Type = rgBaseType[basicType];
+
+ if (address == 0)
+ symbolDetails.top().Value = "NULL";
+ else if (address == DWORD_PTR(-1))
+ symbolDetails.top().Value = "<Unable to read memory>";
+ else
+ {
+ // Get the size of the child member
+ ULONG64 length;
+ SymGetTypeInfo(m_hProcess, modBase, innerTypeID, TI_GET_LENGTH, &length);
+ char buffer2[50];
+ FormatOutputValue(buffer2, basicType, length, (PVOID)address, sizeof(buffer2));
+ symbolDetails.top().Value = buffer2;
+ }
+ bHandled = true;
+ return;
+ }
+ else if (address == 0)
+ symbolDetails.top().Value = "NULL";
+ else if (address == DWORD_PTR(-1))
+ {
+ symbolDetails.top().Value = "<Unable to read memory>";
+ bHandled = true;
+ return;
+ }
+ }
+ break;
+ case SymTagData:
+ if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID))
+ {
+ DWORD innerTypeTag;
+ if (!SymGetTypeInfo(m_hProcess, modBase, innerTypeID, TI_GET_SYMTAG, &innerTypeTag))
+ break;
+
+ switch (innerTypeTag)
+ {
+ case SymTagUDT:
+ if (symbolDetails.size() >= WER_MAX_NESTING_LEVEL)
+ logChildren = false;
+ DumpTypeIndex(modBase, innerTypeID,
+ offset, bHandled, symbolDetails.top().Name.c_str(), "", false, logChildren);
+ break;
+ case SymTagPointerType:
+ if (Name != nullptr && Name[0] != '\0')
+ symbolDetails.top().Name = Name;
+ DumpTypeIndex(modBase, innerTypeID,
+ offset, bHandled, symbolDetails.top().Name.c_str(), "", false, logChildren);
+ break;
+ case SymTagArrayType:
+ DumpTypeIndex(modBase, innerTypeID,
+ offset, bHandled, symbolDetails.top().Name.c_str(), "", false, logChildren);
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case SymTagArrayType:
+ if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID))
+ {
+ symbolDetails.top().HasChildren = true;
+
+ BasicType basicType = btNoType;
+ DumpTypeIndex(modBase, innerTypeID,
+ offset, bHandled, Name, "", false, false);
+
+ // Set Value back to an empty string since the Array object itself has no value, only its elements have
+ std::string firstElementValue = symbolDetails.top().Value;
+ symbolDetails.top().Value.clear();
+
+ DWORD elementsCount;
+ if (SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_COUNT, &elementsCount))
+ symbolDetails.top().Suffix += "[" + std::to_string(elementsCount) + "]";
+ else
+ symbolDetails.top().Suffix += "[<unknown count>]";
+
+ if (!bHandled)
+ {
+ basicType = GetBasicType(dwTypeIndex, modBase);
+ if (symbolDetails.top().Type.empty())
+ symbolDetails.top().Type = rgBaseType[basicType];
+ bHandled = true;
+ }
+
+ // Get the size of the child member
+ ULONG64 length;
+ SymGetTypeInfo(m_hProcess, modBase, innerTypeID, TI_GET_LENGTH, &length);
+
+ char buffer[50];
+ switch (basicType)
+ {
+ case btChar:
+ case btStdString:
+ FormatOutputValue(buffer, basicType, length, (PVOID)offset, sizeof(buffer), elementsCount);
+ symbolDetails.top().Value = buffer;
+ break;
+ default:
+ for (DWORD index = 0; index < elementsCount && index < WER_MAX_ARRAY_ELEMENTS_COUNT; index++)
+ {
+ bool elementHandled = false;
+ PushSymbolDetail();
+ if (index == 0)
+ {
+ if (firstElementValue.empty())
+ {
+ FormatOutputValue(buffer, basicType, length, (PVOID)(offset + length * index), sizeof(buffer));
+ firstElementValue = buffer;
+ }
+ symbolDetails.top().Value = firstElementValue;
+ }
+ else
+ {
+ DumpTypeIndex(modBase, innerTypeID, offset + length * index, elementHandled, "", "", false, false);
+ if (!elementHandled)
+ {
+ FormatOutputValue(buffer, basicType, length, (PVOID)(offset + length * index), sizeof(buffer));
+ symbolDetails.top().Value = buffer;
+ }
+ }
+ symbolDetails.top().Prefix.clear();
+ symbolDetails.top().Type.clear();
+ symbolDetails.top().Suffix = "[" + std::to_string(index) + "]";
+ symbolDetails.top().Name.clear();
+ PopSymbolDetail();
+ }
+ break;
+ }
+
+ return;
+ }
+ break;
+ case SymTagBaseType:
+ break;
+ case SymTagEnum:
+ return;
+ default:
+ break;
+ }
+
+ // Determine how many children this type has.
+ DWORD dwChildrenCount = 0;
+ SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT, &dwChildrenCount);
+
+ if (!dwChildrenCount) // If no children, we're done
+ return;
+
+ // Prepare to get an array of "TypeIds", representing each of the children.
+ // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a
+ // TI_FINDCHILDREN_PARAMS struct has. Use derivation to accomplish this.
+ struct FINDCHILDREN : TI_FINDCHILDREN_PARAMS
+ {
+ ULONG MoreChildIds[1024*2];
+ FINDCHILDREN(){Count = sizeof(MoreChildIds) / sizeof(MoreChildIds[0]);}
+ } children;
+
+ children.Count = dwChildrenCount;
+ children.Start= 0;
+
+ // Get the array of TypeIds, one for each child type
+ if (!SymGetTypeInfo(m_hProcess, modBase, dwTypeIndex, TI_FINDCHILDREN,
+ &children))
+ {
+ return;
+ }
+
+ // Iterate through each of the children
+ for (unsigned i = 0; i < dwChildrenCount; i++)
+ {
+ DWORD symTag;
+ SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i], TI_GET_SYMTAG, &symTag);
+
+ if (symTag == SymTagFunction ||
+ symTag == SymTagEnum ||
+ symTag == SymTagTypedef ||
+ symTag == SymTagVTable)
+ continue;
+
+ // Ignore static fields
+ DWORD dataKind;
+ SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i], TI_GET_DATAKIND, &dataKind);
+ if (dataKind == DataIsStaticLocal ||
+ dataKind == DataIsGlobal ||
+ dataKind == DataIsStaticMember)
+ continue;
+
+ symbolDetails.top().HasChildren = true;
+ if (!logChildren)
+ {
+ bHandled = false;
+ return;
+ }
+
+ // Recurse for each of the child types
+ bool bHandled2;
+ BasicType basicType = GetBasicType(children.ChildId[i], modBase);
+
+ // Get the offset of the child member, relative to its parent
+ DWORD dwMemberOffset;
+ SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i],
+ TI_GET_OFFSET, &dwMemberOffset);
+
+ // Calculate the address of the member
+ DWORD_PTR dwFinalOffset = offset + dwMemberOffset;
+
+ DumpTypeIndex(modBase,
+ children.ChildId[i],
+ dwFinalOffset, bHandled2, ""/*Name */, "", true, true);
+
+ // If the child wasn't a UDT, format it appropriately
+ if (!bHandled2)
+ {
+ if (symbolDetails.top().Type.empty())
+ symbolDetails.top().Type = rgBaseType[basicType];
+
+ // Get the real "TypeId" of the child. We need this for the
+ // SymGetTypeInfo(TI_GET_TYPEID) call below.
+ DWORD typeId;
+ SymGetTypeInfo(m_hProcess, modBase, children.ChildId[i],
+ TI_GET_TYPEID, &typeId);
+
+ // Get the size of the child member
+ ULONG64 length;
+ SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_LENGTH, &length);
+
+ char buffer[50];
+ FormatOutputValue(buffer, basicType, length, (PVOID)dwFinalOffset, sizeof(buffer));
+ symbolDetails.top().Value = buffer;
+ }
+
+ PopSymbolDetail();
+ }
+
+ bHandled = true;
+}
+
+void WheatyExceptionReport::FormatOutputValue(char * pszCurrBuffer,
+BasicType basicType,
+DWORD64 length,
+PVOID pAddress,
+size_t bufferSize,
+size_t countOverride)
+{
+ __try
+ {
+ switch (basicType)
+ {
+ case btChar:
+ {
+ // Special case handling for char[] type
+ if (countOverride != 0)
+ length = countOverride;
+ else
+ length = strlen((char*)pAddress);
+ if (length > bufferSize - 6)
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "\"%.*s...\"", (int)(bufferSize - 6), (char*)pAddress);
+ else
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "\"%.*s\"", (int)length, (char*)pAddress);
+ break;
+ }
+ case btStdString:
+ {
+ std::string* value = static_cast<std::string*>(pAddress);
+ if (value->length() > bufferSize - 6)
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "\"%.*s...\"", (int)(bufferSize - 6), value->c_str());
+ else
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "\"%s\"", value->c_str());
+ break;
+ }
+ default:
+ // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!)
+ if (length == 1)
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%X", *(PBYTE)pAddress);
+ else if (length == 2)
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%X", *(PWORD)pAddress);
+ else if (length == 4)
+ {
+ if (basicType == btFloat)
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "%f", *(PFLOAT)pAddress);
+ else
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%X", *(PDWORD)pAddress);
+ }
+ else if (length == 8)
+ {
+ if (basicType == btFloat)
+ {
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "%f",
+ *(double *)pAddress);
+ }
+ else
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%I64X",
+ *(DWORD64*)pAddress);
+ }
+ else
+ {
+ #if _WIN64
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%I64X", (DWORD64)pAddress);
+ #else
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%X", (DWORD)pAddress);
+ #endif
+ }
+ break;
+ }
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+#if _WIN64
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%I64X <Unable to read memory>", (DWORD64)pAddress);
+#else
+ pszCurrBuffer += snprintf(pszCurrBuffer, bufferSize, "0x%X <Unable to read memory>", (DWORD)pAddress);
+#endif
+ }
+}
+
+BasicType
+WheatyExceptionReport::GetBasicType(DWORD typeIndex, DWORD64 modBase)
+{
+ BasicType basicType;
+ if (SymGetTypeInfo(m_hProcess, modBase, typeIndex,
+ TI_GET_BASETYPE, &basicType))
+ {
+ return basicType;
+ }
+
+ // Get the real "TypeId" of the child. We need this for the
+ // SymGetTypeInfo(TI_GET_TYPEID) call below.
+ DWORD typeId;
+ if (SymGetTypeInfo(m_hProcess, modBase, typeIndex, TI_GET_TYPEID, &typeId))
+ {
+ if (SymGetTypeInfo(m_hProcess, modBase, typeId, TI_GET_BASETYPE,
+ &basicType))
+ {
+ return basicType;
+ }
+ }
+
+ return btNoType;
+}
+
+DWORD_PTR WheatyExceptionReport::DereferenceUnsafePointer(DWORD_PTR address)
+{
+ __try
+ {
+ return *(PDWORD_PTR)address;
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ return DWORD_PTR(-1);
+ }
+}
+
+//============================================================================
+// Helper function that writes to the report file, and allows the user to use
+// printf style formating
+//============================================================================
+int __cdecl WheatyExceptionReport::Log(const TCHAR * format, ...)
+{
+ va_list argptr;
+ va_start(argptr, format);
+ int retValue = _vftprintf(m_hReportFile, format, argptr);
+ va_end(argptr);
+ return retValue;
+}
+
+bool WheatyExceptionReport::StoreSymbol(DWORD type, DWORD_PTR offset)
+{
+ return symbols.insert(SymbolPair(type, offset)).second;
+}
+
+void WheatyExceptionReport::ClearSymbols()
+{
+ symbols.clear();
+ while (!symbolDetails.empty())
+ symbolDetails.pop();
+}
+
+void WheatyExceptionReport::PushSymbolDetail()
+{
+ // Log current symbol and then add another to the stack to keep the hierarchy format
+ PrintSymbolDetail();
+ symbolDetails.emplace();
+}
+
+void WheatyExceptionReport::PopSymbolDetail()
+{
+ PrintSymbolDetail();
+ symbolDetails.pop();
+}
+
+void WheatyExceptionReport::PrintSymbolDetail()
+{
+ if (symbolDetails.empty())
+ return;
+
+ // Don't log anything if has been logged already or if it's empty
+ if (symbolDetails.top().Logged || symbolDetails.top().empty())
+ return;
+
+ // Add appropriate indentation level (since this routine is recursive)
+ for (size_t i = 0; i < symbolDetails.size(); i++)
+ Log(_T("\t"));
+
+ Log(_T("%s\r\n"), symbolDetails.top().ToString().c_str());
+}
+
+std::string SymbolDetail::ToString()
+{
+ Logged = true;
+ std::string formatted = Prefix + Type + Suffix;
+ if (!Name.empty())
+ {
+ if (!formatted.empty())
+ formatted += " ";
+ formatted += Name;
+ }
+ if (!Value.empty())
+ {
+ if (Name == "passwd" || Name == "password")
+ Value = "<sensitive data>";
+ formatted += " = " + Value;
+ }
+ return formatted;
+}
diff --git a/src/common/Debugging/Windows/WheatyExceptionReport.h b/src/common/Debugging/Windows/WheatyExceptionReport.h
new file mode 100644
index 00000000000..d154fce0d13
--- /dev/null
+++ b/src/common/Debugging/Windows/WheatyExceptionReport.h
@@ -0,0 +1,407 @@
+#ifndef _WHEATYEXCEPTIONREPORT_
+#define _WHEATYEXCEPTIONREPORT_
+
+#define _NO_CVCONST_H
+
+#include "Define.h"
+#include "Optional.h"
+#include <windows.h>
+#include <winnt.h>
+#include <winternl.h>
+#include <dbghelp.h>
+#include <compare>
+#include <set>
+#include <cstdlib>
+#include <cstdio>
+#include <stack>
+#include <mutex>
+
+#define WER_MAX_ARRAY_ELEMENTS_COUNT 10
+#define WER_MAX_NESTING_LEVEL 4
+#define WER_SMALL_BUFFER_SIZE 1024
+#define WER_LARGE_BUFFER_SIZE WER_SMALL_BUFFER_SIZE * 16
+
+enum BasicType // Stolen from CVCONST.H in the DIA 2.0 SDK
+{
+ btNoType = 0,
+ btVoid = 1,
+ btChar = 2,
+ btWChar = 3,
+ btInt = 6,
+ btUInt = 7,
+ btFloat = 8,
+ btBCD = 9,
+ btBool = 10,
+ btLong = 13,
+ btULong = 14,
+ btCurrency = 25,
+ btDate = 26,
+ btVariant = 27,
+ btComplex = 28,
+ btBit = 29,
+ btBSTR = 30,
+ btHresult = 31,
+
+ // Custom types
+ btStdString = 101
+};
+
+enum DataKind // Stolen from CVCONST.H in the DIA 2.0 SDK
+{
+ DataIsUnknown,
+ DataIsLocal,
+ DataIsStaticLocal,
+ DataIsParam,
+ DataIsObjectPtr,
+ DataIsFileStatic,
+ DataIsGlobal,
+ DataIsMember,
+ DataIsStaticMember,
+ DataIsConstant
+};
+
+enum CpuRegister // Stolen from CVCONST.H in the DIA SDK
+{
+ CV_ALLREG_VFRAME= 30006,
+
+ //
+ // Register set for the Intel 80x86 and ix86 processor series
+ //
+ CV_REG_NONE = 0,
+ CV_REG_AL = 1,
+ CV_REG_CL = 2,
+ CV_REG_DL = 3,
+ CV_REG_BL = 4,
+ CV_REG_AH = 5,
+ CV_REG_CH = 6,
+ CV_REG_DH = 7,
+ CV_REG_BH = 8,
+ CV_REG_AX = 9,
+ CV_REG_CX = 10,
+ CV_REG_DX = 11,
+ CV_REG_BX = 12,
+ CV_REG_SP = 13,
+ CV_REG_BP = 14,
+ CV_REG_SI = 15,
+ CV_REG_DI = 16,
+ CV_REG_EAX = 17,
+ CV_REG_ECX = 18,
+ CV_REG_EDX = 19,
+ CV_REG_EBX = 20,
+ CV_REG_ESP = 21,
+ CV_REG_EBP = 22,
+ CV_REG_ESI = 23,
+ CV_REG_EDI = 24,
+ CV_REG_EIP = 33,
+
+ //
+ // AMD64 registers
+ //
+ CV_AMD64_AL = 1,
+ CV_AMD64_CL = 2,
+ CV_AMD64_DL = 3,
+ CV_AMD64_BL = 4,
+ CV_AMD64_AH = 5,
+ CV_AMD64_CH = 6,
+ CV_AMD64_DH = 7,
+ CV_AMD64_BH = 8,
+ CV_AMD64_AX = 9,
+ CV_AMD64_CX = 10,
+ CV_AMD64_DX = 11,
+ CV_AMD64_BX = 12,
+ CV_AMD64_SP = 13,
+ CV_AMD64_BP = 14,
+ CV_AMD64_SI = 15,
+ CV_AMD64_DI = 16,
+ CV_AMD64_EAX = 17,
+ CV_AMD64_ECX = 18,
+ CV_AMD64_EDX = 19,
+ CV_AMD64_EBX = 20,
+ CV_AMD64_ESP = 21,
+ CV_AMD64_EBP = 22,
+ CV_AMD64_ESI = 23,
+ CV_AMD64_EDI = 24,
+ CV_AMD64_RIP = 33,
+
+ // Low byte forms of some standard registers
+ CV_AMD64_SIL = 324,
+ CV_AMD64_DIL = 325,
+ CV_AMD64_BPL = 326,
+ CV_AMD64_SPL = 327,
+
+ // 64-bit regular registers
+ CV_AMD64_RAX = 328,
+ CV_AMD64_RBX = 329,
+ CV_AMD64_RCX = 330,
+ CV_AMD64_RDX = 331,
+ CV_AMD64_RSI = 332,
+ CV_AMD64_RDI = 333,
+ CV_AMD64_RBP = 334,
+ CV_AMD64_RSP = 335,
+
+ // 64-bit integer registers with 8-, 16-, and 32-bit forms (B, W, and D)
+ CV_AMD64_R8 = 336,
+ CV_AMD64_R9 = 337,
+ CV_AMD64_R10 = 338,
+ CV_AMD64_R11 = 339,
+ CV_AMD64_R12 = 340,
+ CV_AMD64_R13 = 341,
+ CV_AMD64_R14 = 342,
+ CV_AMD64_R15 = 343,
+
+ CV_AMD64_R8B = 344,
+ CV_AMD64_R9B = 345,
+ CV_AMD64_R10B = 346,
+ CV_AMD64_R11B = 347,
+ CV_AMD64_R12B = 348,
+ CV_AMD64_R13B = 349,
+ CV_AMD64_R14B = 350,
+ CV_AMD64_R15B = 351,
+
+ CV_AMD64_R8W = 352,
+ CV_AMD64_R9W = 353,
+ CV_AMD64_R10W = 354,
+ CV_AMD64_R11W = 355,
+ CV_AMD64_R12W = 356,
+ CV_AMD64_R13W = 357,
+ CV_AMD64_R14W = 358,
+ CV_AMD64_R15W = 359,
+
+ CV_AMD64_R8D = 360,
+ CV_AMD64_R9D = 361,
+ CV_AMD64_R10D = 362,
+ CV_AMD64_R11D = 363,
+ CV_AMD64_R12D = 364,
+ CV_AMD64_R13D = 365,
+ CV_AMD64_R14D = 366,
+ CV_AMD64_R15D = 367,
+
+ //
+ // Register set for ARM64
+ //
+ CV_ARM64_NOREG = CV_REG_NONE,
+
+ // General purpose 32-bit integer registers
+ CV_ARM64_W0 = 10,
+ CV_ARM64_W1 = 11,
+ CV_ARM64_W2 = 12,
+ CV_ARM64_W3 = 13,
+ CV_ARM64_W4 = 14,
+ CV_ARM64_W5 = 15,
+ CV_ARM64_W6 = 16,
+ CV_ARM64_W7 = 17,
+ CV_ARM64_W8 = 18,
+ CV_ARM64_W9 = 19,
+ CV_ARM64_W10 = 20,
+ CV_ARM64_W11 = 21,
+ CV_ARM64_W12 = 22,
+ CV_ARM64_W13 = 23,
+ CV_ARM64_W14 = 24,
+ CV_ARM64_W15 = 25,
+ CV_ARM64_W16 = 26,
+ CV_ARM64_W17 = 27,
+ CV_ARM64_W18 = 28,
+ CV_ARM64_W19 = 29,
+ CV_ARM64_W20 = 30,
+ CV_ARM64_W21 = 31,
+ CV_ARM64_W22 = 32,
+ CV_ARM64_W23 = 33,
+ CV_ARM64_W24 = 34,
+ CV_ARM64_W25 = 35,
+ CV_ARM64_W26 = 36,
+ CV_ARM64_W27 = 37,
+ CV_ARM64_W28 = 38,
+ CV_ARM64_W29 = 39,
+ CV_ARM64_W30 = 40,
+ CV_ARM64_WZR = 41,
+
+ // General purpose 64-bit integer registers
+ CV_ARM64_X0 = 50,
+ CV_ARM64_X1 = 51,
+ CV_ARM64_X2 = 52,
+ CV_ARM64_X3 = 53,
+ CV_ARM64_X4 = 54,
+ CV_ARM64_X5 = 55,
+ CV_ARM64_X6 = 56,
+ CV_ARM64_X7 = 57,
+ CV_ARM64_X8 = 58,
+ CV_ARM64_X9 = 59,
+ CV_ARM64_X10 = 60,
+ CV_ARM64_X11 = 61,
+ CV_ARM64_X12 = 62,
+ CV_ARM64_X13 = 63,
+ CV_ARM64_X14 = 64,
+ CV_ARM64_X15 = 65,
+ CV_ARM64_IP0 = 66,
+ CV_ARM64_IP1 = 67,
+ CV_ARM64_X18 = 68,
+ CV_ARM64_X19 = 69,
+ CV_ARM64_X20 = 70,
+ CV_ARM64_X21 = 71,
+ CV_ARM64_X22 = 72,
+ CV_ARM64_X23 = 73,
+ CV_ARM64_X24 = 74,
+ CV_ARM64_X25 = 75,
+ CV_ARM64_X26 = 76,
+ CV_ARM64_X27 = 77,
+ CV_ARM64_X28 = 78,
+ CV_ARM64_FP = 79,
+ CV_ARM64_LR = 80,
+ CV_ARM64_SP = 81,
+ CV_ARM64_ZR = 82,
+};
+
+char const* const rgBaseType[] =
+{
+ "<user defined>", // btNoType = 0,
+ "void", // btVoid = 1,
+ "char",//char* // btChar = 2,
+ "wchar_t*", // btWChar = 3,
+ "signed char",
+ "unsigned char",
+ "int", // btInt = 6,
+ "unsigned int", // btUInt = 7,
+ "float", // btFloat = 8,
+ "<BCD>", // btBCD = 9,
+ "bool", // btBool = 10,
+ "short",
+ "unsigned short",
+ "long", // btLong = 13,
+ "unsigned long", // btULong = 14,
+ "int8",
+ "int16",
+ "int32",
+ "int64",
+ "int128",
+ "uint8",
+ "uint16",
+ "uint32",
+ "uint64",
+ "uint128",
+ "<currency>", // btCurrency = 25,
+ "<date>", // btDate = 26,
+ "VARIANT", // btVariant = 27,
+ "<complex>", // btComplex = 28,
+ "<bit>", // btBit = 29,
+ "BSTR", // btBSTR = 30,
+ "HRESULT" // btHresult = 31
+};
+
+struct SymbolPair
+{
+ SymbolPair(DWORD type, DWORD_PTR offset)
+ {
+ _type = type;
+ _offset = offset;
+ }
+
+ bool operator==(SymbolPair const& other) const = default;
+ std::strong_ordering operator<=>(SymbolPair const& other) const = default;
+
+ DWORD _type;
+ DWORD_PTR _offset;
+};
+typedef std::set<SymbolPair> SymbolPairs;
+
+struct SymbolDetail
+{
+ SymbolDetail() : Prefix(), Type(), Suffix(), Name(), Value(), Logged(false), HasChildren(false) {}
+
+ std::string ToString();
+
+ bool empty() const
+ {
+ return Value.empty() && !HasChildren;
+ }
+
+ std::string Prefix;
+ std::string Type;
+ std::string Suffix;
+ std::string Name;
+ std::string Value;
+ bool Logged;
+ bool HasChildren;
+};
+
+class TC_COMMON_API WheatyExceptionReport
+{
+ public:
+
+ WheatyExceptionReport();
+ ~WheatyExceptionReport();
+
+ // entry point where control comes on an unhandled exception
+ static LONG WINAPI WheatyUnhandledExceptionFilter(
+ PEXCEPTION_POINTERS pExceptionInfo);
+
+ static void __cdecl WheatyCrtHandler(wchar_t const* expression, wchar_t const* function, wchar_t const* file, unsigned int line, uintptr_t pReserved);
+
+ static void printTracesForAllThreads(bool);
+ private:
+ // where report info is extracted and generated
+ static void GenerateExceptionReport(PEXCEPTION_POINTERS pExceptionInfo);
+ static void PrintSystemInfo();
+ static BOOL _GetWindowsVersion(TCHAR* szVersion, DWORD cntMax);
+ static BOOL _GetWindowsVersionFromWMI(TCHAR* szVersion, DWORD cntMax);
+ static BOOL _GetProcessorName(TCHAR* sProcessorName, DWORD maxcount);
+
+ // Helper functions
+ static LPCTSTR GetExceptionString(DWORD dwCode);
+ static BOOL GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len,
+ DWORD& section, DWORD_PTR& offset);
+
+ static void WriteStackDetails(PCONTEXT pContext, bool bWriteVariables, HANDLE pThreadHandle);
+
+ struct EnumerateSymbolsCallbackContext
+ {
+ LPSTACKFRAME64 sf;
+ PCONTEXT context;
+ };
+
+ static BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO, ULONG, PVOID);
+
+ static bool FormatSymbolValue(PSYMBOL_INFO, EnumerateSymbolsCallbackContext*);
+
+ static void DumpTypeIndex(DWORD64, DWORD, DWORD_PTR, bool &, char const*, char const*, bool, bool);
+
+ static void FormatOutputValue(char * pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress, size_t bufferSize, size_t countOverride = 0);
+
+ static BasicType GetBasicType(DWORD typeIndex, DWORD64 modBase);
+ static DWORD_PTR DereferenceUnsafePointer(DWORD_PTR address);
+
+ static int __cdecl Log(const TCHAR * format, ...);
+
+ static bool StoreSymbol(DWORD type , DWORD_PTR offset);
+ static void ClearSymbols();
+
+ static Optional<DWORD_PTR> GetIntegerRegisterValue(PCONTEXT context, ULONG registerId);
+
+ // Variables used by the class
+ static TCHAR m_szLogFileName[MAX_PATH];
+ static TCHAR m_szDumpFileName[MAX_PATH];
+ static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter;
+ static _invalid_parameter_handler m_previousCrtHandler;
+ static FILE* m_hReportFile;
+ static HANDLE m_hDumpFile;
+ static HANDLE m_hProcess;
+ static SymbolPairs symbols;
+ static std::stack<SymbolDetail> symbolDetails;
+ static bool alreadyCrashed;
+ static std::mutex alreadyCrashedLock;
+ typedef NTSTATUS(NTAPI* pRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation);
+ static pRtlGetVersion RtlGetVersion;
+
+ static void PushSymbolDetail();
+ static void PopSymbolDetail();
+ static void PrintSymbolDetail();
+
+};
+
+#define INIT_CRASH_HANDLER() \
+ __pragma(warning(push)) \
+ __pragma(warning(disable:4073)) /* C4073: initializers put in library initialization area */ \
+ __pragma(init_seg(lib)) \
+ WheatyExceptionReport g_WheatyExceptionReport; \
+ __pragma(warning(pop))
+
+#endif // _WHEATYEXCEPTIONREPORT_