BsWin32CrashHandler.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "BsPrerequisitesUtil.h"
  4. #include "BsDebug.h"
  5. #include "BsDynLib.h"
  6. #include "BsFileSystem.h"
  7. #include "windows.h"
  8. #include <psapi.h>
  9. // Disable warning in VS2015 that's not under my control
  10. #pragma warning(disable : 4091)
  11. #include "DbgHelp.h"
  12. #pragma warning(default : 4091)
  13. namespace BansheeEngine
  14. {
  15. /**
  16. * Returns the raw stack trace using the provided context. Raw stack trace contains only function addresses.
  17. *
  18. * @param[in] context Processor context from which to start the stack trace.
  19. * @param[in] stackTrace Output parameter that will contain the function addresses. First address is the deepest
  20. * called function and following address is its caller and so on.
  21. * @return Number of functions in the call stack.
  22. */
  23. UINT32 win32_getRawStackTrace(CONTEXT context, UINT64 stackTrace[BS_MAX_STACKTRACE_DEPTH])
  24. {
  25. HANDLE hProcess = GetCurrentProcess();
  26. HANDLE hThread = GetCurrentThread();
  27. UINT32 machineType;
  28. STACKFRAME64 stackFrame;
  29. memset(&stackFrame, 0, sizeof(stackFrame));
  30. stackFrame.AddrPC.Mode = AddrModeFlat;
  31. stackFrame.AddrStack.Mode = AddrModeFlat;
  32. stackFrame.AddrFrame.Mode = AddrModeFlat;
  33. #if BS_ARCH_TYPE == BS_ARCHITECTURE_x86_64
  34. stackFrame.AddrPC.Offset = context.Rip;
  35. stackFrame.AddrStack.Offset = context.Rsp;
  36. stackFrame.AddrFrame.Offset = context.Rbp;
  37. machineType = IMAGE_FILE_MACHINE_AMD64;
  38. #else
  39. stackFrame.AddrPC.Offset = context.Eip;
  40. stackFrame.AddrStack.Offset = context.Esp;
  41. stackFrame.AddrFrame.Offset = context.Ebp;
  42. machineType = IMAGE_FILE_MACHINE_I386;
  43. #endif
  44. UINT32 numEntries = 0;
  45. while (true)
  46. {
  47. if (!StackWalk64(machineType, hProcess, hThread, &stackFrame, &context, nullptr,
  48. SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
  49. {
  50. break;
  51. }
  52. if (numEntries < BS_MAX_STACKTRACE_DEPTH)
  53. stackTrace[numEntries] = stackFrame.AddrPC.Offset;
  54. numEntries++;
  55. if (stackFrame.AddrPC.Offset == 0 || stackFrame.AddrFrame.Offset == 0)
  56. break;
  57. }
  58. return numEntries;
  59. }
  60. /**
  61. * Returns a string containing a stack trace using the provided context. If function can be found in the symbol table
  62. * its readable name will be present in the stack trace, otherwise just its address.
  63. *
  64. * @param[in] context Processor context from which to start the stack trace.
  65. * @param[in] skip Number of bottom-most call stack entries to skip.
  66. * @return String containing the call stack with each function on its own line.
  67. */
  68. String win32_getStackTrace(CONTEXT context, UINT32 skip = 0)
  69. {
  70. UINT64 rawStackTrace[BS_MAX_STACKTRACE_DEPTH];
  71. UINT32 numEntries = win32_getRawStackTrace(context, rawStackTrace);
  72. numEntries = std::min((UINT32)BS_MAX_STACKTRACE_DEPTH, numEntries);
  73. UINT32 bufferSize = sizeof(PIMAGEHLP_SYMBOL64) + BS_MAX_STACKTRACE_NAME_BYTES;
  74. UINT8* buffer = (UINT8*)bs_alloc(bufferSize);
  75. PIMAGEHLP_SYMBOL64 symbol = (PIMAGEHLP_SYMBOL64)buffer;
  76. symbol->SizeOfStruct = bufferSize;
  77. symbol->MaxNameLength = BS_MAX_STACKTRACE_NAME_BYTES;
  78. HANDLE hProcess = GetCurrentProcess();
  79. StringStream outputStream;
  80. for (UINT32 i = skip; i < numEntries; i++)
  81. {
  82. if (i > skip)
  83. outputStream << std::endl;
  84. DWORD64 funcAddress = rawStackTrace[i];
  85. // Output function name
  86. DWORD64 dummy;
  87. if (SymGetSymFromAddr64(hProcess, funcAddress, &dummy, symbol))
  88. outputStream << StringUtil::format("{0}() - ", symbol->Name);
  89. // Output file name and line
  90. IMAGEHLP_LINE64 lineData;
  91. lineData.SizeOfStruct = sizeof(lineData);
  92. String addressString = toString(funcAddress, 0, ' ', std::ios::hex);
  93. DWORD column;
  94. if (SymGetLineFromAddr64(hProcess, funcAddress, &column, &lineData))
  95. {
  96. Path filePath = lineData.FileName;
  97. outputStream << StringUtil::format("0x{0} File[{1}:{2} ({3})]", addressString,
  98. filePath.getFilename(), lineData.LineNumber, column);
  99. }
  100. else
  101. {
  102. outputStream << StringUtil::format("0x{0}", addressString);
  103. }
  104. // Output module name
  105. IMAGEHLP_MODULE64 moduleData;
  106. moduleData.SizeOfStruct = sizeof(moduleData);
  107. if (SymGetModuleInfo64(hProcess, funcAddress, &moduleData))
  108. {
  109. Path filePath = moduleData.ImageName;
  110. outputStream << StringUtil::format(" Module[{0}]", filePath.getFilename());
  111. }
  112. }
  113. bs_free(buffer);
  114. return outputStream.str();
  115. }
  116. typedef bool(WINAPI *EnumProcessModulesType)(HANDLE hProcess, HMODULE* lphModule, DWORD cb, LPDWORD lpcbNeeded);
  117. typedef DWORD(WINAPI *GetModuleBaseNameType)(HANDLE hProcess, HMODULE hModule, LPSTR lpBaseName, DWORD nSize);
  118. typedef DWORD(WINAPI *GetModuleFileNameExType)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize);
  119. typedef bool(WINAPI *GetModuleInformationType)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb);
  120. static DynLib* gPSAPILib = nullptr;
  121. static EnumProcessModulesType gEnumProcessModules;
  122. static GetModuleBaseNameType gGetModuleBaseName;
  123. static GetModuleFileNameExType gGetModuleFileNameEx;
  124. static GetModuleInformationType gGetModuleInformation;
  125. /** Dynamically load the PSAPI.dll and the required symbols, if not already loaded. */
  126. void win32_initPSAPI()
  127. {
  128. if (gPSAPILib != nullptr)
  129. return;
  130. gPSAPILib = bs_new<DynLib>("PSAPI.dll");
  131. gEnumProcessModules = (EnumProcessModulesType)gPSAPILib->getSymbol("EnumProcessModules");
  132. gGetModuleBaseName = (GetModuleBaseNameType)gPSAPILib->getSymbol("GetModuleFileNameExA");
  133. gGetModuleFileNameEx = (GetModuleFileNameExType)gPSAPILib->getSymbol("GetModuleBaseNameA");
  134. gGetModuleInformation = (GetModuleInformationType)gPSAPILib->getSymbol("GetModuleInformation");
  135. }
  136. /** Unloads the PSAPI.dll if is loaded. */
  137. void win32_unloadPSAPI()
  138. {
  139. if (gPSAPILib == nullptr)
  140. return;
  141. gPSAPILib->unload();
  142. bs_delete(gPSAPILib);
  143. gPSAPILib = nullptr;
  144. }
  145. static bool gSymbolsLoaded = false;
  146. /**
  147. * Loads symbols for all modules in the current process. Loaded symbols allow the stack walker to retrieve human
  148. * readable method, file, module names and other information.
  149. */
  150. void win32_loadSymbols()
  151. {
  152. if (gSymbolsLoaded)
  153. return;
  154. HANDLE hProcess = GetCurrentProcess();
  155. UINT32 options = SymGetOptions();
  156. options |= SYMOPT_LOAD_LINES;
  157. options |= SYMOPT_EXACT_SYMBOLS;
  158. options |= SYMOPT_UNDNAME;
  159. options |= SYMOPT_FAIL_CRITICAL_ERRORS;
  160. options |= SYMOPT_NO_PROMPTS;
  161. SymSetOptions(options);
  162. if(!SymInitialize(hProcess, nullptr, false))
  163. {
  164. LOGERR("SymInitialize failed. Error code: " + toString((UINT32)GetLastError()));
  165. return;
  166. }
  167. DWORD bufferSize;
  168. gEnumProcessModules(hProcess, nullptr, 0, &bufferSize);
  169. HMODULE* modules = (HMODULE*)bs_alloc(bufferSize);
  170. gEnumProcessModules(hProcess, modules, bufferSize, &bufferSize);
  171. UINT32 numModules = bufferSize / sizeof(HMODULE);
  172. for (UINT32 i = 0; i < numModules; i++)
  173. {
  174. MODULEINFO moduleInfo;
  175. char moduleName[BS_MAX_STACKTRACE_NAME_BYTES];
  176. char imageName[BS_MAX_STACKTRACE_NAME_BYTES];
  177. gGetModuleInformation(hProcess, modules[i], &moduleInfo, sizeof(moduleInfo));
  178. gGetModuleFileNameEx(hProcess, modules[i], imageName, BS_MAX_STACKTRACE_NAME_BYTES);
  179. gGetModuleBaseName(hProcess, modules[i], moduleName, BS_MAX_STACKTRACE_NAME_BYTES);
  180. char pdbSearchPath[BS_MAX_STACKTRACE_NAME_BYTES];
  181. char* fileName = nullptr;
  182. GetFullPathNameA(moduleName, BS_MAX_STACKTRACE_NAME_BYTES, pdbSearchPath, &fileName);
  183. *fileName = '\0';
  184. SymSetSearchPath(GetCurrentProcess(), pdbSearchPath);
  185. DWORD64 moduleAddress = SymLoadModule64(hProcess, modules[i], imageName, moduleName, (DWORD64)moduleInfo.lpBaseOfDll,
  186. (DWORD)moduleInfo.SizeOfImage);
  187. if (moduleAddress != 0)
  188. {
  189. IMAGEHLP_MODULE64 imageInfo;
  190. memset(&imageInfo, 0, sizeof(imageInfo));
  191. imageInfo.SizeOfStruct = sizeof(imageInfo);
  192. if(!SymGetModuleInfo64(GetCurrentProcess(), moduleAddress, &imageInfo))
  193. {
  194. LOGWRN("Failed retrieving module info for module: " + String(moduleName) + ". Error code: " + toString((UINT32)GetLastError()));
  195. }
  196. else
  197. {
  198. // Disabled because too much spam in the log, enable as needed
  199. #if 0
  200. if (imageInfo.SymType == SymNone)
  201. LOGWRN("Failed loading symbols for module: " + String(moduleName));
  202. #endif
  203. }
  204. }
  205. else
  206. {
  207. LOGWRN("Failed loading module " + String(moduleName) + ".Error code: " + toString((UINT32)GetLastError()) +
  208. ". Search path: " + String(pdbSearchPath) + ". Image name: " + String(imageName));
  209. }
  210. }
  211. bs_free(modules);
  212. gSymbolsLoaded = true;
  213. }
  214. /** Converts an exception record into a human readable error message. */
  215. String win32_getExceptionMessage(EXCEPTION_RECORD* record)
  216. {
  217. String exceptionAddress = toString((UINT64)record->ExceptionAddress, 0, ' ', std::ios::hex);
  218. String format;
  219. switch (record->ExceptionCode)
  220. {
  221. case EXCEPTION_ACCESS_VIOLATION:
  222. {
  223. DWORD_PTR violatedAddress = 0;
  224. if (record->NumberParameters == 2)
  225. {
  226. if (record->ExceptionInformation[0] == 0)
  227. format = "Unhandled exception at 0x{0}. Access violation reading 0x{1}.";
  228. else if (record->ExceptionInformation[0] == 8)
  229. format = "Unhandled exception at 0x{0}. Access violation DEP 0x{1}.";
  230. else
  231. format = "Unhandled exception at 0x{0}. Access violation writing 0x{1}.";
  232. violatedAddress = record->ExceptionInformation[1];
  233. }
  234. else
  235. format = "Unhandled exception at 0x{0}. Access violation.";
  236. String violatedAddressStr = toString((UINT64)violatedAddress, 0, ' ', std::ios::hex);
  237. return StringUtil::format(format, exceptionAddress, violatedAddressStr);
  238. }
  239. case EXCEPTION_IN_PAGE_ERROR:
  240. {
  241. DWORD_PTR violatedAddress = 0;
  242. DWORD_PTR code = 0;
  243. if (record->NumberParameters == 3)
  244. {
  245. if (record->ExceptionInformation[0] == 0)
  246. format = "Unhandled exception at 0x{0}. Page fault reading 0x{1} with code 0x{2}.";
  247. else if (record->ExceptionInformation[0] == 8)
  248. format = "Unhandled exception at 0x{0}. Page fault DEP 0x{1} with code 0x{2}.";
  249. else
  250. format = "Unhandled exception at 0x{0}. Page fault writing 0x{1} with code 0x{2}.";
  251. violatedAddress = record->ExceptionInformation[1];
  252. code = record->ExceptionInformation[3];
  253. }
  254. else
  255. format = "Unhandled exception at 0x{0}. Page fault.";
  256. String violatedAddressStr = toString((UINT64)violatedAddress, 0, ' ', std::ios::hex);
  257. String codeStr = toString((UINT64)code, 0, ' ', std::ios::hex);
  258. return StringUtil::format(format, exceptionAddress, violatedAddressStr, codeStr);
  259. }
  260. case STATUS_ARRAY_BOUNDS_EXCEEDED:
  261. {
  262. format = "Unhandled exception at 0x{0}. Attempting to access an out of range array element.";
  263. return StringUtil::format(format, exceptionAddress);
  264. }
  265. case EXCEPTION_DATATYPE_MISALIGNMENT:
  266. {
  267. format = "Unhandled exception at 0x{0}. Attempting to access missaligned data.";
  268. return StringUtil::format(format, exceptionAddress);
  269. }
  270. case EXCEPTION_FLT_DENORMAL_OPERAND:
  271. {
  272. format = "Unhandled exception at 0x{0}. Floating point operand too small.";
  273. return StringUtil::format(format, exceptionAddress);
  274. }
  275. case EXCEPTION_FLT_DIVIDE_BY_ZERO:
  276. {
  277. format = "Unhandled exception at 0x{0}. Floating point operation attempted to divide by zero.";
  278. return StringUtil::format(format, exceptionAddress);
  279. }
  280. case EXCEPTION_FLT_INVALID_OPERATION:
  281. {
  282. format = "Unhandled exception at 0x{0}. Floating point invalid operation.";
  283. return StringUtil::format(format, exceptionAddress);
  284. }
  285. case EXCEPTION_FLT_OVERFLOW:
  286. {
  287. format = "Unhandled exception at 0x{0}. Floating point overflow.";
  288. return StringUtil::format(format, exceptionAddress);
  289. }
  290. case EXCEPTION_FLT_UNDERFLOW:
  291. {
  292. format = "Unhandled exception at 0x{0}. Floating point underflow.";
  293. return StringUtil::format(format, exceptionAddress);
  294. }
  295. case EXCEPTION_FLT_STACK_CHECK:
  296. {
  297. format = "Unhandled exception at 0x{0}. Floating point stack overflow/underflow.";
  298. return StringUtil::format(format, exceptionAddress);
  299. }
  300. case EXCEPTION_ILLEGAL_INSTRUCTION:
  301. {
  302. format = "Unhandled exception at 0x{0}. Attempting to execute an illegal instruction.";
  303. return StringUtil::format(format, exceptionAddress);
  304. }
  305. case EXCEPTION_PRIV_INSTRUCTION:
  306. {
  307. format = "Unhandled exception at 0x{0}. Attempting to execute a private instruction.";
  308. return StringUtil::format(format, exceptionAddress);
  309. }
  310. case EXCEPTION_INT_DIVIDE_BY_ZERO:
  311. {
  312. format = "Unhandled exception at 0x{0}. Integer operation attempted to divide by zero.";
  313. return StringUtil::format(format, exceptionAddress);
  314. }
  315. case EXCEPTION_INT_OVERFLOW:
  316. {
  317. format = "Unhandled exception at 0x{0}. Integer operation result has overflown.";
  318. return StringUtil::format(format, exceptionAddress);
  319. }
  320. case EXCEPTION_STACK_OVERFLOW:
  321. {
  322. format = "Unhandled exception at 0x{0}. Stack overflow.";
  323. return StringUtil::format(format, exceptionAddress);
  324. }
  325. default:
  326. {
  327. format = "Unhandled exception at 0x{0}. Code 0x{1}.";
  328. String exceptionCode = toString((UINT32)record->ExceptionCode, 0, ' ', std::ios::hex);
  329. return StringUtil::format(format, exceptionAddress, exceptionCode);
  330. }
  331. }
  332. }
  333. struct MiniDumpParams
  334. {
  335. Path filePath;
  336. EXCEPTION_POINTERS* exceptionData;
  337. };
  338. DWORD CALLBACK win32_writeMiniDumpWorker(void* data)
  339. {
  340. MiniDumpParams* params = (MiniDumpParams*)data;
  341. HANDLE hFile = CreateFileW(params->filePath.toWString().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
  342. FILE_ATTRIBUTE_NORMAL, nullptr);
  343. if (hFile != INVALID_HANDLE_VALUE)
  344. {
  345. MINIDUMP_EXCEPTION_INFORMATION DumpExceptionInfo;
  346. DumpExceptionInfo.ThreadId = GetCurrentThreadId();
  347. DumpExceptionInfo.ExceptionPointers = params->exceptionData;
  348. DumpExceptionInfo.ClientPointers = false;
  349. MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal,
  350. &DumpExceptionInfo, nullptr, nullptr);
  351. CloseHandle(hFile);
  352. }
  353. return 0;
  354. }
  355. void win32_writeMiniDump(const Path& filePath, EXCEPTION_POINTERS* exceptionData)
  356. {
  357. MiniDumpParams param = { filePath, exceptionData };
  358. // Write minidump on a second thread in order to preserve the current thread's call stack
  359. DWORD threadId = 0;
  360. HANDLE hThread = CreateThread(nullptr, 0, &win32_writeMiniDumpWorker, &param, 0, &threadId);
  361. WaitForSingleObject(hThread, INFINITE);
  362. CloseHandle(hThread);
  363. }
  364. static const wchar_t* gMiniDumpName = L"minidump.dmp";
  365. const wchar_t* CrashHandler::CrashReportFolder = L"CrashReports/{0}/";
  366. const wchar_t* CrashHandler::CrashLogName = L"log.html";
  367. struct CrashHandler::Data
  368. {
  369. Mutex mutex;
  370. };
  371. CrashHandler::CrashHandler()
  372. {
  373. m = bs_new<Data>();
  374. }
  375. CrashHandler::~CrashHandler()
  376. {
  377. win32_unloadPSAPI();
  378. bs_delete(m);
  379. }
  380. void CrashHandler::reportCrash(const String& type, const String& description, const String& function,
  381. const String& file, UINT32 line) const
  382. {
  383. // Win32 debug methods are not thread safe
  384. Lock(m->mutex);
  385. String stackTrace = getStackTrace();
  386. StringStream errorMessageStream;
  387. errorMessageStream << "Fatal error occurred and the program has to terminate!" << std::endl;
  388. errorMessageStream << "\t\t" << type << " - " << description << std::endl;
  389. errorMessageStream << "\t\t in " << function << " [" << file << ":" << line << "]" << std::endl;
  390. errorMessageStream << std::endl;
  391. errorMessageStream << "Stack trace: " << std::endl;
  392. errorMessageStream << stackTrace;
  393. String errorMessage = errorMessageStream.str();
  394. gDebug().logError(errorMessage);
  395. Path crashFolder = getCrashFolder();
  396. FileSystem::createDir(crashFolder);
  397. gDebug().saveLog(crashFolder + WString(CrashLogName));
  398. win32_writeMiniDump(crashFolder + WString(gMiniDumpName), nullptr);
  399. WString simpleErrorMessage = L"Fatal error occurred and the program has to terminate! " \
  400. L"\n\nFor more information check the crash report located at:\n " + crashFolder.toWString();
  401. MessageBoxW(nullptr, simpleErrorMessage.c_str(), L"Banshee fatal error!", MB_OK);
  402. // Note: Potentially also log Windows Error Report and/or send crash data to server
  403. }
  404. int CrashHandler::reportCrash(void* exceptionDataPtr) const
  405. {
  406. EXCEPTION_POINTERS* exceptionData = (EXCEPTION_POINTERS*)exceptionDataPtr;
  407. // Win32 debug methods are not thread safe
  408. Lock(m->mutex);
  409. win32_initPSAPI();
  410. win32_loadSymbols();
  411. String stackTrace = win32_getStackTrace(*exceptionData->ContextRecord, 0);
  412. StringStream errorMessageStream;
  413. errorMessageStream << "Fatal error occurred and the program has to terminate!" << std::endl;
  414. errorMessageStream << "\t\t" << win32_getExceptionMessage(exceptionData->ExceptionRecord) << std::endl;;
  415. errorMessageStream << std::endl;
  416. errorMessageStream << "Stack trace: " << std::endl;
  417. errorMessageStream << stackTrace;
  418. String errorMessage = errorMessageStream.str();
  419. gDebug().logError(errorMessage);
  420. Path crashFolder = getCrashFolder();
  421. FileSystem::createDir(crashFolder);
  422. gDebug().saveLog(crashFolder + WString(CrashLogName));
  423. win32_writeMiniDump(crashFolder + WString(gMiniDumpName), exceptionData);
  424. WString simpleErrorMessage = L"Fatal error occurred and the program has to terminate! " \
  425. L"\n\nFor more information check the crash report located at:\n" + crashFolder.toWString();
  426. MessageBoxW(nullptr, simpleErrorMessage.c_str(), L"Banshee fatal error!", MB_OK);
  427. // Note: Potentially also log Windows Error Report and/or send crash data to server
  428. return EXCEPTION_EXECUTE_HANDLER;
  429. }
  430. Path CrashHandler::getCrashFolder() const
  431. {
  432. SYSTEMTIME systemTime;
  433. GetLocalTime(&systemTime);
  434. WString timeStamp = L"{0}{1}{2}_{3}{4}";
  435. WString strYear = toWString(systemTime.wYear, 4, '0');
  436. WString strMonth = toWString(systemTime.wMonth, 2, '0');
  437. WString strDay = toWString(systemTime.wDay, 2, '0');
  438. WString strHour = toWString(systemTime.wHour, 2, '0');
  439. WString strMinute = toWString(systemTime.wMinute, 2, '0');
  440. timeStamp = StringUtil::format(timeStamp, strYear, strMonth, strDay, strHour, strMinute);
  441. WString folderName = StringUtil::format(CrashReportFolder, timeStamp);
  442. return FileSystem::getWorkingDirectoryPath() + folderName;
  443. }
  444. String CrashHandler::getStackTrace()
  445. {
  446. CONTEXT context;
  447. RtlCaptureContext(&context);
  448. win32_initPSAPI();
  449. win32_loadSymbols();
  450. return win32_getStackTrace(context, 2);
  451. }
  452. CrashHandler& gCrashHandler()
  453. {
  454. return CrashHandler::instance();
  455. }
  456. }