mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-30 21:26:39 +00:00 
			
		
		
		
	* Replace by std::to_string and operator+ where possible. * Move the code requiring to include <sstream> from .hpp to .cpp files.
		
			
				
	
	
		
			309 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			309 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "windows_crashmonitor.hpp"
 | |
| 
 | |
| #include <components/windows.hpp>
 | |
| #include <Psapi.h>
 | |
| 
 | |
| #include <DbgHelp.h>
 | |
| 
 | |
| #include <memory>
 | |
| #include <thread>
 | |
| 
 | |
| #include <SDL_messagebox.h>
 | |
| 
 | |
| #include "windows_crashcatcher.hpp"
 | |
| #include "windows_crashshm.hpp"
 | |
| #include <components/debug/debuglog.hpp>
 | |
| 
 | |
| namespace Crash
 | |
| {
 | |
|     std::unordered_map<HWINEVENTHOOK, CrashMonitor*> CrashMonitor::smEventHookOwners{};
 | |
| 
 | |
|     CrashMonitor::CrashMonitor(HANDLE shmHandle)
 | |
|         : mShmHandle(shmHandle)
 | |
|     {
 | |
|         mShm = reinterpret_cast<CrashSHM*>(MapViewOfFile(mShmHandle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(CrashSHM)));
 | |
|         if (mShm == nullptr)
 | |
|             throw std::runtime_error("Failed to map crash monitor shared memory");
 | |
| 
 | |
|         // accessing SHM without lock is OK here, the parent waits for a signal before continuing
 | |
| 
 | |
|         mShmMutex = mShm->mStartup.mShmMutex;
 | |
|         mAppProcessHandle = mShm->mStartup.mAppProcessHandle;
 | |
|         mAppMainThreadId = mShm->mStartup.mAppMainThreadId;
 | |
|         mSignalAppEvent = mShm->mStartup.mSignalApp;
 | |
|         mSignalMonitorEvent = mShm->mStartup.mSignalMonitor;
 | |
|     }
 | |
| 
 | |
|     CrashMonitor::~CrashMonitor()
 | |
|     {
 | |
|         if (mShm)
 | |
|             UnmapViewOfFile(mShm);
 | |
| 
 | |
|         // the handles received from the app are duplicates, we must close them
 | |
| 
 | |
|         if (mShmHandle)
 | |
|             CloseHandle(mShmHandle);
 | |
| 
 | |
|         if (mShmMutex)
 | |
|             CloseHandle(mShmMutex);
 | |
| 
 | |
|         if (mSignalAppEvent)
 | |
|             CloseHandle(mSignalAppEvent);
 | |
| 
 | |
|         if (mSignalMonitorEvent)
 | |
|             CloseHandle(mSignalMonitorEvent);
 | |
|     }
 | |
| 
 | |
|     void CrashMonitor::shmLock()
 | |
|     {
 | |
|         if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0)
 | |
|             throw std::runtime_error("SHM monitor lock timed out");
 | |
|     }
 | |
| 
 | |
|     void CrashMonitor::shmUnlock()
 | |
|     {
 | |
|         ReleaseMutex(mShmMutex);
 | |
|     }
 | |
| 
 | |
|     void CrashMonitor::signalApp() const
 | |
|     {
 | |
|         SetEvent(mSignalAppEvent);
 | |
|     }
 | |
| 
 | |
|     bool CrashMonitor::waitApp() const
 | |
|     {
 | |
|         return WaitForSingleObject(mSignalMonitorEvent, CrashCatcherTimeout) == WAIT_OBJECT_0;
 | |
|     }
 | |
| 
 | |
|     bool CrashMonitor::isAppAlive() const
 | |
|     {
 | |
|         DWORD code = 0;
 | |
|         GetExitCodeProcess(mAppProcessHandle, &code);
 | |
|         return code == STILL_ACTIVE;
 | |
|     }
 | |
| 
 | |
|     bool CrashMonitor::isAppFrozen()
 | |
|     {
 | |
|         MSG message;
 | |
|         // Allow the event hook callback to run
 | |
|         PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE);
 | |
| 
 | |
|         if (!mAppWindowHandle)
 | |
|         {
 | |
|             EnumWindows([](HWND handle, LPARAM param) -> BOOL {
 | |
|                 CrashMonitor& crashMonitor = *(CrashMonitor*)param;
 | |
|                 DWORD processId;
 | |
|                 if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mAppMainThreadId && processId == GetProcessId(crashMonitor.mAppProcessHandle))
 | |
|                 {
 | |
|                     if (GetWindow(handle, GW_OWNER) == 0)
 | |
|                     {
 | |
|                         crashMonitor.mAppWindowHandle = handle;
 | |
|                         return false;
 | |
|                     }
 | |
|                 }
 | |
|                 return true;
 | |
|                 }, (LPARAM)this);
 | |
