BsWin32CrashHandler.cpp 17 KB

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