TrinityCore
Loading...
Searching...
No Matches
WheatyExceptionReport.cpp
Go to the documentation of this file.
1//==========================================
2// Matt Pietrek
3// MSDN Magazine, 2002
4// FILE: WheatyExceptionReport.CPP
5//==========================================
7#include "Errors.h"
8#include "GitRevision.h"
9#include "Memory.h"
10#include <algorithm>
11#include <array>
12#include <charconv>
13#include <stdexcept>
14#include <utility>
15
16#ifdef __clang__
17// clang-cl doesn't have these hardcoded types available, correct ehdata_forceinclude.h that relies on it
18#define _ThrowInfo ThrowInfo
19#endif
20
21#include <comdef.h>
22#include <crtdbg.h>
23#include <ehdata.h>
24#include <rttidata.h>
25#include <tchar.h>
26#include <tlhelp32.h>
27#include <WbemIdl.h>
28
29#define CrashFolder _T("Crashes")
30#pragma comment(linker, "/DEFAULTLIB:dbghelp.lib")
31#pragma comment(linker, "/DEFAULTLIB:wbemuuid.lib")
32
33#ifdef _UNICODE
34#define PRSTRc "S" // format specifier for char* strings
35#define PRSTRw "s" // format specifier for wchar_t* strings
36#else
37#define PRSTRc "s" // format specifier for char* strings
38#define PRSTRw "S" // format specifier for wchar_t* strings
39#endif
40
41inline LPTSTR ErrorMessage(DWORD dw)
42{
43 LPVOID lpMsgBuf;
44 DWORD formatResult = FormatMessage(
45 FORMAT_MESSAGE_ALLOCATE_BUFFER |
46 FORMAT_MESSAGE_FROM_SYSTEM,
47 nullptr,
48 dw,
49 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
50 (LPTSTR) &lpMsgBuf,
51 0, nullptr);
52 if (formatResult != 0)
53 return (LPTSTR)lpMsgBuf;
54 else
55 {
56 LPTSTR msgBuf = (LPTSTR)LocalAlloc(LPTR, 30);
57 _sntprintf(msgBuf, 30, _T("Unknown error: %d"), (int)dw);
58 return msgBuf;
59 }
60
61}
62
63//============================== Global Variables =============================
64namespace
65{
66WheatyExceptionReport* g_WheatyExceptionReport;
67}
68
69//============================== Class Methods =============================
70
72 m_tempPathBuffer(static_cast<TCHAR*>(HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, m_tempPathBufferChars * sizeof(TCHAR)))),
73 m_previousFilter(SetUnhandledExceptionFilter(WheatyUnhandledExceptionFilter)),
74 m_previousCrtHandler(_set_invalid_parameter_handler(WheatyCrtHandler)),
75 m_reportFile(nullptr),
76 m_dumpFile(),
77 m_process(GetCurrentProcess()),
78 m_alreadyCrashed(false),
79 m_alreadyCrashedLock(SRWLOCK_INIT),
80 RtlGetVersion((pRtlGetVersion)GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlGetVersion"))
81{
82 if (!IsDebuggerPresent())
83 {
84 _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
85 _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
86 _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
87 _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
88 }
89
90 if (std::exchange(g_WheatyExceptionReport, this) != nullptr)
91 throw std::logic_error("Only one instance of WheatyExceptionReport can exist");
92}
93
94//============
95// Destructor
96//============
98{
100 SetUnhandledExceptionFilter(m_previousFilter);
102 _set_invalid_parameter_handler(m_previousCrtHandler);
103 ClearSymbols();
104 HeapFree(GetProcessHeap(), 0, m_tempPathBuffer);
105
106 g_WheatyExceptionReport = nullptr;
107}
108
109//===========================================================
110// Entry point where control comes on an unhandled exception
111//===========================================================
113PEXCEPTION_POINTERS pExceptionInfo)
114{
115 return g_WheatyExceptionReport->UnhandledExceptionFilterImpl(pExceptionInfo);
116}
117
118LONG WheatyExceptionReport::UnhandledExceptionFilterImpl(PEXCEPTION_POINTERS pExceptionInfo) noexcept
119{
120 AcquireSRWLockExclusive(&m_alreadyCrashedLock);
121 auto guard = Trinity::make_unique_ptr_with_deleter<&ReleaseSRWLockExclusive>(&m_alreadyCrashedLock);
122 // Handle only 1 exception in the whole process lifetime
123 if (m_alreadyCrashed)
124 return EXCEPTION_EXECUTE_HANDLER;
125
126 m_alreadyCrashed = true;
127
128 auto prepareCrashFolder = [this]<typename Char>(Char const* path) -> TCHAR*
129 {
130 if (!path)
131 return nullptr;
132
133 std::basic_string_view<Char> processPath = path;
134
135 auto filenameSeparator = processPath.rfind('\\');
136 if (filenameSeparator == std::basic_string_view<Char>::npos)
137 return nullptr;
138
139 std::basic_string_view<Char> processName = processPath.substr(filenameSeparator + 1);
140 if (processName.empty())
141 return nullptr;
142
143 processPath.remove_suffix(processName.length());
144
145 TCHAR* buffer = m_tempPathBuffer;
146
147 // {exe_path}\Crashes
148 if constexpr (std::is_same_v<wchar_t, Char>)
149 buffer += _stprintf_s(buffer, m_tempPathBufferChars, _T("%.*" PRSTRw) CrashFolder, STRING_VIEW_FMT_ARG(processPath));
150 else
151 buffer += _stprintf_s(buffer, m_tempPathBufferChars, _T("%.*" PRSTRc) CrashFolder, STRING_VIEW_FMT_ARG(processPath));
152
153 if (!CreateDirectory(m_tempPathBuffer, nullptr) && GetLastError() != ERROR_ALREADY_EXISTS)
154 return nullptr;
155
156 SYSTEMTIME systime;
157 GetLocalTime(&systime);
158
159 // {exe_path}\Crashes\{commit_hash}_{exe_nam}]_[{year}_{month}_{day}_{hour}_{minute}_{second}].
160 if constexpr (std::is_same_v<wchar_t, Char>)
161 buffer += _stprintf_s(buffer, m_tempPathBufferChars - (buffer - m_tempPathBuffer), _T("\\%" PRSTRc "_%.*" PRSTRw "_[%u_%u_%u_%u_%u_%u]."),
162 GitRevision::GetHash(), STRING_VIEW_FMT_ARG(processName), systime.wYear, systime.wMonth, systime.wDay, systime.wHour, systime.wMinute, systime.wSecond);
163 else
164 buffer += _stprintf_s(buffer, m_tempPathBufferChars - (buffer - m_tempPathBuffer), _T("\\%" PRSTRc "_%.*" PRSTRc "_[%u_%u_%u_%u_%u_%u]."),
165 GitRevision::GetHash(), STRING_VIEW_FMT_ARG(processName), systime.wYear, systime.wMonth, systime.wDay, systime.wHour, systime.wMinute, systime.wSecond);
166
167 return buffer;
168 };
169
170 TCHAR* crashPath = nullptr;
171
172 // which of _pgmptr/_wpgmptr is filled depends if main or wmain is used, not _UNICODE define
173 if (char const* processName = _pgmptr)
174 crashPath = prepareCrashFolder(processName);
175 else if (wchar_t const* wprocessName = _wpgmptr)
176 crashPath = prepareCrashFolder(wprocessName);
177
178 if (!crashPath)
179 return 0;
180
181 _tcscpy_s(crashPath, m_tempPathBufferChars - (crashPath - m_tempPathBuffer), _T("dmp"));
182 m_dumpFile = CreateFile(m_tempPathBuffer,
183 GENERIC_WRITE,
184 0,
185 nullptr,
186 OPEN_ALWAYS,
187 FILE_FLAG_WRITE_THROUGH,
188 nullptr);
189
190 if (m_dumpFile && m_dumpFile != INVALID_HANDLE_VALUE)
191 {
192 MINIDUMP_EXCEPTION_INFORMATION info;
193 info.ClientPointers = FALSE;
194 info.ExceptionPointers = pExceptionInfo;
195 info.ThreadId = GetCurrentThreadId();
196
197 MINIDUMP_USER_STREAM additionalStream = {};
198 MINIDUMP_USER_STREAM_INFORMATION additionalStreamInfo = {};
199
200 if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ASSERTION_FAILURE && pExceptionInfo->ExceptionRecord->NumberParameters > 0)
201 {
202 additionalStream.Type = CommentStreamA;
203 additionalStream.Buffer = reinterpret_cast<PVOID>(pExceptionInfo->ExceptionRecord->ExceptionInformation[0]);
204 additionalStream.BufferSize = strlen(reinterpret_cast<char const*>(pExceptionInfo->ExceptionRecord->ExceptionInformation[0])) + 1;
205
206 additionalStreamInfo.UserStreamArray = &additionalStream;
207 additionalStreamInfo.UserStreamCount = 1;
208 }
209
210 MiniDumpWriteDump(m_process, GetCurrentProcessId(),
211 m_dumpFile, MiniDumpWithIndirectlyReferencedMemory, &info, &additionalStreamInfo, nullptr);
212
213 CloseHandle(m_dumpFile);
214 }
215
216 _tcscpy_s(crashPath, m_tempPathBufferChars - (crashPath - m_tempPathBuffer), _T("txt"));
217 m_reportFile = _tfopen(m_tempPathBuffer, _T("wb"));
218
219 if (m_reportFile)
220 {
221 GenerateExceptionReport(pExceptionInfo);
222
223 fclose(m_reportFile);
224 m_reportFile = nullptr;
225 }
226
227 if (m_previousFilter)
228 return m_previousFilter(pExceptionInfo);
229 else
230 return EXCEPTION_EXECUTE_HANDLER/*EXCEPTION_CONTINUE_SEARCH*/;
231}
232
233void __cdecl WheatyExceptionReport::WheatyCrtHandler(wchar_t const* /*expression*/, wchar_t const* /*function*/, wchar_t const* /*file*/, unsigned int /*line*/, uintptr_t /*pReserved*/)
234{
235 RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, nullptr);
236}
237
238BOOL WheatyExceptionReport::_GetProcessorName(TCHAR* sProcessorName, DWORD maxcount)
239{
240 if (!sProcessorName)
241 return FALSE;
242
243 HKEY hKey;
244 LONG lRet;
245 lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"),
246 0, KEY_QUERY_VALUE, &hKey);
247 if (lRet != ERROR_SUCCESS)
248 return FALSE;
249 TCHAR szTmp[2048];
250 DWORD cntBytes = sizeof(szTmp);
251 lRet = ::RegQueryValueEx(hKey, _T("ProcessorNameString"), nullptr, nullptr,
252 (LPBYTE)szTmp, &cntBytes);
253 if (lRet != ERROR_SUCCESS)
254 return FALSE;
255 ::RegCloseKey(hKey);
256 sProcessorName[0] = '\0';
257 // Skip spaces
258 TCHAR* psz = szTmp;
259 while (_istspace(*psz))
260 ++psz;
261 _tcsncpy(sProcessorName, psz, maxcount);
262 return TRUE;
263}
264
265template<size_t size>
266void ToTchar(wchar_t const* src, TCHAR (&dst)[size])
267{
268 if constexpr (std::is_same_v<TCHAR, char>)
269 ::wcstombs_s(nullptr, dst, src, _TRUNCATE);
270 else
271 ::wcscpy_s(dst, size, src);
272}
273
274BOOL WheatyExceptionReport::_GetWindowsVersion(TCHAR* szVersion, DWORD cntMax)
275{
276 *szVersion = _T('\0');
277
278 if (_GetWindowsVersionFromWMI(szVersion, cntMax))
279 return TRUE;
280
281 // Try calling GetVersionEx using the OSVERSIONINFOEX structure.
282 // If that fails, try using the OSVERSIONINFO structure.
283 RTL_OSVERSIONINFOEXW osvi = { };
284 osvi.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
285 NTSTATUS bVersionEx = RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi);
286 if (FAILED(bVersionEx))
287 {
288 osvi.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW);
289 if (!RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi))
290 return FALSE;
291 }
292
293 TCHAR szCSDVersion[256];
294 ToTchar(osvi.szCSDVersion, szCSDVersion);
295
296 TCHAR wszTmp[128];
297 switch (osvi.dwPlatformId)
298 {
299 // Windows NT product family.
300 case VER_PLATFORM_WIN32_NT:
301 {
302 #if WINVER < 0x0500
303 BYTE suiteMask = osvi.wReserved[0];
304 BYTE productType = osvi.wReserved[1];
305 #else
306 WORD suiteMask = osvi.wSuiteMask;
307 BYTE productType = osvi.wProductType;
308 #endif // WINVER < 0x0500
309
310 // Test for the specific product family.
311 if (osvi.dwMajorVersion == 10)
312 {
313 if (productType == VER_NT_WORKSTATION)
314 _tcsncat(szVersion, _T("Windows 10 "), cntMax);
315 else
316 _tcsncat(szVersion, _T("Windows Server 2016 "), cntMax);
317 }
318 else if (osvi.dwMajorVersion == 6)
319 {
320 if (productType == VER_NT_WORKSTATION)
321 {
322 if (osvi.dwMinorVersion == 3)
323 _tcsncat(szVersion, _T("Windows 8.1 "), cntMax);
324 else if (osvi.dwMinorVersion == 2)
325 _tcsncat(szVersion, _T("Windows 8 "), cntMax);
326 else if (osvi.dwMinorVersion == 1)
327 _tcsncat(szVersion, _T("Windows 7 "), cntMax);
328 else
329 _tcsncat(szVersion, _T("Windows Vista "), cntMax);
330 }
331 else if (osvi.dwMinorVersion == 3)
332 _tcsncat(szVersion, _T("Windows Server 2012 R2 "), cntMax);
333 else if (osvi.dwMinorVersion == 2)
334 _tcsncat(szVersion, _T("Windows Server 2012 "), cntMax);
335 else if (osvi.dwMinorVersion == 1)
336 _tcsncat(szVersion, _T("Windows Server 2008 R2 "), cntMax);
337 else
338 _tcsncat(szVersion, _T("Windows Server 2008 "), cntMax);
339 }
340 else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
341 _tcsncat(szVersion, _T("Microsoft Windows Server 2003 "), cntMax);
342 else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
343 _tcsncat(szVersion, _T("Microsoft Windows XP "), cntMax);
344 else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
345 _tcsncat(szVersion, _T("Microsoft Windows 2000 "), cntMax);
346 else if (osvi.dwMajorVersion <= 4)
347 _tcsncat(szVersion, _T("Microsoft Windows NT "), cntMax);
348
349 // Test for specific product on Windows NT 4.0 SP6 and later.
350 if (bVersionEx >= 0)
351 {
352 // Test for the workstation type.
353 if (productType == VER_NT_WORKSTATION)
354 {
355 if (osvi.dwMajorVersion == 4)
356 _tcsncat(szVersion, _T("Workstation 4.0 "), cntMax);
357 else if (suiteMask & VER_SUITE_PERSONAL)
358 _tcsncat(szVersion, _T("Home Edition "), cntMax);
359 else if (suiteMask & VER_SUITE_EMBEDDEDNT)
360 _tcsncat(szVersion, _T("Embedded "), cntMax);
361 else
362 _tcsncat(szVersion, _T("Professional "), cntMax);
363 }
364 // Test for the server type.
365 else if (productType == VER_NT_SERVER)
366 {
367 if (osvi.dwMajorVersion == 6 || osvi.dwMajorVersion == 10)
368 {
369 if (suiteMask & VER_SUITE_SMALLBUSINESS_RESTRICTED)
370 _tcsncat(szVersion, _T("Essentials "), cntMax);
371 else if (suiteMask & VER_SUITE_DATACENTER)
372 _tcsncat(szVersion, _T("Datacenter "), cntMax);
373 else if (suiteMask & VER_SUITE_ENTERPRISE)
374 _tcsncat(szVersion, _T("Enterprise "), cntMax);
375 else
376 _tcsncat(szVersion, _T("Standard "), cntMax);
377 }
378 else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
379 {
380 if (suiteMask & VER_SUITE_DATACENTER)
381 _tcsncat(szVersion, _T("Datacenter Edition "), cntMax);
382 else if (suiteMask & VER_SUITE_ENTERPRISE)
383 _tcsncat(szVersion, _T("Enterprise Edition "), cntMax);
384 else if (suiteMask == VER_SUITE_BLADE)
385 _tcsncat(szVersion, _T("Web Edition "), cntMax);
386 else
387 _tcsncat(szVersion, _T("Standard Edition "), cntMax);
388 }
389 else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
390 {
391 if (suiteMask & VER_SUITE_DATACENTER)
392 _tcsncat(szVersion, _T("Datacenter Server "), cntMax);
393 else if (suiteMask & VER_SUITE_ENTERPRISE)
394 _tcsncat(szVersion, _T("Advanced Server "), cntMax);
395 else
396 _tcsncat(szVersion, _T("Server "), cntMax);
397 }
398 else // Windows NT 4.0
399 {
400 if (suiteMask & VER_SUITE_ENTERPRISE)
401 _tcsncat(szVersion, _T("Server 4.0, Enterprise Edition "), cntMax);
402 else
403 _tcsncat(szVersion, _T("Server 4.0 "), cntMax);
404 }
405 }
406 }
407
408 // Display service pack (if any) and build number.
409 if (osvi.dwMajorVersion == 4 && _tcsicmp(szCSDVersion, _T("Service Pack 6")) == 0)
410 {
411 HKEY hKey;
412 LONG lRet;
413
414 // Test for SP6 versus SP6a.
415 lRet = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009"), 0, KEY_QUERY_VALUE, &hKey);
416 if (lRet == ERROR_SUCCESS)
417 {
418 _stprintf_s(wszTmp, _T("Service Pack 6a (Version %d.%d, Build %d)"),
419 osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
420 _tcsncat(szVersion, wszTmp, cntMax);
421 }
422 else // Windows NT 4.0 prior to SP6a
423 {
424 _stprintf_s(wszTmp, _T("%s (Version %d.%d, Build %d)"),
425 szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
426 _tcsncat(szVersion, wszTmp, cntMax);
427 }
428 ::RegCloseKey(hKey);
429 }
430 else // Windows NT 3.51 and earlier or Windows 2000 and later
431 {
432 if (!_tcslen(szCSDVersion))
433 _stprintf_s(wszTmp, _T("(Version %d.%d, Build %d)"),
434 osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
435 else
436 _stprintf_s(wszTmp, _T("%s (Version %d.%d, Build %d)"),
437 szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
438 _tcsncat(szVersion, wszTmp, cntMax);
439 }
440 break;
441 }
442 default:
443 _stprintf_s(wszTmp, _T("%s (Version %d.%d, Build %d)"),
444 szCSDVersion, osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);
445 _tcsncat(szVersion, wszTmp, cntMax);
446 break;
447 }
448
449 return TRUE;
450}
451
452template <std::derived_from<IUnknown> T>
453using com_unique_ptr_deleter = decltype(Trinity::unique_ptr_deleter<T*, [](T* ptr) { ptr->Release(); }>());
454
455template <std::derived_from<IUnknown> T>
456using com_unique_ptr = std::unique_ptr<T, com_unique_ptr_deleter<T>>;
457
458BOOL WheatyExceptionReport::_GetWindowsVersionFromWMI(TCHAR* szVersion, DWORD cntMax) noexcept
459{
460 // Step 1: --------------------------------------------------
461 // Initialize COM. ------------------------------------------
462 HRESULT hres = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
463 if (FAILED(hres))
464 return FALSE;
465
466 auto com = Trinity::make_unique_ptr_with_deleter<[](void*) { CoUninitialize(); }>(&hres);
467
468 // Step 2: --------------------------------------------------
469 // Set general COM security levels --------------------------
470 hres = CoInitializeSecurity(
471 nullptr,
472 -1, // COM authentication
473 nullptr, // Authentication services
474 nullptr, // Reserved
475 RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
476 RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
477 nullptr, // Authentication info
478 EOAC_NONE, // Additional capabilities
479 nullptr // Reserved
480 );
481
482 if (FAILED(hres))
483 return FALSE;
484
485 // Step 3: ---------------------------------------------------
486 // Obtain the initial locator to WMI -------------------------
488 {
489 IWbemLocator* tmp = nullptr;
490 HRESULT hres = CoCreateInstance(
491 CLSID_WbemLocator,
492 nullptr,
493 CLSCTX_INPROC_SERVER,
494 IID_IWbemLocator,
495 reinterpret_cast<LPVOID*>(&tmp));
496
497 return SUCCEEDED(hres) ? tmp : nullptr;
498 }());
499
500 if (!loc)
501 return FALSE;
502
503 // Step 4: -----------------------------------------------------
504 // Connect to the root\cimv2 namespace with
505 // the current user and obtain pointer pSvc
506 // to make IWbemServices calls.
508 {
509 IWbemServices* tmp = nullptr;
510 HRESULT hres = loc->ConnectServer(
511 bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
512 nullptr, // User name. NULL = current user
513 nullptr, // User password. NULL = current
514 nullptr, // Locale. NULL indicates current
515 WBEM_FLAG_CONNECT_USE_MAX_WAIT, // Security flags.
516 nullptr, // Authority (for example, Kerberos)
517 nullptr, // Context object
518 &tmp // pointer to IWbemServices proxy
519 );
520
521 return SUCCEEDED(hres) ? tmp : nullptr;
522 }());
523
524 if (!svc)
525 return FALSE;
526
527 // Step 5: --------------------------------------------------
528 // Set security levels on the proxy -------------------------
529 hres = CoSetProxyBlanket(
530 svc.get(), // Indicates the proxy to set
531 RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
532 RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
533 nullptr, // Server principal name
534 RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
535 RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
536 nullptr, // client identity
537 EOAC_NONE // proxy capabilities
538 );
539
540 if (FAILED(hres))
541 return FALSE;
542
543 // Step 6: --------------------------------------------------
544 // Use the IWbemServices pointer to make requests of WMI ----
545
546 // For example, get the name of the operating system
547 com_unique_ptr<IEnumWbemClassObject> queryResult([&]() noexcept
548 {
549 IEnumWbemClassObject* tmp = nullptr;
550 HRESULT hres = svc->ExecQuery(
551 bstr_t("WQL"),
552 bstr_t("SELECT Caption, CSDVersion FROM Win32_OperatingSystem"),
553 WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
554 nullptr,
555 &tmp);
556
557 return SUCCEEDED(hres) ? tmp : nullptr;
558 }());
559
560 BOOL result = FALSE;
561 // Step 7: -------------------------------------------------
562 // Get the data from the query in step 6 -------------------
563 if (queryResult)
564 {
565 do
566 {
567 auto [fields, rows] = [&]
568 {
569 IWbemClassObject* fields = nullptr;
570 ULONG rows = 0;
571 HRESULT hres = queryResult->Next(WBEM_INFINITE, 1, &fields, &rows);
572 using wbem_class_object_ptr = com_unique_ptr<IWbemClassObject>;
573 return SUCCEEDED(hres) && rows
574 ? std::pair(wbem_class_object_ptr(fields), rows)
575 : std::pair(wbem_class_object_ptr(), ULONG(0));
576 }();
577
578 if (!fields || !rows)
579 break;
580
581 VARIANT field;
582 VariantInit(&field);
583 fields->Get(L"Caption", 0, &field, nullptr, nullptr);
584 TCHAR buf[256] = { };
585 ToTchar(field.bstrVal, buf);
586 _tcsncat(szVersion, buf, cntMax);
587 VariantClear(&field);
588
589 fields->Get(L"CSDVersion", 0, &field, nullptr, nullptr);
590 if (field.vt == VT_BSTR)
591 {
592 _tcsncat(szVersion, _T(" "), cntMax);
593 memset(buf, 0, sizeof(buf));
594 ToTchar(field.bstrVal, buf);
595 if (_tcslen(buf))
596 _tcsncat(szVersion, buf, cntMax);
597 }
598 VariantClear(&field);
599
600 result = TRUE;
601 } while (true);
602 }
603
604 return result;
605}
606
608{
609 SYSTEM_INFO SystemInfo;
610 ::GetSystemInfo(&SystemInfo);
611
612 MEMORYSTATUS MemoryStatus;
613 MemoryStatus.dwLength = sizeof (MEMORYSTATUS);
614 ::GlobalMemoryStatus(&MemoryStatus);
615 TCHAR sString[1024];
616 Log(_T("//=====================================================\r\n"));
617 if (_GetProcessorName(sString, std::size(sString)))
618 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"),
619 sString, SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400);
620 else
621 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"),
622 SystemInfo.dwNumberOfProcessors, MemoryStatus.dwTotalPhys/0x400, MemoryStatus.dwAvailPhys/0x400, MemoryStatus.dwTotalPageFile/0x400);
623
624 if (_GetWindowsVersion(sString, std::size(sString)))
625 Log(_T("\r\n*** Operation System ***\r\n%s\r\n"), sString);
626 else
627 Log(_T("\r\n*** Operation System:\r\n<unknown>\r\n"));
628}
629
630//===========================================================================
632{
633 THREADENTRY32 te32;
634
635 DWORD dwOwnerPID = GetProcessId(m_process);
636 DWORD dwCurrentTID = GetCurrentThreadId();
637 // Take a snapshot of all running threads
638 HANDLE hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
639 if (hThreadSnap == INVALID_HANDLE_VALUE)
640 return;
641
642 // Fill in the size of the structure before using it.
643 te32.dwSize = sizeof(THREADENTRY32);
644
645 // Retrieve information about the first thread,
646 // and exit if unsuccessful
647 if (!Thread32First(hThreadSnap, &te32))
648 {
649 CloseHandle(hThreadSnap); // Must clean up the
650 // snapshot object!
651 return;
652 }
653
654 // Now walk the thread list of the system,
655 // and display information about each thread
656 // associated with the specified process
657 do
658 {
659 if (te32.th32OwnerProcessID == dwOwnerPID && te32.th32ThreadID != dwCurrentTID)
660 {
661 CONTEXT context;
662 context.ContextFlags = 0xffffffff;
663 HANDLE threadHandle = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, false, te32.th32ThreadID);
664 if (threadHandle)
665 {
666 if (GetThreadContext(threadHandle, &context))
667 WriteStackDetails(&context, bWriteVariables, threadHandle);
668 CloseHandle(threadHandle);
669 }
670 }
671 } while (Thread32Next(hThreadSnap, &te32));
672
673// Don't forget to clean up the snapshot object.
674 CloseHandle(hThreadSnap);
675}
676
677//===========================================================================
678// Open the report file, and write the desired information to it. Called by
679// WheatyUnhandledExceptionFilter
680//===========================================================================
682PEXCEPTION_POINTERS pExceptionInfo)
683{
684 SYSTEMTIME systime;
685 GetLocalTime(&systime);
686
687 // Start out with a banner
688 Log(_T("Revision: %" PRSTRc "\r\n"), GitRevision::GetFullVersion());
689 Log(_T("Date %u:%u:%u. Time %u:%u \r\n"), systime.wDay, systime.wMonth, systime.wYear, systime.wHour, systime.wMinute);
690 PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;
691
693 // First print information about the type of fault
694 Log(_T("\r\n//=====================================================\r\n"));
695 Log(_T("Exception code: %08X %s\r\n"),
696 pExceptionRecord->ExceptionCode,
697 GetExceptionString(pExceptionRecord->ExceptionCode));
698 if (pExceptionRecord->ExceptionCode == EXCEPTION_ASSERTION_FAILURE && pExceptionRecord->NumberParameters >= 2)
699 {
700 pExceptionRecord->ExceptionAddress = reinterpret_cast<PVOID>(pExceptionRecord->ExceptionInformation[1]);
701 Log(_T("Assertion message: %" PRSTRc "\r\n"), pExceptionRecord->ExceptionInformation[0]);
702 }
703
704 // Now print information about where the fault occured
705 TCHAR* szFaultingModule = m_tempPathBuffer;
706 DWORD section;
707 DWORD_PTR offset;
708 GetLogicalAddress(pExceptionRecord->ExceptionAddress,
709 szFaultingModule,
711 section, offset);
712
713#if defined(_M_IX86) || defined(_M_ARM)
714 Log(_T("Fault address: %08X %02X:%08X %s\r\n"),
715 pExceptionRecord->ExceptionAddress,
716 section, offset, szFaultingModule);
717#endif
718#if defined(_M_X64) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined (_M_ARM64EC)
719 Log(_T("Fault address: %016I64X %02X:%016I64X %s\r\n"),
720 pExceptionRecord->ExceptionAddress,
721 section, offset, szFaultingModule);
722#endif
723
724 PCONTEXT pCtx = pExceptionInfo->ContextRecord;
725
726 // Show the registers
727#ifdef _M_IX86 // X86 Only!
728 Log(_T("\r\nRegisters:\r\n"));
729
730 Log(_T("EAX:%08X\r\nEBX:%08X\r\nECX:%08X\r\nEDX:%08X\r\nESI:%08X\r\nEDI:%08X\r\n")
731 , pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx,
732 pCtx->Esi, pCtx->Edi);
733
734 Log(_T("CS:EIP:%04X:%08X\r\n"), pCtx->SegCs, pCtx->Eip);
735 Log(_T("SS:ESP:%04X:%08X EBP:%08X\r\n"),
736 pCtx->SegSs, pCtx->Esp, pCtx->Ebp);
737 Log(_T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"),
738 pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs);
739 Log(_T("Flags:%08X\r\n"), pCtx->EFlags);
740#endif
741
742#ifdef _M_X64
743 Log(_T("\r\nRegisters:\r\n"));
744 Log(_T("RAX:%016I64X\r\nRBX:%016I64X\r\nRCX:%016I64X\r\nRDX:%016I64X\r\nRSI:%016I64X\r\nRDI:%016I64X\r\n")
745 _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")
746 , pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx,
747 pCtx->Rsi, pCtx->Rdi, pCtx->R8, pCtx->R9, pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13, pCtx->R14, pCtx->R15);
748 Log(_T("CS:RIP:%04X:%016I64X\r\n"), pCtx->SegCs, pCtx->Rip);
749 Log(_T("SS:RSP:%04X:%016I64X RBP:%08X\r\n"),
750 pCtx->SegSs, pCtx->Rsp, pCtx->Rbp);
751 Log(_T("DS:%04X ES:%04X FS:%04X GS:%04X\r\n"),
752 pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs);
753 Log(_T("Flags:%08X\r\n"), pCtx->EFlags);
754#endif
755
756#ifdef _M_ARM64
757 Log(_T("\r\nRegisters:\r\n"));
758 Log(_T("X0:%016I64X\r\nX1:%016I64X\r\nX2:%016I64X\r\nX3:%016I64X\r\nX4:%016I64X\r\nX5:%016I64X\r\n"),
759 _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"),
760 _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"),
761 _T("X22:%016I64X\r\nX23:%016I64X\r\nX24:%016I64X\r\nX25:%016I64X\r\nX26:%016I64X\r\nX27:%016I64X\r\nX28:%016I64X\r\n"),
762 pCtx->X0, pCtx->X1, pCtx->X2, pCtx->X3, pCtx->X4, pCtx->X5,
763 pCtx->X6, pCtx->X7, pCtx->X8, pCtx->X9, pCtx->X10, pCtx->X11, pCtx->X12, pCtx->X13,
764 pCtx->X14, pCtx->X15, pCtx->X16, pCtx->X17, pCtx->X18, pCtx->X19, pCtx->X20, pCtx->X21,
765 pCtx->X22, pCtx->X23, pCtx->X24, pCtx->X25, pCtx->X26, pCtx->X27, pCtx->X28);
766 Log(_T("LR:%016I64X\r\n"), pCtx->Lr);
767 Log(_T("PC:%016I64X\r\n"), pCtx->Pc);
768 Log(_T("SP:%016I64X FP:%016I64X\r\n"), pCtx->Sp, pCtx->Fp);
769 Log(_T("Flags:%08X\r\n"), pCtx->Cpsr);
770#endif
771
772 SymSetOptions(SYMOPT_DEFERRED_LOADS);
773
774 // Initialize DbgHelp
775 if (!SymInitialize(m_process, nullptr, TRUE))
776 {
777 Log(_T("\r\n"));
778 Log(_T("----\r\n"));
779 Log(_T("SYMBOL HANDLER ERROR (THIS IS NOT THE CRASH ERROR)\r\n\r\n"));
780 Log(_T("Couldn't initialize symbol handler for process when generating crash report\r\n"));
781 Log(_T("Error: %s\r\n"), ErrorMessage(GetLastError()));
782 Log(_T("THE BELOW CALL STACKS MIGHT HAVE MISSING OR INACCURATE FILE/FUNCTION NAMES\r\n\r\n"));
783 Log(_T("----\r\n"));
784 }
785
786 if (pExceptionRecord->ExceptionCode == 0xE06D7363 && pExceptionRecord->NumberParameters >= 2)
787 {
788 PVOID exceptionObject = reinterpret_cast<PVOID>(pExceptionRecord->ExceptionInformation[1]);
789 ThrowInfo const* throwInfo = reinterpret_cast<ThrowInfo const*>(pExceptionRecord->ExceptionInformation[2]);
790#if _EH_RELATIVE_TYPEINFO
791 // When _EH_RELATIVE_TYPEINFO is defined, the pointers need to be retrieved with some pointer math
792 auto resolveExceptionRVA = [pExceptionRecord](int32 rva) -> DWORD_PTR
793 {
794 return rva + (pExceptionRecord->NumberParameters >= 4 ? pExceptionRecord->ExceptionInformation[3] : 0);
795 };
796#else
797 // Otherwise the pointers are already there in the API types
798 auto resolveExceptionRVA = [](void const* input) -> void const* { return input; };
799#endif
800
801 CatchableTypeArray const* catchables = reinterpret_cast<CatchableTypeArray const*>(resolveExceptionRVA(throwInfo->pCatchableTypeArray));
802 CatchableType const* catchable = catchables->nCatchableTypes ? reinterpret_cast<CatchableType const*>(resolveExceptionRVA(catchables->arrayOfCatchableTypes[0])) : nullptr;
803 TypeDescriptor const* exceptionTypeinfo = catchable ? reinterpret_cast<TypeDescriptor const*>(resolveExceptionRVA(catchable->pType)) : nullptr;
804
805 if (exceptionTypeinfo)
806 {
807 void* stdExceptionTypeInfo = []() -> void*
808 {
809 try
810 {
811 std::exception fake;
812 return __RTtypeid(&fake);
813 }
814 catch (...)
815 {
816 return nullptr;
817 }
818 }();
819 std::exception const* exceptionPtr = [](void* object, TypeDescriptor const* typeInfo, void* stdExceptionTypeInfo) -> std::exception const*
820 {
821 try
822 {
823 // real_type descriptor is obtained by parsing throwinfo
824 // equivalent to expression like this
825 // std::exception* e = object;
826 // real_type* r = dynamic_cast<real_type*>(e);
827 // return r;
828 return reinterpret_cast<std::exception const*>(__RTDynamicCast(object, 0, stdExceptionTypeInfo, (void*)typeInfo, false));
829 }
830 catch (...)
831 {
832 return nullptr;
833 }
834 }(exceptionObject, exceptionTypeinfo, stdExceptionTypeInfo);
835
836 // dynamic_cast<type>(variable_that_already_has_that_type) is optimized away by compiler and attempting to call __RTDynamicCast fails for it
837 if (!exceptionPtr && exceptionTypeinfo == stdExceptionTypeInfo)
838 exceptionPtr = reinterpret_cast<std::exception*>(exceptionObject);
839
840 Log(_T("\r\nUncaught C++ exception info:"));
841 if (exceptionPtr)
842 Log(_T(" %s"), exceptionPtr->what());
843
844 Log(_T("\r\n"));
845
846 char undName[MAX_SYM_NAME] = { };
847 if (UnDecorateSymbolName(&exceptionTypeinfo->name[1], &undName[0], MAX_SYM_NAME, UNDNAME_32_BIT_DECODE | UNDNAME_NAME_ONLY | UNDNAME_NO_ARGUMENTS))
848 {
849 char buf[MAX_SYM_NAME + sizeof(SYMBOL_INFO)] = { };
850 PSYMBOL_INFO sym = (PSYMBOL_INFO)&buf[0];
851 sym->SizeOfStruct = sizeof(SYMBOL_INFO);
852 sym->MaxNameLen = MAX_SYM_NAME;
853 if (SymGetTypeFromName(m_process, (ULONG64)GetModuleHandle(nullptr), undName, sym))
854 {
855 sym->Address = pExceptionRecord->ExceptionInformation[1];
856 sym->Flags = 0;
857 char const* variableName = "uncaught_exception";
858 memset(sym->Name, 0, MAX_SYM_NAME);
859 memcpy(sym->Name, variableName, strlen(variableName));
860 FormatSymbolValue(sym, nullptr);
861 }
862 }
863 }
864 }
865
866 CONTEXT trashableContext = *pCtx;
867
868 WriteStackDetails(&trashableContext, false, GetCurrentThread());
870
871 // #ifdef _M_IX86 // X86 Only!
872
873 Log(_T("========================\r\n"));
874 Log(_T("Local Variables And Parameters\r\n"));
875
876 trashableContext = *pCtx;
877 WriteStackDetails(&trashableContext, true, GetCurrentThread());
879
880 SymCleanup(m_process);
881
882 Log(_T("\r\n"));
883}
884
885//======================================================================
886// Given an exception code, returns a pointer to a static string with a
887// description of the exception
888//======================================================================
890{
891 #define EXCEPTION(x) case EXCEPTION_##x: return _T(#x);
892
893 switch (dwCode)
894 {
895 EXCEPTION(ACCESS_VIOLATION)
896 EXCEPTION(DATATYPE_MISALIGNMENT)
897 EXCEPTION(BREAKPOINT)
898 EXCEPTION(SINGLE_STEP)
899 EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
900 EXCEPTION(FLT_DENORMAL_OPERAND)
901 EXCEPTION(FLT_DIVIDE_BY_ZERO)
902 EXCEPTION(FLT_INEXACT_RESULT)
903 EXCEPTION(FLT_INVALID_OPERATION)
904 EXCEPTION(FLT_OVERFLOW)
905 EXCEPTION(FLT_STACK_CHECK)
906 EXCEPTION(FLT_UNDERFLOW)
907 EXCEPTION(INT_DIVIDE_BY_ZERO)
908 EXCEPTION(INT_OVERFLOW)
909 EXCEPTION(PRIV_INSTRUCTION)
910 EXCEPTION(IN_PAGE_ERROR)
911 EXCEPTION(ILLEGAL_INSTRUCTION)
912 EXCEPTION(NONCONTINUABLE_EXCEPTION)
913 EXCEPTION(STACK_OVERFLOW)
914 EXCEPTION(INVALID_DISPOSITION)
915 EXCEPTION(GUARD_PAGE)
916 EXCEPTION(INVALID_HANDLE)
917 case 0xE06D7363: return _T("Unhandled C++ exception");
918 }
919
920 // If not one of the "known" exceptions, try to get the string
921 // from NTDLL.DLL's message table.
922
923 static TCHAR szBuffer[512] = { 0 };
924
925 FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
926 GetModuleHandle(_T("NTDLL.DLL")),
927 dwCode, 0, szBuffer, sizeof(szBuffer), nullptr);
928
929 return szBuffer;
930}
931
932//=============================================================================
933// Given a linear address, locates the module, section, and offset containing
934// that address.
935//
936// Note: the szModule paramater buffer is an output buffer of length specified
937// by the len parameter (in characters!)
938//=============================================================================
940PVOID addr, PTSTR szModule, DWORD len, DWORD& section, DWORD_PTR& offset)
941{
942 MEMORY_BASIC_INFORMATION mbi;
943
944 if (!VirtualQuery(addr, &mbi, sizeof(mbi)))
945 return FALSE;
946
947 DWORD_PTR hMod = (DWORD_PTR)mbi.AllocationBase;
948
949 if (!hMod)
950 return FALSE;
951
952 if (!GetModuleFileName((HMODULE)hMod, szModule, len))
953 return FALSE;
954
955 // Point to the DOS header in memory
956 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hMod;
957
958 // From the DOS header, find the NT (PE) header
959 PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod + DWORD_PTR(pDosHdr->e_lfanew));
960
961 PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdr);
962
963 DWORD_PTR rva = (DWORD_PTR)addr - hMod; // RVA is offset from module load address
964
965 // Iterate through the section table, looking for the one that encompasses
966 // the linear address.
967 for (unsigned i = 0;
968 i < pNtHdr->FileHeader.NumberOfSections;
969 i++, pSection++)
970 {
971 DWORD_PTR sectionStart = pSection->VirtualAddress;
972 DWORD_PTR sectionEnd = sectionStart
973 + DWORD_PTR(std::max(pSection->SizeOfRawData, pSection->Misc.VirtualSize));
974
975 // Is the address in this section???
976 if ((rva >= sectionStart) && (rva <= sectionEnd))
977 {
978 // Yes, address is in the section. Calculate section and offset,
979 // and store in the "section" & "offset" params, which were
980 // passed by reference.
981 section = i+1;
982 offset = rva - sectionStart;
983 return TRUE;
984 }
985 }
986
987 return FALSE; // Should never get here!
988}
989
990// It contains SYMBOL_INFO structure plus additional
991// space for the name of the symbol
992struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE
993{
995 {
996 si.SizeOfStruct = sizeof(SYMBOL_INFO);
997 si.MaxNameLen = sizeof(name);
998 }
999};
1000
1001BOOL WheatyExceptionReport::GetSymbolFromAddress(HANDLE hProcess, DWORD64 Address, ULONG InlineContext, PDWORD64 Displacement, PSYMBOL_INFO Symbol)
1002{
1003 if (InlineContext != INLINE_FRAME_CONTEXT_IGNORE)
1004 return SymFromInlineContext(hProcess, Address, InlineContext, Displacement, Symbol);
1005 else
1006 return SymFromAddr(hProcess, Address, Displacement, Symbol);
1007}
1008
1009BOOL WheatyExceptionReport::GetSymbolLineFromAddress(HANDLE hProcess, DWORD64 qwAddr, ULONG InlineContext, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64)
1010{
1011 if (InlineContext != INLINE_FRAME_CONTEXT_IGNORE)
1012 return SymGetLineFromInlineContext(hProcess, qwAddr, InlineContext, 0, pdwDisplacement, Line64);
1013 else
1014 return SymGetLineFromAddr64(hProcess, qwAddr, pdwDisplacement, Line64);
1015}
1016
1017//============================================================
1018// Walks the stack, and writes the results to the report file
1019//============================================================
1021PCONTEXT pContext,
1022bool bWriteVariables, HANDLE pThreadHandle) // true if local/params should be output
1023{
1024 Log(_T("\r\nCall stack:\r\n"));
1025
1026 Log(_T("Address Frame Function SourceFile\r\n"));
1027
1028 DWORD dwMachineType = 0;
1029 // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag
1030
1031 STACKFRAME_EX sf = {};
1032 sf.StackFrameSize = sizeof(sf);
1033
1034 // Initialize the STACKFRAME structure for the first call.
1035 sf.AddrPC.Mode = AddrModeFlat;
1036 sf.AddrStack.Mode = AddrModeFlat;
1037 sf.AddrFrame.Mode = AddrModeFlat;
1038
1039#ifdef _M_IX86
1040 sf.AddrPC.Offset = pContext->Eip;
1041 sf.AddrStack.Offset = pContext->Esp;
1042 sf.AddrFrame.Offset = pContext->Ebp;
1043 dwMachineType = IMAGE_FILE_MACHINE_I386;
1044#elif defined(_M_X64)
1045 sf.AddrPC.Offset = pContext->Rip;
1046 sf.AddrStack.Offset = pContext->Rsp;
1047 sf.AddrFrame.Offset = pContext->Rbp;
1048 dwMachineType = IMAGE_FILE_MACHINE_AMD64;
1049#elif defined(_M_ARM64)
1050 sf.AddrPC.Offset = pContext->Pc;
1051 sf.AddrStack.Offset = pContext->Sp;
1052 sf.AddrFrame.Offset = pContext->Fp;
1053 dwMachineType = IMAGE_FILE_MACHINE_ARM64;
1054#endif
1055
1056 for (;;)
1057 {
1058 // Get the next stack frame
1059 if (!StackWalkEx(dwMachineType,
1060 m_process,
1061 pThreadHandle,
1062 &sf,
1063 pContext,
1064 nullptr,
1065 SymFunctionTableAccess64,
1066 SymGetModuleBase64,
1067 nullptr,
1068 SYM_STKWALK_DEFAULT))
1069 break;
1070 if (0 == sf.AddrFrame.Offset) // Basic sanity check to make sure
1071 break; // the frame is OK. Bail if not.
1072#ifdef _M_IX86
1073 Log(_T("%08X %08X "), sf.AddrPC.Offset, sf.AddrFrame.Offset);
1074#elif defined(_M_X64) || defined(_M_ARM64)
1075 Log(_T("%016I64X %016I64X "), sf.AddrPC.Offset, sf.AddrFrame.Offset);
1076#endif
1077
1078 DWORD64 symDisplacement = 0; // Displacement of the input address,
1079 // relative to the start of the symbol
1080
1081 bool isInline = sf.InlineFrameContext != INLINE_FRAME_CONTEXT_IGNORE && sf.InlineFrameContext & 0x200;
1082 if (isInline)
1083 Log(_T("[inline] "));
1084
1085 // Get the name of the function for this stack frame entry
1088 m_process, // Process handle of the current process
1089 sf.AddrPC.Offset, // Symbol address
1090 sf.InlineFrameContext, // Inline frame context identifier
1091 &symDisplacement, // Address of the variable that will receive the displacement
1092 &sip.si)) // Address of the SYMBOL_INFO structure (inside "sip" object)
1093 {
1094 Log(_T("%h" PRSTRc "+%I64X"), sip.si.Name, symDisplacement);
1095 }
1096 else // No symbol found. Print out the logical address instead.
1097 {
1098 TCHAR* szModule = m_tempPathBuffer;
1099 DWORD section = 0;
1100 DWORD_PTR offset = 0;
1101
1102 GetLogicalAddress((PVOID)sf.AddrPC.Offset,
1103 szModule, m_tempPathBufferChars, section, offset);
1104#ifdef _M_IX86
1105 Log(_T("%04X:%08X %s"), section, offset, szModule);
1106#elif defined(_M_X64) || defined(_M_ARM64)
1107 Log(_T("%04X:%016I64X %s"), section, offset, szModule);
1108#endif
1109 }
1110
1111 // Get the source line for this stack frame entry
1112 IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE64) };
1113 DWORD dwLineDisplacement;
1114 if (GetSymbolLineFromAddress(m_process, sf.AddrPC.Offset, sf.InlineFrameContext,
1115 &dwLineDisplacement, &lineInfo))
1116 {
1117 Log(_T(" %" PRSTRc " line %u"), lineInfo.FileName, lineInfo.LineNumber);
1118 }
1119
1120 Log(_T("\r\n"));
1121
1122 // Write out the variables, if desired
1123 if (bWriteVariables && !isInline)
1124 {
1125 // Use SymSetContext to get just the locals/params for this frame
1126 IMAGEHLP_STACK_FRAME imagehlpStackFrame;
1127 imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset;
1128 SymSetContext(m_process, &imagehlpStackFrame, nullptr);
1129
1130 // Enumerate the locals/parameters
1132 ctx.sf = &sf;
1133 ctx.context = pContext;
1134 ctx.report = this;
1135 SymEnumSymbols(m_process, 0, nullptr, EnumerateSymbolsCallback, &ctx);
1136
1137 Log(_T("\r\n"));
1138 }
1139 }
1140
1141}
1142
1144// The function invoked by SymEnumSymbols
1146
1147BOOL CALLBACK
1149PSYMBOL_INFO pSymInfo,
1150ULONG /*SymbolSize*/,
1151PVOID UserContext)
1152{
1153 EnumerateSymbolsCallbackContext* context = static_cast<EnumerateSymbolsCallbackContext*>(UserContext);
1154 context->report->ClearSymbols();
1155 context->report->FormatSymbolValue(pSymInfo, context);
1156 return TRUE;
1157}
1158
1160{
1161#define REG_L(x) ((BYTE)(((DWORD_PTR)(x)) & 0xff))
1162#define REG_H(x) ((BYTE)((((DWORD_PTR)(x)) >> 8) & 0xff))
1163#define REG_X(x) ((WORD)(((DWORD_PTR)(x)) & 0xffff))
1164#define REG_E(x) ((DWORD)(((DWORD_PTR)(x)) & 0xffffffff))
1165#define REG_R(x) ((DWORD64)(((DWORD_PTR)(x)) & 0xffffffffffffffff))
1166#define CPU_REG(reg, field, part) case reg: return part(context->field)
1167 switch (registerId)
1168 {
1169#ifdef _M_IX86
1170 CPU_REG(CV_REG_AL, Eax, REG_L);
1171 CPU_REG(CV_REG_CL, Ecx, REG_L);
1172 CPU_REG(CV_REG_DL, Edx, REG_L);
1173 CPU_REG(CV_REG_BL, Ebx, REG_L);
1174 CPU_REG(CV_REG_AH, Eax, REG_H);
1175 CPU_REG(CV_REG_CH, Ecx, REG_H);
1176 CPU_REG(CV_REG_DH, Edx, REG_H);
1177 CPU_REG(CV_REG_BH, Ebx, REG_H);
1178 CPU_REG(CV_REG_AX, Eax, REG_X);
1179 CPU_REG(CV_REG_CX, Ecx, REG_X);
1180 CPU_REG(CV_REG_DX, Edx, REG_X);
1181 CPU_REG(CV_REG_BX, Ebx, REG_X);
1182 CPU_REG(CV_REG_SP, Esp, REG_X);
1183 CPU_REG(CV_REG_BP, Ebp, REG_X);
1184 CPU_REG(CV_REG_SI, Esi, REG_X);
1185 CPU_REG(CV_REG_DI, Edi, REG_X);
1186 CPU_REG(CV_REG_EAX, Eax, REG_E);
1187 CPU_REG(CV_REG_ECX, Ecx, REG_E);
1188 CPU_REG(CV_REG_EDX, Edx, REG_E);
1189 CPU_REG(CV_REG_EBX, Ebx, REG_E);
1190 CPU_REG(CV_REG_ESP, Esp, REG_E);
1191 CPU_REG(CV_REG_EBP, Ebp, REG_E);
1192 CPU_REG(CV_REG_ESI, Esi, REG_E);
1193 CPU_REG(CV_REG_EDI, Edi, REG_E);
1194 CPU_REG(CV_REG_EIP, Eip, REG_E);
1195#elif defined (_M_X64)
1196 CPU_REG(CV_AMD64_AL, Rax, REG_L);
1197 CPU_REG(CV_AMD64_CL, Rcx, REG_L);
1198 CPU_REG(CV_AMD64_DL, Rdx, REG_L);
1199 CPU_REG(CV_AMD64_BL, Rbx, REG_L);
1200 CPU_REG(CV_AMD64_SIL, Rsi, REG_L);
1201 CPU_REG(CV_AMD64_DIL, Rdi, REG_L);
1202 CPU_REG(CV_AMD64_BPL, Rbp, REG_L);
1203 CPU_REG(CV_AMD64_SPL, Rsp, REG_L);
1212 CPU_REG(CV_AMD64_AH, Rax, REG_H);
1213 CPU_REG(CV_AMD64_CH, Rcx, REG_H);
1214 CPU_REG(CV_AMD64_DH, Rdx, REG_H);
1215 CPU_REG(CV_AMD64_BH, Rbx, REG_H);
1216 CPU_REG(CV_AMD64_AX, Rax, REG_X);
1217 CPU_REG(CV_AMD64_CX, Rcx, REG_X);
1218 CPU_REG(CV_AMD64_DX, Rdx, REG_X);
1219 CPU_REG(CV_AMD64_BX, Rbx, REG_X);
1220 CPU_REG(CV_AMD64_SP, Rsp, REG_X);
1221 CPU_REG(CV_AMD64_BP, Rbp, REG_X);
1222 CPU_REG(CV_AMD64_SI, Rsi, REG_X);
1223 CPU_REG(CV_AMD64_DI, Rdi, REG_X);
1232 CPU_REG(CV_AMD64_EAX, Rax, REG_E);
1233 CPU_REG(CV_AMD64_ECX, Rcx, REG_E);
1234 CPU_REG(CV_AMD64_EDX, Rdx, REG_E);
1235 CPU_REG(CV_AMD64_EBX, Rbx, REG_E);
1236 CPU_REG(CV_AMD64_ESP, Rsp, REG_E);
1237 CPU_REG(CV_AMD64_EBP, Rbp, REG_E);
1238 CPU_REG(CV_AMD64_ESI, Rsi, REG_E);
1239 CPU_REG(CV_AMD64_EDI, Rdi, REG_E);
1248 CPU_REG(CV_AMD64_RIP, Rip, REG_R);
1249 CPU_REG(CV_AMD64_RAX, Rax, REG_R);
1250 CPU_REG(CV_AMD64_RBX, Rbx, REG_R);
1251 CPU_REG(CV_AMD64_RCX, Rcx, REG_R);
1252 CPU_REG(CV_AMD64_RDX, Rdx, REG_R);
1253 CPU_REG(CV_AMD64_RSI, Rsi, REG_R);
1254 CPU_REG(CV_AMD64_RDI, Rdi, REG_R);
1255 CPU_REG(CV_AMD64_RBP, Rbp, REG_R);
1256 CPU_REG(CV_AMD64_RSP, Rsp, REG_R);
1259 CPU_REG(CV_AMD64_R10, R10, REG_R);
1260 CPU_REG(CV_AMD64_R11, R11, REG_R);
1261 CPU_REG(CV_AMD64_R12, R12, REG_R);
1262 CPU_REG(CV_AMD64_R13, R13, REG_R);
1263 CPU_REG(CV_AMD64_R14, R14, REG_R);
1264 CPU_REG(CV_AMD64_R15, R15, REG_R);
1265#elif defined(_M_ARM64)
1276 CPU_REG(CV_ARM64_W10, X10, REG_E);
1277 CPU_REG(CV_ARM64_W11, X11, REG_E);
1278 CPU_REG(CV_ARM64_W12, X12, REG_E);
1279 CPU_REG(CV_ARM64_W13, X13, REG_E);
1280 CPU_REG(CV_ARM64_W14, X14, REG_E);
1281 CPU_REG(CV_ARM64_W15, X15, REG_E);
1282 CPU_REG(CV_ARM64_W16, X16, REG_E);
1283 CPU_REG(CV_ARM64_W17, X17, REG_E);
1284 CPU_REG(CV_ARM64_W18, X18, REG_E);
1285 CPU_REG(CV_ARM64_W19, X19, REG_E);
1286 CPU_REG(CV_ARM64_W20, X20, REG_E);
1287 CPU_REG(CV_ARM64_W21, X21, REG_E);
1288 CPU_REG(CV_ARM64_W22, X22, REG_E);
1289 CPU_REG(CV_ARM64_W23, X23, REG_E);
1290 CPU_REG(CV_ARM64_W24, X24, REG_E);
1291 CPU_REG(CV_ARM64_W25, X25, REG_E);
1292 CPU_REG(CV_ARM64_W26, X26, REG_E);
1293 CPU_REG(CV_ARM64_W27, X27, REG_E);
1294 CPU_REG(CV_ARM64_W28, X28, REG_E);
1297 case CV_ARM64_WZR: return 0;
1308 CPU_REG(CV_ARM64_X10, X10, REG_R);
1309 CPU_REG(CV_ARM64_X11, X11, REG_R);
1310 CPU_REG(CV_ARM64_X12, X12, REG_R);
1311 CPU_REG(CV_ARM64_X13, X13, REG_R);
1312 CPU_REG(CV_ARM64_X14, X14, REG_R);
1313 CPU_REG(CV_ARM64_X15, X15, REG_R);
1314 CPU_REG(CV_ARM64_IP0, X16, REG_R);
1315 CPU_REG(CV_ARM64_IP1, X17, REG_R);
1316 CPU_REG(CV_ARM64_X18, X18, REG_R);
1317 CPU_REG(CV_ARM64_X19, X19, REG_R);
1318 CPU_REG(CV_ARM64_X20, X20, REG_R);
1319 CPU_REG(CV_ARM64_X21, X21, REG_R);
1320 CPU_REG(CV_ARM64_X22, X22, REG_R);
1321 CPU_REG(CV_ARM64_X23, X23, REG_R);
1322 CPU_REG(CV_ARM64_X24, X24, REG_R);
1323 CPU_REG(CV_ARM64_X25, X25, REG_R);
1324 CPU_REG(CV_ARM64_X26, X26, REG_R);
1325 CPU_REG(CV_ARM64_X27, X27, REG_R);
1326 CPU_REG(CV_ARM64_X28, X28, REG_R);
1330 case CV_ARM64_ZR: return 0;
1331#endif
1332 default:
1333 break;
1334 }
1335 return {};
1336#undef CPU_REG
1337#undef REG_R
1338#undef REG_E
1339#undef REG_X
1340#undef REG_H
1341#undef REG_L
1342}
1343
1345// Given a SYMBOL_INFO representing a particular variable, displays its
1346// contents. If it's a user defined type, display the members and their
1347// values.
1350PSYMBOL_INFO pSym,
1352{
1353 // If it's a function, don't do anything.
1354 if (pSym->Tag == SymTagFunction) // SymTagFunction from CVCONST.H from the DIA SDK
1355 return false;
1356
1357 DWORD_PTR pVariable = pSym->Address; // Will point to the variable's data in memory
1358 Optional<DWORD_PTR> registerVariableStorage;
1359
1360 if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_FRAMERELATIVE
1361 || (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE && pSym->Register == CV_ALLREG_VFRAME))
1362 {
1363 pVariable += pCtx->sf->AddrFrame.Offset;
1364 }
1365 else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGRELATIVE)
1366 {
1367 Optional<DWORD_PTR> registerValue = GetIntegerRegisterValue(pCtx->context, pSym->Register);
1368 if (!registerValue)
1369 return false;
1370
1371 pVariable += *registerValue;
1372 }
1373 else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_REGISTER)
1374 {
1375 registerVariableStorage = GetIntegerRegisterValue(pCtx->context, pSym->Register);
1376 if (!registerVariableStorage)
1377 return false; // Don't try to report non-integer register variable
1378
1379 pVariable = reinterpret_cast<DWORD_PTR>(&*registerVariableStorage);
1380 }
1381 else
1382 {
1383 // It must be a global variable
1384 }
1385
1387
1388 // Indicate if the variable is a local or parameter
1389 if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER)
1390 m_symbolDetails.top().Prefix = "Parameter ";
1391 else if (pSym->Flags & IMAGEHLP_SYMBOL_INFO_LOCAL)
1392 m_symbolDetails.top().Prefix = "Local ";
1393
1394 // Determine if the variable is a user defined type (UDT). IF so, bHandled
1395 // will return true.
1396 bool bHandled;
1397 DumpTypeIndex(pSym->ModBase, pSym->TypeIndex, pVariable, bHandled, pSym->Name, "", false, true);
1398
1399 if (!bHandled)
1400 {
1401 // The symbol wasn't a UDT, so do basic, stupid formatting of the
1402 // variable. Based on the size, we're assuming it's a char, WORD, or
1403 // DWORD.
1404 BasicType basicType = GetBasicType(pSym->TypeIndex, pSym->ModBase);
1405 if (m_symbolDetails.top().Type.empty())
1406 m_symbolDetails.top().Type = rgBaseType[basicType];
1407
1408 // Emit the variable name
1409 if (pSym->Name[0] != '\0')
1410 m_symbolDetails.top().Name = pSym->Name;
1411
1412 char buffer[50];
1413 FormatOutputValue(buffer, basicType, pSym->Size, (PVOID)pVariable, sizeof(buffer), 1);
1414 m_symbolDetails.top().Value = buffer;
1415 }
1416
1418 return true;
1419}
1420
1422// If it's a user defined type (UDT), recurse through its members until we're
1423// at fundamental types. When he hit fundamental types, return
1424// bHandled = false, so that FormatSymbolValue() will format them.
1427DWORD64 modBase,
1428DWORD dwTypeIndex,
1429DWORD_PTR offset,
1430bool & bHandled,
1431char const* Name,
1432char const* /*suffix*/,
1433bool newSymbol,
1434bool logChildren)
1435{
1436 bHandled = false;
1437
1438 if (newSymbol)
1440
1441 DWORD typeTag;
1442 if (!SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_SYMTAG, &typeTag))
1443 return;
1444
1445 // Get the name of the symbol. This will either be a Type name (if a UDT),
1446 // or the structure member name.
1447 WCHAR * pwszTypeName;
1448 if (SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_SYMNAME,
1449 &pwszTypeName))
1450 {
1451 // handle special cases
1452 if (wcscmp(pwszTypeName, L"std::basic_string<char,std::char_traits<char>,std::allocator<char> >") == 0)
1453 {
1454 LocalFree(pwszTypeName);
1455 m_symbolDetails.top().Type = "std::string";
1456 char buffer[50];
1457 FormatOutputValue(buffer, btStdString, 0, (PVOID)offset, sizeof(buffer));
1458 m_symbolDetails.top().Value = buffer;
1459 if (Name != nullptr && Name[0] != '\0')
1460 m_symbolDetails.top().Name = Name;
1461 bHandled = true;
1462 return;
1463 }
1464
1465 char buffer[WER_SMALL_BUFFER_SIZE];
1466 wcstombs(buffer, pwszTypeName, sizeof(buffer));
1467 buffer[WER_SMALL_BUFFER_SIZE - 1] = '\0';
1468 if (Name != nullptr && Name[0] != '\0')
1469 {
1470 m_symbolDetails.top().Type = buffer;
1471 m_symbolDetails.top().Name = Name;
1472 }
1473 else if (buffer[0] != '\0')
1474 m_symbolDetails.top().Name = buffer;
1475
1476 LocalFree(pwszTypeName);
1477 }
1478 else if (Name != nullptr && Name[0] != '\0')
1479 m_symbolDetails.top().Name = Name;
1480
1481 if (!StoreSymbol(dwTypeIndex, offset))
1482 {
1483 // Skip printing address and base class if it has been printed already
1484 if (typeTag == SymTagBaseClass)
1485 bHandled = true;
1486 return;
1487 }
1488
1489 DWORD innerTypeID;
1490 switch (typeTag)
1491 {
1492 case SymTagPointerType:
1493 if (SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID))
1494 {
1495 if (Name != nullptr && Name[0] != '\0')
1496 m_symbolDetails.top().Name = Name;
1497
1498 BOOL isReference;
1499 SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_IS_REFERENCE, &isReference);
1500
1501 char addressStr[40];
1502 memset(addressStr, 0, sizeof(addressStr));
1503
1504 if (isReference)
1505 m_symbolDetails.top().Suffix += "&";
1506 else
1507 m_symbolDetails.top().Suffix += "*";
1508
1509 // Try to dereference the pointer in a try/except block since it might be invalid
1510 DWORD_PTR address = DereferenceUnsafePointer<DWORD_PTR>(reinterpret_cast<LPCVOID>(offset)).value_or(DWORD_PTR(-1));
1511
1512 char buffer[WER_SMALL_BUFFER_SIZE];
1513 FormatOutputValue(buffer, btVoid, sizeof(PVOID), (PVOID)offset, sizeof(buffer));
1514 m_symbolDetails.top().Value = buffer;
1515
1517 logChildren = false;
1518
1519 // no need to log any children since the address is invalid anyway
1520 if (address == 0 || address == DWORD_PTR(-1))
1521 logChildren = false;
1522
1523 DumpTypeIndex(modBase, innerTypeID, address, bHandled, Name, addressStr, false, logChildren);
1524
1525 if (!bHandled)
1526 {
1527 BasicType basicType = GetBasicType(dwTypeIndex, modBase);
1528 if (m_symbolDetails.top().Type.empty())
1529 m_symbolDetails.top().Type = rgBaseType[basicType];
1530
1531 if (address == 0)
1532 m_symbolDetails.top().Value = "NULL";
1533 else if (address == DWORD_PTR(-1))
1534 m_symbolDetails.top().Value = "<Unable to read memory>";
1535 else
1536 {
1537 // Get the size of the child member
1538 ULONG64 length;
1539 SymGetTypeInfo(m_process, modBase, innerTypeID, TI_GET_LENGTH, &length);
1540 char buffer2[50];
1541 FormatOutputValue(buffer2, basicType, length, (PVOID)address, sizeof(buffer2));
1542 m_symbolDetails.top().Value = buffer2;
1543 }
1544 bHandled = true;
1545 return;
1546 }
1547 else if (address == 0)
1548 m_symbolDetails.top().Value = "NULL";
1549 else if (address == DWORD_PTR(-1))
1550 {
1551 m_symbolDetails.top().Value = "<Unable to read memory>";
1552 bHandled = true;
1553 return;
1554 }
1555 }
1556 break;
1557 case SymTagData:
1558 if (SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID))
1559 {
1560 DWORD innerTypeTag;
1561 if (!SymGetTypeInfo(m_process, modBase, innerTypeID, TI_GET_SYMTAG, &innerTypeTag))
1562 break;
1563
1564 switch (innerTypeTag)
1565 {
1566 case SymTagUDT:
1568 logChildren = false;
1569 DumpTypeIndex(modBase, innerTypeID,
1570 offset, bHandled, m_symbolDetails.top().Name.c_str(), "", false, logChildren);
1571 break;
1572 case SymTagPointerType:
1573 if (Name != nullptr && Name[0] != '\0')
1574 m_symbolDetails.top().Name = Name;
1575 DumpTypeIndex(modBase, innerTypeID,
1576 offset, bHandled, m_symbolDetails.top().Name.c_str(), "", false, logChildren);
1577 break;
1578 case SymTagArrayType:
1579 DumpTypeIndex(modBase, innerTypeID,
1580 offset, bHandled, m_symbolDetails.top().Name.c_str(), "", false, logChildren);
1581 break;
1582 default:
1583 break;
1584 }
1585 }
1586 break;
1587 case SymTagArrayType:
1588 if (SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_TYPEID, &innerTypeID))
1589 {
1590 m_symbolDetails.top().HasChildren = true;
1591
1592 BasicType basicType = btNoType;
1593 DumpTypeIndex(modBase, innerTypeID,
1594 offset, bHandled, Name, "", false, false);
1595
1596 // Set Value back to an empty string since the Array object itself has no value, only its elements have
1597 std::string firstElementValue = std::move(m_symbolDetails.top().Value);
1598
1599 DWORD elementsCount;
1600 if (SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_COUNT, &elementsCount))
1601 {
1602 char countStr[11] = { };
1603 std::string& suffix = m_symbolDetails.top().Suffix;
1604 suffix += '[';
1605 suffix.append(countStr, std::to_chars(std::begin(countStr), std::end(countStr), elementsCount).ptr - std::begin(countStr));
1606 suffix += ']';
1607 }
1608 else
1609 m_symbolDetails.top().Suffix += "[<unknown count>]";
1610
1611 if (!bHandled)
1612 {
1613 basicType = GetBasicType(dwTypeIndex, modBase);
1614 if (m_symbolDetails.top().Type.empty())
1615 m_symbolDetails.top().Type = rgBaseType[basicType];
1616 bHandled = true;
1617 }
1618
1619 // Get the size of the child member
1620 ULONG64 length;
1621 SymGetTypeInfo(m_process, modBase, innerTypeID, TI_GET_LENGTH, &length);
1622
1623 char buffer[50];
1624 switch (basicType)
1625 {
1626 case btChar:
1627 case btStdString:
1628 FormatOutputValue(buffer, basicType, length, (PVOID)offset, sizeof(buffer), elementsCount);
1629 m_symbolDetails.top().Value = buffer;
1630 break;
1631 default:
1632 for (DWORD index = 0; index < elementsCount && index < WER_MAX_ARRAY_ELEMENTS_COUNT; index++)
1633 {
1634 bool elementHandled = false;
1635 SymbolDetail& arrayElement = PushSymbolDetail();
1636 if (index == 0)
1637 {
1638 if (firstElementValue.empty())
1639 {
1640 FormatOutputValue(buffer, basicType, length, (PVOID)(offset + length * index), sizeof(buffer));
1641 firstElementValue = buffer;
1642 }
1643 arrayElement.Value = std::move(firstElementValue);
1644 }
1645 else
1646 {
1647 DumpTypeIndex(modBase, innerTypeID, offset + length * index, elementHandled, "", "", false, false);
1648 if (!elementHandled)
1649 {
1650 FormatOutputValue(buffer, basicType, length, (PVOID)(offset + length * index), sizeof(buffer));
1651 arrayElement.Value = buffer;
1652 }
1653 }
1654 static_assert(WER_MAX_ARRAY_ELEMENTS_COUNT <= 10);
1655 arrayElement.Prefix.clear();
1656 arrayElement.Type.clear();
1657 arrayElement.Suffix.resize(3);
1658 arrayElement.Suffix[0] = '[';
1659 arrayElement.Suffix[1] = '0' + index;
1660 arrayElement.Suffix[2] = ']';
1661 arrayElement.Name.clear();
1663 }
1664 break;
1665 }
1666
1667 return;
1668 }
1669 break;
1670 case SymTagBaseType:
1671 break;
1672 case SymTagEnum:
1673 return;
1674 default:
1675 break;
1676 }
1677
1678 // Determine how many children this type has.
1679 DWORD dwChildrenCount = 0;
1680 SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_GET_CHILDRENCOUNT, &dwChildrenCount);
1681
1682 if (!dwChildrenCount) // If no children, we're done
1683 return;
1684
1685 // Prepare to get an array of "TypeIds", representing each of the children.
1686 // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a
1687 // TI_FINDCHILDREN_PARAMS struct has. Use derivation to accomplish this.
1688 TI_FINDCHILDREN_PARAMS* children = static_cast<TI_FINDCHILDREN_PARAMS*>(alloca(sizeof(TI_FINDCHILDREN_PARAMS) + sizeof(ULONG) * (dwChildrenCount - 1)));
1689 children->Count = dwChildrenCount;
1690 children->Start = 0;
1691
1692 // Get the array of TypeIds, one for each child type
1693 if (!SymGetTypeInfo(m_process, modBase, dwTypeIndex, TI_FINDCHILDREN,
1694 children))
1695 {
1696 return;
1697 }
1698
1699 // Iterate through each of the children
1700 for (unsigned i = 0; i < dwChildrenCount; i++)
1701 {
1702 DWORD symTag;
1703 SymGetTypeInfo(m_process, modBase, children->ChildId[i], TI_GET_SYMTAG, &symTag);
1704
1705 if (symTag == SymTagFunction ||
1706 symTag == SymTagEnum ||
1707 symTag == SymTagTypedef ||
1708 symTag == SymTagVTable)
1709 continue;
1710
1711 // Ignore static fields
1712 DWORD dataKind;
1713 SymGetTypeInfo(m_process, modBase, children->ChildId[i], TI_GET_DATAKIND, &dataKind);
1714 if (dataKind == DataIsStaticLocal ||
1715 dataKind == DataIsGlobal ||
1716 dataKind == DataIsStaticMember)
1717 continue;
1718
1719 m_symbolDetails.top().HasChildren = true;
1720 if (!logChildren)
1721 {
1722 bHandled = false;
1723 return;
1724 }
1725
1726 // Recurse for each of the child types
1727 bool bHandled2;
1728 BasicType basicType = GetBasicType(children->ChildId[i], modBase);
1729
1730 // Get the offset of the child member, relative to its parent
1731 DWORD dwMemberOffset;
1732 SymGetTypeInfo(m_process, modBase, children->ChildId[i],
1733 TI_GET_OFFSET, &dwMemberOffset);
1734
1735 // Calculate the address of the member
1736 DWORD_PTR dwFinalOffset = offset + dwMemberOffset;
1737
1738 DumpTypeIndex(modBase,
1739 children->ChildId[i],
1740 dwFinalOffset, bHandled2, ""/*Name */, "", true, true);
1741
1742 // If the child wasn't a UDT, format it appropriately
1743 if (!bHandled2)
1744 {
1745 if (m_symbolDetails.top().Type.empty())
1746 m_symbolDetails.top().Type = rgBaseType[basicType];
1747
1748 // Get the real "TypeId" of the child. We need this for the
1749 // SymGetTypeInfo(TI_GET_TYPEID) call below.
1750 DWORD typeId;
1751 SymGetTypeInfo(m_process, modBase, children->ChildId[i],
1752 TI_GET_TYPEID, &typeId);
1753
1754 // Get the size of the child member
1755 ULONG64 length;
1756 SymGetTypeInfo(m_process, modBase, typeId, TI_GET_LENGTH, &length);
1757
1758 char buffer[50];
1759 FormatOutputValue(buffer, basicType, length, (PVOID)dwFinalOffset, sizeof(buffer), 1);
1760 m_symbolDetails.top().Value = buffer;
1761 }
1762
1764 }
1765
1766 bHandled = true;
1767}
1768
1769template <typename T>
1770void WheatyExceptionReport::FormatOutputValueNumeric(char* buffer, size_t bufferSize, char const* format, LPCVOID address)
1771{
1772 if (Optional<T> value = DereferenceUnsafePointer<T>(address))
1773 (void)::snprintf(buffer, bufferSize, format, *value);
1774 else
1775 (void)snprintf(buffer, bufferSize, "%p <Unable to read memory>", address);
1776}
1777
1779BasicType basicType,
1780DWORD64 length,
1781PVOID pAddress,
1782size_t bufferSize,
1783size_t countOverride)
1784{
1785 switch (basicType)
1786 {
1787 case btChar:
1788 {
1789 // Special case handling for char[] type
1790 if (countOverride != 0)
1791 {
1792 *pszCurrBuffer = '"';
1793 if (countOverride > bufferSize - 3)
1794 {
1795 if (ReadProcessMemory(m_process, pAddress, pszCurrBuffer + 1, bufferSize - 6, nullptr))
1796 {
1797 strcpy(pszCurrBuffer + bufferSize - 5, "...\"");
1798 return;
1799 }
1800 }
1801 else
1802 {
1803 if (ReadProcessMemory(m_process, pAddress, pszCurrBuffer + 1, countOverride, nullptr))
1804 {
1805 strcpy(pszCurrBuffer + 1 + countOverride, "\"");
1806 return;
1807 }
1808 }
1809
1810 (void)snprintf(pszCurrBuffer, bufferSize, "%p <Unable to read memory>", pAddress);
1811 }
1812 else
1813 {
1814 *pszCurrBuffer = '"';
1815 size_t i = 1;
1816 for (; i < bufferSize - 2; ++i)
1817 {
1818 Optional<char> character = DereferenceUnsafePointer<char>(static_cast<char const*>(pAddress) + i - 1);
1819 if (!character)
1820 {
1821 (void)snprintf(pszCurrBuffer, bufferSize, "%p <Unable to read memory>", pAddress);
1822 return;
1823 }
1824 *(pszCurrBuffer + i) = *character;
1825 if (!*character)
1826 break; // end of string
1827 }
1828 if (i == bufferSize - 2)
1829 {
1830 // too long
1831 strcpy(pszCurrBuffer + bufferSize - 6, "...");
1832 }
1833
1834 *(pszCurrBuffer + i) = '"';
1835 *(pszCurrBuffer + i + 1) = '\0';
1836 }
1837 break;
1838 }
1839 case btStdString:
1840 {
1841 alignas(std::string) std::array<char, sizeof(std::string)> rawBytes = { };
1842 if (ReadProcessMemory(m_process, pAddress, rawBytes.data(), rawBytes.size(), nullptr))
1843 {
1844 std::string const* value = reinterpret_cast<std::string const*>(rawBytes.data());
1845
1846 *pszCurrBuffer = '"';
1847 if (value->length() > bufferSize - 3)
1848 {
1849 if (ReadProcessMemory(m_process, value->data(), pszCurrBuffer + 1, bufferSize - 6, nullptr))
1850 {
1851 strcpy(pszCurrBuffer + bufferSize - 6, "...\"");
1852 return;
1853 }
1854 }
1855 else
1856 {
1857 if (ReadProcessMemory(m_process, value->data(), pszCurrBuffer + 1, value->length(), nullptr))
1858 {
1859 strcpy(pszCurrBuffer + 1 + value->length(), "\"");
1860 return;
1861 }
1862 }
1863 }
1864
1865 (void)snprintf(pszCurrBuffer, bufferSize, "%p <Unable to read memory>", pAddress);
1866
1867 break;
1868 }
1869 default:
1870 // Format appropriately (assuming it's a 1, 2, or 4 bytes (!!!)
1871 if (length == 1)
1872 FormatOutputValueNumeric<BYTE>(pszCurrBuffer, bufferSize, "0x%X", pAddress);
1873 else if (length == 2)
1874 FormatOutputValueNumeric<WORD>(pszCurrBuffer, bufferSize, "0x%X", pAddress);
1875 else if (length == 4)
1876 {
1877 if (basicType == btFloat)
1878 FormatOutputValueNumeric<float>(pszCurrBuffer, bufferSize, "%f", pAddress);
1879 else
1880 FormatOutputValueNumeric<DWORD>(pszCurrBuffer, bufferSize, "0x%lX", pAddress);
1881 }
1882 else if (length == 8)
1883 {
1884 if (basicType == btFloat)
1885 FormatOutputValueNumeric<double>(pszCurrBuffer, bufferSize, "%f", pAddress);
1886 else
1887 FormatOutputValueNumeric<DWORD64>(pszCurrBuffer, bufferSize, "0x%llX", pAddress);
1888 }
1889 else
1890 (void)snprintf(pszCurrBuffer, bufferSize, "%p", pAddress);
1891
1892 break;
1893 }
1894}
1895
1897WheatyExceptionReport::GetBasicType(DWORD typeIndex, DWORD64 modBase) const
1898{
1899 BasicType basicType;
1900 if (SymGetTypeInfo(m_process, modBase, typeIndex,
1901 TI_GET_BASETYPE, &basicType))
1902 {
1903 return basicType;
1904 }
1905
1906 // Get the real "TypeId" of the child. We need this for the
1907 // SymGetTypeInfo(TI_GET_TYPEID) call below.
1908 DWORD typeId;
1909 if (SymGetTypeInfo(m_process, modBase, typeIndex, TI_GET_TYPEID, &typeId))
1910 {
1911 if (SymGetTypeInfo(m_process, modBase, typeId, TI_GET_BASETYPE,
1912 &basicType))
1913 {
1914 return basicType;
1915 }
1916 }
1917
1918 return btNoType;
1919}
1920
1921template <typename T> requires (std::is_scalar_v<T>)
1923{
1924 Optional<T> result;
1925 SIZE_T numberOfBytesRead = 0;
1926 if (!::ReadProcessMemory(m_process, address, &result.emplace(), sizeof(T), &numberOfBytesRead) || numberOfBytesRead != sizeof(T))
1927 result.reset();
1928
1929 return result;
1930}
1931
1932//============================================================================
1933// Helper function that writes to the report file, and allows the user to use
1934// printf style formating
1935//============================================================================
1936int WheatyExceptionReport::Log(const TCHAR * format, ...)
1937{
1938 va_list argptr;
1939 va_start(argptr, format);
1940 int retValue = _vftprintf(m_reportFile, format, argptr);
1941 va_end(argptr);
1942 return retValue;
1943}
1944
1945bool WheatyExceptionReport::StoreSymbol(DWORD type, DWORD_PTR offset)
1946{
1947 return m_symbols.insert(SymbolPair(type, offset)).second;
1948}
1949
1951{
1952 m_symbols.clear();
1953 while (!m_symbolDetails.empty())
1954 m_symbolDetails.pop();
1955}
1956
1958{
1959 // Log current symbol and then add another to the stack to keep the hierarchy format
1961 return m_symbolDetails.emplace();
1962}
1963
1969
1971{
1972 if (m_symbolDetails.empty())
1973 return;
1974
1975 SymbolDetail& symbol = m_symbolDetails.top();
1976
1977 // Don't log anything if has been logged already or if it's empty
1978 if (symbol.Logged || symbol.empty())
1979 return;
1980
1981 // Add appropriate indentation level (since this routine is recursive)
1982 for (size_t i = 0; i < m_symbolDetails.size(); i++)
1983 _fputtc(_T('\t'), m_reportFile);
1984
1985 symbol.Logged = true;
1986
1987 int printed = Log(_T("%" PRSTRc "%" PRSTRc "%" PRSTRc), symbol.Prefix.c_str(), symbol.Type.c_str(), symbol.Suffix.c_str());
1988 if (!symbol.Name.empty())
1989 {
1990 if (printed > 0)
1991 _fputtc(_T(' '), m_reportFile);
1992
1993 Log(_T("%" PRSTRc), symbol.Name.c_str());
1994 }
1995
1996 if (!symbol.Value.empty())
1997 {
1998 _fputts(_T(" = "), m_reportFile);
1999 if (symbol.Name != "passwd" && symbol.Name != "password")
2000 Log(_T("%" PRSTRc), symbol.Value.c_str());
2001 else
2002 _fputts(_T("<sensitive data>"), m_reportFile);
2003 }
2004
2005 _fputts(_T("\r\n"), m_reportFile);
2006}
#define STRING_VIEW_FMT_ARG(str)
Definition Define.h:147
int32_t int32
Definition Define.h:150
#define EXCEPTION_ASSERTION_FAILURE
Definition Errors.h:56
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
const size_t bufferSize
Definition RASession.h:28
#define EXCEPTION(x)
#define REG_E(x)
#define CPU_REG(reg, field, part)
void ToTchar(wchar_t const *src, TCHAR(&dst)[size])
#define CrashFolder
#define REG_X(x)
LPTSTR ErrorMessage(DWORD dw)
#define PRSTRc
#define REG_L(x)
decltype(Trinity::unique_ptr_deleter< T *,[](T *ptr) { ptr->Release() com_unique_ptr_deleter
std::unique_ptr< T, com_unique_ptr_deleter< T > > com_unique_ptr
#define REG_H(x)
#define REG_R(x)
#define PRSTRw
char const *const rgBaseType[]
#define WER_SMALL_BUFFER_SIZE
#define WER_MAX_NESTING_LEVEL
#define WER_MAX_ARRAY_ELEMENTS_COUNT
@ CV_ALLREG_VFRAME
@ SymTagArrayType
@ SymTagFunction
@ SymTagPointerType
@ SymTagTypedef
@ SymTagBaseType
@ SymTagBaseClass
@ DataIsStaticMember
@ DataIsStaticLocal
Definition Log.h:52
static LPCTSTR GetExceptionString(DWORD dwCode)
static BOOL GetLogicalAddress(PVOID addr, PTSTR szModule, DWORD len, DWORD &section, DWORD_PTR &offset)
static BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO, ULONG, PVOID)
static BOOL _GetProcessorName(TCHAR *sProcessorName, DWORD maxcount)
bool FormatSymbolValue(PSYMBOL_INFO, EnumerateSymbolsCallbackContext *)
void FormatOutputValue(char *pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress, size_t bufferSize, size_t countOverride=0)
LONG UnhandledExceptionFilterImpl(PEXCEPTION_POINTERS pExceptionInfo) noexcept
void printTracesForAllThreads(bool bWriteVariables)
void DumpTypeIndex(DWORD64, DWORD, DWORD_PTR, bool &, char const *, char const *, bool, bool)
LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter
_invalid_parameter_handler m_previousCrtHandler
BasicType GetBasicType(DWORD typeIndex, DWORD64 modBase) const
static BOOL GetSymbolLineFromAddress(HANDLE hProcess, DWORD64 qwAddr, ULONG InlineContext, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line64)
void FormatOutputValueNumeric(char *buffer, size_t bufferSize, char const *format, LPCVOID address)
static BOOL _GetWindowsVersionFromWMI(TCHAR *szVersion, DWORD cntMax) noexcept
Optional< T > DereferenceUnsafePointer(LPCVOID address)
int Log(const TCHAR *format,...)
BOOL _GetWindowsVersion(TCHAR *szVersion, DWORD cntMax)
static constexpr SIZE_T m_tempPathBufferChars
void WriteStackDetails(PCONTEXT pContext, bool bWriteVariables, HANDLE pThreadHandle)
bool StoreSymbol(DWORD type, DWORD_PTR offset)
static Optional< DWORD_PTR > GetIntegerRegisterValue(PCONTEXT context, ULONG registerId)
std::stack< SymbolDetail > m_symbolDetails
void GenerateExceptionReport(PEXCEPTION_POINTERS pExceptionInfo)
static BOOL GetSymbolFromAddress(HANDLE hProcess, DWORD64 Address, ULONG InlineContext, PDWORD64 Displacement, PSYMBOL_INFO Symbol)
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)
TC_COMMON_API char const * GetFullVersion()
TC_COMMON_API char const * GetHash()
Impl::stateless_unique_ptr_deleter< Ptr, Del > unique_ptr_deleter()
Definition Memory.h:102
std::unique_ptr< T, Impl::stateful_unique_ptr_deleter< Ptr, Del > > make_unique_ptr_with_deleter(Ptr ptr, Del deleter)
Definition Memory.h:133