mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 02:26:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			205 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "windows_crashcatcher.hpp"
 | 
						|
 | 
						|
#include <cassert>
 | 
						|
#include <cwchar>
 | 
						|
#include <iostream>
 | 
						|
#include <sstream>
 | 
						|
#include <thread>
 | 
						|
 | 
						|
#include "windows_crashmonitor.hpp"
 | 
						|
#include "windows_crashshm.hpp"
 | 
						|
#include <SDL_messagebox.h>
 | 
						|
 | 
						|
namespace Crash
 | 
						|
{
 | 
						|
 | 
						|
    HANDLE duplicateHandle(HANDLE handle)
 | 
						|
    {
 | 
						|
        HANDLE duplicate;
 | 
						|
        if (!DuplicateHandle(GetCurrentProcess(), handle,
 | 
						|
                             GetCurrentProcess(), &duplicate,
 | 
						|
                             0, TRUE, DUPLICATE_SAME_ACCESS))
 | 
						|
        {
 | 
						|
            throw std::runtime_error("Crash monitor could not duplicate handle");
 | 
						|
        }
 | 
						|
        return duplicate;
 | 
						|
    }
 | 
						|
 | 
						|
    CrashCatcher* CrashCatcher::sInstance = nullptr;
 | 
						|
 | 
						|
    CrashCatcher::CrashCatcher(int argc, char **argv, const std::string& crashLogPath)
 | 
						|
    {
 | 
						|
        assert(sInstance == nullptr); // don't allow two instances
 | 
						|
 | 
						|
        sInstance = this;
 | 
						|
 | 
						|
        HANDLE shmHandle = nullptr;
 | 
						|
        for (int i=0; i<argc; ++i)
 | 
						|
        {
 | 
						|
            if (strcmp(argv[i], "--crash-monitor"))
 | 
						|
                continue;
 | 
						|
 | 
						|
            if (i >= argc - 1)
 | 
						|
                throw std::runtime_error("Crash monitor is missing the SHM handle argument");
 | 
						|
 | 
						|
            sscanf(argv[i + 1], "%p", &shmHandle);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!shmHandle)
 | 
						|
        {
 | 
						|
            setupIpc();
 | 
						|
            startMonitorProcess(crashLogPath);
 | 
						|
            installHandler();
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            CrashMonitor(shmHandle).run();
 | 
						|
            exit(0);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    CrashCatcher::~CrashCatcher()
 | 
						|
    {
 | 
						|
        sInstance = nullptr;
 | 
						|
 | 
						|
        if (mShm && mSignalMonitorEvent)
 | 
						|
        {
 | 
						|
            shmLock();
 | 
						|
            mShm->mEvent = CrashSHM::Event::Shutdown;
 | 
						|
            shmUnlock();
 | 
						|
 | 
						|
            SetEvent(mSignalMonitorEvent);
 | 
						|
        }
 | 
						|
 | 
						|
        if (mShmHandle)
 | 
						|
            CloseHandle(mShmHandle);
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::setupIpc()
 | 
						|
    {
 | 
						|
        SECURITY_ATTRIBUTES attributes;
 | 
						|
        ZeroMemory(&attributes, sizeof(attributes));
 | 
						|
        attributes.bInheritHandle = TRUE;
 | 
						|
 | 
						|
        mSignalAppEvent = CreateEventW(&attributes, FALSE, FALSE, NULL);
 | 
						|
        mSignalMonitorEvent = CreateEventW(&attributes, FALSE, FALSE, NULL);
 | 
						|
 | 
						|
        mShmHandle = CreateFileMappingW(INVALID_HANDLE_VALUE, &attributes, PAGE_READWRITE, HIWORD(sizeof(CrashSHM)), LOWORD(sizeof(CrashSHM)), NULL);
 | 
						|
        if (mShmHandle == nullptr)
 | 
						|
            throw std::runtime_error("Failed to allocate crash catcher shared memory");
 | 
						|
 | 
						|
        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 catcher shared memory");
 | 
						|
 | 
						|
        mShmMutex = CreateMutexW(&attributes, FALSE, NULL);
 | 
						|
        if (mShmMutex == nullptr)
 | 
						|
            throw std::runtime_error("Failed to create crash catcher shared memory mutex");
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::shmLock()
 | 
						|
    {
 | 
						|
        if (WaitForSingleObject(mShmMutex, CrashCatcherTimeout) != WAIT_OBJECT_0)
 | 
						|
            throw std::runtime_error("SHM lock timed out");
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::shmUnlock()
 | 
						|
    {
 | 
						|
        ReleaseMutex(mShmMutex);
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::waitMonitor()
 | 
						|
    {
 | 
						|
        if (WaitForSingleObject(mSignalAppEvent, CrashCatcherTimeout) != WAIT_OBJECT_0)
 | 
						|
            throw std::runtime_error("Waiting for monitor failed");
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::signalMonitor()
 | 
						|
    {
 | 
						|
        SetEvent(mSignalMonitorEvent);
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::installHandler()
 | 
						|
    {
 | 
						|
        SetUnhandledExceptionFilter(vectoredExceptionHandler);
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::startMonitorProcess(const std::string& crashLogPath)
 | 
						|
    {
 | 
						|
        std::wstring executablePath;
 | 
						|
        DWORD copied = 0;
 | 
						|
        do {
 | 
						|
            executablePath.resize(executablePath.size() + MAX_PATH);
 | 
						|
            copied = GetModuleFileNameW(nullptr, executablePath.data(), static_cast<DWORD>(executablePath.size()));
 | 
						|
        } while (copied >= executablePath.size());
 | 
						|
        executablePath.resize(copied);
 | 
						|
 | 
						|
        memset(mShm->mStartup.mLogFilePath, 0, sizeof(mShm->mStartup.mLogFilePath));
 | 
						|
        size_t length = crashLogPath.length();
 | 
						|
        if (length >= MAX_LONG_PATH) length = MAX_LONG_PATH - 1;
 | 
						|
        strncpy(mShm->mStartup.mLogFilePath, crashLogPath.c_str(), length);
 | 
						|
        mShm->mStartup.mLogFilePath[length] = '\0';
 | 
						|
 | 
						|
        // note that we don't need to lock the SHM here, the other process has not started yet
 | 
						|
        mShm->mEvent = CrashSHM::Event::Startup;
 | 
						|
        mShm->mStartup.mShmMutex = duplicateHandle(mShmMutex);
 | 
						|
        mShm->mStartup.mAppProcessHandle = duplicateHandle(GetCurrentProcess());
 | 
						|
        mShm->mStartup.mAppMainThreadId = GetThreadId(GetCurrentThread());
 | 
						|
        mShm->mStartup.mSignalApp = duplicateHandle(mSignalAppEvent);
 | 
						|
        mShm->mStartup.mSignalMonitor = duplicateHandle(mSignalMonitorEvent);
 | 
						|
 | 
						|
        std::wstringstream ss;
 | 
						|
        ss << "--crash-monitor " << std::hex << duplicateHandle(mShmHandle);
 | 
						|
        std::wstring arguments(ss.str());
 | 
						|
 | 
						|
        STARTUPINFOW si;
 | 
						|
        ZeroMemory(&si, sizeof(si));
 | 
						|
 | 
						|
        PROCESS_INFORMATION pi;
 | 
						|
        ZeroMemory(&pi, sizeof(pi));
 | 
						|
 | 
						|
        if (!CreateProcessW(executablePath.data(), arguments.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
 | 
						|
            throw std::runtime_error("Could not start crash monitor process");
 | 
						|
 | 
						|
        waitMonitor();
 | 
						|
    }
 | 
						|
 | 
						|
    LONG CrashCatcher::vectoredExceptionHandler(PEXCEPTION_POINTERS info)
 | 
						|
    {
 | 
						|
        switch (info->ExceptionRecord->ExceptionCode)
 | 
						|
        {
 | 
						|
        case EXCEPTION_SINGLE_STEP:
 | 
						|
        case EXCEPTION_BREAKPOINT:
 | 
						|
        case DBG_PRINTEXCEPTION_C:
 | 
						|
            return EXCEPTION_EXECUTE_HANDLER;
 | 
						|
        }
 | 
						|
        if (!sInstance)
 | 
						|
            return EXCEPTION_EXECUTE_HANDLER;
 | 
						|
 | 
						|
        sInstance->handleVectoredException(info);
 | 
						|
 | 
						|
        _Exit(1);
 | 
						|
    }
 | 
						|
 | 
						|
    void CrashCatcher::handleVectoredException(PEXCEPTION_POINTERS info)
 | 
						|
    {
 | 
						|
        shmLock();
 | 
						|
 | 
						|
        mShm->mEvent = CrashSHM::Event::Crashed;
 | 
						|
        mShm->mCrashed.mThreadId = GetCurrentThreadId();
 | 
						|
        mShm->mCrashed.mContext = *info->ContextRecord;
 | 
						|
        mShm->mCrashed.mExceptionRecord = *info->ExceptionRecord;
 | 
						|
 | 
						|
        shmUnlock();
 | 
						|
 | 
						|
        signalMonitor();
 | 
						|
 | 
						|
        // must remain until monitor has finished
 | 
						|
        waitMonitor();
 | 
						|
 | 
						|
        std::string message = "OpenMW has encountered a fatal error.\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);
 | 
						|
    }
 | 
						|
 | 
						|
} // namespace Crash
 |