|             if (mAppWindowHandle)
 | |
|             {
 | |
|                 DWORD processId;
 | |
|                 GetWindowThreadProcessId(mAppWindowHandle, &processId);
 | |
|                 HWINEVENTHOOK eventHookHandle = SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY, nullptr,
 | |
|                     [](HWINEVENTHOOK hWinEventHook, DWORD event, HWND windowHandle, LONG objectId, LONG childId, DWORD eventThread, DWORD eventTime)
 | |
|                     {
 | |
|                         CrashMonitor& crashMonitor = *smEventHookOwners[hWinEventHook];
 | |
|                         if (event == EVENT_OBJECT_DESTROY && windowHandle == crashMonitor.mAppWindowHandle && objectId == OBJID_WINDOW && childId == INDEXID_CONTAINER)
 | |
|                         {
 | |
|                             crashMonitor.mAppWindowHandle = nullptr;
 | |
|                             smEventHookOwners.erase(hWinEventHook);
 | |
|                             UnhookWinEvent(hWinEventHook);
 | |
|                         }
 | |
|                     }, processId, mAppMainThreadId, WINEVENT_OUTOFCONTEXT);
 | |
|                 smEventHookOwners[eventHookHandle] = this;
 | |
|             }
 | |
|             else
 | |
|                 return false;
 | |
|         }
 | |
|         if (IsHungAppWindow)
 | |
|             return IsHungAppWindow(mAppWindowHandle);
 | |
|         else
 | |
|         {
 | |
|             BOOL debuggerPresent;
 | |
| 
 | |
|             if (CheckRemoteDebuggerPresent(mAppProcessHandle, &debuggerPresent) && debuggerPresent)
 | |
|                 return false;
 | |
|             if (SendMessageTimeoutA(mAppWindowHandle, WM_NULL, 0, 0, 0, 5000, nullptr) == 0)
 | |
|                 return GetLastError() == ERROR_TIMEOUT;
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     void CrashMonitor::run()
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             // app waits for monitor start up, let it continue
 | |
|             signalApp();
 | |
| 
 | |
|             bool running = true;
 | |
|             bool frozen = false;
 | |
|             while (isAppAlive() && running && !mFreezeAbort)
 | |
|             {
 | |
|                 if (isAppFrozen())
 | |
|                 {
 | |
|                     if (!frozen)
 | |
|                     {
 | |
|                         showFreezeMessageBox();
 | |
|                         frozen = true;
 | |
|                     }
 | |
|                 }
 | |
|                 else if (frozen)
 | |
|                 {
 | |
|                     hideFreezeMessageBox();
 | |
|                     frozen = false;
 | |
|                 }
 | |
| 
 | |
|                 if (!mFreezeAbort && waitApp())
 | |
|                 {
 | |
|                     shmLock();
 | |
| 
 | |
|                     switch (mShm->mEvent)
 | |
|                     {
 | |
|                     case CrashSHM::Event::None:
 | |
|                         break;
 | |
|                     case CrashSHM::Event::Crashed:
 | |
|                         handleCrash();
 | |
|                         running = false;
 | |
|                         break;
 | |
|                     case CrashSHM::Event::Shutdown:
 | |
|                         running = false;
 | |
|                         break;
 | |
|                     case CrashSHM::Event::Startup:
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     shmUnlock();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (frozen)
 | |
|                 hideFreezeMessageBox();
 | |
| 
 | |
|             if (mFreezeAbort)
 | |
|             {
 | |
|                 TerminateProcess(mAppProcessHandle, 0xDEAD);
 | |
|                 std::string message = "OpenMW appears to have frozen.\nCrash log saved to '" + std::string(mShm->mStartup.mLogFilePath) + "'.\nPlease report this to https://gitlab.com/OpenMW/openmw/issues !";
 | |
|                 SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), nullptr);
 | |
|             }
 | |
| 
 | |
|         } 
 | |
|         catch (...)
 | |
|         {
 | |
|             Log(Debug::Error) << "Exception in crash monitor, exiting";
 | |
|         }
 | |
|         signalApp();
 | |
|     }
 | |
| 
 | |
|     std::wstring utf8ToUtf16(const std::string& utf8)
 | |
|     {
 | |
|         const int nLenWide = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), nullptr, 0);
 | |
| 
 | |
|         std::wstring utf16;
 | |
|         utf16.resize(nLenWide);
 | |
|         if (MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), utf16.data(), nLenWide) != nLenWide)
 | |
|             return {};
 | |
| 
 | |
