IDebugCallStack.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. // Description : A multiplatform base class for handling errors and collecting call stacks
  9. #include "CrySystem_precompiled.h"
  10. #include "IDebugCallStack.h"
  11. #include "System.h"
  12. #include <AzFramework/IO/FileOperations.h>
  13. #include <AzCore/NativeUI/NativeUIRequests.h>
  14. #include <AzCore/StringFunc/StringFunc.h>
  15. #include <AzCore/Utils/Utils.h>
  16. //#if !defined(LINUX)
  17. #include <ISystem.h>
  18. const char* const IDebugCallStack::s_szFatalErrorCode = "FATAL_ERROR";
  19. IDebugCallStack::IDebugCallStack()
  20. : m_bIsFatalError(false)
  21. , m_postBackupProcess(0)
  22. , m_memAllocFileHandle(AZ::IO::InvalidHandle)
  23. {
  24. }
  25. IDebugCallStack::~IDebugCallStack()
  26. {
  27. StopMemLog();
  28. }
  29. #if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_SINGLETON
  30. IDebugCallStack* IDebugCallStack::instance()
  31. {
  32. static IDebugCallStack sInstance;
  33. return &sInstance;
  34. }
  35. #endif
  36. void IDebugCallStack::FileCreationCallback(void (* postBackupProcess)())
  37. {
  38. m_postBackupProcess = postBackupProcess;
  39. }
  40. //////////////////////////////////////////////////////////////////////////
  41. void IDebugCallStack::LogCallstack()
  42. {
  43. AZ::Debug::Trace::Instance().PrintCallstack("", 2);
  44. }
  45. const char* IDebugCallStack::TranslateExceptionCode(DWORD dwExcept)
  46. {
  47. switch (dwExcept)
  48. {
  49. #if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_TRANSLATE
  50. case EXCEPTION_ACCESS_VIOLATION:
  51. return "EXCEPTION_ACCESS_VIOLATION";
  52. break;
  53. case EXCEPTION_DATATYPE_MISALIGNMENT:
  54. return "EXCEPTION_DATATYPE_MISALIGNMENT";
  55. break;
  56. case EXCEPTION_BREAKPOINT:
  57. return "EXCEPTION_BREAKPOINT";
  58. break;
  59. case EXCEPTION_SINGLE_STEP:
  60. return "EXCEPTION_SINGLE_STEP";
  61. break;
  62. case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
  63. return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
  64. break;
  65. case EXCEPTION_FLT_DENORMAL_OPERAND:
  66. return "EXCEPTION_FLT_DENORMAL_OPERAND";
  67. break;
  68. case EXCEPTION_FLT_DIVIDE_BY_ZERO:
  69. return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
  70. break;
  71. case EXCEPTION_FLT_INEXACT_RESULT:
  72. return "EXCEPTION_FLT_INEXACT_RESULT";
  73. break;
  74. case EXCEPTION_FLT_INVALID_OPERATION:
  75. return "EXCEPTION_FLT_INVALID_OPERATION";
  76. break;
  77. case EXCEPTION_FLT_OVERFLOW:
  78. return "EXCEPTION_FLT_OVERFLOW";
  79. break;
  80. case EXCEPTION_FLT_STACK_CHECK:
  81. return "EXCEPTION_FLT_STACK_CHECK";
  82. break;
  83. case EXCEPTION_FLT_UNDERFLOW:
  84. return "EXCEPTION_FLT_UNDERFLOW";
  85. break;
  86. case EXCEPTION_INT_DIVIDE_BY_ZERO:
  87. return "EXCEPTION_INT_DIVIDE_BY_ZERO";
  88. break;
  89. case EXCEPTION_INT_OVERFLOW:
  90. return "EXCEPTION_INT_OVERFLOW";
  91. break;
  92. case EXCEPTION_PRIV_INSTRUCTION:
  93. return "EXCEPTION_PRIV_INSTRUCTION";
  94. break;
  95. case EXCEPTION_IN_PAGE_ERROR:
  96. return "EXCEPTION_IN_PAGE_ERROR";
  97. break;
  98. case EXCEPTION_ILLEGAL_INSTRUCTION:
  99. return "EXCEPTION_ILLEGAL_INSTRUCTION";
  100. break;
  101. case EXCEPTION_NONCONTINUABLE_EXCEPTION:
  102. return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
  103. break;
  104. case EXCEPTION_STACK_OVERFLOW:
  105. return "EXCEPTION_STACK_OVERFLOW";
  106. break;
  107. case EXCEPTION_INVALID_DISPOSITION:
  108. return "EXCEPTION_INVALID_DISPOSITION";
  109. break;
  110. case EXCEPTION_GUARD_PAGE:
  111. return "EXCEPTION_GUARD_PAGE";
  112. break;
  113. case EXCEPTION_INVALID_HANDLE:
  114. return "EXCEPTION_INVALID_HANDLE";
  115. break;
  116. //case EXCEPTION_POSSIBLE_DEADLOCK: return "EXCEPTION_POSSIBLE_DEADLOCK"; break ;
  117. case STATUS_FLOAT_MULTIPLE_FAULTS:
  118. return "STATUS_FLOAT_MULTIPLE_FAULTS";
  119. break;
  120. case STATUS_FLOAT_MULTIPLE_TRAPS:
  121. return "STATUS_FLOAT_MULTIPLE_TRAPS";
  122. break;
  123. #endif
  124. default:
  125. return "Unknown";
  126. break;
  127. }
  128. }
  129. void IDebugCallStack::PutVersion(char* str, size_t length)
  130. {
  131. AZ_PUSH_DISABLE_WARNING(4996, "-Wunknown-warning-option")
  132. if (!gEnv || !gEnv->pSystem)
  133. {
  134. return;
  135. }
  136. char sFileVersion[128];
  137. gEnv->pSystem->GetFileVersion().ToString(sFileVersion, sizeof(sFileVersion));
  138. char sProductVersion[128];
  139. gEnv->pSystem->GetProductVersion().ToString(sProductVersion, sizeof(sFileVersion));
  140. //! Get time.
  141. time_t ltime;
  142. time(&ltime);
  143. tm* today = localtime(&ltime);
  144. char s[1024];
  145. //! Use strftime to build a customized time string.
  146. strftime(s, 128, "Logged at %c\n", today);
  147. azstrcat(str, length, s);
  148. sprintf_s(s, "FileVersion: %s\n", sFileVersion);
  149. azstrcat(str, length, s);
  150. sprintf_s(s, "ProductVersion: %s\n", sProductVersion);
  151. azstrcat(str, length, s);
  152. if (gEnv->pLog)
  153. {
  154. const char* logfile = gEnv->pLog->GetFileName();
  155. if (logfile)
  156. {
  157. azsnprintf (s, AZ_ARRAY_SIZE(s), "LogFile: %s\n", logfile);
  158. azstrcat(str, length, s);
  159. }
  160. }
  161. AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
  162. azstrcat(str, length, "ProjectDir: ");
  163. azstrcat(str, length, projectPath.c_str());
  164. azstrcat(str, length, "\n");
  165. #if AZ_LEGACY_CRYSYSTEM_TRAIT_DEBUGCALLSTACK_APPEND_MODULENAME
  166. AZ::Utils::GetExecutablePath(s, sizeof(s));
  167. // Log EXE filename only if possible (not full EXE path which could contain sensitive info)
  168. AZStd::string exeName;
  169. if (AZ::StringFunc::Path::GetFullFileName(s, exeName))
  170. {
  171. azstrcat(str, length, "Executable: ");
  172. azstrcat(str, length, exeName.c_str());
  173. # ifdef AZ_DEBUG_BUILD
  174. azstrcat(str, length, " (debug: yes");
  175. # else
  176. azstrcat(str, length, " (debug: no");
  177. # endif
  178. }
  179. #endif
  180. AZ_POP_DISABLE_WARNING
  181. }
  182. //Crash the application, in this way the debug callstack routine will be called and it will create all the necessary files (error.log, dump, and eventually screenshot)
  183. void IDebugCallStack::FatalError(const char* description)
  184. {
  185. m_bIsFatalError = true;
  186. WriteLineToLog(description);
  187. #ifndef _RELEASE
  188. bool bShowDebugScreen = g_cvars.sys_no_crash_dialog == 0;
  189. // showing the debug screen is not safe when not called from mainthread
  190. // it normally leads to a infinity recursion followed by a stack overflow, preventing
  191. // useful call stacks, thus they are disabled
  192. bShowDebugScreen = bShowDebugScreen && gEnv->mMainThreadId == CryGetCurrentThreadId();
  193. if (bShowDebugScreen)
  194. {
  195. EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "Open 3D Engine Fatal Error", description, false);
  196. }
  197. #endif
  198. #if defined(WIN32) || !defined(_RELEASE)
  199. int* p = 0x0;
  200. *p = 1; // we're intentionally crashing here
  201. #endif
  202. }
  203. void IDebugCallStack::WriteLineToLog(const char* format, ...)
  204. {
  205. va_list ArgList;
  206. char szBuffer[MAX_WARNING_LENGTH];
  207. va_start(ArgList, format);
  208. vsnprintf_s(szBuffer, sizeof(szBuffer), sizeof(szBuffer) - 1, format, ArgList);
  209. azstrcat(szBuffer, MAX_WARNING_LENGTH, "\n");
  210. szBuffer[sizeof(szBuffer) - 1] = '\0';
  211. va_end(ArgList);
  212. AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
  213. AZ::IO::FileIOBase::GetDirectInstance()->Open("@log@\\error.log", AZ::IO::GetOpenModeFromStringMode("a+t"), fileHandle);
  214. if (fileHandle != AZ::IO::InvalidHandle)
  215. {
  216. AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, szBuffer, strlen(szBuffer));
  217. AZ::IO::FileIOBase::GetDirectInstance()->Flush(fileHandle);
  218. AZ::IO::FileIOBase::GetDirectInstance()->Close(fileHandle);
  219. }
  220. }
  221. //////////////////////////////////////////////////////////////////////////
  222. void IDebugCallStack::StartMemLog()
  223. {
  224. AZ::IO::FileIOBase::GetDirectInstance()->Open("@log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle);
  225. assert(m_memAllocFileHandle != AZ::IO::InvalidHandle);
  226. }
  227. //////////////////////////////////////////////////////////////////////////
  228. void IDebugCallStack::StopMemLog()
  229. {
  230. if (m_memAllocFileHandle != AZ::IO::InvalidHandle)
  231. {
  232. AZ::IO::FileIOBase::GetDirectInstance()->Close(m_memAllocFileHandle);
  233. m_memAllocFileHandle = AZ::IO::InvalidHandle;
  234. }
  235. }
  236. //#endif //!defined(LINUX)