# 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. \n Crash log saved to ' " + std : : string ( mShm - > mStartup . mLogFilePath ) + " '. \n Please 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. \n If 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