BsWin32CrashHandler.cpp 15 KB

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