fix: make exception handler be singleton in same process.
- use CreateMutexW to make sure exception handler is singleton in the same process. - Because one process may load multiple DLLs using YYCC. - According to this, it will produce N times error log. N is the count of DLLs using YYCC exception handler. - refactor exception handler. use a class instance to manage all global variables and add a std::mutex to let module be thread safe. - change the way of check in console input testbench.
This commit is contained in:
parent
3075ec583d
commit
4f1e2447d0
@ -11,6 +11,7 @@
|
|||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "WinImportPrefix.hpp"
|
#include "WinImportPrefix.hpp"
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@ -19,38 +20,153 @@
|
|||||||
|
|
||||||
namespace YYCC::ExceptionHelper {
|
namespace YYCC::ExceptionHelper {
|
||||||
|
|
||||||
/**
|
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS);
|
||||||
* @brief True if the exception handler already registered, otherwise false.
|
class ExceptionRegister {
|
||||||
* @details
|
public:
|
||||||
* This variable is designed to prevent multiple register operation
|
ExceptionRegister() :
|
||||||
* because unhandled exception handler should only be registered once.
|
m_CoreMutex(),
|
||||||
* \n
|
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
|
||||||
* Register function should check whether this variable is false before registering,
|
m_SingletonMutex(NULL) {}
|
||||||
* and set this variable to true after registing.
|
~ExceptionRegister() {
|
||||||
* Unregister as well as should do the same check.
|
Unregister();
|
||||||
*/
|
}
|
||||||
static bool g_IsRegistered = false;
|
|
||||||
/**
|
public:
|
||||||
* @brief True if a exception handler is running, otherwise false.
|
/**
|
||||||
* @details
|
* @brief Try to register unhandled exception handler.
|
||||||
* This variable is served for blocking possible infinity recursive exception handling.
|
*/
|
||||||
* \n
|
void Register() {
|
||||||
* When entering unhandled exception handler, we must check whether this variable is true.
|
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||||
* If it is true, it mean that there is another unhandled exception handler running.
|
// if we have registered, return
|
||||||
* Then we should exit immediately.
|
if (m_IsRegistered) return;
|
||||||
* Otherwise, this variable should be set to true indicating we are processing unhandled exception.
|
|
||||||
* After processing exception, at the end of unhandled exception handler,
|
// check singleton
|
||||||
* we should restore this value to false.
|
// build mutex string first
|
||||||
*
|
yycc_u8string mutex_name;
|
||||||
*/
|
if (!StringHelper::Printf(mutex_name, YYCC_U8("Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}"), GetCurrentProcessId()))
|
||||||
static bool g_IsProcessing = false;
|
return;
|
||||||
/**
|
std::wstring mutex_wname;
|
||||||
* @brief The backup of original exception handler.
|
if (!EncodingHelper::UTF8ToWchar(mutex_name, mutex_wname))
|
||||||
* @details
|
return;
|
||||||
* This variable was set when registering unhandled exception handler.
|
// create mutex
|
||||||
* And will be used when unregistering for restoring.
|
m_SingletonMutex = CreateMutexW(NULL, FALSE, mutex_wname.c_str());
|
||||||
*/
|
DWORD errcode = GetLastError();
|
||||||
static LPTOP_LEVEL_EXCEPTION_FILTER g_ProcBackup;
|
// 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);
|
||||||
|
// 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
|
||||||
|
// 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 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 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
static ExceptionRegister g_ExceptionRegister;
|
||||||
|
|
||||||
#pragma region Exception Handler Implementation
|
#pragma region Exception Handler Implementation
|
||||||
|
|
||||||
@ -316,28 +432,24 @@ namespace YYCC::ExceptionHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool UExceptionFetchRecordPath(yycc_u8string& log_path, yycc_u8string& coredump_path) {
|
static bool UExceptionFetchRecordPath(yycc_u8string& log_path, yycc_u8string& coredump_path) {
|
||||||
// build two file names like: "module.dll.1234.log" and "module.dll.1234.dmp".
|
// build two file names like: "error.exe.1234.log" and "error.exe.1234.dmp".
|
||||||
// "module.dll" is the name of current module. "1234" is current process id.
|
// "error.exe" is the name of current process. "1234" is current process id.
|
||||||
// get self module name
|
// get process name
|
||||||
yycc_u8string u8_self_module_name;
|
yycc_u8string u8_process_name;
|
||||||
{
|
{
|
||||||
// get module handle
|
// get full path of process
|
||||||
HMODULE hSelfModule = YYCC::WinFctHelper::GetCurrentModule();
|
yycc_u8string u8_process_path;
|
||||||
if (hSelfModule == nullptr)
|
if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path))
|
||||||
return false;
|
|
||||||
// get full path of self module
|
|
||||||
yycc_u8string u8_self_module_path;
|
|
||||||
if (!YYCC::WinFctHelper::GetModuleFileName(hSelfModule, u8_self_module_path))
|
|
||||||
return false;
|
return false;
|
||||||
// extract file name from full path by std::filesystem::path
|
// extract file name from full path by std::filesystem::path
|
||||||
std::filesystem::path self_module_path(FsPathPatch::FromUTF8Path(u8_self_module_path.c_str()));
|
std::filesystem::path process_path(FsPathPatch::FromUTF8Path(u8_process_path.c_str()));
|
||||||
u8_self_module_name = FsPathPatch::ToUTF8Path(self_module_path.filename());
|
u8_process_name = FsPathPatch::ToUTF8Path(process_path.filename());
|
||||||
}
|
}
|
||||||
// then get process id
|
// then get process id
|
||||||
DWORD process_id = GetCurrentProcessId();
|
DWORD process_id = GetCurrentProcessId();
|
||||||
// conbine them as a file name prefix
|
// conbine them as a file name prefix
|
||||||
yycc_u8string u8_filename_prefix;
|
yycc_u8string u8_filename_prefix;
|
||||||
if (!YYCC::StringHelper::Printf(u8_filename_prefix, YYCC_U8("%s.%" PRIu32), u8_self_module_name.c_str(), process_id))
|
if (!YYCC::StringHelper::Printf(u8_filename_prefix, YYCC_U8("%s.%" PRIu32), u8_process_name.c_str(), process_id))
|
||||||
return false;
|
return false;
|
||||||
// then get file name for log and minidump
|
// then get file name for log and minidump
|
||||||
yycc_u8string u8_log_filename = u8_filename_prefix + YYCC_U8(".log");
|
yycc_u8string u8_log_filename = u8_filename_prefix + YYCC_U8(".log");
|
||||||
@ -367,10 +479,9 @@ namespace YYCC::ExceptionHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
|
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
|
||||||
// detect loop calling
|
// try to start process current unhandled exception
|
||||||
if (g_IsProcessing) goto end_proc;
|
// to prevent any possible recursive calling.
|
||||||
// start process
|
if (!g_ExceptionRegister.StartProcessing()) goto end_proc;
|
||||||
g_IsProcessing = true;
|
|
||||||
|
|
||||||
// core implementation
|
// core implementation
|
||||||
{
|
{
|
||||||
@ -396,14 +507,15 @@ namespace YYCC::ExceptionHelper {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// end process
|
// stop process
|
||||||
failed:
|
g_ExceptionRegister.StartProcessing();
|
||||||
g_IsProcessing = false;
|
|
||||||
|
end_proc:
|
||||||
// if backup proc can be run, run it
|
// if backup proc can be run, run it
|
||||||
// otherwise directly return.
|
// otherwise directly return.
|
||||||
end_proc:
|
auto prev_proc = g_ExceptionRegister.GetPrevProcHandler();
|
||||||
if (g_ProcBackup != nullptr) {
|
if (prev_proc != nullptr) {
|
||||||
return g_ProcBackup(info);
|
return prev_proc(info);
|
||||||
} else {
|
} else {
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
@ -412,15 +524,11 @@ namespace YYCC::ExceptionHelper {
|
|||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
void Register() {
|
void Register() {
|
||||||
if (g_IsRegistered) return;
|
g_ExceptionRegister.Register();
|
||||||
g_ProcBackup = SetUnhandledExceptionFilter(UExceptionImpl);
|
|
||||||
g_IsRegistered = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unregister() {
|
void Unregister() {
|
||||||
if (!g_IsRegistered) return;
|
g_ExceptionRegister.Unregister();
|
||||||
SetUnhandledExceptionFilter(g_ProcBackup);
|
|
||||||
g_IsRegistered = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,8 @@ namespace YYCCTestbench {
|
|||||||
Console::Write(YYCC_U8("\t> "));
|
Console::Write(YYCC_U8("\t> "));
|
||||||
|
|
||||||
YYCC::yycc_u8string gotten(Console::ReadLine());
|
YYCC::yycc_u8string gotten(Console::ReadLine());
|
||||||
Assert(gotten == strl, YYCC::StringHelper::Printf(YYCC_U8("Got: %s"), gotten.c_str()).c_str());
|
if (gotten == strl) Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("\tMatched! Got: %s")), gotten.c_str());
|
||||||
|
else Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("\tNOT Matched! Got: %s")), gotten.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user