From 0ac6b477f9dccceb358cbbc1f80f408c3be9183e Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 31 Jul 2024 20:32:11 +0800 Subject: [PATCH] fix: fix fatal error of ExceptionHelper in x86 environemnt. - fix a wrong placeholder of printf in ExceptionHelper which cause crash in unhandled exception handler. - improve format function in ExceptionHelper. - add a new debugging option and macro in CMake script and code for the convenience of debugging unhandled exception handler. - add docuementation about previous term. --- CMakeLists.txt | 1 + doc/src/intro.dox | 7 ++++ src/CMakeLists.txt | 5 ++- src/ExceptionHelper.cpp | 71 +++++++++++++++++++++-------------------- src/ExceptionHelper.hpp | 4 +++ testbench/main.cpp | 12 +++++++ 6 files changed, 65 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72f7c45..dda94e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ project(YYCC # Provide options option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF) option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF) +option(YYCC_DEBUG_UE_FILTER "YYCC developer used switch for testing Windows unhandled exception filter. Should not set to ON!!!" OFF) # Setup install path from CMake provided install path for convenient use. include(GNUInstallDirs) diff --git a/doc/src/intro.dox b/doc/src/intro.dox index 63bf00e..17c8745 100644 --- a/doc/src/intro.dox +++ b/doc/src/intro.dox @@ -159,4 +159,11 @@ that MSVC distribution places all static library under one director \c lib. Thus in MSVC project user can simply spcify the install path of YYCC, and use MSVC macros in path to choose correct static library for linking +\section intro__debug Debug Tips + +YYCC CMake build script contains a special option called \c YYCC_DEBUG_UE_FILTER. +If you set it to true, it will add a public macro \c YYCC_DEBUG_UE_FILTER to YYCC project. +This macro will enable special code path for the convenience of debugging \ref exception_helper related features. +So in common use, user should not enable this option. + */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efe6a8d..7a67907 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,8 +62,11 @@ PROPERTIES CXX_STANDARD_REQUIRED 17 CXX_EXTENSION OFF ) -# Order Unicode charset for private using target_compile_definitions(YYCCommonplace +# Debug macro should populate to child projects +PUBLIC + $<$:YYCC_DEBUG_UE_FILTER> +# Unicode charset for private using PRIVATE $<$:UNICODE> $<$:_UNICODE> diff --git a/src/ExceptionHelper.cpp b/src/ExceptionHelper.cpp index f02e9b8..467bab8 100644 --- a/src/ExceptionHelper.cpp +++ b/src/ExceptionHelper.cpp @@ -242,33 +242,6 @@ namespace YYCC::ExceptionHelper { } } - /** - * @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, ...) { - // write to file - if (fs != nullptr) { - va_list arg1; - va_start(arg1, fmt); - std::vfprintf(fs, EncodingHelper::ToOrdinary(fmt), arg1); - std::fputs("\n", fs); - va_end(arg1); - } - // write to stderr - va_list arg2; - va_start(arg2, fmt); - ConsoleHelper::ErrWriteLine(YYCC::StringHelper::VPrintf(fmt, arg2).c_str()); - va_end(arg2); - } - /** * @brief Error log (including backtrace) used output function * @details @@ -288,6 +261,27 @@ namespace YYCC::ExceptionHelper { 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. @@ -343,7 +337,7 @@ namespace YYCC::ExceptionHelper { 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)) { @@ -356,12 +350,12 @@ namespace YYCC::ExceptionHelper { } // get module name - const yycc_char8_t* module_name = YYCC_U8(""); - yycc_u8string module_name_raw; + const yycc_char8_t* no_module_name = YYCC_U8(""); + 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_raw)) { - module_name = module_name_raw.c_str(); + if (!WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name)) { + module_name = no_module_name; } } @@ -377,9 +371,12 @@ namespace YYCC::ExceptionHelper { } // write to file - UExceptionErrLogFormatLine(fs, YYCC_U8("0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64), + // 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, frame.AddrPC.Offset - module_base, // module name + relative address + module_name.c_str(), frame.AddrPC.Offset - module_base, // module name + relative address source_file, source_file_line // source file + source line ); @@ -555,6 +552,12 @@ namespace YYCC::ExceptionHelper { g_ExceptionRegister.Unregister(); } +#if defined(YYCC_DEBUG_UE_FILTER) + long __stdcall DebugCallUExceptionImpl(void* data) { + return UExceptionImpl(static_cast(data)); + } +#endif + } #endif diff --git a/src/ExceptionHelper.hpp b/src/ExceptionHelper.hpp index ab73f40..23b871b 100644 --- a/src/ExceptionHelper.hpp +++ b/src/ExceptionHelper.hpp @@ -55,6 +55,10 @@ namespace YYCC::ExceptionHelper { */ void Unregister(); +#if defined(YYCC_DEBUG_UE_FILTER) + long __stdcall DebugCallUExceptionImpl(void*); +#endif + } #endif diff --git a/testbench/main.cpp b/testbench/main.cpp index 16b7383..878e914 100644 --- a/testbench/main.cpp +++ b/testbench/main.cpp @@ -349,8 +349,20 @@ namespace YYCCTestbench { }); // Perform a div zero exception. +#if defined (YYCC_DEBUG_UE_FILTER) + // Reference: https://stackoverflow.com/questions/20981982/is-it-possible-to-debug-unhandledexceptionfilters-with-a-debugger + __try { + // all of code normally inside of main or WinMain here... + int i = 1, j = 0; + int k = i / j; + } + __except (YYCC::ExceptionHelper::DebugCallUExceptionImpl(GetExceptionInformation())) { + OutputDebugStringW(L"executed filter function\n"); + } +#else int i = 1, j = 0; int k = i / j; +#endif YYCC::ExceptionHelper::Unregister();