From 170fca0e015d7a2e00ec4439de29512fcf7e90cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 26 Jul 2025 22:14:14 -0300 Subject: [PATCH] Improve Windows crash report. --- .gitignore | 1 + src/thirdparty/backward-cpp/backward.hpp | 4 + src/tools/ecode/backward.cpp | 285 ++++++++++++++++++++--- 3 files changed, 254 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 1f90993e0..6ac3de328 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,4 @@ ecode.dmg /bin/unit_tests/eepp* /bin/unit_tests/lib* /projects/linux/ecode/polyfill-glibc +/bin/crashes/* diff --git a/src/thirdparty/backward-cpp/backward.hpp b/src/thirdparty/backward-cpp/backward.hpp index ba0c6323d..8c44d657d 100644 --- a/src/thirdparty/backward-cpp/backward.hpp +++ b/src/thirdparty/backward-cpp/backward.hpp @@ -4281,6 +4281,8 @@ public: #elif defined(__mips__) error_addr = reinterpret_cast( reinterpret_cast(&uctx->uc_mcontext)->sc_pc); +#elif defined(__APPLE__) && defined(__POWERPC__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__srr0); #elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || \ defined(__POWERPC__) error_addr = reinterpret_cast(uctx->uc_mcontext.regs->nip); @@ -4292,6 +4294,8 @@ public: error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__rip); #elif defined(__APPLE__) error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__eip); +#elif defined(__loongarch__) + error_addr = reinterpret_cast(uctx->uc_mcontext.__pc); #else #warning ":/ sorry, ain't know no nothing none not of your architecture!" #endif diff --git a/src/tools/ecode/backward.cpp b/src/tools/ecode/backward.cpp index 2ef0c4780..18f7499c1 100644 --- a/src/tools/ecode/backward.cpp +++ b/src/tools/ecode/backward.cpp @@ -1,43 +1,13 @@ -// Pick your poison. -// -// On GNU/Linux, you have few choices to get the most out of your stack trace. -// -// By default you get: -// - object filename -// - function name -// -// In order to add: -// - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) - -// Install one of the following libraries then uncomment one of the macro (or -// better, add the detection of the lib and the macro definition in your build -// system) - -// - apt-get install libdw-dev ... -// - g++/clang++ -ldw ... -// #define BACKWARD_HAS_DW 1 - -// - apt-get install binutils-dev ... -// - g++/clang++ -lbfd ... -// #define BACKWARD_HAS_BFD 1 - -// - apt-get install libdwarf-dev ... -// - g++/clang++ -ldwarf ... -// #define BACKWARD_HAS_DWARF 1 - -// Regardless of the library you choose to read the debug information, -// for potentially more detailed stack traces you can use libunwind -// - apt-get install libunwind-dev -// - g++/clang++ -lunwind -// #define BACKWARD_HAS_LIBUNWIND 1 - #include +#include +#include + +using namespace EE; +using namespace EE::System; #if EE_PLATFORM != EE_PLATFORM_ANDROID && EE_PLATFORM != EE_PLATFORM_IOS -#if EE_PLATFORM == EE_PLATFORM_LINUX && defined( ECODE_HAS_DW ) +#if defined( ECODE_HAS_DW ) #define BACKWARD_HAS_DW 1 #endif @@ -45,8 +15,251 @@ namespace backward { +#if EE_PLATFORM == EE_PLATFORM_WIN + +class WindowsSignalHandling { + public: + WindowsSignalHandling( const std::vector& = std::vector() ) : + reporter_thread_( []() { + /* We handle crashes in a utility thread: + backward structures and some Windows functions called here + need stack space, which we do not have when we encounter a + stack overflow. + To support reporting stack traces during a stack overflow, + we create a utility thread at startup, which waits until a + crash happens or the program exits normally. */ + + { + std::unique_lock lk( mtx() ); + cv().wait( lk, [] { return crashed() != crash_status::running; } ); + } + if ( crashed() == crash_status::crashed ) { + handle_stacktrace( skip_recs() ); + } + { + std::unique_lock lk( mtx() ); + crashed() = crash_status::ending; + } + cv().notify_one(); + } ) { + + // Disable Windows Error Reporting dialogs + // SetErrorMode( SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS ); + + SetUnhandledExceptionFilter( crash_handler ); + + signal( SIGABRT, signal_handler ); + + std::set_terminate( &terminator ); +#ifndef BACKWARD_ATLEAST_CXX17 + std::set_unexpected( &terminator ); +#endif + _set_purecall_handler( &terminator ); + _set_invalid_parameter_handler( &invalid_parameter_handler ); + } + + bool loaded() const { return true; } + + ~WindowsSignalHandling() { + { + std::unique_lock lk( mtx() ); + crashed() = crash_status::normal_exit; + } + + cv().notify_one(); + + reporter_thread_.join(); + } + + private: + static CONTEXT* ctx() { + static CONTEXT data; + return &data; + } + + enum class crash_status { running, crashed, normal_exit, ending }; + + static crash_status& crashed() { + static crash_status data; + return data; + } + + static std::mutex& mtx() { + static std::mutex data; + return data; + } + + static std::condition_variable& cv() { + static std::condition_variable data; + return data; + } + + static HANDLE& thread_handle() { + static HANDLE handle; + return handle; + } + + std::thread reporter_thread_; + + static const constexpr int signal_skip_recs = +#ifdef __clang__ + 4 +#else + 3 +#endif + ; + + static int& skip_recs() { + static int data; + return data; + } + + static inline void terminator() { + crash_handler( signal_skip_recs ); + abort(); + } + + static inline void signal_handler( int ) { + crash_handler( signal_skip_recs ); + abort(); + } + + static inline void __cdecl invalid_parameter_handler( const wchar_t*, const wchar_t*, + const wchar_t*, unsigned int, + uintptr_t ) { + crash_handler( signal_skip_recs ); + abort(); + } + + NOINLINE static LONG WINAPI crash_handler( EXCEPTION_POINTERS* info ) { + crash_handler( 0, info->ContextRecord ); + return EXCEPTION_CONTINUE_SEARCH; + } + + NOINLINE static void crash_handler( int skip, CONTEXT* ct = nullptr ) { + if ( ct == nullptr ) { + RtlCaptureContext( ctx() ); + } else { + memcpy( ctx(), ct, sizeof( CONTEXT ) ); + } + DuplicateHandle( GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), + &thread_handle(), 0, FALSE, DUPLICATE_SAME_ACCESS ); + + skip_recs() = skip; + + { + std::unique_lock lk( mtx() ); + crashed() = crash_status::crashed; + } + + cv().notify_one(); + + { + std::unique_lock lk( mtx() ); + cv().wait( lk, [] { return crashed() != crash_status::crashed; } ); + } + } + + static void display_crash_message( const std::string& crashFilePath ) { + std::string crashMsg( + String::format( "ecode has encountered an unrecoverable error and crashed! :'(\n" + "A crash log has been saved at:\n%s\n" + "Please feel free to use this file to report a bug at the ecode Github " + "page so it can be fixed as soon as possible.", + crashFilePath ) ); + + std::wstring wCrashMsg( crashMsg.begin(), crashMsg.end() ); + std::wstring wTitle = L"ecode crashed!"; + + MessageBoxW( nullptr, wCrashMsg.c_str(), wTitle.c_str(), + MB_OK | MB_ICONERROR | MB_TOPMOST ); + } + + static void handle_stacktrace( int skip_frames = 0 ) { + std::string crashesPath( Sys::getProcessPath() ); + FileSystem::dirAddSlashAtEnd( crashesPath ); + crashesPath += "crashes"; + FileSystem::dirAddSlashAtEnd( crashesPath ); + std::string dateTimeStr( Sys::getDateTimeStr() ); + String::replaceAll( dateTimeStr, " ", "_" ); + String::replaceAll( dateTimeStr, ":", "-" ); + std::string crashFilePath( + String::format( "%sstacktrace_%s.log", crashesPath, dateTimeStr ) ); + + FileSystem::makeDir( crashesPath ); + + std::ofstream outFile( crashFilePath ); + if ( !outFile.is_open() ) { + print_to_console( "Error: Failed to open " + crashFilePath + + " for writing stack trace\n" ); + print_stacktrace_to_console( skip_frames, nullptr ); + return; + } + + Printer printer; + printer.address = true; + printer.object = true; + + StackTrace st; + st.set_machine_type( printer.resolver().machine_type() ); + st.set_thread_handle( thread_handle() ); + st.load_here( 64 + skip_frames, ctx() ); // Increased frame limit + st.skip_n_firsts( skip_frames ); + + printer.print( st, outFile ); + print_stacktrace_to_console( skip_frames, &st ); + + outFile.close(); + + display_crash_message( crashFilePath ); + } + + static void print_stacktrace_to_console( int skip_frames, const StackTrace* st = nullptr ) { + if ( AttachConsole( ATTACH_PARENT_PROCESS ) ) { + FILE* console = nullptr; + freopen_s( &console, "CONOUT$", "w", stderr ); + if ( console ) { + Printer printer; + printer.address = true; + printer.object = true; + + if ( st ) { + printer.print( *st, std::cerr ); + } else { + StackTrace new_st; + new_st.set_machine_type( printer.resolver().machine_type() ); + new_st.set_thread_handle( thread_handle() ); + new_st.load_here( 64 + skip_frames, ctx() ); + new_st.skip_n_firsts( skip_frames ); + printer.print( new_st, std::cerr ); + } + fflush( stderr ); + } + FreeConsole(); + } + } + + static void print_to_console( const std::string& message ) { + if ( AttachConsole( ATTACH_PARENT_PROCESS ) ) { + FILE* console = nullptr; + freopen_s( &console, "CONOUT$", "w", stderr ); + if ( console ) { + std::cerr << message; + fflush( stderr ); + } + FreeConsole(); + } + } +}; + +backward::WindowsSignalHandling sh; + +#else + backward::SignalHandling sh; +#endif + } // namespace backward #endif