aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/mptrack/ExceptionHandler.cpp
diff options
context:
space:
mode:
authorJean-Francois Mauguit <jfmauguit@mac.com>2024-09-24 09:03:25 -0400
committerGitHub <noreply@github.com>2024-09-24 09:03:25 -0400
commitbab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/ExceptionHandler.cpp
parent4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff)
parent20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff)
downloadwinamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/ExceptionHandler.cpp')
-rw-r--r--Src/external_dependencies/openmpt-trunk/mptrack/ExceptionHandler.cpp853
1 files changed, 853 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/ExceptionHandler.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/ExceptionHandler.cpp
new file mode 100644
index 00000000..c4d8c5d5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/mptrack/ExceptionHandler.cpp
@@ -0,0 +1,853 @@
+/*
+ * ExceptionHandler.cpp
+ * --------------------
+ * Purpose: Code for handling crashes (unhandled exceptions) in OpenMPT.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+#include "Mainfrm.h"
+#include "Mptrack.h"
+#include "AboutDialog.h"
+#include "InputHandler.h"
+#include "openmpt/sounddevice/SoundDevice.hpp"
+#include "Moddoc.h"
+#include <shlwapi.h>
+#include "ExceptionHandler.h"
+#include "../misc/WriteMemoryDump.h"
+#include "../common/version.h"
+#include "../common/mptFileIO.h"
+#include "../soundlib/mod_specifications.h"
+
+#include <atomic>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+// Write full memory dump instead of minidump.
+bool ExceptionHandler::fullMemDump = false;
+
+bool ExceptionHandler::stopSoundDeviceOnCrash = true;
+
+bool ExceptionHandler::stopSoundDeviceBeforeDump = false;
+
+// Delegate to system-specific crash processing once our own crash handler is
+// finished. This is useful to allow attaching a debugger.
+bool ExceptionHandler::delegateToWindowsHandler = false;
+
+// Allow debugging the unhandled exception filter. Normally, if a debugger is
+// attached, no exceptions are unhandled because the debugger handles them. If
+// debugExceptionHandler is true, an additional __try/__catch is inserted around
+// InitInstance(), ExitInstance() and the main message loop, which will call our
+// filter, which then can be stepped through in a debugger.
+bool ExceptionHandler::debugExceptionHandler = false;
+
+
+bool ExceptionHandler::useAnyCrashHandler = false;
+bool ExceptionHandler::useImplicitFallbackSEH = false;
+bool ExceptionHandler::useExplicitSEH = false;
+bool ExceptionHandler::handleStdTerminate = false;
+bool ExceptionHandler::handleMfcExceptions = false;
+
+
+static thread_local ExceptionHandler::Context *g_Context = nullptr;
+
+static LPTOP_LEVEL_EXCEPTION_FILTER g_OriginalUnhandledExceptionFilter = nullptr;
+static std::terminate_handler g_OriginalTerminateHandler = nullptr;
+
+static UINT g_OriginalErrorMode = 0;
+
+
+ExceptionHandler::Context *ExceptionHandler::SetContext(Context *newContext) noexcept
+{
+ Context *oldContext = g_Context;
+ g_Context = newContext;
+ return oldContext;
+}
+
+
+static std::atomic<int> & g_CrashCount()
+{
+ static std::atomic<int> s_CrashCount(0);
+ return s_CrashCount;
+}
+
+
+static std::atomic<int> & g_TaintCountDriver()
+{
+ static std::atomic<int> s_TaintCountDriver(0);
+ return s_TaintCountDriver;
+}
+
+
+static std::atomic<int> & g_TaintCountPlugin()
+{
+ static std::atomic<int> s_TaintCountPlugin(0);
+ return s_TaintCountPlugin;
+}
+
+
+void ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason reason)
+{
+ switch(reason)
+ {
+ case ExceptionHandler::TaintReason::Driver:
+ g_TaintCountDriver().fetch_add(1);
+ break;
+ case ExceptionHandler::TaintReason::Plugin:
+ g_TaintCountPlugin().fetch_add(1);
+ break;
+ default:
+ MPT_ASSERT_NOTREACHED();
+ break;
+ }
+}
+
+
+enum DumpMode
+{
+ DumpModeCrash = 0, // crash
+ DumpModeWarning = 1, // assert
+ DumpModeDebug = 2, // debug output (e.g. trace log)
+};
+
+
+struct CrashOutputDirectory
+{
+ bool valid;
+ mpt::PathString path;
+ CrashOutputDirectory()
+ : valid(true)
+ {
+ const mpt::PathString timestampDir = mpt::PathString::FromCString((CTime::GetCurrentTime()).Format(_T("%Y-%m-%d %H.%M.%S\\")));
+ // Create a crash directory
+ path = mpt::GetTempDirectory() + P_("OpenMPT Crash Files\\");
+ if(!path.IsDirectory())
+ {
+ CreateDirectory(path.AsNative().c_str(), nullptr);
+ }
+ // Set compressed attribute in order to save disk space.
+ // Debugging information should clutter the users computer as little as possible.
+ // Performance is not important here.
+ // Ignore any errors.
+ SetFilesystemCompression(path);
+ // Compression will be inherited by children directories and files automatically.
+ path += timestampDir;
+ if(!path.IsDirectory())
+ {
+ if(!CreateDirectory(path.AsNative().c_str(), nullptr))
+ {
+ valid = false;
+ }
+ }
+ }
+};
+
+
+class DebugReporter
+{
+private:
+ int crashCount;
+ int taintCountDriver;
+ int taintCountPlugin;
+ bool stateFrozen;
+ const DumpMode mode;
+ const CrashOutputDirectory crashDirectory;
+ bool writtenMiniDump;
+ bool writtenTraceLog;
+ int rescuedFiles;
+private:
+ static bool FreezeState(DumpMode mode);
+ static bool Cleanup(DumpMode mode);
+ bool GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo);
+ bool GenerateTraceLog();
+ int RescueFiles();
+ bool HasWrittenDebug() const { return writtenMiniDump || writtenTraceLog; }
+ static void StopSoundDevice();
+public:
+ DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo);
+ ~DebugReporter();
+ void ReportError(mpt::ustring errorMessage);
+};
+
+
+DebugReporter::DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo)
+ : crashCount(g_CrashCount().fetch_add(1) + 1)
+ , taintCountDriver(g_TaintCountDriver().load())
+ , taintCountPlugin(g_TaintCountPlugin().load())
+ , stateFrozen(FreezeState(mode))
+ , mode(mode)
+ , writtenMiniDump(false)
+ , writtenTraceLog(false)
+ , rescuedFiles(0)
+{
+ if(mode == DumpModeCrash || mode == DumpModeWarning)
+ {
+ writtenMiniDump = GenerateDump(pExceptionInfo);
+ }
+ if(mode == DumpModeCrash || mode == DumpModeWarning || mode == DumpModeDebug)
+ {
+ writtenTraceLog = GenerateTraceLog();
+ }
+ if(mode == DumpModeCrash || mode == DumpModeWarning)
+ {
+ rescuedFiles = RescueFiles();
+ }
+}
+
+
+DebugReporter::~DebugReporter()
+{
+ Cleanup(mode);
+}
+
+
+bool DebugReporter::GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo)
+{
+ return WriteMemoryDump(pExceptionInfo, (crashDirectory.path + P_("crash.dmp")).AsNative().c_str(), ExceptionHandler::fullMemDump);
+}
+
+
+bool DebugReporter::GenerateTraceLog()
+{
+ return mpt::log::Trace::Dump(crashDirectory.path + P_("trace.log"));
+}
+
+
+static void SaveDocumentSafe(CModDoc *pModDoc, const mpt::PathString &filename)
+{
+ __try
+ {
+ pModDoc->OnSaveDocument(filename);
+ } __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ // nothing
+ }
+}
+
+
+// Rescue modified files...
+int DebugReporter::RescueFiles()
+{
+ int numFiles = 0;
+ auto documents = theApp.GetOpenDocuments();
+ for(auto modDoc : documents)
+ {
+ if(modDoc->IsModified())
+ {
+ if(numFiles == 0)
+ {
+ // Show the rescue directory in Explorer...
+ CTrackApp::OpenDirectory(crashDirectory.path);
+ }
+
+ mpt::PathString filename;
+ filename += crashDirectory.path;
+ filename += mpt::PathString::FromUnicode(mpt::ufmt::val(++numFiles));
+ filename += P_("_");
+ filename += mpt::PathString::FromCString(modDoc->GetTitle()).SanitizeComponent();
+ filename += P_(".");
+ filename += mpt::PathString::FromUTF8(modDoc->GetSoundFile().GetModSpecifications().fileExtension);
+
+ try
+ {
+ SaveDocumentSafe(modDoc, filename);
+ } catch(...)
+ {
+ continue;
+ }
+ }
+ }
+ return numFiles;
+}
+
+
+void DebugReporter::ReportError(mpt::ustring errorMessage)
+{
+
+ if(!crashDirectory.valid)
+ {
+ errorMessage += UL_("\n\n");
+ errorMessage += UL_("Could not create the following directory for saving debug information and modified files to:\n");
+ errorMessage += crashDirectory.path.ToUnicode();
+ }
+
+ if(HasWrittenDebug())
+ {
+ errorMessage += UL_("\n\n");
+ errorMessage += UL_("Debug information has been saved to\n");
+ errorMessage += crashDirectory.path.ToUnicode();
+ }
+
+ if(rescuedFiles > 0)
+ {
+ errorMessage += UL_("\n\n");
+ if(rescuedFiles == 1)
+ {
+ errorMessage += UL_("1 modified file has been rescued, but it cannot be guaranteed that it is still intact.");
+ } else
+ {
+ errorMessage += MPT_UFORMAT("{} modified files have been rescued, but it cannot be guaranteed that they are still intact.")(rescuedFiles);
+ }
+ }
+
+ errorMessage += UL_("\n\n");
+ errorMessage += MPT_UFORMAT("OpenMPT {} {} ({} ({}))")
+ ( Build::GetVersionStringExtended()
+ , mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture())
+ , SourceInfo::Current().GetUrlWithRevision()
+ , SourceInfo::Current().GetStateString()
+ );
+
+ errorMessage += UL_("\n\n");
+ errorMessage += MPT_UFORMAT("Session error count: {}\n")(crashCount);
+ if(taintCountDriver > 0 || taintCountPlugin > 0)
+ {
+ errorMessage += UL_("Process is in tainted state!\n");
+ errorMessage += MPT_UFORMAT("Previously masked driver crashes: {}\n")(taintCountDriver);
+ errorMessage += MPT_UFORMAT("Previously masked plugin crashes: {}\n")(taintCountPlugin);
+ }
+
+ errorMessage += UL_("\n");
+
+ {
+ mpt::SafeOutputFile sf(crashDirectory.path + P_("error.txt"), std::ios::binary, mpt::FlushMode::Full);
+ mpt::ofstream& f = sf;
+ f.imbue(std::locale::classic());
+ f << mpt::replace(mpt::ToCharset(mpt::Charset::UTF8, errorMessage), std::string("\n"), std::string("\r\n"));
+ }
+
+ if(auto ih = CMainFrame::GetInputHandler(); ih != nullptr)
+ {
+ mpt::SafeOutputFile sf(crashDirectory.path + P_("last-commands.txt"), std::ios::binary, mpt::FlushMode::Full);
+ mpt::ofstream &f = sf;
+ f.imbue(std::locale::classic());
+
+ const auto commandSet = ih->m_activeCommandSet.get();
+ f << "Last commands:\n";
+ for(size_t i = 0; i < ih->m_lastCommands.size(); i++)
+ {
+ CommandID id = ih->m_lastCommands[(ih->m_lastCommandPos + i) % ih->m_lastCommands.size()];
+ if(id == kcNull)
+ continue;
+ f << mpt::afmt::val(id);
+ if(commandSet)
+ f << " (" << mpt::ToCharset(mpt::Charset::UTF8, commandSet->GetCommandText(id)) << ")";
+ f << "\n";
+ }
+ }
+
+ {
+ mpt::SafeOutputFile sf(crashDirectory.path + P_("threads.txt"), std::ios::binary, mpt::FlushMode::Full);
+ mpt::ofstream& f = sf;
+ f.imbue(std::locale::classic());
+ f << MPT_AFORMAT("current : {}")(mpt::afmt::hex0<8>(GetCurrentThreadId())) << "\r\n";
+ f << MPT_AFORMAT("GUI : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n";
+ f << MPT_AFORMAT("Audio : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindAudio))) << "\r\n";
+ f << MPT_AFORMAT("Notify : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindNotify))) << "\r\n";
+ f << MPT_AFORMAT("WatchDir: {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindWatchdir))) << "\r\n";
+ }
+
+ static constexpr struct { const mpt::uchar * section; const mpt::uchar * key; } configAnonymize[] =
+ {
+ { UL_("Version"), UL_("InstallGUID") },
+ { UL_("Recent File List"), nullptr },
+ };
+
+ {
+ mpt::SafeOutputFile sf(crashDirectory.path + P_("active-settings.txt"), std::ios::binary, mpt::FlushMode::Full);
+ mpt::ofstream& f = sf;
+ f.imbue(std::locale::classic());
+ if(theApp.GetpSettings())
+ {
+ SettingsContainer &settings = theApp.GetSettings();
+ for(const auto &it : settings)
+ {
+ bool skipPath = false;
+ for(const auto &path : configAnonymize)
+ {
+ if((path.key == nullptr && path.section == it.first.GetRefSection()) // Omit entire section
+ || (path.key != nullptr && it.first == SettingPath(path.section, path.key))) // Omit specific key
+ {
+ skipPath = true;
+ }
+ }
+ if(skipPath)
+ {
+ continue;
+ }
+ f
+ << mpt::ToCharset(mpt::Charset::UTF8, it.first.FormatAsString() + U_(" = ") + it.second.GetRefValue().FormatValueAsString())
+ << std::endl;
+ }
+ }
+ }
+
+ {
+ const mpt::PathString crashStoredSettingsFilename = crashDirectory.path + P_("stored-mptrack.ini");
+ CopyFile
+ ( theApp.GetConfigFileName().AsNative().c_str()
+ , crashStoredSettingsFilename.AsNative().c_str()
+ , FALSE
+ );
+ IniFileSettingsContainer crashStoredSettings{crashStoredSettingsFilename};
+ for(const auto &path : configAnonymize)
+ {
+ if(path.key)
+ {
+ crashStoredSettings.Write(SettingPath(path.section, path.key), SettingValue(mpt::ustring()));
+ } else
+ {
+ crashStoredSettings.Remove(path.section);
+ }
+ }
+ crashStoredSettings.Flush();
+ }
+
+ /*
+ // This is very slow, we instead write active-settings.txt above.
+ {
+ IniFileSettingsBackend f(crashDirectory.path + P_("active-mptrack.ini"));
+ if(theApp.GetpSettings())
+ {
+ SettingsContainer & settings = theApp.GetSettings();
+ for(const auto &it : settings)
+ {
+ f.WriteSetting(it.first, it.second.GetRefValue());
+ }
+ }
+ }
+ */
+
+ {
+ mpt::SafeOutputFile sf(crashDirectory.path + P_("about-openmpt.txt"), std::ios::binary, mpt::FlushMode::Full);
+ mpt::ofstream& f = sf;
+ f.imbue(std::locale::classic());
+ f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(0));
+ }
+
+ {
+ mpt::SafeOutputFile sf(crashDirectory.path + P_("about-components.txt"), std::ios::binary, mpt::FlushMode::Full);
+ mpt::ofstream& f = sf;
+ f.imbue(std::locale::classic());
+ f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(1));
+ }
+
+ Reporting::Error(errorMessage, (mode == DumpModeWarning) ? "OpenMPT Warning" : "OpenMPT Crash", CMainFrame::GetMainFrame());
+
+}
+
+
+// Freezes the state as much as possible in order to avoid further confusion by
+// other (possibly still running) threads
+bool DebugReporter::FreezeState(DumpMode mode)
+{
+ MPT_TRACE();
+
+ // seal the trace log as early as possible
+ mpt::log::Trace::Seal();
+
+ if(mode == DumpModeCrash || mode == DumpModeWarning)
+ {
+ if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
+ {
+ // For fragile devices, always stop the device. Stop before the dumping if not in realtime context.
+ if(!CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
+ {
+ StopSoundDevice();
+ }
+ } else
+ {
+ if(ExceptionHandler::stopSoundDeviceOnCrash && ExceptionHandler::stopSoundDeviceBeforeDump)
+ {
+ StopSoundDevice();
+ }
+ }
+ }
+
+ return true;
+}
+
+
+static void StopSoundDeviceSafe(CMainFrame *pMainFrame)
+{
+ __try
+ {
+ if(pMainFrame->gpSoundDevice)
+ {
+ pMainFrame->gpSoundDevice->Close();
+ }
+ if(pMainFrame->m_NotifyTimer)
+ {
+ pMainFrame->KillTimer(pMainFrame->m_NotifyTimer);
+ pMainFrame->m_NotifyTimer = 0;
+ }
+ } __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ // nothing
+ }
+}
+
+
+void DebugReporter::StopSoundDevice()
+{
+ CMainFrame* pMainFrame = CMainFrame::GetMainFrame();
+ if(pMainFrame)
+ {
+ try
+ {
+ StopSoundDeviceSafe(pMainFrame);
+ } catch(...)
+ {
+ // nothing
+ }
+ }
+}
+
+
+bool DebugReporter::Cleanup(DumpMode mode)
+{
+ MPT_TRACE();
+
+ if(mode == DumpModeCrash || mode == DumpModeWarning)
+ {
+ if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
+ {
+ // For fragile devices, always stop the device. Stop after the dumping if in realtime context.
+ if(CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
+ {
+ StopSoundDevice();
+ }
+ } else
+ {
+ if(ExceptionHandler::stopSoundDeviceOnCrash && !ExceptionHandler::stopSoundDeviceBeforeDump)
+ {
+ StopSoundDevice();
+ }
+ }
+ }
+
+ return true;
+}
+
+
+// Different entry points for different situations in which we want to dump some information
+
+
+static bool IsCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
+{
+ if (!pExceptionInfo)
+ return false;
+ if (!pExceptionInfo->ExceptionRecord)
+ return false;
+ if (pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
+ return false;
+ return true;
+}
+
+
+template <typename E>
+static const E * GetCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
+{
+ // https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273
+ struct info_a_t
+ {
+ DWORD bitmask; // Probably: 1=Const, 2=Volatile
+ DWORD destructor; // RVA (Relative Virtual Address) of destructor for that exception object
+ DWORD unknown;
+ DWORD catchableTypesPtr; // RVA of instance of type "B"
+ };
+ struct info_c_t
+ {
+ DWORD someBitmask;
+ DWORD typeInfo; // RVA of std::type_info for that type
+ DWORD memberDisplacement; // Add to ExceptionInformation[1] in EXCEPTION_RECORD to obtain 'this' pointer.
+ DWORD virtBaseRelated1; // -1 if no virtual base
+ DWORD virtBaseRelated2; // ?
+ DWORD objectSize; // Size of the object in bytes
+ DWORD probablyCopyCtr; // RVA of copy constructor (?)
+ };
+ if(!pExceptionInfo)
+ return nullptr;
+ if (!pExceptionInfo->ExceptionRecord)
+ return nullptr;
+ if(pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
+ return nullptr;
+ #ifdef _WIN64
+ if(pExceptionInfo->ExceptionRecord->NumberParameters != 4)
+ return nullptr;
+ #else
+ if(pExceptionInfo->ExceptionRecord->NumberParameters != 3)
+ return nullptr;
+ #endif
+ if(pExceptionInfo->ExceptionRecord->ExceptionInformation[0] != 0x19930520u)
+ return nullptr;
+ std::uintptr_t base_address = 0;
+ #ifdef _WIN64
+ base_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[3];
+ #else
+ base_address = 0;
+ #endif
+ std::uintptr_t obj_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[1];
+ if(!obj_address)
+ return nullptr;
+ std::uintptr_t info_a_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[2];
+ if(!info_a_address)
+ return nullptr;
+ const info_a_t * info_a = reinterpret_cast<const info_a_t *>(info_a_address);
+ std::uintptr_t info_b_offset = info_a->catchableTypesPtr;
+ if(!info_b_offset)
+ return nullptr;
+ const DWORD * info_b = reinterpret_cast<const DWORD *>(base_address + info_b_offset);
+ for(DWORD type = 1; type <= info_b[0]; ++type)
+ {
+ std::uintptr_t info_c_offset = info_b[type];
+ if(!info_c_offset)
+ continue;
+ const info_c_t * info_c = reinterpret_cast<const info_c_t *>(base_address + info_c_offset);
+ if(!info_c->typeInfo)
+ continue;
+ const std::type_info * ti = reinterpret_cast<const std::type_info *>(base_address + info_c->typeInfo);
+ if(*ti != typeid(E))
+ continue;
+ const E * e = reinterpret_cast<const E *>(obj_address + info_c->memberDisplacement);
+ return e;
+ }
+ return nullptr;
+}
+
+
+void ExceptionHandler::UnhandledMFCException(CException * e, const MSG * pMsg)
+{
+ DebugReporter report(DumpModeCrash, nullptr);
+ mpt::ustring errorMessage;
+ if(e && dynamic_cast<CSimpleException*>(e))
+ {
+ TCHAR tmp[1024 + 1];
+ MemsetZero(tmp);
+ if(dynamic_cast<CSimpleException*>(e)->GetErrorMessage(tmp, static_cast<UINT>(std::size(tmp) - 1)) != 0)
+ {
+ tmp[1024] = 0;
+ errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
+ (mpt::ufmt::dec(pMsg ? pMsg->message : 0)
+ , mpt::ToUnicode(CString(tmp))
+ );
+ } else
+ {
+ errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
+ (mpt::ufmt::dec(pMsg ? pMsg->message : 0)
+ , mpt::ToUnicode(CString(tmp))
+ );
+ }
+ }
+ else
+ {
+ errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}'.")
+ ( mpt::ufmt::dec(pMsg ? pMsg->message : 0)
+ );
+ }
+ report.ReportError(errorMessage);
+}
+
+
+static void UnhandledExceptionFilterImpl(_EXCEPTION_POINTERS *pExceptionInfo)
+{
+ DebugReporter report(DumpModeCrash, pExceptionInfo);
+
+ mpt::ustring errorMessage;
+ const std::exception * pE = GetCxxException<std::exception>(pExceptionInfo);
+ if(g_Context)
+ {
+ if(!g_Context->description.empty())
+ {
+ errorMessage += MPT_UFORMAT("OpenMPT detected a crash in '{}'.\nThis is very likely not an OpenMPT bug. Please report the problem to the respective software author.\n")(g_Context->description);
+ } else
+ {
+ errorMessage += MPT_UFORMAT("OpenMPT detected a crash in unknown foreign code.\nThis is likely not an OpenMPT bug.\n")();
+ }
+ }
+ if(pE)
+ {
+ const std::exception & e = *pE;
+ errorMessage += MPT_UFORMAT("Unhandled C++ exception '{}' occurred at address 0x{}: '{}'.")
+ ( mpt::ToUnicode(mpt::Charset::ASCII, typeid(e).name())
+ , mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
+ , mpt::get_exception_text<mpt::ustring>(e)
+ );
+ } else
+ {
+ errorMessage += MPT_UFORMAT("Unhandled exception 0x{} at address 0x{} occurred.")
+ ( mpt::ufmt::HEX0<8>(pExceptionInfo->ExceptionRecord->ExceptionCode)
+ , mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
+ );
+ }
+ report.ReportError(errorMessage);
+
+}
+
+
+LONG ExceptionHandler::UnhandledExceptionFilterContinue(_EXCEPTION_POINTERS *pExceptionInfo)
+{
+
+ UnhandledExceptionFilterImpl(pExceptionInfo);
+
+ // Disable the call to std::terminate() as that would re-renter the crash
+ // handler another time, but with less information available.
+#if 0
+ // MSVC implements calling std::terminate by its own UnhandledExeptionFilter.
+ // However, we do overwrite it here, thus we have to call std::terminate
+ // ourselves.
+ if (IsCxxException(pExceptionInfo))
+ {
+ std::terminate();
+ }
+#endif
+
+ // Let a potential debugger handle the exception...
+ return EXCEPTION_CONTINUE_SEARCH;
+
+}
+
+
+LONG ExceptionHandler::ExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo)
+{
+ UnhandledExceptionFilterImpl(pExceptionInfo);
+ // Let a potential debugger handle the exception...
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+
+static void mpt_unexpected_handler();
+static void mpt_terminate_handler();
+
+
+void ExceptionHandler::Register()
+{
+ if(useImplicitFallbackSEH)
+ {
+ g_OriginalUnhandledExceptionFilter = ::SetUnhandledExceptionFilter(&UnhandledExceptionFilterContinue);
+ }
+ if(handleStdTerminate)
+ {
+ g_OriginalTerminateHandler = std::set_terminate(&mpt_terminate_handler);
+ }
+}
+
+
+void ExceptionHandler::ConfigureSystemHandler()
+{
+#if (_WIN32_WINNT >= 0x0600)
+ if(delegateToWindowsHandler)
+ {
+ //SetErrorMode(0);
+ g_OriginalErrorMode = ::GetErrorMode();
+ } else
+ {
+ g_OriginalErrorMode = ::SetErrorMode(::GetErrorMode() | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
+ }
+#else // _WIN32_WINNT < 0x0600
+ if(delegateToWindowsHandler)
+ {
+ g_OriginalErrorMode = ::SetErrorMode(0);
+ } else
+ {
+ g_OriginalErrorMode = ::SetErrorMode(::SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
+ }
+#endif // _WIN32_WINNT
+}
+
+
+void ExceptionHandler::UnconfigureSystemHandler()
+{
+ ::SetErrorMode(g_OriginalErrorMode);
+ g_OriginalErrorMode = 0;
+}
+
+
+void ExceptionHandler::Unregister()
+{
+ if(handleStdTerminate)
+ {
+ std::set_terminate(g_OriginalTerminateHandler);
+ g_OriginalTerminateHandler = nullptr;
+ }
+ if(useImplicitFallbackSEH)
+ {
+ ::SetUnhandledExceptionFilter(g_OriginalUnhandledExceptionFilter);
+ g_OriginalUnhandledExceptionFilter = nullptr;
+ }
+}
+
+
+static void mpt_terminate_handler()
+{
+ DebugReporter(DumpModeCrash, nullptr).ReportError(U_("A C++ runtime crash occurred: std::terminate() called."));
+#if 1
+ std::abort();
+#else
+ if(g_OriginalTerminateHandler)
+ {
+ g_OriginalTerminateHandler();
+ } else
+ {
+ std::abort();
+ }
+#endif
+}
+
+
+#if defined(MPT_ASSERT_HANDLER_NEEDED)
+
+MPT_NOINLINE void AssertHandler(const mpt::source_location &loc, const char *expr, const char *msg)
+{
+ DebugReporter report(msg ? DumpModeWarning : DumpModeCrash, nullptr);
+ if(IsDebuggerPresent())
+ {
+ OutputDebugString(_T("ASSERT("));
+ OutputDebugString(mpt::ToWin(mpt::Charset::ASCII, expr).c_str());
+ OutputDebugString(_T(") failed\n"));
+ DebugBreak();
+ } else
+ {
+ mpt::ustring errorMessage;
+ if(msg)
+ {
+ errorMessage = MPT_UFORMAT("Internal state inconsistency detected at {}({}). This is just a warning that could potentially lead to a crash later on: {} [{}].")
+ ( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
+ , loc.line()
+ , mpt::ToUnicode(mpt::Charset::ASCII, msg)
+ , mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
+ );
+ } else
+ {
+ errorMessage = MPT_UFORMAT("Internal error occurred at {}({}): ASSERT({}) failed in [{}].")
+ ( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
+ , loc.line()
+ , mpt::ToUnicode(mpt::Charset::ASCII, expr)
+ , mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
+ );
+ }
+ report.ReportError(errorMessage);
+ }
+}
+
+#endif // MPT_ASSERT_HANDLER_NEEDED
+
+
+void DebugInjectCrash()
+{
+ DebugReporter(DumpModeCrash, nullptr).ReportError(U_("Injected crash."));
+}
+
+
+void DebugTraceDump()
+{
+ DebugReporter report(DumpModeDebug, nullptr);
+}
+
+
+OPENMPT_NAMESPACE_END