DebugCallStack.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "DebugCallStack.h"
  9. #include "CrySystem_precompiled.h"
  10. #if defined(WIN32) || defined(WIN64)
  11. #include "System.h"
  12. #include <CryPath.h>
  13. #include <IConsole.h>
  14. #include <AzCore/Debug/StackTracer.h>
  15. #include <AzCore/Interface/Interface.h>
  16. #include <AzCore/NativeUI/NativeUIRequests.h>
  17. #include <AzCore/Settings/SettingsRegistry.h>
  18. #include <AzCore/Utils/Utils.h>
  19. #include <AzCore/std/parallel/spin_mutex.h>
  20. #include <DbgHelp.h>
  21. #include <Shellapi.h>
  22. #define MAX_PATH_LENGTH 1024
  23. static HWND hwndException = 0;
  24. static bool g_bUserDialog = true; // true=on crash show dialog box, false=supress user interaction
  25. static constexpr const char* SettingKey_IssueReportLink = "/O3DE/Settings/Links/Issue/Create";
  26. static constexpr const char* IssueReportLinkFallback = "https://github.com/o3de/o3de/issues/new/choose";
  27. extern int prev_sys_float_exceptions;
  28. DWORD g_idDebugThreads[10];
  29. const char* g_nameDebugThreads[10];
  30. int g_nDebugThreads = 0;
  31. AZStd::spin_mutex g_lockThreadDumpList;
  32. static bool IsFloatingPointException(EXCEPTION_POINTERS* pex);
  33. extern LONG WINAPI CryEngineExceptionFilterWER(struct _EXCEPTION_POINTERS* pExceptionPointers);
  34. extern LONG WINAPI
  35. CryEngineExceptionFilterMiniDump(struct _EXCEPTION_POINTERS* pExceptionPointers, const char* szDumpPath, MINIDUMP_TYPE mdumpValue);
  36. void MarkThisThreadForDebugging(const char* name);
  37. void UnmarkThisThreadFromDebugging();
  38. void UpdateFPExceptionsMaskForThreads();
  39. //=============================================================================
  40. CONTEXT CaptureCurrentContext()
  41. {
  42. CONTEXT context;
  43. memset(&context, 0, sizeof(context));
  44. context.ContextFlags = CONTEXT_FULL;
  45. RtlCaptureContext(&context);
  46. return context;
  47. }
  48. LONG __stdcall CryUnhandledExceptionHandler(EXCEPTION_POINTERS* pex)
  49. {
  50. return DebugCallStack::instance()->handleException(pex);
  51. }
  52. BOOL CALLBACK EnumModules(PCSTR ModuleName, DWORD64 BaseOfDll, PVOID UserContext)
  53. {
  54. DebugCallStack::TModules& modules = *static_cast<DebugCallStack::TModules*>(UserContext);
  55. modules[(void*)BaseOfDll] = ModuleName;
  56. return TRUE;
  57. }
  58. //=============================================================================
  59. // Class Statics
  60. //=============================================================================
  61. // Return single instance of class.
  62. IDebugCallStack* IDebugCallStack::instance()
  63. {
  64. static DebugCallStack sInstance;
  65. return &sInstance;
  66. }
  67. //------------------------------------------------------------------------------------------------------------------------
  68. // Sets up the symbols for functions in the debug file.
  69. //------------------------------------------------------------------------------------------------------------------------
  70. DebugCallStack::DebugCallStack()
  71. : prevExceptionHandler(0)
  72. , m_pSystem(0)
  73. , m_nSkipNumFunctions(0)
  74. , m_bCrash(false)
  75. , m_szBugMessage(NULL)
  76. {
  77. }
  78. DebugCallStack::~DebugCallStack()
  79. {
  80. }
  81. void DebugCallStack::RemoveOldFiles()
  82. {
  83. RemoveFile("error.log");
  84. RemoveFile("error.bmp");
  85. RemoveFile("error.dmp");
  86. }
  87. void DebugCallStack::RemoveFile(const char* szFileName)
  88. {
  89. FILE* pFile = nullptr;
  90. azfopen(&pFile, szFileName, "r");
  91. const bool bFileExists = (pFile != NULL);
  92. if (bFileExists)
  93. {
  94. fclose(pFile);
  95. WriteLineToLog("Removing file \"%s\"...", szFileName);
  96. if (remove(szFileName) == 0)
  97. {
  98. WriteLineToLog("File successfully removed.");
  99. }
  100. else
  101. {
  102. WriteLineToLog("Couldn't remove file!");
  103. }
  104. }
  105. }
  106. void DebugCallStack::installErrorHandler(ISystem* pSystem)
  107. {
  108. m_pSystem = pSystem;
  109. prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler);
  110. MarkThisThreadForDebugging("main");
  111. }
  112. //////////////////////////////////////////////////////////////////////////
  113. void DebugCallStack::SetUserDialogEnable(const bool bUserDialogEnable)
  114. {
  115. g_bUserDialog = bUserDialogEnable;
  116. }
  117. void MarkThisThreadForDebugging(const char* name)
  118. {
  119. AZStd::scoped_lock lock(g_lockThreadDumpList);
  120. DWORD id = GetCurrentThreadId();
  121. if (g_nDebugThreads == sizeof(g_idDebugThreads) / sizeof(g_idDebugThreads[0]))
  122. {
  123. return;
  124. }
  125. for (int i = 0; i < g_nDebugThreads; i++)
  126. {
  127. if (g_idDebugThreads[i] == id)
  128. {
  129. return;
  130. }
  131. }
  132. g_nameDebugThreads[g_nDebugThreads] = name;
  133. g_idDebugThreads[g_nDebugThreads++] = id;
  134. ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions);
  135. }
  136. void UnmarkThisThreadFromDebugging()
  137. {
  138. AZStd::scoped_lock lock(g_lockThreadDumpList);
  139. DWORD id = GetCurrentThreadId();
  140. for (int i = g_nDebugThreads - 1; i >= 0; i--)
  141. {
  142. if (g_idDebugThreads[i] == id)
  143. {
  144. memmove(g_idDebugThreads + i, g_idDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_idDebugThreads[0]));
  145. memmove(g_nameDebugThreads + i, g_nameDebugThreads + i + 1, (g_nDebugThreads - 1 - i) * sizeof(g_nameDebugThreads[0]));
  146. --g_nDebugThreads;
  147. }
  148. }
  149. }
  150. void UpdateFPExceptionsMaskForThreads()
  151. {
  152. int mask = -iszero(g_cvars.sys_float_exceptions);
  153. CONTEXT ctx;
  154. for (int i = 0; i < g_nDebugThreads; i++)
  155. {
  156. if (g_idDebugThreads[i] != GetCurrentThreadId())
  157. {
  158. HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]);
  159. ctx.ContextFlags = CONTEXT_ALL;
  160. SuspendThread(hThread);
  161. GetThreadContext(hThread, &ctx);
  162. #ifndef WIN64
  163. (ctx.FloatSave.ControlWord |= 7) &= ~5 | mask;
  164. (*(WORD*)(ctx.ExtendedRegisters + 24) |= 0x280) &= ~0x280 | mask;
  165. #else
  166. (ctx.FltSave.ControlWord |= 7) &= ~5 | mask;
  167. (ctx.FltSave.MxCsr |= 0x280) &= ~0x280 | mask;
  168. #endif
  169. SetThreadContext(hThread, &ctx);
  170. ResumeThread(hThread);
  171. }
  172. }
  173. }
  174. //////////////////////////////////////////////////////////////////////////
  175. int DebugCallStack::handleException(EXCEPTION_POINTERS* exception_pointer)
  176. {
  177. AZ_TracePrintf("Exit", "Exception with exit code: 0x%x", exception_pointer->ExceptionRecord->ExceptionCode);
  178. AZ::Debug::Trace::Instance().PrintCallstack("Exit");
  179. if (gEnv == NULL)
  180. {
  181. return EXCEPTION_EXECUTE_HANDLER;
  182. }
  183. ResetFPU(exception_pointer);
  184. prev_sys_float_exceptions = 0;
  185. const int cached_sys_float_exceptions = g_cvars.sys_float_exceptions;
  186. ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(0);
  187. if (g_cvars.sys_WER)
  188. {
  189. gEnv->pLog->FlushAndClose();
  190. return CryEngineExceptionFilterWER(exception_pointer);
  191. }
  192. if (g_cvars.sys_no_crash_dialog)
  193. {
  194. DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
  195. SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);
  196. }
  197. m_bCrash = true;
  198. if (g_cvars.sys_no_crash_dialog)
  199. {
  200. DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
  201. SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);
  202. }
  203. static bool firstTime = true;
  204. if (g_cvars.sys_dump_aux_threads)
  205. {
  206. for (int i = 0; i < g_nDebugThreads; i++)
  207. {
  208. if (g_idDebugThreads[i] != GetCurrentThreadId())
  209. {
  210. SuspendThread(OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]));
  211. }
  212. }
  213. }
  214. // uninstall our exception handler.
  215. SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)prevExceptionHandler);
  216. if (!firstTime)
  217. {
  218. WriteLineToLog("Critical Exception! Called Multiple Times!");
  219. gEnv->pLog->FlushAndClose();
  220. // Exception called more then once.
  221. return EXCEPTION_EXECUTE_HANDLER;
  222. }
  223. // Print exception info:
  224. {
  225. char excCode[80];
  226. char excAddr[80];
  227. WriteLineToLog("<CRITICAL EXCEPTION>");
  228. sprintf_s(excAddr, "0x%04X:0x%p", exception_pointer->ContextRecord->SegCs, exception_pointer->ExceptionRecord->ExceptionAddress);
  229. sprintf_s(excCode, "0x%08lX", exception_pointer->ExceptionRecord->ExceptionCode);
  230. WriteLineToLog("Exception: %s, at Address: %s", excCode, excAddr);
  231. }
  232. firstTime = false;
  233. const UserPostExceptionChoice ret = SubmitBugAndAskToRecoverOrCrash(exception_pointer);
  234. if (ret != UserPostExceptionChoice::Recover)
  235. {
  236. CryEngineExceptionFilterWER(exception_pointer);
  237. }
  238. gEnv->pLog->FlushAndClose();
  239. if (exception_pointer->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
  240. {
  241. // This is non continuable exception. abort application now.
  242. exit(exception_pointer->ExceptionRecord->ExceptionCode);
  243. }
  244. if (ret == UserPostExceptionChoice::Exit)
  245. {
  246. // Immediate exit.
  247. // on windows, exit() and _exit() do all sorts of things, unfortuantely
  248. // TerminateProcess is the only way to die.
  249. TerminateProcess(
  250. GetCurrentProcess(), exception_pointer->ExceptionRecord->ExceptionCode); // we crashed, so don't return a zero exit code!
  251. // on linux based systems, _exit will not call ATEXIT and other things, which makes it more suitable for termination in an emergency
  252. // such as an unhandled exception. however, this function is a windows exception handler.
  253. }
  254. else if (ret == UserPostExceptionChoice::Recover)
  255. {
  256. #ifndef WIN64
  257. exception_pointer->ContextRecord->FloatSave.StatusWord &= ~31;
  258. exception_pointer->ContextRecord->FloatSave.ControlWord |= 7;
  259. (*(WORD*)(exception_pointer->ContextRecord->ExtendedRegisters + 24) &= 31) |= 0x1F80;
  260. #else
  261. exception_pointer->ContextRecord->FltSave.StatusWord &= ~31;
  262. exception_pointer->ContextRecord->FltSave.ControlWord |= 7;
  263. (exception_pointer->ContextRecord->FltSave.MxCsr &= 31) |= 0x1F80;
  264. #endif
  265. firstTime = true;
  266. prevExceptionHandler = (void*)SetUnhandledExceptionFilter(CryUnhandledExceptionHandler);
  267. g_cvars.sys_float_exceptions = cached_sys_float_exceptions;
  268. ((CSystem*)gEnv->pSystem)->EnableFloatExceptions(g_cvars.sys_float_exceptions);
  269. return EXCEPTION_CONTINUE_EXECUTION;
  270. }
  271. // Continue;
  272. return EXCEPTION_EXECUTE_HANDLER;
  273. }
  274. void DebugCallStack::ReportBug(const char* szErrorMessage)
  275. {
  276. WriteLineToLog("Reporting bug: %s", szErrorMessage);
  277. m_szBugMessage = szErrorMessage;
  278. m_context = CaptureCurrentContext();
  279. SubmitBugAndAskToRecoverOrCrash(NULL);
  280. m_szBugMessage = NULL;
  281. }
  282. void DebugCallStack::dumpCallStack(AZStd::vector<AZStd::string>& funcs)
  283. {
  284. WriteLineToLog("=============================================================================");
  285. int len = (int)funcs.size();
  286. for (int i = 0; i < len; i++)
  287. {
  288. const char* str = funcs[i].c_str();
  289. WriteLineToLog("%2d) %s", len - i, str);
  290. }
  291. WriteLineToLog("=============================================================================");
  292. }
  293. //////////////////////////////////////////////////////////////////////////
  294. void DebugCallStack::SaveExceptionInfoAndShowUserReportDialogs(EXCEPTION_POINTERS* pex)
  295. {
  296. AZStd::string path("");
  297. if ((gEnv) && (gEnv->pFileIO))
  298. {
  299. const char* logAlias = gEnv->pFileIO->GetAlias("@log@");
  300. if (!logAlias)
  301. {
  302. logAlias = gEnv->pFileIO->GetAlias("@products@");
  303. }
  304. if (logAlias)
  305. {
  306. path = logAlias;
  307. path += "\\";
  308. }
  309. }
  310. AZStd::string fileName = path;
  311. fileName += "error.log";
  312. struct stat fileInfo;
  313. AZStd::string timeStamp;
  314. AZStd::string backupPath;
  315. if (gEnv->IsDedicated())
  316. {
  317. backupPath = PathUtil::ToUnixPath(PathUtil::AddSlash(path + "DumpBackups"));
  318. gEnv->pFileIO->CreatePath(backupPath.c_str());
  319. if (stat(fileName.c_str(), &fileInfo) == 0)
  320. {
  321. // Backup log
  322. tm creationTime;
  323. localtime_s(&creationTime, &fileInfo.st_mtime);
  324. char tempBuffer[32];
  325. strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime);
  326. timeStamp = tempBuffer;
  327. AZStd::string backupFileName = backupPath + timeStamp + " error.log";
  328. AZStd::wstring fileNameW;
  329. AZStd::to_wstring(fileNameW, fileName.c_str());
  330. AZStd::wstring backupFileNameW;
  331. AZStd::to_wstring(backupFileNameW, backupFileName.c_str());
  332. CopyFileW(fileNameW.c_str(), backupFileNameW.c_str(), true);
  333. }
  334. }
  335. FILE* f = nullptr;
  336. azfopen(&f, fileName.c_str(), "wt");
  337. static char errorString[s_iCallStackSize];
  338. errorString[0] = 0;
  339. // Time and Version.
  340. char versionbuf[1024];
  341. azstrcpy(versionbuf, AZ_ARRAY_SIZE(versionbuf), "");
  342. PutVersion(versionbuf, AZ_ARRAY_SIZE(versionbuf));
  343. azstrcat(errorString, AZ_ARRAY_SIZE(errorString), versionbuf);
  344. azstrcat(errorString, AZ_ARRAY_SIZE(errorString), "\n");
  345. char excCode[MAX_WARNING_LENGTH];
  346. char excAddr[80];
  347. char desc[1024];
  348. char excDesc[MAX_WARNING_LENGTH];
  349. // make sure the mouse cursor is visible
  350. ShowCursor(TRUE);
  351. const char* excName;
  352. if (m_bIsFatalError || !pex)
  353. {
  354. const char* const szMessage = m_bIsFatalError ? s_szFatalErrorCode : m_szBugMessage;
  355. excName = szMessage;
  356. azstrcpy(excCode, AZ_ARRAY_SIZE(excCode), szMessage);
  357. azstrcpy(excAddr, AZ_ARRAY_SIZE(excAddr), "");
  358. azstrcpy(desc, AZ_ARRAY_SIZE(desc), "");
  359. azstrcpy(m_excModule, AZ_ARRAY_SIZE(m_excModule), "");
  360. azstrcpy(excDesc, AZ_ARRAY_SIZE(excDesc), szMessage);
  361. }
  362. else
  363. {
  364. sprintf_s(excAddr, "0x%04X:0x%p", pex->ContextRecord->SegCs, pex->ExceptionRecord->ExceptionAddress);
  365. sprintf_s(excCode, "0x%08lX", pex->ExceptionRecord->ExceptionCode);
  366. excName = TranslateExceptionCode(pex->ExceptionRecord->ExceptionCode);
  367. azstrcpy(desc, AZ_ARRAY_SIZE(desc), "");
  368. sprintf_s(excDesc, "%s\r\n%s", excName, desc);
  369. if (pex->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
  370. {
  371. if (pex->ExceptionRecord->NumberParameters > 1)
  372. {
  373. ULONG_PTR iswrite = pex->ExceptionRecord->ExceptionInformation[0];
  374. DWORD64 accessAddr = pex->ExceptionRecord->ExceptionInformation[1];
  375. if (iswrite)
  376. {
  377. sprintf_s(desc, "Attempt to write data to address 0x%08llu\r\nThe memory could not be \"written\"", accessAddr);
  378. }
  379. else
  380. {
  381. sprintf_s(desc, "Attempt to read from address 0x%08llu\r\nThe memory could not be \"read\"", accessAddr);
  382. }
  383. }
  384. }
  385. }
  386. WriteLineToLog("Exception Code: %s", excCode);
  387. WriteLineToLog("Exception Addr: %s", excAddr);
  388. WriteLineToLog("Exception Module: %s", m_excModule);
  389. WriteLineToLog("Exception Name : %s", excName);
  390. WriteLineToLog("Exception Description: %s", desc);
  391. azstrcpy(m_excDesc, AZ_ARRAY_SIZE(m_excDesc), excDesc);
  392. azstrcpy(m_excAddr, AZ_ARRAY_SIZE(m_excAddr), excAddr);
  393. azstrcpy(m_excCode, AZ_ARRAY_SIZE(m_excCode), excCode);
  394. char errs[32768];
  395. sprintf_s(
  396. errs,
  397. "Exception Code: %s\nException Addr: %s\nException Module: %s\nException Description: %s, %s\n",
  398. excCode,
  399. excAddr,
  400. m_excModule,
  401. excName,
  402. desc);
  403. azstrcat(errs, AZ_ARRAY_SIZE(errs), "\nCall Stack Trace:\n");
  404. AZStd::vector<AZStd::string> funcs;
  405. {
  406. AZ::Debug::StackFrame frames[25];
  407. AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
  408. unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 3);
  409. if (numFrames)
  410. {
  411. AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines);
  412. for (unsigned int i = 0; i < numFrames; i++)
  413. {
  414. funcs.push_back(lines[i]);
  415. }
  416. }
  417. dumpCallStack(funcs);
  418. // Fill call stack.
  419. char str[s_iCallStackSize];
  420. azstrcpy(str, AZ_ARRAY_SIZE(str), "");
  421. for (unsigned int i = 0; i < funcs.size(); i++)
  422. {
  423. char temp[s_iCallStackSize];
  424. sprintf_s(temp, "%2zd) %s", funcs.size() - i, (const char*)funcs[i].c_str());
  425. azstrcat(str, AZ_ARRAY_SIZE(str), temp);
  426. azstrcat(str, AZ_ARRAY_SIZE(str), "\r\n");
  427. azstrcat(errs, AZ_ARRAY_SIZE(errs), temp);
  428. azstrcat(errs, AZ_ARRAY_SIZE(errs), "\n");
  429. }
  430. azstrcpy(m_excCallstack, AZ_ARRAY_SIZE(m_excCallstack), str);
  431. }
  432. azstrcat(errorString, AZ_ARRAY_SIZE(errorString), errs);
  433. if (f)
  434. {
  435. fwrite(errorString, strlen(errorString), 1, f);
  436. {
  437. if (g_cvars.sys_dump_aux_threads)
  438. {
  439. for (int i = 0; i < g_nDebugThreads; i++)
  440. {
  441. if (g_idDebugThreads[i] != GetCurrentThreadId())
  442. {
  443. fprintf(f, "\n\nSuspended thread (%s):\n", g_nameDebugThreads[i]);
  444. HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, g_idDebugThreads[i]);
  445. // mirrors the AZ::Debug::Trace::PrintCallstack() functionality, but prints to a file
  446. {
  447. AZ::Debug::StackFrame frames[10];
  448. // Without StackFrame explicit alignment frames array is aligned to 4 bytes
  449. // which causes the stack tracing to fail.
  450. AZ::Debug::SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
  451. unsigned int numFrames = AZ::Debug::StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), 0, hThread);
  452. if (numFrames)
  453. {
  454. AZ::Debug::SymbolStorage::DecodeFrames(frames, numFrames, lines);
  455. for (unsigned int i2 = 0; i2 < numFrames; ++i2)
  456. {
  457. fprintf(f, "%2d) %s\n", numFrames - i2, lines[i2]);
  458. }
  459. }
  460. }
  461. ResumeThread(hThread);
  462. }
  463. }
  464. }
  465. }
  466. fflush(f);
  467. fclose(f);
  468. }
  469. if (pex)
  470. {
  471. MINIDUMP_TYPE mdumpValue = MiniDumpNormal;
  472. bool bDump = true;
  473. switch (g_cvars.sys_dump_type)
  474. {
  475. case 0:
  476. bDump = false;
  477. break;
  478. case 1:
  479. mdumpValue = MiniDumpNormal;
  480. break;
  481. case 2:
  482. mdumpValue = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithDataSegs);
  483. break;
  484. case 3:
  485. mdumpValue = MiniDumpWithFullMemory;
  486. break;
  487. default:
  488. mdumpValue = (MINIDUMP_TYPE)g_cvars.sys_dump_type;
  489. break;
  490. }
  491. if (bDump)
  492. {
  493. fileName = path + "error.dmp";
  494. if (gEnv->IsDedicated() && stat(fileName.c_str(), &fileInfo) == 0)
  495. {
  496. // Backup dump (use timestamp from error.log if available)
  497. if (timeStamp.empty())
  498. {
  499. tm creationTime;
  500. localtime_s(&creationTime, &fileInfo.st_mtime);
  501. char tempBuffer[32];
  502. strftime(tempBuffer, sizeof(tempBuffer), "%d %b %Y (%H %M %S)", &creationTime);
  503. timeStamp = tempBuffer;
  504. }
  505. AZStd::string backupFileName = backupPath + timeStamp + " error.dmp";
  506. AZStd::wstring fileNameW;
  507. AZStd::to_wstring(fileNameW, fileName.c_str());
  508. AZStd::wstring backupFileNameW;
  509. AZStd::to_wstring(backupFileNameW, backupFileName.c_str());
  510. CopyFileW(fileNameW.c_str(), backupFileNameW.c_str(), true);
  511. }
  512. CryEngineExceptionFilterMiniDump(pex, fileName.c_str(), mdumpValue);
  513. }
  514. }
  515. // if no crash dialog don't even submit the bug
  516. if (m_postBackupProcess && g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog)
  517. {
  518. m_postBackupProcess();
  519. }
  520. else if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
  521. {
  522. AZStd::string msg = AZStd::string::format(
  523. "O3DE has encountered an unexpected error.\n\nDo you want to manually report the issue on Github ?\nInformation about the "
  524. "crash are located in %s via error.log and error.dmp",
  525. path.c_str());
  526. constexpr bool showCancel = false;
  527. AZStd::string res = nativeUI->DisplayYesNoDialog("O3DE unexpected error", msg, showCancel);
  528. if (res == "Yes")
  529. {
  530. AZStd::wstring arg(path.begin(), path.end());
  531. ShellExecuteW(nullptr, L"open", arg.c_str(), NULL, NULL, SW_SHOWNORMAL);
  532. AZStd::string reportIssueUrl;
  533. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  534. settingsRegistry->Get(reportIssueUrl, SettingKey_IssueReportLink);
  535. if (reportIssueUrl.empty())
  536. reportIssueUrl = IssueReportLinkFallback;
  537. arg = AZStd::wstring(reportIssueUrl.begin(), reportIssueUrl.end());
  538. ShellExecuteW(nullptr, L"open", arg.c_str(), NULL, NULL, SW_SHOWNORMAL);
  539. }
  540. }
  541. const bool bQuitting = !gEnv || !gEnv->pSystem || gEnv->pSystem->IsQuitting();
  542. if (g_cvars.sys_no_crash_dialog == 0 && g_bUserDialog && gEnv->IsEditor() && !bQuitting && pex)
  543. {
  544. BackupCurrentLevel();
  545. if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
  546. {
  547. constexpr bool showCancel = false;
  548. AZStd::string res = nativeUI->DisplayYesNoDialog(
  549. "Save your changes?",
  550. "Do you want to try to save your changes?\nAs O3DE is in a panic state, it might corrupt your data",
  551. showCancel);
  552. if (res == "Yes")
  553. {
  554. if (SaveCurrentLevel())
  555. nativeUI->DisplayOkDialog("Save", "Level has been successfully saved!\r\nPress Ok to proceed.", showCancel);
  556. else
  557. nativeUI->DisplayOkDialog("Save", "Error saving level.\r\nPress Ok to proceed.", showCancel);
  558. }
  559. }
  560. }
  561. if (g_cvars.sys_no_crash_dialog != 0 || !g_bUserDialog)
  562. {
  563. // terminate immediately - since we're in a crash, there is no point unwinding stack, we've already done access violation or worse.
  564. // calling exit will only cause further death down the line...
  565. TerminateProcess(GetCurrentProcess(), pex->ExceptionRecord->ExceptionCode);
  566. }
  567. }
  568. bool DebugCallStack::BackupCurrentLevel()
  569. {
  570. CSystem* pSystem = static_cast<CSystem*>(m_pSystem);
  571. if (pSystem && pSystem->GetUserCallback())
  572. {
  573. return pSystem->GetUserCallback()->OnBackupDocument();
  574. }
  575. return false;
  576. }
  577. bool DebugCallStack::SaveCurrentLevel()
  578. {
  579. CSystem* pSystem = static_cast<CSystem*>(m_pSystem);
  580. if (pSystem && pSystem->GetUserCallback())
  581. {
  582. return pSystem->GetUserCallback()->OnSaveDocument();
  583. }
  584. return false;
  585. }
  586. DebugCallStack::UserPostExceptionChoice DebugCallStack::SubmitBugAndAskToRecoverOrCrash(EXCEPTION_POINTERS* exception_pointer)
  587. {
  588. UserPostExceptionChoice ret = UserPostExceptionChoice::Exit;
  589. assert(!hwndException);
  590. RemoveOldFiles();
  591. AZ::Debug::Trace::Instance().PrintCallstack("", 2);
  592. SaveExceptionInfoAndShowUserReportDialogs(exception_pointer);
  593. if (IsFloatingPointException(exception_pointer))
  594. {
  595. ret = AskUserToRecoverOrCrash(exception_pointer);
  596. }
  597. return ret;
  598. }
  599. void DebugCallStack::ResetFPU(EXCEPTION_POINTERS* pex)
  600. {
  601. if (IsFloatingPointException(pex))
  602. {
  603. // How to reset FPU: http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_10310953.html
  604. _clearfp();
  605. #ifndef WIN64
  606. pex->ContextRecord->FloatSave.ControlWord |= 0x2F;
  607. pex->ContextRecord->FloatSave.StatusWord &= ~0x8080;
  608. #endif
  609. }
  610. }
  611. AZStd::string DebugCallStack::GetModuleNameForAddr(void* addr)
  612. {
  613. if (m_modules.empty())
  614. {
  615. return "[unknown]";
  616. }
  617. if (addr < m_modules.begin()->first)
  618. {
  619. return "[unknown]";
  620. }
  621. TModules::const_iterator it = m_modules.begin();
  622. TModules::const_iterator end = m_modules.end();
  623. for (; ++it != end;)
  624. {
  625. if (addr < it->first)
  626. {
  627. return (--it)->second;
  628. }
  629. }
  630. // if address is higher than the last module, we simply assume it is in the last module.
  631. return m_modules.rbegin()->second;
  632. }
  633. void DebugCallStack::GetProcNameForAddr(void* addr, AZStd::string& procName, void*& baseAddr, AZStd::string& filename, int& line)
  634. {
  635. AZ::Debug::SymbolStorage::StackLine func, file, module;
  636. AZ::Debug::SymbolStorage::FindFunctionFromIP(addr, &func, &file, &module, line, baseAddr);
  637. procName = func;
  638. filename = file;
  639. }
  640. AZStd::string DebugCallStack::GetCurrentFilename()
  641. {
  642. char fullpath[MAX_PATH_LENGTH + 1];
  643. AZ::Utils::GetExecutablePath(fullpath, MAX_PATH_LENGTH);
  644. return fullpath;
  645. }
  646. static bool IsFloatingPointException(EXCEPTION_POINTERS* pex)
  647. {
  648. if (!pex)
  649. {
  650. return false;
  651. }
  652. DWORD exceptionCode = pex->ExceptionRecord->ExceptionCode;
  653. switch (exceptionCode)
  654. {
  655. case EXCEPTION_FLT_DENORMAL_OPERAND:
  656. case EXCEPTION_FLT_DIVIDE_BY_ZERO:
  657. case EXCEPTION_FLT_INEXACT_RESULT:
  658. case EXCEPTION_FLT_INVALID_OPERATION:
  659. case EXCEPTION_FLT_OVERFLOW:
  660. case EXCEPTION_FLT_UNDERFLOW:
  661. case STATUS_FLOAT_MULTIPLE_FAULTS:
  662. case STATUS_FLOAT_MULTIPLE_TRAPS:
  663. return true;
  664. default:
  665. return false;
  666. }
  667. }
  668. DebugCallStack::UserPostExceptionChoice DebugCallStack::AskUserToRecoverOrCrash(EXCEPTION_POINTERS* exception_pointer)
  669. {
  670. if (exception_pointer->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
  671. {
  672. return UserPostExceptionChoice::Exit;
  673. }
  674. UserPostExceptionChoice res = UserPostExceptionChoice::Exit;
  675. if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
  676. {
  677. DebugCallStack* pDCS = static_cast<DebugCallStack*>(DebugCallStack::instance());
  678. AZStd::string msg = AZStd::string::format(
  679. "O3DE encountered an error but can recover from it.\nDo you want to try to recover ?\n\nException Code: %s\nException Addr: "
  680. "%s\nException Module: %s\nException Description: %s\nCallstack:\n%.600s",
  681. pDCS->m_excCode,
  682. pDCS->m_excAddr,
  683. pDCS->m_excModule,
  684. pDCS->m_excDesc,
  685. pDCS->m_excCallstack);
  686. constexpr bool showCancel = false;
  687. AZStd::string dialogRes = nativeUI->DisplayYesNoDialog("Try to recover?", msg, showCancel);
  688. if (dialogRes == "Yes")
  689. {
  690. res = UserPostExceptionChoice::Recover;
  691. }
  692. }
  693. return res;
  694. }
  695. #else
  696. void MarkThisThreadForDebugging(const char*)
  697. {
  698. }
  699. void UnmarkThisThreadFromDebugging()
  700. {
  701. }
  702. void UpdateFPExceptionsMaskForThreads()
  703. {
  704. }
  705. #endif // WIN32