Sfoglia il codice sorgente

Implement CrashHandler methods on Unix

Also, this commit extracts bits of code from the Windows-only
implementation to minimize platform-specific code and to make behavior
across platforms.
Marc Legendre 9 anni fa
parent
commit
cf14356fb4

+ 2 - 0
Source/BansheeUtility/CMakeSources.cmake

@@ -27,6 +27,7 @@ set(BS_BANSHEEUTILITY_SRC_WIN32
 )
 
 set(BS_BANSHEEUTILITY_SRC_UNIX
+	"Source/Unix/BsUnixCrashHandler.cpp"
 	"Source/Unix/BsUnixPlatformUtility.cpp"
 )
 
@@ -215,6 +216,7 @@ set(BS_BANSHEEUTILITY_INC_MATH
 
 set(BS_BANSHEEUTILITY_SRC_ERROR
 	"Source/BsException.cpp"
+	"Source/BsCrashHandler.cpp"
 )
 
 set(BS_BANSHEEUTILITY_INC_SERIALIZATION

+ 27 - 9
Source/BansheeUtility/Include/BsCrashHandler.h

@@ -42,7 +42,7 @@ namespace BansheeEngine
 
 		/**
 		 * Records a crash with a custom error message.
-		 * 			
+		 *
 		 * @param[in]	type		Type of the crash that occurred. For example "InvalidParameter".
 		 * @param[in]	description	More detailed description of the issue that caused the crash.
 		 * @param[in]	function	Optional name of the function where the error occurred.
@@ -54,8 +54,8 @@ namespace BansheeEngine
 
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 		/**
-		 * Records a crash resulting from a Windows-specific SEH exception. 
-		 * 			
+		 * Records a crash resulting from a Windows-specific SEH exception.
+		 *
 		 * @param[in]	exceptionData	Exception data returned from GetExceptionInformation()
 		 * @return						Code that signals the __except exception handler on how to proceed.
 		 *
@@ -65,24 +65,42 @@ namespace BansheeEngine
 #endif
 
 		/**
-		 * Returns a string containing a current stack trace. If function can be found in the symbol table its readable 
+		 * Returns a string containing a current stack trace. If function can be found in the symbol table its readable
 		 * name will be present in the stack trace, otherwise just its address.
-		 * 						
+		 *
 		 * @return	String containing the call stack with each function on its own line.
 		 */
 		static String getStackTrace();
 	private:
-		/** Returns path to the folder into which to store the crash reports. */
-		Path getCrashFolder() const;
+		/** Does what it says. Internal utility function used by reportCrash(). */
+		void logErrorAndStackTrace(const String& message, const String& stackTrace) const;
+		/** Does what it says. Internal utility function used by reportCrash(). */
+		void logErrorAndStackTrace(const String& type,
+		                           const String& description,
+		                           const String& function,
+		                           const String& file,
+		                           UINT32 line) const;
+		/** Does what it says. Internal utility function used by reportCrash(). */
+		void saveCrashLog() const;
+		/** Creates the crash report directory and returns its path. */
+		static const Path& getCrashFolder();
+		/** Returns the current time as a string timestamp.  This is used
+		 * to name the crash report directory.. */
+		static String getCrashTimestamp();
 
 		/** Returns a singleton instance of this module. */
 		static CrashHandler*& _instance() { static CrashHandler* inst = nullptr; return inst; }
 
-		static const wchar_t* CrashReportFolder;
-		static const wchar_t* CrashLogName;
+		/** The name of the crash reports directory. */
+		static const String sCrashReportFolder;
+		/** The name of the HTML crash log file. */
+		static const String sCrashLogName;
 
+		static const String sFatalErrorMsg;
+#if BS_PLATFORM == BS_PLATFORM_WIN32
 		struct Data;
 		Data* m;
+#endif
 	};
 
 	/** Easier way of accessing the CrashHandler. */

+ 65 - 0
Source/BansheeUtility/Source/BsCrashHandler.cpp

@@ -0,0 +1,65 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsCrashHandler.h"
+
+#include "BsDebug.h"
+#include "BsFileSystem.h"
+#include "BsPath.h"
+
+namespace BansheeEngine
+{
+	const String CrashHandler::sCrashReportFolder = "CrashReports";
+	const String CrashHandler::sCrashLogName = "log.html";
+	const String CrashHandler::sFatalErrorMsg =
+	    "A fatal error occurred and the program has to terminate!";
+
+	CrashHandler& gCrashHandler()
+	{
+		return CrashHandler::instance();
+	}
+
+	const Path& CrashHandler::getCrashFolder()
+	{
+		static const Path path =
+			FileSystem::getWorkingDirectoryPath()
+			+ Path(sCrashReportFolder)
+			+ Path(getCrashTimestamp());
+		static bool first = true;
+		if (first) {
+			FileSystem::createDir(path);
+			first = false;
+		}
+		return path;
+	}
+
+	void CrashHandler::logErrorAndStackTrace(const String& errorMsg,
+	                                         const String& stackTrace) const
+	{
+		StringStream errorMessage;
+		errorMessage << sFatalErrorMsg << std::endl;
+		errorMessage << errorMsg;
+		errorMessage << "\n\nStack trace: \n";
+		errorMessage << stackTrace;
+
+		gDebug().logError(errorMessage.str());
+	}
+
+	void CrashHandler::logErrorAndStackTrace(const String& type,
+	                                         const String& description,
+	                                         const String& function,
+	                                         const String& file,
+	                                         UINT32 line) const
+	{
+		StringStream errorMessage;
+		errorMessage << "  - Error: " << type << std::endl;
+		errorMessage << "  - Description: " << description << std::endl;
+		errorMessage << "  - In function: " << function << std::endl;
+		errorMessage << "  - In file: " << file << ":" << line;
+		logErrorAndStackTrace(errorMessage.str(), getStackTrace());
+	}
+
+	void CrashHandler::saveCrashLog() const
+	{
+		gDebug().saveLog(getCrashFolder() + toWString(sCrashLogName));
+	}
+}

+ 107 - 0
Source/BansheeUtility/Source/Unix/BsUnixCrashHandler.cpp

@@ -0,0 +1,107 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsCrashHandler.h"
+
+#include "BsDebug.h"
+#include "BsFileSystem.h"
+
+#include <cxxabi.h>
+#include <execinfo.h>
+
+#include <ctime>
+#include <sstream>
+
+namespace BansheeEngine
+{
+	CrashHandler::CrashHandler() {}
+	CrashHandler::~CrashHandler() {}
+
+	String CrashHandler::getCrashTimestamp()
+	{
+		std::time_t t = time(0);
+		struct tm *now = localtime(&t);
+
+		String timeStamp = "{0}{1}{2}_{3}{4}";
+		String strYear = toString(now->tm_year, 4, '0');
+		String strMonth = toString(now->tm_mon, 2, '0');
+		String strDay = toString(now->tm_mday, 2, '0');
+		String strHour = toString(now->tm_hour, 2, '0');
+		String strMinute = toString(now->tm_min, 2, '0');
+		return StringUtil::format(timeStamp, strYear, strMonth, strDay, strHour, strMinute);
+	}
+
+	String CrashHandler::getStackTrace()
+	{
+		StringStream stackTrace;
+		void *trace[BS_MAX_STACKTRACE_DEPTH];
+
+		int trace_size = backtrace(trace, BS_MAX_STACKTRACE_DEPTH);
+		char **messages = backtrace_symbols(trace, trace_size);
+
+		// Most lines returned by backtrace_symbols() look like this:
+		//
+		//     <path/to/binary>(mangled_symbol+offset) [address]
+		//
+		// For those lines, we demangle the symbol with abi::__cxa_demangle(),
+		// others are good as is.
+
+		for (int i = 0; i < trace_size && messages != NULL; ++i)
+		{
+			// Try to find the characters surrounding the mangled name: '(' and '+'
+			char *mangled_name = NULL, *offset_begin = NULL, *offset_end = NULL;
+			for (char *p = messages[i]; *p; ++p)
+			{
+				if (*p == '(')
+					mangled_name = p;
+				else if (*p == '+')
+					offset_begin = p;
+				else if (*p == ')') {
+					offset_end = p;
+					break;
+				}
+			}
+
+			bool lineContainsMangledSymbol = mangled_name != NULL
+			                                 && offset_begin != NULL
+			                                 && offset_end != NULL
+			                                 && mangled_name < offset_begin;
+
+			stackTrace << toString(i) << ") ";
+
+			if (lineContainsMangledSymbol)
+			{
+				*mangled_name++ = '\0';
+				*offset_begin++ = '\0';
+				*offset_end++ = '\0';
+
+				int status;
+				char *real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
+				char *output_name = status == 0 /* Demangling successful */? real_name : mangled_name;
+				stackTrace << String(messages[i])
+				           << ": " << output_name
+				           << "+" << offset_begin << offset_end;
+
+				free(real_name);
+			}
+			else
+				stackTrace << String(messages[i]);
+
+			if (i < trace_size - 1)
+				stackTrace << "\n";
+		}
+
+		free(messages);
+
+		return stackTrace.str();
+	}
+
+	void CrashHandler::reportCrash(const String& type,
+	                               const String& description,
+	                               const String& function,
+	                               const String& file,
+	                               UINT32 line) const
+	{
+		logErrorAndStackTrace(type, description, function, file, line);
+		saveCrashLog();
+	}
+}

+ 43 - 76
Source/BansheeUtility/Source/Win32/BsWin32CrashHandler.cpp

@@ -1,5 +1,7 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsCrashHandler.h"
+
 #include "BsPrerequisitesUtil.h"
 #include "BsDebug.h"
 #include "BsDynLib.h"
@@ -12,8 +14,21 @@
 #include "DbgHelp.h"
 #pragma warning(default : 4091)
 
+static const String sMiniDumpName = "minidump.dmp";
+static const WString sMoreInfoMsg = ;
+
 namespace BansheeEngine
 {
+	CrashHandler::CrashHandler()
+	{
+		m = bs_new<Data>();
+	}
+
+	CrashHandler::~CrashHandler()
+	{
+		bs_delete(m);
+	}
+
 	/**
 	 * Returns the raw stack trace using the provided context. Raw stack trace contains only function addresses.
 	 * 			
@@ -431,56 +446,33 @@ namespace BansheeEngine
 		CloseHandle(hThread);
 	}
 
-	static const wchar_t* gMiniDumpName = L"minidump.dmp";
-	const wchar_t* CrashHandler::CrashReportFolder = L"CrashReports/{0}/";
-	const wchar_t* CrashHandler::CrashLogName = L"log.html";
-
 	struct CrashHandler::Data
 	{
 		Mutex mutex;
 	};
 
-	CrashHandler::CrashHandler()
-	{
-		m = bs_new<Data>();
-	}
-
-	CrashHandler::~CrashHandler()
+	void win32_popupErrorMessageBox()
 	{
-		win32_unloadPSAPI();
+		WString simpleErrorMessage = toWString(sFatalErrorMsg)
+			+ L"\n\nFor more information check the crash report located at:\n "
+			+ getCrashFolder().toWString();
+		MessageBoxW(nullptr, simpleErrorMessage.c_str(), L"Banshee fatal error!", MB_OK);
 
-		bs_delete(m);
 	}
-
-	void CrashHandler::reportCrash(const String& type, const String& description, const String& function,
-		const String& file, UINT32 line) const
+	void CrashHandler::reportCrash(const String& type,
+	                               const String& description,
+	                               const String& function,
+	                               const String& file,
+	                               UINT32 line) const
 	{
 		// Win32 debug methods are not thread safe
 		Lock(m->mutex);
 
-		String stackTrace = getStackTrace();
-
-		StringStream errorMessageStream;
-		errorMessageStream << "Fatal error occurred and the program has to terminate!" << std::endl;
-		errorMessageStream << "\t\t" << type << " - " << description << std::endl;
-		errorMessageStream << "\t\t in " << function << " [" << file << ":" << line << "]" << std::endl;
-		errorMessageStream << std::endl;
-		errorMessageStream << "Stack trace: " << std::endl;
-		errorMessageStream << stackTrace;
-
-		String errorMessage = errorMessageStream.str();
-		gDebug().logError(errorMessage);
-
-		Path crashFolder = getCrashFolder();
-		FileSystem::createDir(crashFolder);
+		logErrorAndStackTrace(type, description, function, file, line);
+		saveCrashLog();
 
-		gDebug().saveLog(crashFolder + WString(CrashLogName));
-		win32_writeMiniDump(crashFolder + WString(gMiniDumpName), nullptr);
-
-		WString simpleErrorMessage = L"Fatal error occurred and the program has to terminate! " \
-			L"\n\nFor more information check the crash report located at:\n " + crashFolder.toWString();
-
-		MessageBoxW(nullptr, simpleErrorMessage.c_str(), L"Banshee fatal error!", MB_OK);
+		win32_writeMiniDump(getCrashFolder() + WString(sMiniDumpName), nullptr);
+		win32_popupErrorMessageBox();
 
 		// Note: Potentially also log Windows Error Report and/or send crash data to server
 	}
@@ -494,51 +486,31 @@ namespace BansheeEngine
 
 		win32_initPSAPI();
 		win32_loadSymbols();
-		String stackTrace = win32_getStackTrace(*exceptionData->ContextRecord, 0);
-
-		StringStream errorMessageStream;
-		errorMessageStream << "Fatal error occurred and the program has to terminate!" << std::endl;
-		errorMessageStream << "\t\t" << win32_getExceptionMessage(exceptionData->ExceptionRecord) << std::endl;;
-		errorMessageStream << std::endl;
-		errorMessageStream << "Stack trace: " << std::endl;
-		errorMessageStream << stackTrace;
-
-		String errorMessage = errorMessageStream.str();
-		gDebug().logError(errorMessage);
 
-		Path crashFolder = getCrashFolder();
-		FileSystem::createDir(crashFolder);
+		logErrorAndStackTrace(win32_getExceptionMessage(exceptionData->ExceptionRecord),
+		                      win32_getStackTrace(*exceptionData->ContextRecord, 0));
+		saveCrashLog();
 
-		gDebug().saveLog(crashFolder + WString(CrashLogName));
-		win32_writeMiniDump(crashFolder + WString(gMiniDumpName), exceptionData);
-
-		WString simpleErrorMessage = L"Fatal error occurred and the program has to terminate! " \
-			L"\n\nFor more information check the crash report located at:\n" + crashFolder.toWString();
-
-		MessageBoxW(nullptr, simpleErrorMessage.c_str(), L"Banshee fatal error!", MB_OK);
+		win32_writeMiniDump(getCrashFolder() + WString(MiniDumpName), exceptionData);
+		win32_popupErrorMessageBox();
 
 		// Note: Potentially also log Windows Error Report and/or send crash data to server
 
 		return EXCEPTION_EXECUTE_HANDLER;
 	}
 
-	Path CrashHandler::getCrashFolder() const
+	String CrashHandler::getCrashTimestamp()
 	{
 		SYSTEMTIME systemTime;
 		GetLocalTime(&systemTime);
 
-		WString timeStamp = L"{0}{1}{2}_{3}{4}";
-		WString strYear = toWString(systemTime.wYear, 4, '0');
-		WString strMonth = toWString(systemTime.wMonth, 2, '0');
-		WString strDay = toWString(systemTime.wDay, 2, '0');
-		WString strHour = toWString(systemTime.wHour, 2, '0');
-		WString strMinute = toWString(systemTime.wMinute, 2, '0');
-
-		timeStamp = StringUtil::format(timeStamp, strYear, strMonth, strDay, strHour, strMinute);
-
-		WString folderName = StringUtil::format(CrashReportFolder, timeStamp);
-
-		return FileSystem::getWorkingDirectoryPath() + folderName;
+		String timeStamp = L"{0}{1}{2}_{3}{4}";
+		String strYear = toString(systemTime.wYear, 4, '0');
+		String strMonth = toString(systemTime.wMonth, 2, '0');
+		String strDay = toString(systemTime.wDay, 2, '0');
+		String strHour = toString(systemTime.wHour, 2, '0');
+		String strMinute = toString(systemTime.wMinute, 2, '0');
+		return StringUtil::format(timeStamp, strYear, strMonth, strDay, strHour, strMinute);
 	}
 
 	String CrashHandler::getStackTrace()
@@ -550,9 +522,4 @@ namespace BansheeEngine
 		win32_loadSymbols();
 		return win32_getStackTrace(context, 2);
 	}
-
-	CrashHandler& gCrashHandler()
-	{
-		return CrashHandler::instance();
-	}
-}
+}