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