refactor: finish ironpad migration
- finish ironpad migration but no test because it is not easy to test.
This commit is contained in:
		@ -29,6 +29,7 @@ PRIVATE
 | 
				
			|||||||
    yycc/carton/termcolor.cpp
 | 
					    yycc/carton/termcolor.cpp
 | 
				
			||||||
    yycc/carton/wcwidth.cpp
 | 
					    yycc/carton/wcwidth.cpp
 | 
				
			||||||
    yycc/carton/tabulate.cpp
 | 
					    yycc/carton/tabulate.cpp
 | 
				
			||||||
 | 
					    yycc/carton/ironpad.cpp
 | 
				
			||||||
    yycc/carton/csconsole.cpp
 | 
					    yycc/carton/csconsole.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
target_sources(YYCCommonplace 
 | 
					target_sources(YYCCommonplace 
 | 
				
			||||||
@ -78,6 +79,7 @@ FILES
 | 
				
			|||||||
    yycc/carton/termcolor.hpp
 | 
					    yycc/carton/termcolor.hpp
 | 
				
			||||||
    yycc/carton/wcwidth.hpp
 | 
					    yycc/carton/wcwidth.hpp
 | 
				
			||||||
    yycc/carton/tabulate.hpp
 | 
					    yycc/carton/tabulate.hpp
 | 
				
			||||||
 | 
					    yycc/carton/ironpad.hpp
 | 
				
			||||||
    yycc/carton/csconsole.hpp
 | 
					    yycc/carton/csconsole.hpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
# Setup header infomations
 | 
					# Setup header infomations
 | 
				
			||||||
 | 
				
			|||||||
@ -1,563 +0,0 @@
 | 
				
			|||||||
#include "ExceptionHelper.hpp"
 | 
					 | 
				
			||||||
#if defined(YYCC_OS_WINDOWS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "WinFctHelper.hpp"
 | 
					 | 
				
			||||||
#include "ConsoleHelper.hpp"
 | 
					 | 
				
			||||||
#include "StringHelper.hpp"
 | 
					 | 
				
			||||||
#include "IOHelper.hpp"
 | 
					 | 
				
			||||||
#include "EncodingHelper.hpp"
 | 
					 | 
				
			||||||
#include "StdPatch.hpp"
 | 
					 | 
				
			||||||
#include <filesystem>
 | 
					 | 
				
			||||||
#include <cstdarg>
 | 
					 | 
				
			||||||
#include <cstdio>
 | 
					 | 
				
			||||||
#include <cinttypes>
 | 
					 | 
				
			||||||
#include <mutex>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "WinImportPrefix.hpp"
 | 
					 | 
				
			||||||
#include <Windows.h>
 | 
					 | 
				
			||||||
#include <DbgHelp.h>
 | 
					 | 
				
			||||||
#include "WinImportSuffix.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace YYCC::ExceptionHelper {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS);
 | 
					 | 
				
			||||||
	class ExceptionRegister {
 | 
					 | 
				
			||||||
	public:
 | 
					 | 
				
			||||||
		ExceptionRegister() :
 | 
					 | 
				
			||||||
			m_CoreMutex(),
 | 
					 | 
				
			||||||
			m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
 | 
					 | 
				
			||||||
			m_UserCallback(nullptr),
 | 
					 | 
				
			||||||
			m_SingletonMutex(NULL) {}
 | 
					 | 
				
			||||||
		~ExceptionRegister() {
 | 
					 | 
				
			||||||
			Unregister();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public:
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Try to register unhandled exception handler.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		void Register(ExceptionCallback callback) {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			// if we have registered, return
 | 
					 | 
				
			||||||
			if (m_IsRegistered) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// check singleton
 | 
					 | 
				
			||||||
			// build mutex string first
 | 
					 | 
				
			||||||
			yycc_u8string mutex_name;
 | 
					 | 
				
			||||||
			if (!StringHelper::Printf(mutex_name, YYCC_U8("Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}"), GetCurrentProcessId()))
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			std::wstring mutex_wname;
 | 
					 | 
				
			||||||
			if (!EncodingHelper::UTF8ToWchar(mutex_name, mutex_wname))
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			// create mutex
 | 
					 | 
				
			||||||
			m_SingletonMutex = CreateMutexW(NULL, FALSE, mutex_wname.c_str());
 | 
					 | 
				
			||||||
			DWORD errcode = GetLastError();
 | 
					 | 
				
			||||||
			// check whether be created
 | 
					 | 
				
			||||||
			if (m_SingletonMutex == NULL)
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			if (errcode == ERROR_ALREADY_EXISTS) {
 | 
					 | 
				
			||||||
				CloseHandle(m_SingletonMutex);
 | 
					 | 
				
			||||||
				m_SingletonMutex = NULL;
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// okey, we can register it.
 | 
					 | 
				
			||||||
			// backup old handler
 | 
					 | 
				
			||||||
			m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
 | 
					 | 
				
			||||||
			// set user callback
 | 
					 | 
				
			||||||
			m_UserCallback = callback;
 | 
					 | 
				
			||||||
			// mark registered
 | 
					 | 
				
			||||||
			m_IsRegistered = true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Try to unregister unhandled exception handler.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		void Unregister() {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			// if we are not registered, skip
 | 
					 | 
				
			||||||
			if (!m_IsRegistered) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// unregister handler
 | 
					 | 
				
			||||||
			// reset user callback
 | 
					 | 
				
			||||||
			m_UserCallback = nullptr;
 | 
					 | 
				
			||||||
			// restore old handler
 | 
					 | 
				
			||||||
			SetUnhandledExceptionFilter(m_PrevProcHandler);
 | 
					 | 
				
			||||||
			m_PrevProcHandler = nullptr;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// release singleton handler
 | 
					 | 
				
			||||||
			if (m_SingletonMutex != NULL) {
 | 
					 | 
				
			||||||
				CloseHandle(m_SingletonMutex);
 | 
					 | 
				
			||||||
				m_SingletonMutex = NULL;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// mark unregistered
 | 
					 | 
				
			||||||
			m_IsRegistered = false;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public:
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Check whether handler is registered.
 | 
					 | 
				
			||||||
		 * @return True if it is, otherwise false.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		bool IsRegistered() const {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			return m_IsRegistered;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Check whether we are processing unhandled exception.
 | 
					 | 
				
			||||||
		 * @return True if it is, otherwise false.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		bool IsProcessing() const {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			return m_IsProcessing;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Get the old unhandled exception handler before registering.
 | 
					 | 
				
			||||||
		 * @return The fucntion pointer to old unhandled exception handler. May be nullptr.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		LPTOP_LEVEL_EXCEPTION_FILTER GetPrevProcHandler() const {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			return m_PrevProcHandler;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Get user specified callback.
 | 
					 | 
				
			||||||
		 * @return The function pointer to user callback. nullptr if no associated callback.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		ExceptionCallback GetUserCallback() const {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			return m_UserCallback;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Try to start process unhandled exception.
 | 
					 | 
				
			||||||
		 * @return True if you can start to process.
 | 
					 | 
				
			||||||
		 * False means there is already a process running. You should not process it now.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		bool StartProcessing() {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			if (m_IsProcessing) return false;
 | 
					 | 
				
			||||||
			else {
 | 
					 | 
				
			||||||
				m_IsProcessing = true;
 | 
					 | 
				
			||||||
				return true;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Mark current process of unhandled exception has done.
 | 
					 | 
				
			||||||
		 * @details This should only be called when StartProcessing() return true.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		void StopProcessing() {
 | 
					 | 
				
			||||||
			std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
					 | 
				
			||||||
			m_IsProcessing = false;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private:
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief The core mutex for keeping this class is in synchronized.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		mutable std::mutex m_CoreMutex;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Whether we have registered unhandled exception handler.
 | 
					 | 
				
			||||||
		 * True if it is, otherwise false.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		bool m_IsRegistered;
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief Whether we are processing unhandled exception.
 | 
					 | 
				
			||||||
		 * True if it is, otherwise false.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		bool m_IsProcessing;
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief User defined callback.
 | 
					 | 
				
			||||||
		 * @details It will be called at the tail of unhandled exception handler, because it may raise exception.
 | 
					 | 
				
			||||||
		 * We must make sure all log and coredump have been done before calling it.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		ExceptionCallback m_UserCallback;
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief The backup of old unhandled exception handler.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		LPTOP_LEVEL_EXCEPTION_FILTER m_PrevProcHandler;
 | 
					 | 
				
			||||||
		/**
 | 
					 | 
				
			||||||
		 * @brief The Windows mutex handle for singleton implementation.
 | 
					 | 
				
			||||||
		 * Because we may have many DLLs using YYCC in the same process.
 | 
					 | 
				
			||||||
		 * But the unhandled exception handler only need to be registered once.
 | 
					 | 
				
			||||||
		*/
 | 
					 | 
				
			||||||
		HANDLE m_SingletonMutex;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/// @brief Core register singleton.
 | 
					 | 
				
			||||||
	static ExceptionRegister g_ExceptionRegister;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma region Exception Handler Implementation
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @brief Get human-readable exception string from given exception code.
 | 
					 | 
				
			||||||
	 * @param[in] code Exception code
 | 
					 | 
				
			||||||
	 * @return The const string pointer to corresponding exception explanation string.
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
	static const yycc_char8_t* UExceptionGetCodeName(DWORD code) {
 | 
					 | 
				
			||||||
		switch (code) {
 | 
					 | 
				
			||||||
			case EXCEPTION_ACCESS_VIOLATION:
 | 
					 | 
				
			||||||
				return YYCC_U8("access violation");
 | 
					 | 
				
			||||||
			case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
 | 
					 | 
				
			||||||
				return YYCC_U8("array index out of bound");
 | 
					 | 
				
			||||||
			case EXCEPTION_BREAKPOINT:
 | 
					 | 
				
			||||||
				return YYCC_U8("breakpoint reached");
 | 
					 | 
				
			||||||
			case EXCEPTION_DATATYPE_MISALIGNMENT:
 | 
					 | 
				
			||||||
				return YYCC_U8("misaligned data access");
 | 
					 | 
				
			||||||
			case EXCEPTION_FLT_DENORMAL_OPERAND:
 | 
					 | 
				
			||||||
				return YYCC_U8("operand had denormal value");
 | 
					 | 
				
			||||||
			case EXCEPTION_FLT_DIVIDE_BY_ZERO:
 | 
					 | 
				
			||||||
				return YYCC_U8("floating-point division by zero");
 | 
					 | 
				
			||||||
			case EXCEPTION_FLT_INEXACT_RESULT:
 | 
					 | 
				
			||||||
				return YYCC_U8("no decimal fraction representation for value");
 | 
					 | 
				
			||||||
			case EXCEPTION_FLT_INVALID_OPERATION:
 | 
					 | 
				
			||||||
				return YYCC_U8("invalid floating-point operation");
 | 
					 | 
				
			||||||
			case EXCEPTION_FLT_OVERFLOW:
 | 
					 | 
				
			||||||
				return YYCC_U8("floating-point overflow");
 | 
					 | 
				
			||||||
			case EXCEPTION_FLT_STACK_CHECK:
 | 
					 | 
				
			||||||
				return YYCC_U8("floating-point stack corruption");
 | 
					 | 
				
			||||||
			case EXCEPTION_FLT_UNDERFLOW:
 | 
					 | 
				
			||||||
				return YYCC_U8("floating-point underflow");
 | 
					 | 
				
			||||||
			case EXCEPTION_ILLEGAL_INSTRUCTION:
 | 
					 | 
				
			||||||
				return YYCC_U8("illegal instruction");
 | 
					 | 
				
			||||||
			case EXCEPTION_IN_PAGE_ERROR:
 | 
					 | 
				
			||||||
				return YYCC_U8("inaccessible page");
 | 
					 | 
				
			||||||
			case EXCEPTION_INT_DIVIDE_BY_ZERO:
 | 
					 | 
				
			||||||
				return YYCC_U8("integer division by zero");
 | 
					 | 
				
			||||||
			case EXCEPTION_INT_OVERFLOW:
 | 
					 | 
				
			||||||
				return YYCC_U8("integer overflow");
 | 
					 | 
				
			||||||
			case EXCEPTION_INVALID_DISPOSITION:
 | 
					 | 
				
			||||||
				return YYCC_U8("documentation says this should never happen");
 | 
					 | 
				
			||||||
			case EXCEPTION_NONCONTINUABLE_EXCEPTION:
 | 
					 | 
				
			||||||
				return YYCC_U8("can't continue after a noncontinuable exception");
 | 
					 | 
				
			||||||
			case EXCEPTION_PRIV_INSTRUCTION:
 | 
					 | 
				
			||||||
				return YYCC_U8("attempted to execute a privileged instruction");
 | 
					 | 
				
			||||||
			case EXCEPTION_SINGLE_STEP:
 | 
					 | 
				
			||||||
				return YYCC_U8("one instruction has been executed");
 | 
					 | 
				
			||||||
			case EXCEPTION_STACK_OVERFLOW:
 | 
					 | 
				
			||||||
				return YYCC_U8("stack overflow");
 | 
					 | 
				
			||||||
			default:
 | 
					 | 
				
			||||||
				return YYCC_U8("unknown exception");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @brief Error log (including backtrace) used output function
 | 
					 | 
				
			||||||
	 * @details
 | 
					 | 
				
			||||||
	 * This function will write given string into given file stream and stderr.
 | 
					 | 
				
			||||||
	 * @param[in] fs
 | 
					 | 
				
			||||||
	 * The file stream where we write.
 | 
					 | 
				
			||||||
	 * If it is nullptr, function will skip writing for file stream.
 | 
					 | 
				
			||||||
	 * @param[in] strl The string to be written.
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
	static void UExceptionErrLogWriteLine(std::FILE* fs, const yycc_char8_t* strl) {
 | 
					 | 
				
			||||||
		// write to file
 | 
					 | 
				
			||||||
		if (fs != nullptr) {
 | 
					 | 
				
			||||||
			std::fputs(EncodingHelper::ToOrdinary(strl), fs);
 | 
					 | 
				
			||||||
			std::fputs("\n", fs);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		// write to stderr
 | 
					 | 
				
			||||||
		ConsoleHelper::ErrWriteLine(strl);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @brief Error log (including backtrace) used output function with format feature
 | 
					 | 
				
			||||||
	 * @details
 | 
					 | 
				
			||||||
	 * This function will format message first.
 | 
					 | 
				
			||||||
	 * And write them into given file stream and stderr.
 | 
					 | 
				
			||||||
	 * @param[in] fs
 | 
					 | 
				
			||||||
	 * The file stream where we write.
 | 
					 | 
				
			||||||
	 * If it is nullptr, function will skip writing for file stream.
 | 
					 | 
				
			||||||
	 * @param[in] fmt The format string.
 | 
					 | 
				
			||||||
	 * @param[in] ... The argument to be formatted.
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
	static void UExceptionErrLogFormatLine(std::FILE* fs, const yycc_char8_t* fmt, ...) {
 | 
					 | 
				
			||||||
		// do format first
 | 
					 | 
				
			||||||
		va_list arg;
 | 
					 | 
				
			||||||
		va_start(arg, fmt);
 | 
					 | 
				
			||||||
		auto fmt_result = YYCC::StringHelper::VPrintf(fmt, arg);
 | 
					 | 
				
			||||||
		va_end(arg);
 | 
					 | 
				
			||||||
		// write to file and console
 | 
					 | 
				
			||||||
		UExceptionErrLogWriteLine(fs, fmt_result.c_str());
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
 | 
					 | 
				
			||||||
		// setup loading symbol options
 | 
					 | 
				
			||||||
		SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);	// lazy load symbol, and load line number.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// setup handle
 | 
					 | 
				
			||||||
		HANDLE process = GetCurrentProcess();
 | 
					 | 
				
			||||||
		HANDLE thread = GetCurrentThread();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// init symbol
 | 
					 | 
				
			||||||
		if (!SymInitialize(process, 0, TRUE)) {
 | 
					 | 
				
			||||||
			// fail to init. return
 | 
					 | 
				
			||||||
			UExceptionErrLogWriteLine(fs, YYCC_U8("Fail to initialize symbol handle for process!"));
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// ========== CORE DUMP ==========
 | 
					 | 
				
			||||||
		// prepare frame. setup correct fields
 | 
					 | 
				
			||||||
		// references: 
 | 
					 | 
				
			||||||
		// https://github.com/rust-lang/backtrace-rs/blob/9ed25b581cfd2ee60e5a3b9054fd023bf6dced90/src/backtrace/dbghelp.rs
 | 
					 | 
				
			||||||
		// https://sourceforge.net/p/predef/wiki/Architectures/
 | 
					 | 
				
			||||||
		DWORD machine_type = 0;
 | 
					 | 
				
			||||||
		STACKFRAME64 frame;
 | 
					 | 
				
			||||||
		memset(&frame, 0, sizeof(frame));
 | 
					 | 
				
			||||||
#if defined(_M_IX86) || defined(__i386__)
 | 
					 | 
				
			||||||
		// x86
 | 
					 | 
				
			||||||
		machine_type = IMAGE_FILE_MACHINE_I386;
 | 
					 | 
				
			||||||
		frame.AddrPC.Offset = context->Eip;
 | 
					 | 
				
			||||||
		frame.AddrStack.Offset = context->Esp;
 | 
					 | 
				
			||||||
		frame.AddrFrame.Offset = context->Ebp;
 | 
					 | 
				
			||||||
#elif defined(_M_AMD64) || defined(__amd64__)
 | 
					 | 
				
			||||||
		// amd64
 | 
					 | 
				
			||||||
		machine_type = IMAGE_FILE_MACHINE_AMD64;
 | 
					 | 
				
			||||||
		frame.AddrPC.Offset = context->Rip;
 | 
					 | 
				
			||||||
		frame.AddrStack.Offset = context->Rsp;
 | 
					 | 
				
			||||||
		frame.AddrFrame.Offset = context->Rbp;
 | 
					 | 
				
			||||||
#elif defined(_M_ARM) || defined(__arm__)
 | 
					 | 
				
			||||||
		// arm (32bit)
 | 
					 | 
				
			||||||
		machine_type = IMAGE_FILE_MACHINE_ARMNT;
 | 
					 | 
				
			||||||
		frame.AddrPC.Offset = context->Pc;
 | 
					 | 
				
			||||||
		frame.AddrStack.Offset = context->Sp;
 | 
					 | 
				
			||||||
		frame.AddrFrame.Offset = context->R11;
 | 
					 | 
				
			||||||
#elif defined(_M_ARM64) || defined(__aarch64__)
 | 
					 | 
				
			||||||
		// arm64
 | 
					 | 
				
			||||||
		machine_type = IMAGE_FILE_MACHINE_ARM64;
 | 
					 | 
				
			||||||
		frame.AddrPC.Offset = context->Pc;
 | 
					 | 
				
			||||||
		frame.AddrStack.Offset = context->Sp;
 | 
					 | 
				
			||||||
		frame.AddrFrame.Offset = context->DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp;
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
#error "Unsupported platform"
 | 
					 | 
				
			||||||
		//IA-64 anybody?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
		frame.AddrPC.Mode = AddrModeFlat;
 | 
					 | 
				
			||||||
		frame.AddrStack.Mode = AddrModeFlat;
 | 
					 | 
				
			||||||
		frame.AddrFrame.Mode = AddrModeFlat;
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		// stack walker
 | 
					 | 
				
			||||||
		while (StackWalk64(machine_type, process, thread, &frame, context,
 | 
					 | 
				
			||||||
			0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// depth breaker
 | 
					 | 
				
			||||||
			--maxdepth;
 | 
					 | 
				
			||||||
			if (maxdepth < 0) {
 | 
					 | 
				
			||||||
				UExceptionErrLogWriteLine(fs, YYCC_U8("..."));		// indicate there are some frames not listed
 | 
					 | 
				
			||||||
				break;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// get module name
 | 
					 | 
				
			||||||
			const yycc_char8_t* no_module_name = YYCC_U8("<unknown module>");
 | 
					 | 
				
			||||||
			yycc_u8string module_name(no_module_name);
 | 
					 | 
				
			||||||
			DWORD64 module_base;
 | 
					 | 
				
			||||||
			if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
 | 
					 | 
				
			||||||
				if (!WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name)) {
 | 
					 | 
				
			||||||
					module_name = no_module_name;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// get source file and line
 | 
					 | 
				
			||||||
			const yycc_char8_t* source_file = YYCC_U8("<unknown source>");
 | 
					 | 
				
			||||||
			DWORD64 source_file_line = 0;
 | 
					 | 
				
			||||||
			DWORD dwDisplacement;
 | 
					 | 
				
			||||||
			IMAGEHLP_LINE64 winline;
 | 
					 | 
				
			||||||
			winline.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
 | 
					 | 
				
			||||||
			if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &dwDisplacement, &winline)) {
 | 
					 | 
				
			||||||
				source_file = EncodingHelper::ToUTF8(winline.FileName); // TODO: check whether there is UNICODE file name.
 | 
					 | 
				
			||||||
				source_file_line = winline.LineNumber;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// write to file
 | 
					 | 
				
			||||||
			// MARK: should not use PRIXPTR to print adddress.
 | 
					 | 
				
			||||||
			// because Windows always use DWORD64 as the type of address.
 | 
					 | 
				
			||||||
			// use PRIX64 instead.
 | 
					 | 
				
			||||||
			UExceptionErrLogFormatLine(fs, YYCC_U8("0x%" PRI_XPTR_LEFT_PADDING PRIX64 "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIX64 "]\t%s#L%" PRIu64),
 | 
					 | 
				
			||||||
				frame.AddrPC.Offset, // memory adress
 | 
					 | 
				
			||||||
				module_name.c_str(), frame.AddrPC.Offset - module_base, // module name + relative address
 | 
					 | 
				
			||||||
				source_file, source_file_line // source file + source line
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// ========== END CORE DUMP ==========
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// free symbol
 | 
					 | 
				
			||||||
		SymCleanup(process);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static void UExceptionErrorLog(const yycc_u8string& u8_filename, LPEXCEPTION_POINTERS info) {
 | 
					 | 
				
			||||||
		// open file stream if we have file name
 | 
					 | 
				
			||||||
		std::FILE* fs = nullptr;
 | 
					 | 
				
			||||||
		if (!u8_filename.empty()) {
 | 
					 | 
				
			||||||
			fs = IOHelper::UTF8FOpen(u8_filename.c_str(), YYCC_U8("wb"));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// record exception type first
 | 
					 | 
				
			||||||
		PEXCEPTION_RECORD rec = info->ExceptionRecord;
 | 
					 | 
				
			||||||
		UExceptionErrLogFormatLine(fs, YYCC_U8("Unhandled exception occured at 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR ": %s (%" PRIu32 ")."),
 | 
					 | 
				
			||||||
			rec->ExceptionAddress,
 | 
					 | 
				
			||||||
			UExceptionGetCodeName(rec->ExceptionCode),
 | 
					 | 
				
			||||||
			rec->ExceptionCode
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// special proc for 2 exceptions
 | 
					 | 
				
			||||||
		if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
 | 
					 | 
				
			||||||
			if (rec->NumberParameters >= 2) {
 | 
					 | 
				
			||||||
				const yycc_char8_t* op =
 | 
					 | 
				
			||||||
					rec->ExceptionInformation[0] == 0 ? YYCC_U8("read") :
 | 
					 | 
				
			||||||
					rec->ExceptionInformation[0] == 1 ? YYCC_U8("written") : YYCC_U8("executed");
 | 
					 | 
				
			||||||
				UExceptionErrLogFormatLine(fs, YYCC_U8("The data at memory address 0x%" PRI_XPTR_LEFT_PADDING PRIxPTR " could not be %s."),
 | 
					 | 
				
			||||||
					rec->ExceptionInformation[1], op);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// output stacktrace
 | 
					 | 
				
			||||||
		UExceptionBacktrace(fs, info->ContextRecord, 1024);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// close file if necessary
 | 
					 | 
				
			||||||
		if (fs != nullptr) {
 | 
					 | 
				
			||||||
			std::fclose(fs);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static void UExceptionCoreDump(const yycc_u8string& u8_filename, LPEXCEPTION_POINTERS info) {
 | 
					 | 
				
			||||||
		// convert file encoding
 | 
					 | 
				
			||||||
		std::wstring filename;
 | 
					 | 
				
			||||||
		if (u8_filename.empty())
 | 
					 | 
				
			||||||
			return; // if no given file name, return
 | 
					 | 
				
			||||||
		if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filename, filename))
 | 
					 | 
				
			||||||
			return; // if convertion failed, return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// open file and write
 | 
					 | 
				
			||||||
		HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 | 
					 | 
				
			||||||
		if (hFile != INVALID_HANDLE_VALUE) {
 | 
					 | 
				
			||||||
			MINIDUMP_EXCEPTION_INFORMATION exception_info;
 | 
					 | 
				
			||||||
			exception_info.ThreadId = GetCurrentThreadId();
 | 
					 | 
				
			||||||
			exception_info.ExceptionPointers = info;
 | 
					 | 
				
			||||||
			exception_info.ClientPointers = TRUE;
 | 
					 | 
				
			||||||
			MiniDumpWriteDump(
 | 
					 | 
				
			||||||
				GetCurrentProcess(), GetCurrentProcessId(), hFile,
 | 
					 | 
				
			||||||
				MiniDumpNormal,
 | 
					 | 
				
			||||||
				&exception_info,
 | 
					 | 
				
			||||||
				NULL, NULL
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			CloseHandle(hFile);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static bool UExceptionFetchRecordPath(yycc_u8string& log_path, yycc_u8string& coredump_path) {
 | 
					 | 
				
			||||||
		// build two file names like: "error.exe.1234.log" and "error.exe.1234.dmp".
 | 
					 | 
				
			||||||
		// "error.exe" is the name of current process. "1234" is current process id.
 | 
					 | 
				
			||||||
		// get process name
 | 
					 | 
				
			||||||
		yycc_u8string u8_process_name;
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// get full path of process
 | 
					 | 
				
			||||||
			yycc_u8string u8_process_path;
 | 
					 | 
				
			||||||
			if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path))
 | 
					 | 
				
			||||||
				return false;
 | 
					 | 
				
			||||||
			// extract file name from full path by std::filesystem::path
 | 
					 | 
				
			||||||
			std::filesystem::path process_path(StdPatch::ToStdPath(u8_process_path));
 | 
					 | 
				
			||||||
			u8_process_name = StdPatch::ToUTF8Path(process_path.filename());
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		// then get process id
 | 
					 | 
				
			||||||
		DWORD process_id = GetCurrentProcessId();
 | 
					 | 
				
			||||||
		// conbine them as a file name prefix
 | 
					 | 
				
			||||||
		yycc_u8string u8_filename_prefix;
 | 
					 | 
				
			||||||
		if (!YYCC::StringHelper::Printf(u8_filename_prefix, YYCC_U8("%s.%" PRIu32), u8_process_name.c_str(), process_id))
 | 
					 | 
				
			||||||
			return false;
 | 
					 | 
				
			||||||
		// then get file name for log and minidump
 | 
					 | 
				
			||||||
		yycc_u8string u8_log_filename = u8_filename_prefix + YYCC_U8(".log");
 | 
					 | 
				
			||||||
		yycc_u8string u8_coredump_filename = u8_filename_prefix + YYCC_U8(".dmp");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// fetch crash report path
 | 
					 | 
				
			||||||
		// get local appdata folder
 | 
					 | 
				
			||||||
		yycc_u8string u8_localappdata_path;
 | 
					 | 
				
			||||||
		if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
 | 
					 | 
				
			||||||
			return false;
 | 
					 | 
				
			||||||
		// convert to std::filesystem::path
 | 
					 | 
				
			||||||
		std::filesystem::path crash_report_path(StdPatch::ToStdPath(u8_localappdata_path));
 | 
					 | 
				
			||||||
		// slash into crash report folder
 | 
					 | 
				
			||||||
		crash_report_path /= StdPatch::ToStdPath(YYCC_U8("CrashDumps"));
 | 
					 | 
				
			||||||
		// use create function to make sure it is existing
 | 
					 | 
				
			||||||
		std::filesystem::create_directories(crash_report_path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// build log path and coredump path
 | 
					 | 
				
			||||||
		// build std::filesystem::path first
 | 
					 | 
				
			||||||
		std::filesystem::path log_filepath = crash_report_path / StdPatch::ToStdPath(u8_log_filename);
 | 
					 | 
				
			||||||
		std::filesystem::path coredump_filepath = crash_report_path / StdPatch::ToStdPath(u8_coredump_filename);
 | 
					 | 
				
			||||||
		// output to result
 | 
					 | 
				
			||||||
		log_path = StdPatch::ToUTF8Path(log_filepath);
 | 
					 | 
				
			||||||
		coredump_path = StdPatch::ToUTF8Path(coredump_filepath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return true;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
 | 
					 | 
				
			||||||
		// try to start process current unhandled exception
 | 
					 | 
				
			||||||
		// to prevent any possible recursive calling.
 | 
					 | 
				
			||||||
		if (!g_ExceptionRegister.StartProcessing()) goto end_proc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// core implementation
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// fetch error report path first
 | 
					 | 
				
			||||||
			yycc_u8string log_path, coredump_path;
 | 
					 | 
				
			||||||
			if (!UExceptionFetchRecordPath(log_path, coredump_path)) {
 | 
					 | 
				
			||||||
				// fail to fetch path, clear them.
 | 
					 | 
				
			||||||
				// we still can handle crash without them
 | 
					 | 
				
			||||||
				log_path.clear();
 | 
					 | 
				
			||||||
				coredump_path.clear();
 | 
					 | 
				
			||||||
				// and tell user we can not output file
 | 
					 | 
				
			||||||
				ConsoleHelper::ErrWriteLine(YYCC_U8("Crash occurs, but we can not create crash log and coredump!"));
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				// okey. output file path to tell user the path where you can find.
 | 
					 | 
				
			||||||
				ConsoleHelper::ErrFormatLine(YYCC_U8("Crash Log: %s"), log_path.c_str());
 | 
					 | 
				
			||||||
				ConsoleHelper::ErrFormatLine(YYCC_U8("Crash Coredump: %s"), coredump_path.c_str());
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// write crash log
 | 
					 | 
				
			||||||
			UExceptionErrorLog(log_path, info);
 | 
					 | 
				
			||||||
			// write crash coredump
 | 
					 | 
				
			||||||
			UExceptionCoreDump(coredump_path, info);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// call user callback
 | 
					 | 
				
			||||||
			ExceptionCallback user_callback = g_ExceptionRegister.GetUserCallback();
 | 
					 | 
				
			||||||
			if (user_callback != nullptr) 
 | 
					 | 
				
			||||||
				user_callback(log_path, coredump_path);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// stop process
 | 
					 | 
				
			||||||
		g_ExceptionRegister.StopProcessing();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	end_proc:
 | 
					 | 
				
			||||||
		// if backup proc can be run, run it
 | 
					 | 
				
			||||||
		// otherwise directly return.
 | 
					 | 
				
			||||||
		auto prev_proc = g_ExceptionRegister.GetPrevProcHandler();
 | 
					 | 
				
			||||||
		if (prev_proc != nullptr) {
 | 
					 | 
				
			||||||
			return prev_proc(info);
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return EXCEPTION_CONTINUE_SEARCH;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void Register(ExceptionCallback callback) {
 | 
					 | 
				
			||||||
		g_ExceptionRegister.Register(callback);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void Unregister() {
 | 
					 | 
				
			||||||
		g_ExceptionRegister.Unregister();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if defined(YYCC_DEBUG_UE_FILTER)
 | 
					 | 
				
			||||||
	long __stdcall DebugCallUExceptionImpl(void* data) {
 | 
					 | 
				
			||||||
		return UExceptionImpl(static_cast<LPEXCEPTION_POINTERS>(data));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
@ -1,64 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
#include "YYCCInternal.hpp"
 | 
					 | 
				
			||||||
#if defined(YYCC_OS_WINDOWS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @brief Windows specific unhandled exception processor.
 | 
					 | 
				
			||||||
 * @details
 | 
					 | 
				
			||||||
 * This namespace is Windows specific. On other platforms, the whole namespace is unavailable.
 | 
					 | 
				
			||||||
 * For how to utilize this namespace, please see \ref exception_helper.
 | 
					 | 
				
			||||||
 * 
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
namespace YYCC::ExceptionHelper {
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @brief The callback function prototype which will be called when unhandled exception happened after registering.
 | 
					 | 
				
			||||||
	 * @details
 | 
					 | 
				
			||||||
	 * During registering unhandled exception handler,
 | 
					 | 
				
			||||||
	 * caller can optionally provide a function pointer matching this prorotype to register.
 | 
					 | 
				
			||||||
	 * Then it will be called if unhandled exception hanppened.
 | 
					 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * This callback will provide 2 readonly arguments.
 | 
					 | 
				
			||||||
	 * First is the path to error log file.
 | 
					 | 
				
			||||||
	 * Second is the path to core dump file.
 | 
					 | 
				
			||||||
	 * These pathes may be empty if internal handler fail to create them.
 | 
					 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * This callback is convenient for programmer using an explicit way to tell user an exception happened.
 | 
					 | 
				
			||||||
	 * Because in default, handler will only write error log to \c stderr and file.
 | 
					 | 
				
			||||||
	 * It will be totally invisible on a GUI application.
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
	using ExceptionCallback = void(*)(const yycc_u8string& log_path, const yycc_u8string& coredump_path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @brief Register unhandled exception handler
 | 
					 | 
				
			||||||
	 * @details
 | 
					 | 
				
			||||||
	 * This function will set an internal function as unhandled exception handler on Windows.
 | 
					 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * When unhandled exception raised, 
 | 
					 | 
				
			||||||
	 * That internal function will output error stacktrace in standard output,
 | 
					 | 
				
			||||||
	 * and generate log file and dump file in \c \%APPDATA\%/CrashDumps folder if it is possible.
 | 
					 | 
				
			||||||
	 * (for convenient debugging of developer when reporting bugs.)
 | 
					 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * This function usually is called at the start of program.
 | 
					 | 
				
			||||||
	 * @param[in] callback User defined callback called when unhandled exception happened. nullptr if no callback.
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
	void Register(ExceptionCallback callback = nullptr);
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @brief Unregister unhandled exception handler
 | 
					 | 
				
			||||||
	 * @details 
 | 
					 | 
				
			||||||
	 * The reverse operation of Register().
 | 
					 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * This function and Register() should always be used as a pair.
 | 
					 | 
				
			||||||
	 * You must call this function to release reources if you have called Register().
 | 
					 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * This function usually is called at the end of program.
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
	void Unregister();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#if defined(YYCC_DEBUG_UE_FILTER)
 | 
					 | 
				
			||||||
	long __stdcall DebugCallUExceptionImpl(void*);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
							
								
								
									
										626
									
								
								src/yycc/carton/ironpad.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										626
									
								
								src/yycc/carton/ironpad.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,626 @@
 | 
				
			|||||||
 | 
					#include "ironpad.hpp"
 | 
				
			||||||
 | 
					#include "../macro/os_detector.hpp"
 | 
				
			||||||
 | 
					#include "../macro/stl_detector.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../windows/winfct.hpp"
 | 
				
			||||||
 | 
					#include "../string/op.hpp"
 | 
				
			||||||
 | 
					#include "../string/reinterpret.hpp"
 | 
				
			||||||
 | 
					#include "../encoding/windows.hpp"
 | 
				
			||||||
 | 
					#include "../patch/ptr_pad.hpp"
 | 
				
			||||||
 | 
					#include "../patch/fopen.hpp"
 | 
				
			||||||
 | 
					#include "../macro/class_copy_move.hpp"
 | 
				
			||||||
 | 
					#include <filesystem>
 | 
				
			||||||
 | 
					#include <cstdarg>
 | 
				
			||||||
 | 
					#include <cstdio>
 | 
				
			||||||
 | 
					#include <cinttypes>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../windows/import_guard_head.hpp"
 | 
				
			||||||
 | 
					#include <Windows.h>
 | 
				
			||||||
 | 
					#include <DbgHelp.h>
 | 
				
			||||||
 | 
					#include "../windows/import_guard_tail.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define OP ::yycc::string::op
 | 
				
			||||||
 | 
					#define ENC ::yycc::encoding::windows
 | 
				
			||||||
 | 
					#define REINTERPRET ::yycc::string::reinterpret
 | 
				
			||||||
 | 
					#define WINFCT ::yycc::windows::winfct
 | 
				
			||||||
 | 
					#define FOPEN ::yycc::patch::fopen
 | 
				
			||||||
 | 
					using namespace std::literals::string_view_literals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace yycc::carton::ironpad {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma region Singleton Guard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static LONG WINAPI unhandled_exception_handler(LPEXCEPTION_POINTERS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief The "singleton" guard class for unhandled excepetion handler.
 | 
				
			||||||
 | 
					     * @details
 | 
				
			||||||
 | 
					     * The class making sure that there is only one unhandled exception handler was run in the same process,
 | 
				
			||||||
 | 
					     * when unhandled exception occurs, and prevent any futher possible recursive calling (exception in exception handler).
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    class SingletonGuard {
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        SingletonGuard() :
 | 
				
			||||||
 | 
					            m_CoreMutex(), m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr), m_UserCallback(nullptr),
 | 
				
			||||||
 | 
					            m_SingletonMutex(NULL) {}
 | 
				
			||||||
 | 
					        ~SingletonGuard() { shutdown(); }
 | 
				
			||||||
 | 
					        YYCC_DELETE_COPY_MOVE(SingletonGuard)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Try to register unhandled exception handler.
 | 
				
			||||||
 | 
					         * @details There is no bad outcome when calling this function multiple times.
 | 
				
			||||||
 | 
					         * @return True if success, otherwise false.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        bool startup(ExceptionCallback callback) {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            // if we have registered, return
 | 
				
			||||||
 | 
					            if (m_IsRegistered) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // check singleton
 | 
				
			||||||
 | 
					            // build mutex string first
 | 
				
			||||||
 | 
					            auto mutex_name = OP::printf(u8"Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}", GetCurrentProcessId());
 | 
				
			||||||
 | 
					            if (!mutex_name.has_value()) return false;
 | 
				
			||||||
 | 
					            auto w_mutex_name = ENC::to_wchar(mutex_name.value());
 | 
				
			||||||
 | 
					            if (!w_mutex_name.has_value()) return false;
 | 
				
			||||||
 | 
					            // create mutex
 | 
				
			||||||
 | 
					            m_SingletonMutex = CreateMutexW(NULL, FALSE, w_mutex_name.value().c_str());
 | 
				
			||||||
 | 
					            DWORD errcode = GetLastError();
 | 
				
			||||||
 | 
					            // check whether be created
 | 
				
			||||||
 | 
					            if (m_SingletonMutex == NULL) return false;
 | 
				
			||||||
 | 
					            if (errcode == ERROR_ALREADY_EXISTS) {
 | 
				
			||||||
 | 
					                CloseHandle(m_SingletonMutex);
 | 
				
			||||||
 | 
					                m_SingletonMutex = NULL;
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // okey, we can register it.
 | 
				
			||||||
 | 
					            // backup old handler
 | 
				
			||||||
 | 
					            m_PrevProcHandler = SetUnhandledExceptionFilter(unhandled_exception_handler);
 | 
				
			||||||
 | 
					            // set user callback
 | 
				
			||||||
 | 
					            m_UserCallback = callback;
 | 
				
			||||||
 | 
					            // mark registered
 | 
				
			||||||
 | 
					            m_IsRegistered = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Try to unregister unhandled exception handler.
 | 
				
			||||||
 | 
					         * @details There is no bad outcome when calling this function multiple times.
 | 
				
			||||||
 | 
					         * @return True if success, otherwise false.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        bool shutdown() {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            // if we are not registered, skip
 | 
				
			||||||
 | 
					            if (!m_IsRegistered) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // unregister handler
 | 
				
			||||||
 | 
					            // reset user callback
 | 
				
			||||||
 | 
					            m_UserCallback = nullptr;
 | 
				
			||||||
 | 
					            // restore old handler
 | 
				
			||||||
 | 
					            SetUnhandledExceptionFilter(m_PrevProcHandler);
 | 
				
			||||||
 | 
					            m_PrevProcHandler = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // release singleton handler
 | 
				
			||||||
 | 
					            if (m_SingletonMutex != NULL) {
 | 
				
			||||||
 | 
					                CloseHandle(m_SingletonMutex);
 | 
				
			||||||
 | 
					                m_SingletonMutex = NULL;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // mark unregistered
 | 
				
			||||||
 | 
					            m_IsRegistered = false;
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Check whether handler is registered.
 | 
				
			||||||
 | 
							 * @return True if it is, otherwise false.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        bool is_registered() const {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            return m_IsRegistered;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Check whether we are processing unhandled exception.
 | 
				
			||||||
 | 
							 * @return True if it is, otherwise false.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        bool is_processing() const {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            return m_IsProcessing;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Get the old unhandled exception handler before registering.
 | 
				
			||||||
 | 
							 * @return The fucntion pointer to old unhandled exception handler. May be nullptr.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        LPTOP_LEVEL_EXCEPTION_FILTER get_prev_proc_handler() const {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            return m_PrevProcHandler;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Get user specified callback.
 | 
				
			||||||
 | 
							 * @return The function pointer to user callback. nullptr if no associated callback.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        ExceptionCallback get_user_callback() const {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            return m_UserCallback;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Try to start process unhandled exception.
 | 
				
			||||||
 | 
							 * @return True if you can start to process.
 | 
				
			||||||
 | 
							 * False means there is already a process running. You should not process it now.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        bool start_processing() {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            if (m_IsProcessing) return false;
 | 
				
			||||||
 | 
					            else {
 | 
				
			||||||
 | 
					                m_IsProcessing = true;
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Mark current process of unhandled exception has done.
 | 
				
			||||||
 | 
							 * @details This should only be called when start_processing() return true.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        void stop_processing() {
 | 
				
			||||||
 | 
					            std::lock_guard<std::mutex> locker(m_CoreMutex);
 | 
				
			||||||
 | 
					            m_IsProcessing = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief The core mutex for keeping this class is in synchronized.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        mutable std::mutex m_CoreMutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Whether we have registered unhandled exception handler.
 | 
				
			||||||
 | 
							 * True if it is, otherwise false.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        bool m_IsRegistered;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief Whether we are processing unhandled exception.
 | 
				
			||||||
 | 
							 * True if it is, otherwise false.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        bool m_IsProcessing;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief User defined callback.
 | 
				
			||||||
 | 
							 * @details It will be called at the tail of unhandled exception handler, because it may raise exception.
 | 
				
			||||||
 | 
							 * We must make sure all log and coredump have been done before calling it.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        ExceptionCallback m_UserCallback;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief The backup of old unhandled exception handler.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        LPTOP_LEVEL_EXCEPTION_FILTER m_PrevProcHandler;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
							 * @brief The Windows mutex handle for singleton implementation.
 | 
				
			||||||
 | 
							 * Because we may have many DLLs using YYCC in the same process.
 | 
				
			||||||
 | 
							 * But the unhandled exception handler only need to be registered once.
 | 
				
			||||||
 | 
							*/
 | 
				
			||||||
 | 
					        HANDLE m_SingletonMutex;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// @brief Core register singleton.
 | 
				
			||||||
 | 
					    static SingletonGuard g_SingletonGuard;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma region Exception Dumper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class ExceptionDumper {
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        ExceptionDumper() {}
 | 
				
			||||||
 | 
					        ~ExceptionDumper() {}
 | 
				
			||||||
 | 
					        YYCC_DELETE_COPY_MOVE(ExceptionDumper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        CallbackInfo execute(LPEXCEPTION_POINTERS info) {
 | 
				
			||||||
 | 
					            // fetch file path first
 | 
				
			||||||
 | 
					            auto log_path = build_file_path(FileKind::LogFile);
 | 
				
			||||||
 | 
					            auto coredump_path = build_file_path(FileKind::CoredumpFile);
 | 
				
			||||||
 | 
					            // and report their status
 | 
				
			||||||
 | 
					            if (log_path.has_value()) {
 | 
				
			||||||
 | 
					                log_format_line(nullptr, u8"Crash Log: %s", log_path.value().c_str());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                log_write_line(nullptr, u8"Crash occurs, but we can not create crash log!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (coredump_path.has_value()) {
 | 
				
			||||||
 | 
					                log_format_line(nullptr, u8"Crash Coredump: %s", coredump_path.value().c_str());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                log_write_line(nullptr, u8"Crash occurs, but we can not create coredump!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write crash log
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // open file stream if possible
 | 
				
			||||||
 | 
					                FOPEN::SmartStdFile fs(nullptr);
 | 
				
			||||||
 | 
					                if (log_path.has_value()) {
 | 
				
			||||||
 | 
					                    fs.reset(FOPEN::fopen(log_path.value().c_str(), u8"wb"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // output basic infos
 | 
				
			||||||
 | 
					                // record exception type first
 | 
				
			||||||
 | 
					                PEXCEPTION_RECORD rec = info->ExceptionRecord;
 | 
				
			||||||
 | 
					                log_format_line(fs.get(),
 | 
				
			||||||
 | 
					                                u8"Unhandled exception occured at 0x%" PRIXPTR_LPAD PRIXPTR ": %s (%" PRIu32 ").",
 | 
				
			||||||
 | 
					                                rec->ExceptionAddress,
 | 
				
			||||||
 | 
					                                get_code_message(rec->ExceptionCode),
 | 
				
			||||||
 | 
					                                rec->ExceptionCode);
 | 
				
			||||||
 | 
					                // special proc for 2 exceptions
 | 
				
			||||||
 | 
					                if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
 | 
				
			||||||
 | 
					                    if (rec->NumberParameters >= 2) {
 | 
				
			||||||
 | 
					                        const char8_t* op = rec->ExceptionInformation[0] == 0   ? u8"read"
 | 
				
			||||||
 | 
					                                            : rec->ExceptionInformation[0] == 1 ? u8"written"
 | 
				
			||||||
 | 
					                                                                                : u8"executed";
 | 
				
			||||||
 | 
					                        log_format_line(fs.get(),
 | 
				
			||||||
 | 
					                                        u8"The data at memory address 0x%" PRIXPTR_LPAD PRIxPTR " could not be %s.",
 | 
				
			||||||
 | 
					                                        rec->ExceptionInformation[1],
 | 
				
			||||||
 | 
					                                        op);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // output stacktrace
 | 
				
			||||||
 | 
					                do_backtrace(fs.get(), info->ContextRecord, 1024);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Write coredump
 | 
				
			||||||
 | 
					            if (coredump_path.has_value()) {
 | 
				
			||||||
 | 
					                do_coredump(coredump_path.value(), info);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // return path for user callback
 | 
				
			||||||
 | 
					            return CallbackInfo{.log_path = std::move(log_path), .coredump_path = std::move(coredump_path)};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @brief All kind of dumped files.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        enum class FileKind { LogFile, CoredumpFile };
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @brief Build path to file stored on disk including exception data with given kind.
 | 
				
			||||||
 | 
					         * @param[in] kind The kind of file to be built.
 | 
				
			||||||
 | 
					         * @return The built path or nothing if error occurs.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        std::optional<std::u8string> build_file_path(FileKind kind) {
 | 
				
			||||||
 | 
					            // build file names like: "error.exe.1234.log" and "error.exe.1234.dmp".
 | 
				
			||||||
 | 
					            // "error.exe" is the name of current process. "1234" is current process id.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // get process name
 | 
				
			||||||
 | 
					            std::u8string u8_process_name;
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // get full path of process
 | 
				
			||||||
 | 
					                auto u8_process_path = WINFCT::get_module_file_name(NULL);
 | 
				
			||||||
 | 
					                if (!u8_process_path.has_value()) return std::nullopt;
 | 
				
			||||||
 | 
					                // extract file name from full path by std::filesystem::path
 | 
				
			||||||
 | 
					                std::filesystem::path process_path(u8_process_path.value());
 | 
				
			||||||
 | 
					                u8_process_name = process_path.filename().u8string();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // then get process id
 | 
				
			||||||
 | 
					            DWORD process_id = GetCurrentProcessId();
 | 
				
			||||||
 | 
					            // conbine them as a file name prefix
 | 
				
			||||||
 | 
					            auto u8_filename_prefix = OP::printf(u8"%s.%" PRIu32, u8_process_name.c_str(), process_id);
 | 
				
			||||||
 | 
					            if (!u8_filename_prefix.has_value()) return std::nullopt;
 | 
				
			||||||
 | 
					            // then get file name for log and minidump
 | 
				
			||||||
 | 
					            std::u8string u8_filename;
 | 
				
			||||||
 | 
					            switch (kind) {
 | 
				
			||||||
 | 
					                case FileKind::LogFile:
 | 
				
			||||||
 | 
					                    u8_filename = u8_filename_prefix.value() + u8".log";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case FileKind::CoredumpFile:
 | 
				
			||||||
 | 
					                    u8_filename = u8_filename_prefix.value() + u8".dmp";
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    u8_filename = u8_filename_prefix.value();
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fetch crash report path
 | 
				
			||||||
 | 
					            // get local appdata folder
 | 
				
			||||||
 | 
					            auto u8_localappdata_path = WINFCT::get_known_path(WINFCT::KnownDirectory::LocalAppData);
 | 
				
			||||||
 | 
					            if (!u8_localappdata_path.has_value()) return std::nullopt;
 | 
				
			||||||
 | 
					            // convert to std::filesystem::path
 | 
				
			||||||
 | 
					            std::filesystem::path crashreport_path(u8_localappdata_path.value());
 | 
				
			||||||
 | 
					            // slash into crash report folder
 | 
				
			||||||
 | 
					            crashreport_path /= u8"IronPad";
 | 
				
			||||||
 | 
					            // use create function to make sure it is existing
 | 
				
			||||||
 | 
					            std::filesystem::create_directories(crashreport_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // build log path and coredump path
 | 
				
			||||||
 | 
					            // build std::filesystem::path first
 | 
				
			||||||
 | 
					            std::filesystem::path file_path = crashreport_path / u8_filename;
 | 
				
			||||||
 | 
					            // output to result
 | 
				
			||||||
 | 
					            return file_path.u8string();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @brief Do stack trace for given exception.
 | 
				
			||||||
 | 
					         * @details
 | 
				
			||||||
 | 
					         * This function will do stack trace with given maximum depth as much as possible
 | 
				
			||||||
 | 
					         * and output trace info into given file stream and \c stderr.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        void do_backtrace(FILE* fs, LPCONTEXT context, int maxdepth) {
 | 
				
			||||||
 | 
					            // setup loading symbol options
 | 
				
			||||||
 | 
					            SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // setup handle
 | 
				
			||||||
 | 
					            HANDLE process = GetCurrentProcess();
 | 
				
			||||||
 | 
					            HANDLE thread = GetCurrentThread();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // init symbol
 | 
				
			||||||
 | 
					            if (!SymInitialize(process, 0, TRUE)) {
 | 
				
			||||||
 | 
					                // fail to init. return
 | 
				
			||||||
 | 
					                log_write_line(fs, u8"Fail to initialize symbol handle for process!");
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // ========== CORE DUMP ==========
 | 
				
			||||||
 | 
					            // prepare frame. setup correct fields
 | 
				
			||||||
 | 
					            // references:
 | 
				
			||||||
 | 
					            // https://github.com/rust-lang/backtrace-rs/blob/9ed25b581cfd2ee60e5a3b9054fd023bf6dced90/src/backtrace/dbghelp.rs
 | 
				
			||||||
 | 
					            // https://sourceforge.net/p/predef/wiki/Architectures/
 | 
				
			||||||
 | 
					            DWORD machine_type = 0;
 | 
				
			||||||
 | 
					            STACKFRAME64 frame;
 | 
				
			||||||
 | 
					            memset(&frame, 0, sizeof(frame));
 | 
				
			||||||
 | 
					#if defined(_M_IX86) || defined(__i386__)
 | 
				
			||||||
 | 
					            // x86
 | 
				
			||||||
 | 
					            machine_type = IMAGE_FILE_MACHINE_I386;
 | 
				
			||||||
 | 
					            frame.AddrPC.Offset = context->Eip;
 | 
				
			||||||
 | 
					            frame.AddrStack.Offset = context->Esp;
 | 
				
			||||||
 | 
					            frame.AddrFrame.Offset = context->Ebp;
 | 
				
			||||||
 | 
					#elif defined(_M_AMD64) || defined(__amd64__)
 | 
				
			||||||
 | 
					            // amd64
 | 
				
			||||||
 | 
					            machine_type = IMAGE_FILE_MACHINE_AMD64;
 | 
				
			||||||
 | 
					            frame.AddrPC.Offset = context->Rip;
 | 
				
			||||||
 | 
					            frame.AddrStack.Offset = context->Rsp;
 | 
				
			||||||
 | 
					            frame.AddrFrame.Offset = context->Rbp;
 | 
				
			||||||
 | 
					#elif defined(_M_ARM) || defined(__arm__)
 | 
				
			||||||
 | 
					            // arm (32bit)
 | 
				
			||||||
 | 
					            machine_type = IMAGE_FILE_MACHINE_ARMNT;
 | 
				
			||||||
 | 
					            frame.AddrPC.Offset = context->Pc;
 | 
				
			||||||
 | 
					            frame.AddrStack.Offset = context->Sp;
 | 
				
			||||||
 | 
					            frame.AddrFrame.Offset = context->R11;
 | 
				
			||||||
 | 
					#elif defined(_M_ARM64) || defined(__aarch64__)
 | 
				
			||||||
 | 
					            // arm64
 | 
				
			||||||
 | 
					            machine_type = IMAGE_FILE_MACHINE_ARM64;
 | 
				
			||||||
 | 
					            frame.AddrPC.Offset = context->Pc;
 | 
				
			||||||
 | 
					            frame.AddrStack.Offset = context->Sp;
 | 
				
			||||||
 | 
					            frame.AddrFrame.Offset = context->DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					#error "Unsupported platform"
 | 
				
			||||||
 | 
					            //IA-64 anybody?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					            frame.AddrPC.Mode = AddrModeFlat;
 | 
				
			||||||
 | 
					            frame.AddrStack.Mode = AddrModeFlat;
 | 
				
			||||||
 | 
					            frame.AddrFrame.Mode = AddrModeFlat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // stack walker
 | 
				
			||||||
 | 
					            while (StackWalk64(machine_type, process, thread, &frame, context, 0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
 | 
				
			||||||
 | 
					                // depth breaker
 | 
				
			||||||
 | 
					                --maxdepth;
 | 
				
			||||||
 | 
					                if (maxdepth < 0) {
 | 
				
			||||||
 | 
					                    log_write_line(fs, u8"..."); // indicate there are some frames not listed
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // get module name
 | 
				
			||||||
 | 
					                std::u8string module_name(u8"<unknown module>");
 | 
				
			||||||
 | 
					                DWORD64 module_base;
 | 
				
			||||||
 | 
					                if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
 | 
				
			||||||
 | 
					                    auto rv = WINFCT::get_module_file_name((HINSTANCE) module_base);
 | 
				
			||||||
 | 
					                    if (rv.has_value()) module_name = rv.value();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // get source file and line
 | 
				
			||||||
 | 
					                const char8_t* source_file = u8"<unknown source>";
 | 
				
			||||||
 | 
					                DWORD64 source_file_line = 0;
 | 
				
			||||||
 | 
					                DWORD dwDisplacement;
 | 
				
			||||||
 | 
					                IMAGEHLP_LINE64 winline;
 | 
				
			||||||
 | 
					                winline.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
 | 
				
			||||||
 | 
					                if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &dwDisplacement, &winline)) {
 | 
				
			||||||
 | 
					                    source_file = REINTERPRET::as_utf8(winline.FileName); // TODO: check whether there is UNICODE file name.
 | 
				
			||||||
 | 
					                    source_file_line = winline.LineNumber;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // write to file
 | 
				
			||||||
 | 
					                // MARK: should not use PRIXPTR to print adddress.
 | 
				
			||||||
 | 
					                // because Windows always use DWORD64 as the type of address.
 | 
				
			||||||
 | 
					                // use PRIX64 instead.
 | 
				
			||||||
 | 
					                log_format_line(fs,
 | 
				
			||||||
 | 
					                                u8"0x%" PRIXPTR_LPAD PRIX64 "[%s+0x%" PRIXPTR_LPAD PRIX64 "]\t%s#L%" PRIu64,
 | 
				
			||||||
 | 
					                                frame.AddrPC.Offset, // memory adress
 | 
				
			||||||
 | 
					                                module_name.c_str(),
 | 
				
			||||||
 | 
					                                frame.AddrPC.Offset - module_base, // module name + relative address
 | 
				
			||||||
 | 
					                                source_file,
 | 
				
			||||||
 | 
					                                source_file_line // source file + source line
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // ========== END CORE DUMP ==========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // free symbol
 | 
				
			||||||
 | 
					            SymCleanup(process);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * @brief Do coredump for given exception.
 | 
				
			||||||
 | 
					         * @details This function will write coredump of given exception into given file path.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        void do_coredump(const std::u8string_view& u8_filename, LPEXCEPTION_POINTERS info) {
 | 
				
			||||||
 | 
					            // convert file encoding
 | 
				
			||||||
 | 
					            // if convertion failed, return
 | 
				
			||||||
 | 
					            auto filename_rv = ENC::to_wchar(u8_filename);
 | 
				
			||||||
 | 
					            if (!filename_rv.has_value()) return;
 | 
				
			||||||
 | 
					            std::wstring filename = filename_rv.value();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // open file and write
 | 
				
			||||||
 | 
					            HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 | 
				
			||||||
 | 
					            if (hFile != INVALID_HANDLE_VALUE) {
 | 
				
			||||||
 | 
					                MINIDUMP_EXCEPTION_INFORMATION exception_info;
 | 
				
			||||||
 | 
					                exception_info.ThreadId = GetCurrentThreadId();
 | 
				
			||||||
 | 
					                exception_info.ExceptionPointers = info;
 | 
				
			||||||
 | 
					                exception_info.ClientPointers = TRUE;
 | 
				
			||||||
 | 
					                MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &exception_info, NULL, NULL);
 | 
				
			||||||
 | 
					                CloseHandle(hFile);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
						     * @brief Get human-readable exception string from given exception code.
 | 
				
			||||||
 | 
						     * @param[in] code Exception code
 | 
				
			||||||
 | 
						     * @return The string view to corresponding exception explanation string.
 | 
				
			||||||
 | 
						    */
 | 
				
			||||||
 | 
					        const std::u8string_view get_code_message(DWORD code) {
 | 
				
			||||||
 | 
					            switch (code) {
 | 
				
			||||||
 | 
					                case EXCEPTION_ACCESS_VIOLATION:
 | 
				
			||||||
 | 
					                    return u8"access violation"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
 | 
				
			||||||
 | 
					                    return u8"array index out of bound"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_BREAKPOINT:
 | 
				
			||||||
 | 
					                    return u8"breakpoint reached"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_DATATYPE_MISALIGNMENT:
 | 
				
			||||||
 | 
					                    return u8"misaligned data access"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_FLT_DENORMAL_OPERAND:
 | 
				
			||||||
 | 
					                    return u8"operand had denormal value"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_FLT_DIVIDE_BY_ZERO:
 | 
				
			||||||
 | 
					                    return u8"floating-point division by zero"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_FLT_INEXACT_RESULT:
 | 
				
			||||||
 | 
					                    return u8"no decimal fraction representation for value"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_FLT_INVALID_OPERATION:
 | 
				
			||||||
 | 
					                    return u8"invalid floating-point operation"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_FLT_OVERFLOW:
 | 
				
			||||||
 | 
					                    return u8"floating-point overflow"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_FLT_STACK_CHECK:
 | 
				
			||||||
 | 
					                    return u8"floating-point stack corruption"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_FLT_UNDERFLOW:
 | 
				
			||||||
 | 
					                    return u8"floating-point underflow"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_ILLEGAL_INSTRUCTION:
 | 
				
			||||||
 | 
					                    return u8"illegal instruction"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_IN_PAGE_ERROR:
 | 
				
			||||||
 | 
					                    return u8"inaccessible page"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_INT_DIVIDE_BY_ZERO:
 | 
				
			||||||
 | 
					                    return u8"integer division by zero"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_INT_OVERFLOW:
 | 
				
			||||||
 | 
					                    return u8"integer overflow"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_INVALID_DISPOSITION:
 | 
				
			||||||
 | 
					                    return u8"documentation says this should never happen"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_NONCONTINUABLE_EXCEPTION:
 | 
				
			||||||
 | 
					                    return u8"can't continue after a noncontinuable exception"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_PRIV_INSTRUCTION:
 | 
				
			||||||
 | 
					                    return u8"attempted to execute a privileged instruction"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_SINGLE_STEP:
 | 
				
			||||||
 | 
					                    return u8"one instruction has been executed"sv;
 | 
				
			||||||
 | 
					                case EXCEPTION_STACK_OVERFLOW:
 | 
				
			||||||
 | 
					                    return u8"stack overflow"sv;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    return u8"unknown exception"sv;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
						     * @brief Write error message
 | 
				
			||||||
 | 
						     * @details
 | 
				
			||||||
 | 
						     * This function will write given string into given file stream and \c stderr.
 | 
				
			||||||
 | 
						     * @param[in] fs
 | 
				
			||||||
 | 
						     * The file stream where we write.
 | 
				
			||||||
 | 
						     * If it is nullptr, function will skip writing for file stream.
 | 
				
			||||||
 | 
						     * @param[in] strl The string to be written.
 | 
				
			||||||
 | 
						    */
 | 
				
			||||||
 | 
					        void log_write_line(std::FILE* fs, const char8_t* strl) {
 | 
				
			||||||
 | 
					            // write to file
 | 
				
			||||||
 | 
					            if (fs != nullptr) {
 | 
				
			||||||
 | 
					                std::fputs(REINTERPRET::as_ordinary(strl), fs);
 | 
				
			||||||
 | 
					                std::fputs("\n", fs);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // write to stderr
 | 
				
			||||||
 | 
					            std::fputs(REINTERPRET::as_ordinary(strl), stderr);
 | 
				
			||||||
 | 
					            std::fputs("\n", stderr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
						     * @brief Format error message.
 | 
				
			||||||
 | 
						     * @details
 | 
				
			||||||
 | 
						     * This function will format message first.
 | 
				
			||||||
 | 
						     * And write them into given file stream and \c stderr.
 | 
				
			||||||
 | 
						     * @param[in] fs
 | 
				
			||||||
 | 
						     * The file stream where we write.
 | 
				
			||||||
 | 
						     * If it is nullptr, function will skip writing for file stream.
 | 
				
			||||||
 | 
						     * @param[in] fmt The format string.
 | 
				
			||||||
 | 
						     * @param[in] ... The argument to be formatted.
 | 
				
			||||||
 | 
						    */
 | 
				
			||||||
 | 
					        void log_format_line(std::FILE* fs, const char8_t* fmt, ...) {
 | 
				
			||||||
 | 
					            // do format first
 | 
				
			||||||
 | 
					            va_list arg;
 | 
				
			||||||
 | 
					            va_start(arg, fmt);
 | 
				
			||||||
 | 
					            auto fmt_rv = OP::vprintf(fmt, arg);
 | 
				
			||||||
 | 
					            va_end(arg);
 | 
				
			||||||
 | 
					            // write to file and console
 | 
				
			||||||
 | 
					            if (fmt_rv.has_value()) {
 | 
				
			||||||
 | 
					                log_write_line(fs, fmt_rv.value().c_str());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma region Core Implementation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static LONG WINAPI unhandled_exception_handler(LPEXCEPTION_POINTERS info) {
 | 
				
			||||||
 | 
					        // try to start process current unhandled exception
 | 
				
			||||||
 | 
					        // to prevent any possible recursive calling.
 | 
				
			||||||
 | 
					        if (!g_SingletonGuard.start_processing()) {
 | 
				
			||||||
 | 
					            // process exception
 | 
				
			||||||
 | 
					            ExceptionDumper dumper;
 | 
				
			||||||
 | 
					            auto pathinfo = dumper.execute(info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // call user callback
 | 
				
			||||||
 | 
					            ExceptionCallback user_callback = g_SingletonGuard.get_user_callback();
 | 
				
			||||||
 | 
					            if (user_callback != nullptr) user_callback(pathinfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // stop process
 | 
				
			||||||
 | 
					            g_SingletonGuard.stop_processing();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if backup proc can be run, run it
 | 
				
			||||||
 | 
					        // otherwise directly return.
 | 
				
			||||||
 | 
					        auto prev_proc = g_SingletonGuard.get_prev_proc_handler();
 | 
				
			||||||
 | 
					        if (prev_proc != nullptr) {
 | 
				
			||||||
 | 
					            return prev_proc(info);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return EXCEPTION_CONTINUE_SEARCH;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace yycc::carton::ironpad
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma region Exposed Function
 | 
				
			||||||
 | 
					namespace yycc::carton::ironpad {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool startup(ExceptionCallback callback) {
 | 
				
			||||||
 | 
					#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
 | 
				
			||||||
 | 
					        return g_SingletonGuard.startup(callback);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        // Do nothing
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void shutdown() {
 | 
				
			||||||
 | 
					#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
 | 
				
			||||||
 | 
					        g_SingletonGuard.shutdown();
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        // Do nothing
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace yycc::carton::ironpad
 | 
				
			||||||
 | 
					#pragma endregion
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/yycc/carton/ironpad.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/yycc/carton/ironpad.hpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <string_view>
 | 
				
			||||||
 | 
					#include <optional>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Windows specific unhandled exception handler.
 | 
				
			||||||
 | 
					 * @details
 | 
				
			||||||
 | 
					 * This namespace is currently works on Windows.
 | 
				
			||||||
 | 
					 * On other platforms, this namespace provided functions do nothing.
 | 
				
			||||||
 | 
					 * For how to utilize this namespace, please see \ref exception_helper.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * This feature is originate from my created Virtools plugin.
 | 
				
			||||||
 | 
					 * Because its user frequently trigger some weird behaviors but I have no idea about the detail of them.
 | 
				
			||||||
 | 
					 * So I create this feature. So that I can order user upload error log and coredump to help my debugging.
 | 
				
			||||||
 | 
					 * The original implementation of this feature is copied from chirs241097's open source project whose name I forgotten.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * After that, I split it from my plugin and let it become an independent library call IronPad,
 | 
				
			||||||
 | 
					 * and use it in libcmo21 and etc.
 | 
				
			||||||
 | 
					 * After few months, I created first version of YYCC and I move it into it and rename it as Exception Helper.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * Now we entering the second major version of YYCC, I decide restore its original name and put it with other homebrew features.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					namespace yycc::carton::ironpad {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief The path info passed into user callback.
 | 
				
			||||||
 | 
					     * @details
 | 
				
			||||||
 | 
					     * For all pathes stored in this struct, if it is \c std::nullopt,
 | 
				
			||||||
 | 
					     * it means that handler fail to create this, otherwise it must be created.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    struct CallbackInfo {
 | 
				
			||||||
 | 
					        std::optional<std::u8string> log_path;      ///< The path to crash log file.
 | 
				
			||||||
 | 
					        std::optional<std::u8string> coredump_path; ///< The path to coredump file.
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
						 * @brief The callback function prototype which will be called when unhandled exception happened.
 | 
				
			||||||
 | 
						 * @details
 | 
				
			||||||
 | 
						 * During registering unhandled exception handler,
 | 
				
			||||||
 | 
						 * caller can optionally provide a function pointer matching this prorotype to register.
 | 
				
			||||||
 | 
						 * Then it will be called if unhandled exception hanppened.
 | 
				
			||||||
 | 
					     * The timing of calling this callback is the end of writing all essential files and before exiting handler.
 | 
				
			||||||
 | 
					     * In other words, all passed pathes are valid for visiting if they are not \c std::nullopt.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * This callback is convenient for programmer using an explicit way to tell user an exception happened.
 | 
				
			||||||
 | 
						 * Because in default, handler will only write error log to \c stderr and file.
 | 
				
			||||||
 | 
						 * It will be totally invisible in GUI application.
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
					    using ExceptionCallback = void (*)(const CallbackInfo& info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
						 * @brief Register unhandled exception handler
 | 
				
			||||||
 | 
						 * @details
 | 
				
			||||||
 | 
						 * This function will set an internal function as unhandled exception handler.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * When unhandled exception raised, 
 | 
				
			||||||
 | 
						 * That internal function will output error stacktrace in standard output,
 | 
				
			||||||
 | 
						 * and generate log file and dump file in \c \%LOCALAPPDATA\%/IronPad folder if it is possible.
 | 
				
			||||||
 | 
						 * (for convenient debugging of developer when reporting bugs.)
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * This function usually is called at the start of program.
 | 
				
			||||||
 | 
						 * @param[in] callback User defined callback called when unhandled exception happened. nullptr if no callback.
 | 
				
			||||||
 | 
					     * @return True when success, otherwise false.
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
					    bool startup(ExceptionCallback callback = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
						 * @brief Unregister unhandled exception handler
 | 
				
			||||||
 | 
						 * @details 
 | 
				
			||||||
 | 
						 * The reverse operation of startup().
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * This function and startup() should always be used as a pair.
 | 
				
			||||||
 | 
						 * You must call this function to release reources if you have called startup().
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * This function usually is called at the end of program.
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * It is safe that call this function multiple times, or call this function when startup() return false.
 | 
				
			||||||
 | 
					     * It means that there is no compulsory check for the return value of startup() if you don't care it.
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
					    void shutdown();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace yycc::carton::ironpad
 | 
				
			||||||
		Reference in New Issue
	
	Block a user