DebugCallStack.cpp 29 KB

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