|         return utf16;
 | |
|     }
 | |
| 
 | |
|     void CrashMonitor::handleCrash()
 | |
|     {
 | |
|         DWORD processId = GetProcessId(mAppProcessHandle);
 | |
| 
 | |
|         try
 | |
|         {
 | |
|             HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
 | |
|             if (dbghelp == NULL)
 | |
|                 return;
 | |
|     
 | |
|             using MiniDumpWirteDumpFn = BOOL (WINAPI*)(
 | |
|                 HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
 | |
|                 PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam
 | |
|             );
 | |
|     
 | |
|             MiniDumpWirteDumpFn miniDumpWriteDump = (MiniDumpWirteDumpFn)GetProcAddress(dbghelp, "MiniDumpWriteDump");
 | |
|             if (miniDumpWriteDump == NULL)
 | |
|                 return;
 | |
| 
 | |
|             std::wstring utf16Path = utf8ToUtf16(mShm->mStartup.mLogFilePath);
 | |
|             if (utf16Path.empty())
 | |
|                 return;
 | |
| 
 | |
|             if (utf16Path.length() > MAX_PATH)
 | |
|                 utf16Path = LR"(\\?\)" + utf16Path;
 | |
| 
 | |
|             HANDLE hCrashLog = CreateFileW(utf16Path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
 | |
|             if (hCrashLog == NULL || hCrashLog == INVALID_HANDLE_VALUE)
 | |
|                 return;
 | |
|             if (auto err = GetLastError(); err != ERROR_ALREADY_EXISTS && err != 0)
 | |
|                 return;
 | |
| 
 | |
|             EXCEPTION_POINTERS exp;
 | |
|             exp.ContextRecord = &mShm->mCrashed.mContext;
 | |
|             exp.ExceptionRecord = &mShm->mCrashed.mExceptionRecord;
 | |
|             MINIDUMP_EXCEPTION_INFORMATION infos = {};
 | |
|             infos.ThreadId = mShm->mCrashed.mThreadId;
 | |
|             infos.ExceptionPointers = &exp;
 | |
|             infos.ClientPointers = FALSE;
 | |
|             MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithHandleData);
 | |
|             miniDumpWriteDump(mAppProcessHandle, processId, hCrashLog, type, &infos, 0, 0);
 | |
|         }
 | |
|         catch (const std::exception&e)
 | |
|         {
 | |
|             Log(Debug::Error) << "CrashMonitor: " << e.what();
 | |
|         }
 | |
|         catch (...)
 | |
|         {
 | |
|             Log(Debug::Error) << "CrashMonitor: unknown exception";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void CrashMonitor::showFreezeMessageBox()
 | |
|     {
 | |
|         std::thread messageBoxThread([&]() {
 | |
|             SDL_MessageBoxButtonData button = { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Abort" };
 | |
|             SDL_MessageBoxData messageBoxData = {
 | |
|                 SDL_MESSAGEBOX_ERROR,
 | |
|                 nullptr,
 | |
|                 "OpenMW appears to have frozen",
 | |
|                 "OpenMW appears to have frozen. Press Abort to terminate it and generate a crash dump.\nIf OpenMW hasn't actually frozen, this message box will disappear a within a few seconds of it becoming responsive.",
 | |
|                 1,
 | |
|                 &button,
 | |
|                 nullptr
 | |
|             };
 | |
| 
 | |
|             int buttonId;
 | |
|             if (SDL_ShowMessageBox(&messageBoxData, &buttonId) == 0 && buttonId == 0)
 | |
|                 mFreezeAbort = true;
 | |
|             });
 | |
| 
 | |
|         mFreezeMessageBoxThreadId = GetThreadId(messageBoxThread.native_handle());
 | |
|         messageBoxThread.detach();
 | |
|     }
 | |
| 
 | |
|     void CrashMonitor::hideFreezeMessageBox()
 | |
|     {
 | |
|         if (!mFreezeMessageBoxThreadId)
 | |
|             return;
 | |
| 
 | |
|         EnumWindows([](HWND handle, LPARAM param) -> BOOL {
 | |
|             CrashMonitor& crashMonitor = *(CrashMonitor*)param;
 | |
|             DWORD processId;
 | |
|             if (GetWindowThreadProcessId(handle, &processId) == crashMonitor.mFreezeMessageBoxThreadId && processId == GetCurrentProcessId())
 | |
|                 PostMessage(handle, WM_CLOSE, 0, 0);
 | |
|             return true;
 | |
|             }, (LPARAM)this);
 | |
| 
 | |
|         mFreezeMessageBoxThreadId = 0;
 | |
|     }
 | |
| 
 | |
| } // namespace Crash
 |