From c0715866fff208b102b3f7c50fcc8de8e8c50558 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 22 Jun 2021 09:53:23 +0200 Subject: [PATCH] Win32 specific: SEH handler on background thread. Catches Windows structured exceptions (hard crashes, segmentation faults...), converts them to Slic3r::HardCrash exceptions and displays the exception using the usual PrusaSlicer way. The SEH handler is installed on the main background slicing thread as of now, therefore hard crashes in TBB worker threads are not handled. --- src/libslic3r/Exception.hpp | 1 + src/slic3r/GUI/BackgroundSlicingProcess.cpp | 145 +++++++++++++++++--- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 25 +++- 3 files changed, 154 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/Exception.hpp b/src/libslic3r/Exception.hpp index fababa47d..1b8ad50a7 100644 --- a/src/libslic3r/Exception.hpp +++ b/src/libslic3r/Exception.hpp @@ -15,6 +15,7 @@ class Exception : public std::runtime_error { using std::runtime_error::runtime_ SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception); SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException); SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(HardCrash, CriticalException); SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError); SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError); SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 8b3b37513..482900c50 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -74,11 +74,15 @@ std::pair SlicingProcessCompletedEvent::format_error_message( bool monospace = false; try { this->rethrow_exception(); - } catch (const std::bad_alloc& ex) { + } catch (const std::bad_alloc &ex) { wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. " "If you are sure you have enough RAM on your system, this may also be a bug and we would " "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); + } catch (const HardCrash &ex) { + error = GUI::format("PrusaSlicer has encountered a fatal error: \"%1%\"", ex.what()) + "\n\n" + + _u8L("Please save your project and restart PrusaSlicer. " + "We would be glad if you reported the issue."); } catch (PlaceholderParserError &ex) { error = ex.what(); monospace = true; @@ -277,19 +281,11 @@ void BackgroundSlicingProcess::thread_proc() m_state = STATE_RUNNING; lck.unlock(); std::exception_ptr exception; - try { - assert(m_print != nullptr); - switch(m_print->technology()) { - case ptFFF: this->process_fff(); break; - case ptSLA: this->process_sla(); break; - default: m_print->process(); break; - } - } catch (CanceledException & /* ex */) { - // Canceled, this is all right. - assert(m_print->canceled()); - } catch (...) { - exception = std::current_exception(); - } +#ifdef _WIN32 + this->call_process_seh_throw(exception); +#else + this->call_process(exception); +#endif m_print->finalize(); lck.lock(); m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED; @@ -312,7 +308,118 @@ void BackgroundSlicingProcess::thread_proc() // End of the background processing thread. The UI thread should join m_thread now. } -void BackgroundSlicingProcess::thread_proc_safe() +#ifdef _WIN32 +// Only these SEH exceptions will be catched and turned into Slic3r::HardCrash C++ exceptions. +static bool is_win32_seh_harware_exception(unsigned long ex) throw() { + return + ex == STATUS_ACCESS_VIOLATION || + ex == STATUS_DATATYPE_MISALIGNMENT || + ex == STATUS_FLOAT_DIVIDE_BY_ZERO || + ex == STATUS_FLOAT_OVERFLOW || + ex == STATUS_FLOAT_UNDERFLOW || +#ifdef STATUS_FLOATING_RESEVERED_OPERAND + ex == STATUS_FLOATING_RESEVERED_OPERAND || +#endif // STATUS_FLOATING_RESEVERED_OPERAND + ex == STATUS_ILLEGAL_INSTRUCTION || + ex == STATUS_PRIVILEGED_INSTRUCTION || + ex == STATUS_INTEGER_DIVIDE_BY_ZERO || + ex == STATUS_INTEGER_OVERFLOW || + ex == STATUS_STACK_OVERFLOW; +} + +// Rethrow some SEH exceptions as Slic3r::HardCrash C++ exceptions. +static void rethrow_seh_exception(unsigned long win32_seh_catched) +{ + if (win32_seh_catched) { + // Rethrow SEH exception as Slicer::HardCrash. + if (win32_seh_catched == STATUS_ACCESS_VIOLATION || win32_seh_catched == STATUS_DATATYPE_MISALIGNMENT) + throw Slic3r::HardCrash(_u8L("Access violation")); + if (win32_seh_catched == STATUS_ILLEGAL_INSTRUCTION || win32_seh_catched == STATUS_PRIVILEGED_INSTRUCTION) + throw Slic3r::HardCrash(_u8L("Illegal instruction")); + if (win32_seh_catched == STATUS_FLOAT_DIVIDE_BY_ZERO || win32_seh_catched == STATUS_INTEGER_DIVIDE_BY_ZERO) + throw Slic3r::HardCrash(_u8L("Divide by zero")); + if (win32_seh_catched == STATUS_FLOAT_OVERFLOW || win32_seh_catched == STATUS_INTEGER_OVERFLOW) + throw Slic3r::HardCrash(_u8L("Overflow")); + if (win32_seh_catched == STATUS_FLOAT_UNDERFLOW) + throw Slic3r::HardCrash(_u8L("Underflow")); +#ifdef STATUS_FLOATING_RESEVERED_OPERAND + if (win32_seh_catched == STATUS_FLOATING_RESEVERED_OPERAND) + throw Slic3r::HardCrash(_u8L("Floating reserved operand")); +#endif // STATUS_FLOATING_RESEVERED_OPERAND + if (win32_seh_catched == STATUS_STACK_OVERFLOW) + throw Slic3r::HardCrash(_u8L("Stack overflow")); + } +} + +// Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function. +unsigned long BackgroundSlicingProcess::call_process_seh(std::exception_ptr &ex) throw() +{ + unsigned long win32_seh_catched = 0; + __try { + this->call_process(ex); + } __except (is_win32_seh_harware_exception(GetExceptionCode())) { + win32_seh_catched = GetExceptionCode(); + } + return win32_seh_catched; +} +void BackgroundSlicingProcess::call_process_seh_throw(std::exception_ptr &ex) throw() +{ + unsigned long win32_seh_catched = this->call_process_seh(ex); + if (win32_seh_catched) { + // Rethrow SEH exception as Slicer::HardCrash. + try { + rethrow_seh_exception(win32_seh_catched); + } catch (...) { + ex = std::current_exception(); + } + } +} +#endif // _WIN32 + +void BackgroundSlicingProcess::call_process(std::exception_ptr &ex) throw() +{ + try { + assert(m_print != nullptr); + switch (m_print->technology()) { + case ptFFF: this->process_fff(); break; + case ptSLA: this->process_sla(); break; + default: m_print->process(); break; + } + } catch (CanceledException& /* ex */) { + // Canceled, this is all right. + assert(m_print->canceled()); + ex = std::current_exception(); + } catch (...) { + ex = std::current_exception(); + } +} + +#ifdef _WIN32 +unsigned long BackgroundSlicingProcess::thread_proc_safe_seh() throw() +{ + unsigned long win32_seh_catched = 0; + __try { + this->thread_proc_safe(); + } __except (is_win32_seh_harware_exception(GetExceptionCode())) { + win32_seh_catched = GetExceptionCode(); + } + return win32_seh_catched; +} +void BackgroundSlicingProcess::thread_proc_safe_seh_throw() throw() +{ + unsigned long win32_seh_catched = this->thread_proc_safe_seh(); + if (win32_seh_catched) { + // Rethrow SEH exception as Slicer::HardCrash. + try { + rethrow_seh_exception(win32_seh_catched); + } catch (...) { + wxTheApp->OnUnhandledException(); + } + } +} +#endif // _WIN32 + +void BackgroundSlicingProcess::thread_proc_safe() throw() { try { this->thread_proc(); @@ -349,7 +456,13 @@ bool BackgroundSlicingProcess::start() if (m_state == STATE_INITIAL) { // The worker thread is not running yet. Start it. assert(! m_thread.joinable()); - m_thread = create_thread([this]{this->thread_proc_safe();}); + m_thread = create_thread([this]{ +#ifdef _WIN32 + this->thread_proc_safe_seh_throw(); +#else // _WIN32 + this->thread_proc_safe(); +#endif // _WIN32 + }); // Wait until the worker thread is ready to execute the background processing task. m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; }); } diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 12bf6fe02..6f5cd8852 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -174,7 +174,16 @@ public: private: void thread_proc(); - void thread_proc_safe(); + // Calls thread_proc(), catches all C++ exceptions and shows them using wxApp::OnUnhandledException(). + void thread_proc_safe() throw(); +#ifdef _WIN32 + // Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function. + // Catch a SEH exception and return its ID or zero if no SEH exception has been catched. + unsigned long thread_proc_safe_seh() throw(); + // Calls thread_proc_safe_seh(), rethrows a Slic3r::HardCrash exception based on SEH exception + // returned by thread_proc_safe_seh() and lets wxApp::OnUnhandledException() display it. + void thread_proc_safe_seh_throw() throw(); +#endif // _WIN32 void join_background_thread(); // To be called by Print::apply() through the Print::m_cancel_callback to stop the background // processing before changing any data of running or finalized milestones. @@ -187,6 +196,20 @@ private: // Temporary: for mimicking the fff file export behavior with the raster output void process_sla(); + // Call Print::process() and catch all exceptions into ex, thus no exception could be thrown + // by this method. This exception behavior is required to combine C++ exceptions with Win32 SEH exceptions + // on the same thread. + void call_process(std::exception_ptr &ex) throw(); + +#ifdef _WIN32 + // Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function. + // Catch a SEH exception and return its ID or zero if no SEH exception has been catched. + unsigned long call_process_seh(std::exception_ptr &ex) throw(); + // Calls call_process_seh(), rethrows a Slic3r::HardCrash exception based on SEH exception + // returned by call_process_seh(). + void call_process_seh_throw(std::exception_ptr &ex) throw(); +#endif // _WIN32 + // Currently active print. It is one of m_fff_print and m_sla_print. PrintBase *m_print = nullptr; // Non-owned pointers to Print instances.