// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2023 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.

// Stuff related to catching SEH exceptions, monitoring threads, and trapping
// debugger messages; used by both core client and by apps.

#include "boinc_win.h"
#include "win_util.h"

#ifndef __CYGWIN32__
#include "stackwalker_win.h"
#endif

#include "diagnostics.h"
#include "error_numbers.h"
#include "str_util.h"
#include "str_replace.h"
#include "util.h"
#include "version.h"

#if _MSC_VER > 1600
  //required for compiling with v110_xp to create XP compatible executables
  #ifndef FACILITY_VISUALCPP
    #define FACILITY_VISUALCPP  ((LONG)0x6d)  //now defined in winerror.h
  #endif
#endif

#include "diagnostics_win.h"

#ifndef PRIz
#ifdef _WIN64
#define PRIz "ll"
#else
#define PRIz "l"
#endif
#endif

// NtQuerySystemInformation
typedef NTSTATUS (WINAPI *tNTQSI)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);


// Look in the registry for the specified value user the BOINC diagnostics
//   hive.
BOOL diagnostics_get_registry_value(LPCSTR lpName, LPDWORD lpdwType, LPDWORD lpdwSize, LPBYTE lpData) {
	LONG  lRetVal;
	HKEY  hKey;

    // Detect platform information
    OSVERSIONINFO osvi;
    osvi.dwOSVersionInfoSize = sizeof(osvi);
    GetVersionEx(&osvi);

    if (VER_PLATFORM_WIN32_WINDOWS == osvi.dwPlatformId) {
		lRetVal = RegOpenKeyExA(
            HKEY_LOCAL_MACHINE,
            "SOFTWARE\\Space Sciences Laboratory, U.C. Berkeley\\BOINC Diagnostics",
			(DWORD)NULL,
            KEY_READ,
            &hKey
        );
		if (lRetVal != ERROR_SUCCESS) return FALSE;
	} else {
		lRetVal = RegOpenKeyExA(
            HKEY_CURRENT_USER,
            "SOFTWARE\\Space Sciences Laboratory, U.C. Berkeley\\BOINC Diagnostics",
			(DWORD)NULL,
            KEY_READ,
            &hKey
        );
		if (lRetVal != ERROR_SUCCESS) return FALSE;
	}

	lRetVal = RegQueryValueExA(hKey, lpName, NULL, lpdwType, lpData, lpdwSize);

	RegCloseKey(hKey);

	return (lRetVal == ERROR_SUCCESS);
}


// Provide a structure to store process measurements at the time of a
//   crash.
struct BOINC_PROCESSENTRY {
    DWORD               process_id;
    VM_COUNTERS         vm_counters;
    IO_COUNTERS         io_counters;
};

static BOINC_PROCESSENTRY diagnostics_process;


// Provide a set of API's which can be used to display more friendly
//   information about each thread.  These should also be used to
//   dump the callstacks for each executing thread when an unhandled
//   SEH exception is thrown.
//

// This structure is used to keep track of stuff necessary
//   to dump backtraces for all threads during an abort or
//   crash.  This is platform specific in nature since it
//   depends on the OS datatypes.
struct BOINC_THREADLISTENTRY {
    DWORD               thread_id;
    HANDLE              thread_handle;
    BOOL                crash_suspend_exempt;
    double              crash_kernel_time;
    double              crash_user_time;
    ULONG               crash_wait_time;
    INT                 crash_priority;
    INT                 crash_base_priority;
    INT                 crash_state;
    INT                 crash_wait_reason;
    PEXCEPTION_POINTERS crash_exception_record;
    char                crash_message[1024];
};

static std::vector<BOINC_THREADLISTENTRY*> diagnostics_threads;
static HANDLE hThreadListSync;


// Initialize the thread list entry.
int diagnostics_init_thread_entry(BOINC_THREADLISTENTRY *entry) {
    entry->thread_id = 0;
    entry->thread_handle = 0;
    entry->crash_suspend_exempt = FALSE;
    entry->crash_kernel_time = 0.0;
    entry->crash_user_time = 0.0;
    entry->crash_wait_time = 0;
    entry->crash_priority = 0;
    entry->crash_base_priority = 0;
    entry->crash_state = 0;
    entry->crash_wait_reason = 0;
    entry->crash_exception_record = NULL;
    entry->crash_message[0] = '\0';
    return 0;
}


// Initialize the thread list, which means empty it if anything is
//   in it.
int diagnostics_init_thread_list() {
    int retval = 0;
    size_t i;
    size_t size;

    // Create a Mutex that can be used to synchronize data access
    //   to the global thread list.
    hThreadListSync = CreateMutex(NULL, TRUE, NULL);
    if (!hThreadListSync) {
        fprintf(
            stderr, "diagnostics_init_thread_list(): Creating hThreadListSync failed, GLE %lu\n", GetLastError()
        );
        retval = GetLastError();
    } else {

        size = diagnostics_threads.size();
        for (i=0; i<size; i++) {
            delete diagnostics_threads[i];
        }
        diagnostics_threads.clear();

        // Release the Mutex
        ReleaseMutex(hThreadListSync);

    }

    return retval;
}


// Finish the thread list, which means empty it if anything is
//   in it.
int diagnostics_finish_thread_list() {
    size_t i;
    size_t size;

    // Wait for the ThreadListSync mutex before writing updates
    WaitForSingleObject(hThreadListSync, INFINITE);

    size = diagnostics_threads.size();
    for (i=0; i<size; i++) {
        delete diagnostics_threads[i];
    }
    diagnostics_threads.clear();

    // Release the Mutex
    ReleaseMutex(hThreadListSync);
    CloseHandle(hThreadListSync);

    return 0;
}


// Return a pointer to the thread entry.
//
BOINC_THREADLISTENTRY* diagnostics_find_thread_entry(DWORD dwThreadId) {
    BOINC_THREADLISTENTRY* pThread = NULL;
    UINT                   uiIndex = 0;
    size_t                 size = 0;

    size = diagnostics_threads.size();
    for (uiIndex = 0; uiIndex < size; uiIndex++) {
        if (diagnostics_threads[uiIndex]) {
            if (dwThreadId == diagnostics_threads[uiIndex]->thread_id) {
                pThread = diagnostics_threads[uiIndex];
            }
        }
    }

    return pThread;
}


// Use the native NT API to get all the process and thread information
//   about the current process.  This isn't a fully documented API but
//   enough information exists that we can rely on it for the known
//   Windows OS versions.  For each new Windows version check the
//   _SYSTEM_PROCESS and _SYSTEM_THREAD structures in the DDK to make
//   sure it is compatible with the existing stuff.
int diagnostics_get_process_information(PVOID* ppBuffer, PULONG pcbBuffer) {
    int      retval = 0;
    NTSTATUS Status = STATUS_INFO_LENGTH_MISMATCH;
    HANDLE   hHeap  = GetProcessHeap();
    HMODULE  hNTDllLib = NULL;
    tNTQSI   pNTQSI = NULL;

    hNTDllLib = GetModuleHandleA("ntdll.dll");
    pNTQSI = (tNTQSI)GetProcAddress(hNTDllLib, "NtQuerySystemInformation");

    do {
        *ppBuffer = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, *pcbBuffer);
        if (*ppBuffer == NULL) {
            return ERROR_NOT_ENOUGH_MEMORY;
        }

        Status = pNTQSI(
            SystemProcessInformation,
            *ppBuffer,
            *pcbBuffer,
            pcbBuffer
        );

        if (Status == STATUS_INFO_LENGTH_MISMATCH) {
            HeapFree(hHeap, 0, *ppBuffer);
            *pcbBuffer *= 2;
        } else if (!NT_SUCCESS(Status)) {
            HeapFree(hHeap, 0, *ppBuffer);
            retval = Status;
        }
    } while (Status == STATUS_INFO_LENGTH_MISMATCH);

    return retval;
}


// Enumerate the running threads in the process space and add them to
//   the list.  This only works on XP or better based machines.  This also
//   includes additional information which can be logged during a crash
//   event.
int diagnostics_update_thread_list() {
    DWORD                   dwCurrentProcessId = GetCurrentProcessId();
    HANDLE                  hThread = NULL;
    BOINC_THREADLISTENTRY   *pThreadEntry = NULL;
    ULONG                   cbBuffer = 32*1024;    // 32k initial buffer
    PVOID                   pBuffer = NULL;
    PSYSTEM_PROCESSES       pProcesses = NULL;
    PSYSTEM_THREADS         pThread = NULL;
    UINT                    uiSystemIndex = 0;


    // Get a snapshot of the process and thread information.
    diagnostics_get_process_information(&pBuffer, &cbBuffer);

    // Wait for the ThreadListSync mutex before writing updates
    WaitForSingleObject(hThreadListSync, INFINITE);

    // Lets start walking the structures to find the good stuff.
    pProcesses = (PSYSTEM_PROCESSES)pBuffer;
    do {
        // Okay, found the current process entry now we just need to
        //   update the thread data.
        if (pProcesses->ProcessId == dwCurrentProcessId) {

            // Store the process information we now know about.
            diagnostics_process.process_id = pProcesses->ProcessId;
            diagnostics_process.vm_counters = pProcesses->VmCounters;
            diagnostics_process.io_counters = pProcesses->IoCounters;

            // Enumerate the threads
            for(uiSystemIndex = 0; uiSystemIndex < pProcesses->ThreadCount; uiSystemIndex++) {
                pThread = &pProcesses->Threads[uiSystemIndex];
                pThreadEntry = diagnostics_find_thread_entry((DWORD)(uintptr_t)pThread->ClientId.UniqueThread);

                if (pThreadEntry) {
                    pThreadEntry->crash_kernel_time = pThread->KernelTime.QuadPart / 1e7;
                    pThreadEntry->crash_user_time = pThread->UserTime.QuadPart / 1e7;
                    pThreadEntry->crash_wait_time = pThread->WaitTime;
                    pThreadEntry->crash_priority = pThread->Priority;
                    pThreadEntry->crash_base_priority = pThread->BasePriority;
                    pThreadEntry->crash_state = pThread->State;
                    pThreadEntry->crash_wait_reason = pThread->WaitReason;
                } else {
                    hThread = OpenThread(
                        THREAD_ALL_ACCESS,
                        FALSE,
                        (DWORD)(uintptr_t)(pThread->ClientId.UniqueThread)
                    );

                    pThreadEntry = new BOINC_THREADLISTENTRY;
                    diagnostics_init_thread_entry(pThreadEntry);
                    pThreadEntry->thread_id = (DWORD)(uintptr_t)(pThread->ClientId.UniqueThread);
                    pThreadEntry->thread_handle = hThread;
                    pThreadEntry->crash_kernel_time = pThread->KernelTime.QuadPart / 1e7;
                    pThreadEntry->crash_user_time = pThread->UserTime.QuadPart / 1e7;
                    pThreadEntry->crash_wait_time = pThread->WaitTime;
                    pThreadEntry->crash_priority = pThread->Priority;
                    pThreadEntry->crash_base_priority = pThread->BasePriority;
                    pThreadEntry->crash_state = pThread->State;
                    pThreadEntry->crash_wait_reason = pThread->WaitReason;
                    diagnostics_threads.push_back(pThreadEntry);
                }
            }
        }

        // Move to the next structure if one exists
        if (!pProcesses->NextEntryDelta) {
            break;
        }
        pProcesses = (PSYSTEM_PROCESSES)(((LPBYTE)pProcesses) + pProcesses->NextEntryDelta);
    } while (pProcesses);

    // Release resources
    if (hThreadListSync) ReleaseMutex(hThreadListSync);
    if (pBuffer) HeapFree(GetProcessHeap(), 0, pBuffer);

    return 0;
}


// Set the cached exception record for the current thread, let the exception monitor
// thread dump the human readable exception information.
int diagnostics_set_thread_exception_record(PEXCEPTION_POINTERS pExPtrs) {
    HANDLE hThread;
    BOINC_THREADLISTENTRY *pThreadEntry = NULL;

    // Wait for the ThreadListSync mutex before writing updates
    WaitForSingleObject(hThreadListSync, INFINITE);

    pThreadEntry = diagnostics_find_thread_entry(GetCurrentThreadId());
    if (pThreadEntry) {
        pThreadEntry->crash_exception_record = pExPtrs;
    } else {
        DuplicateHandle(
            GetCurrentProcess(),
            GetCurrentThread(),
            GetCurrentProcess(),
            &hThread,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS
        );

        pThreadEntry = new BOINC_THREADLISTENTRY;
        diagnostics_init_thread_entry(pThreadEntry);
        pThreadEntry->thread_id = GetCurrentThreadId();
        pThreadEntry->thread_handle = hThread;
        pThreadEntry->crash_exception_record = pExPtrs;
        diagnostics_threads.push_back(pThreadEntry);
    }

    // Release the Mutex
    ReleaseMutex(hThreadListSync);

    return 0;
}


// Set the current thread to suspend exempt status.  Prevents deadlocks.
int diagnostics_set_thread_exempt_suspend() {
    HANDLE hThread;
    BOINC_THREADLISTENTRY *pThreadEntry = NULL;

    // Wait for the ThreadListSync mutex before writing updates
    WaitForSingleObject(hThreadListSync, INFINITE);

    pThreadEntry = diagnostics_find_thread_entry(GetCurrentThreadId());
    if (pThreadEntry) {
        pThreadEntry->crash_suspend_exempt = TRUE;
    } else {
        DuplicateHandle(
            GetCurrentProcess(),
            GetCurrentThread(),
            GetCurrentProcess(),
            &hThread,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS
        );

        pThreadEntry = new BOINC_THREADLISTENTRY;
        diagnostics_init_thread_entry(pThreadEntry);
        pThreadEntry->thread_id = GetCurrentThreadId();
        pThreadEntry->thread_handle = hThread;
        pThreadEntry->crash_suspend_exempt = TRUE;
        diagnostics_threads.push_back(pThreadEntry);
    }

    // Release the Mutex
    ReleaseMutex(hThreadListSync);

    return 0;
}


// Checks to see if the specified thread id is flagged for suspend exempt status.
// returns 0 on true, 1 on false.  Couldn't use a bool data type since the function
// prototype needs to be compatible with C.
int diagnostics_is_thread_exempt_suspend(long thread_id) {
    int retval = 1;
    BOINC_THREADLISTENTRY *pThreadEntry = NULL;

    // Wait for the ThreadListSync mutex before writing updates
    WaitForSingleObject(hThreadListSync, INFINITE);

    pThreadEntry = diagnostics_find_thread_entry(thread_id);
    if (pThreadEntry) {
        if (pThreadEntry->crash_suspend_exempt) {
            retval = 0;
        }
    }

    // Release the Mutex
    ReleaseMutex(hThreadListSync);

    return retval;
}


// Set the current thread's crash message.
int diagnostics_set_thread_crash_message(const char* message) {
    HANDLE hThread;
    BOINC_THREADLISTENTRY *pThreadEntry = NULL;

    // Wait for the ThreadListSync mutex before writing updates
    WaitForSingleObject(hThreadListSync, INFINITE);

    pThreadEntry = diagnostics_find_thread_entry(GetCurrentThreadId());
    if (pThreadEntry) {
        int buffer_used = snprintf(
            pThreadEntry->crash_message,
            sizeof(pThreadEntry->crash_message),
            "%s",
            message
        );
        if ((sizeof(pThreadEntry->crash_message) == buffer_used) || (-1 == buffer_used)) {
            pThreadEntry->crash_message[sizeof(pThreadEntry->crash_message)-1] = '\0';
        }
    } else {
        DuplicateHandle(
            GetCurrentProcess(),
            GetCurrentThread(),
            GetCurrentProcess(),
            &hThread,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS
        );

        pThreadEntry = new BOINC_THREADLISTENTRY;
        diagnostics_init_thread_entry(pThreadEntry);
        pThreadEntry->thread_id = GetCurrentThreadId();
        pThreadEntry->thread_handle = hThread;
        int buffer_used = snprintf(
            pThreadEntry->crash_message,
            sizeof(pThreadEntry->crash_message),
            "%s",
            message
        );
        if ((sizeof(pThreadEntry->crash_message) == buffer_used) || (-1 == buffer_used)) {
            pThreadEntry->crash_message[sizeof(pThreadEntry->crash_message)-1] = '\0';
        }
        diagnostics_threads.push_back(pThreadEntry);
    }

    // Release the Mutex
    ReleaseMutex(hThreadListSync);

    return 0;
}


// Translate the thread state into a human readable form.
//
// See: http://support.microsoft.com/?kbid=837372
//
const char* diagnostics_format_thread_state(int thread_state) {
    switch(thread_state) {
        case StateInitialized: return "Initialized";
        case StateReady: return "Ready";
        case StateRunning: return "Running";
        case StateStandby: return "Standby";
        case StateTerminated: return "Terminated";
        case StateWait: return "Waiting";
        case StateTransition: return "Transition";
    }
    return "Unknown";
}


// Translate the thread wait reason into a human readable form.
//
// See: http://support.microsoft.com/?kbid=837372
//
const char* diagnostics_format_thread_wait_reason(int thread_wait_reason) {
    switch(thread_wait_reason) {
        case ThreadWaitReasonExecutive: return "Executive";
        case ThreadWaitReasonFreePage: return "FreePage";
        case ThreadWaitReasonPageIn: return "PageIn";
        case ThreadWaitReasonPoolAllocation: return "PoolAllocation";
        case ThreadWaitReasonDelayExecution: return "ExecutionDelay";
        case ThreadWaitReasonSuspended: return "Suspended";
        case ThreadWaitReasonUserRequest: return "UserRequest";
        case ThreadWaitReasonWrExecutive: return "Executive";
        case ThreadWaitReasonWrFreePage: return "FreePage";
        case ThreadWaitReasonWrPageIn: return "PageIn";
        case ThreadWaitReasonWrPoolAllocation: return "PoolAllocation";
        case ThreadWaitReasonWrDelayExecution: return "ExecutionDelay";
        case ThreadWaitReasonWrSuspended: return "Suspended";
        case ThreadWaitReasonWrUserRequest: return "UserRequest";
        case ThreadWaitReasonWrEventPairHigh: return "EventPairHigh";
        case ThreadWaitReasonWrEventPairLow: return "EventPairLow";
        case ThreadWaitReasonWrLpcReceive: return "LPCReceive";
        case ThreadWaitReasonWrLpcReply: return "LPCReply";
        case ThreadWaitReasonWrVirtualMemory: return "VirtualMemory";
        case ThreadWaitReasonWrPageOut: return "PageOut";
    }
    return "Unknown";
}


// Translate the process priority class into a human readable form.
//
// See: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/scheduling_priorities.asp
//
const char* diagnostics_format_process_priority(int process_priority) {
    switch(process_priority) {
        case IDLE_PRIORITY_CLASS: return "Idle";
        case BELOW_NORMAL_PRIORITY_CLASS: return "Below Normal";
        case NORMAL_PRIORITY_CLASS: return "Normal";
        case ABOVE_NORMAL_PRIORITY_CLASS: return "Above Normal";
        case HIGH_PRIORITY_CLASS: return "High";
        case REALTIME_PRIORITY_CLASS: return "Realtime";
    }
    return "Unknown";
}


// Translate the thread priority class into a human readable form.
//
// See: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/scheduling_priorities.asp
//
const char* diagnostics_format_thread_priority(int thread_priority) {
    switch(thread_priority) {
        case THREAD_PRIORITY_IDLE: return "Idle";
        case THREAD_PRIORITY_LOWEST: return "Lowest";
        case THREAD_PRIORITY_BELOW_NORMAL: return "Below Normal";
        case THREAD_PRIORITY_NORMAL: return "Normal";
        case THREAD_PRIORITY_ABOVE_NORMAL: return "Above Normal";
        case THREAD_PRIORITY_HIGHEST: return "Highest";
        case THREAD_PRIORITY_TIME_CRITICAL: return "Time Critical";
    }
    return "Unknown";
}


// Provide a mechanism to trap and report messages sent to the debugger's
//   viewport.  This should only been enabled if a debugger isn't running
//   against the current process already.
//
// Documentation about the protocol can be found here:
//   http://www.unixwiz.net/techtips/outputdebugstring.html
//

typedef struct _DEBUGGERMESSAGE {
        DWORD   dwProcessId;
        char    data[4096 - sizeof(DWORD)];
} DEBUGGERMESSAGE, *PDEBUGGERMESSAGE;

typedef struct _BOINC_MESSAGEMONITORENTRY {
    double      timestamp;
    std::string message;
} BOINC_MESSAGEMONITORENTRY, *PBOINC_MESSAGEMONITORENTRY;

static std::vector<PBOINC_MESSAGEMONITORENTRY> diagnostics_monitor_messages;
static PDEBUGGERMESSAGE pMessageBuffer;
static UINT   uiMessageMonitorThreadId;
static HANDLE hMessageMonitorThread;
static HANDLE hMessageMonitorSync;
static HANDLE hMessageSharedMap;
static HANDLE hMessageAckEvent;
static HANDLE hMessageReadyEvent;
static HANDLE hMessageQuitEvent;
static HANDLE hMessageQuitFinishedEvent;


// Initialize the needed structures and startup the message processing thread.
//
int diagnostics_init_message_monitor() {
    int retval = 0;
    unsigned int i;
    DWORD dwType;
    DWORD dwSize;
    DWORD dwCaptureMessages;

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = &sd;

    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&sd, TRUE, (PACL)NULL, FALSE);


    // Create a mutex that can be used to synchronize data access
    //   to the global thread list.
    hMessageMonitorSync = CreateMutex(NULL, TRUE, NULL);
    if (!hMessageMonitorSync) {
        fprintf(
            stderr, "diagnostics_init_message_monitor(): Creating hMessageMonitorSync failed, GLE %lu\n", GetLastError()
        );
    }

    // Clear out any previous messages.
    for (i=0; i<diagnostics_monitor_messages.size(); i++) {
        delete diagnostics_monitor_messages[i];
    }
    diagnostics_monitor_messages.clear();

    // Check the registry to see if we are allowed to capture debugger messages.
    //   Apparently many audio and visual playback programs dump serious
    //   amounts of data to the debugger viewport even on a release build.
    //   When this feature is enabled it slows down the replay of DVDs and CDs
    //   such that they become jerky and unpleasent to watch or listen too.
    //
    // We'll turn it off by default, but keep it around just in case we need
    //   it.
    //
    dwCaptureMessages = 0;
    dwType = REG_DWORD;
    dwSize = sizeof(dwCaptureMessages);
    diagnostics_get_registry_value(
        "CaptureMessages",
        &dwType,
        &dwSize,
        (LPBYTE)&dwCaptureMessages
    );

    // If a debugger is present then let it capture the debugger messages
    if (!IsDebuggerPresent() && hMessageMonitorSync && dwCaptureMessages) {
        hMessageAckEvent = CreateEventA(&sa, FALSE, FALSE, "DBWIN_BUFFER_READY");
        if (!hMessageAckEvent) {
            fprintf(
                stderr, "diagnostics_init_message_monitor(): Creating hMessageAckEvent failed, GLE %lu\n", GetLastError()
            );
        }

        hMessageReadyEvent = CreateEventA(&sa, FALSE, FALSE, "DBWIN_DATA_READY");
        if (!hMessageReadyEvent) {
            fprintf(
                stderr, "diagnostics_init_message_monitor(): Creating hMessageReadyEvent failed, GLE %lu\n", GetLastError()
            );
        }

        hMessageQuitEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
        if (!hMessageQuitEvent) {
            fprintf(
                stderr, "diagnostics_init_message_monitor(): Creating hMessageQuitEvent failed, GLE %lu\n", GetLastError()
            );
        }

        hMessageQuitFinishedEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
        if (!hMessageQuitFinishedEvent) {
            fprintf(
                stderr, "diagnostics_init_message_monitor(): Creating hMessageQuitFinishedEvent failed, GLE %lu\n", GetLastError()
            );
        }

        hMessageSharedMap = CreateFileMappingA(
            INVALID_HANDLE_VALUE,    // use paging file
            &sa,                     // default security
            PAGE_READWRITE,          // read/write access
            0,                       // max. object size
            sizeof(DEBUGGERMESSAGE), // buffer size
            "DBWIN_BUFFER"           // name of mapping object
        );
        if (!hMessageSharedMap) {
            fprintf(
                stderr, "diagnostics_init_message_monitor(): CreateFileMapping hMessageSharedMap failed, GLE %lu\n", GetLastError()
            );
        }

        pMessageBuffer = (PDEBUGGERMESSAGE)MapViewOfFile(
            hMessageSharedMap,
            FILE_MAP_READ | FILE_MAP_WRITE,
            0,                       // file offset high
            0,                       // file offset low
            sizeof(DEBUGGERMESSAGE)  // # of bytes to map (entire file)
        );
        if (!pMessageBuffer) {
            fprintf(
                stderr, "diagnostics_init_message_monitor(): MapViewOfFile pMessageBuffer failed, GLE %lu\n", GetLastError()
            );
        }

        hMessageMonitorThread = (HANDLE)_beginthreadex(
            NULL,
            0,
            diagnostics_message_monitor,
            0,
            0,
            &uiMessageMonitorThreadId
        );
        if (!hMessageMonitorThread) {
            fprintf(
                stderr, "diagnostics_init_message_monitor(): _beginthreadex, errno %d\n", errno
            );
        }
    } else {
        retval = ERROR_NOT_SUPPORTED;
    }

    // Release the Mutex
    ReleaseMutex(hMessageMonitorSync);

    return retval;
}


// Shutdown the message monitoring thread and cleanup any of the in memory
//   structures.
int diagnostics_finish_message_monitor() {
    unsigned int i;

    // Begin the cleanup process by means of shutting down the
    //   message monitoring thread.
    SetEvent(hMessageQuitEvent);

    // Wait until it is message monitoring thread is shutdown before
    //   cleaning up the structure since we'll need to acquire the
    //   MessageMonitorSync mutex.
    WaitForSingleObject(hMessageQuitFinishedEvent, INFINITE);
    WaitForSingleObject(hMessageMonitorSync, INFINITE);


    // Now clean up everything we can.
    //

    // Clear out any previous messages.
    for (i=0; i<diagnostics_monitor_messages.size(); i++) {
        delete diagnostics_monitor_messages[i];
    }
    diagnostics_monitor_messages.clear();


    // Cleanup the handles
    if (pMessageBuffer) UnmapViewOfFile(pMessageBuffer);
    if (hMessageSharedMap) CloseHandle(hMessageSharedMap);
    if (hMessageAckEvent) CloseHandle(hMessageAckEvent);
    if (hMessageReadyEvent) CloseHandle(hMessageReadyEvent);
    if (hMessageQuitEvent) CloseHandle(hMessageQuitEvent);
    if (hMessageQuitFinishedEvent) CloseHandle(hMessageQuitFinishedEvent);
    if (hMessageMonitorThread) CloseHandle(hMessageMonitorThread);
    if (hMessageMonitorSync) CloseHandle(hMessageMonitorSync);

    return 0;
}


int diagnostics_message_monitor_dump() {
    unsigned int i;
    PBOINC_MESSAGEMONITORENTRY pMessageEntry = NULL;

    // Wait for the MessageMonitorSync mutex before writing updates
    WaitForSingleObject(hMessageMonitorSync, INFINITE);

    fprintf(stderr, "\n*** Debug Message Dump ****\n");

    // Clear out any previous messages.
    for (i=0; i<diagnostics_monitor_messages.size(); i++) {
        pMessageEntry = diagnostics_monitor_messages[i];
        fprintf(
            stderr,
            "[%s] %s",
            time_to_string(pMessageEntry->timestamp),
            pMessageEntry->message.c_str()
        );
    }

    fprintf(stderr, "\n\n");

    // Release the Mutex
    ReleaseMutex(hMessageMonitorSync);

    return 0;
}


// This thread monitors the shared memory buffer used to pass debug messages
//   around.  due to an anomaly in the Windows debug environment it is
//   suggested that a sleep(0) be introduced before any
//   SetEvent/ResetEvent/PulseEvent function is called.
//
// See: http://support.microsoft.com/kb/q173260/
//
UINT WINAPI diagnostics_message_monitor(LPVOID /* lpParameter */) {
    DWORD       dwEvent = (DWORD)NULL;
    DWORD       dwCurrentProcessId = (DWORD)NULL;
    BOOL        bContinue = TRUE;
    DWORD       dwRepeatMessageCounter = 0;
    DWORD       dwRepeatMessageProcessId = 0;
    std::string strRepeatMessage;
    PBOINC_MESSAGEMONITORENTRY pMessageEntry = NULL;
    HANDLE      hEvents[2];

    // Make sure this thread doesn't get suspended during
    //   a crash dump: the DBGHELP library is pretty verbose.
    //   Suspending this thread will cause a deadlock.
    diagnostics_set_thread_exempt_suspend();


    // Which events do we want to wait for?
    hEvents[0] = hMessageQuitEvent;
    hEvents[1] = hMessageReadyEvent;

    // Cache the current process id
    dwCurrentProcessId = GetCurrentProcessId();

    // Signal that the buffer is ready for action.
    Sleep(0);
    SetEvent(hMessageAckEvent);

    while (bContinue) {
        dwEvent = WaitForMultipleObjects(
            2,           // number of objects in array
            hEvents,     // array of objects
            FALSE,       // wait for any
            INFINITE     // wait
        );
        switch(dwEvent) {
            // hMessageQuitEvent was signaled.
            case WAIT_OBJECT_0 + 0:

                // We are shutting down so lets cleanup and exit.
                bContinue = false;

                break;

            // hMessageReadyEvent was signaled.
            case WAIT_OBJECT_0 + 1:
                // The debugger protocol assumes that only one debugger is going
                //   to exist on the system, but we are only interested in messages
                //   from the current process id.  Since we are here we can assume
                //   that no debugger was present when the application was launched
                //   so we can safely ignore messages that didn't come from us
                //   because that means they are from another application.
                //
                // If we detect a message from a different process just ignore it
                //   and re-signal the event.  We'll go to sleep for 100 milliseconds
                //   and let the other BOINC based applications have a shot at it.
                //
                // If we see the same message four times that means it is from an
                //   application that doesn't understand our modificatons, so we'll
                //   process the message just like we were a regular debugger and
                //   signal that the buffer is available again.
                if (dwCurrentProcessId != pMessageBuffer->dwProcessId) {
                    // Message from a different process.

                    // Is this the same message as before?
                    if ((dwRepeatMessageProcessId != pMessageBuffer->dwProcessId) ||
                        (strRepeatMessage != pMessageBuffer->data)
                    ) {
                        dwRepeatMessageCounter = 0;

                        // Cache the data for future checks.
                        dwRepeatMessageProcessId = pMessageBuffer->dwProcessId;
                        strRepeatMessage = pMessageBuffer->data;
                    } else {
                        dwRepeatMessageCounter++;
                    }

                    if (dwRepeatMessageCounter > 4) {
                        // Buffer is ready to receive a new message.
                        Sleep(0);
                        SetEvent(hMessageAckEvent);

                        dwRepeatMessageCounter = 0;
                        dwRepeatMessageProcessId = 0;
                        strRepeatMessage = "";
                    } else {
                        // Let another application have a go at the message.
                        Sleep(0);
                        SetEvent(hMessageReadyEvent);
                        Sleep(100);
                    }
                } else {
                    // A message for us to process
                    pMessageEntry = new BOINC_MESSAGEMONITORENTRY;
                    pMessageEntry->timestamp = dtime();
                    pMessageEntry->message = pMessageBuffer->data;

                    // Wait for the MessageMonitorSync mutex before writing updates
                    WaitForSingleObject(hMessageMonitorSync, INFINITE);

                    diagnostics_monitor_messages.push_back(pMessageEntry);

                    // Trim back the number of messages in memory
                    if (diagnostics_monitor_messages.size() > 50) {
                        delete diagnostics_monitor_messages[0];
                        diagnostics_monitor_messages.erase(diagnostics_monitor_messages.begin());
                    }

                    // Release the Mutex
                    ReleaseMutex(hMessageMonitorSync);

                    // Clear out the old message
                    ZeroMemory(pMessageBuffer, sizeof(DEBUGGERMESSAGE));

                    // Buffer is ready to receive a new message.
                    Sleep(0);
                    SetEvent(hMessageAckEvent);
                }
                break;
        }
    }

    // Notify the calling thread that the message monitoring thread is
    //   finished.
    SetEvent(hMessageQuitFinishedEvent);
    return 0;
}


// Dump a message to the debuggers viewport if we are allowed to.
//
int diagnostics_trace_to_debugger(const char* msg) {
    DWORD dwType;
    DWORD dwSize;
    DWORD dwTraceToViewport;

    // Check the registry to see if we are allowed to dump debugger messages.
    //
    // We'll turn it off by default, but keep it around just in case we need
    //   it or want to use it.
    //
    dwTraceToViewport = 0;
    dwType = REG_DWORD;
    dwSize = sizeof(dwTraceToViewport);
    diagnostics_get_registry_value(
        "TraceToViewport",
        &dwType,
        &dwSize,
        (LPBYTE)&dwTraceToViewport
    );

    if (dwTraceToViewport) {
        OutputDebugStringA(msg);
    }

    return 0;
}




// Structured Exceptions are Windows primary mechanism for dealing with
//   badly behaved applications or applications where something bad has
//   happened underneath them and they need to clean up after themselves.
//
// Applications can define an unhandled exception filter to handle any
//   exception event that Windows will throw.  If you leave things to
//   the OS defaults, you'll end up with the annoying Windows Error
//   Reporting dialog and they user will be asked if they want to report
//   the crash to Microsoft.  Most of the time this is okay for regular
//   applications, but for BOINC based applications this is really bad.
//
// BOINC based applications need to be completely autonomous.  Unhandled
//   exceptions are caught and we dump as much information, about what
//   has happened, to stderr so that project administrators can look at
//   it and fix whatever bug might have caused the event.
//
// To accomplish this BOINC starts up a thread that will handle any
//   unhandled exceptions when one is detected.  By using a separate
//   thread the runtime debugger can avoid stack corruption issues and
//   multiple unhandled exceptions.  In a multi-processor system it is
//   possible that both the graphics thread and the worker threads would
//   be referencing the same corrupted area of memory.  Previous
//   implementations of the runtime debugger would have just terminated
//   the process believing it was a nested unhandled exception instead
//   of believing it to be two separate exceptions thrown from different
//   threads.
//

// This structure is used to keep track of stuff necessary
//   to dump information about the top most window during
//   a crash event.
typedef struct _BOINC_WINDOWCAPTURE {
    HWND         hwnd;
    char         window_name[256];
    char         window_class[256];
    DWORD        window_process_id;
    DWORD        window_thread_id;
} BOINC_WINDOWCAPTURE, *PBOINC_WINDOWCAPTURE;

static UINT   uiExceptionMonitorThreadId = (UINT)NULL;
static HANDLE hExceptionMonitorThread = NULL;
static HANDLE hExceptionMonitorHalt = NULL;
static HANDLE hExceptionMonitorStartedEvent = NULL;
static HANDLE hExceptionDetectedEvent = NULL;
static HANDLE hExceptionQuitEvent = NULL;
static HANDLE hExceptionQuitFinishedEvent = NULL;
static CRITICAL_SECTION csExceptionMonitorFallback;

// Initialize the needed structures and startup the unhandled exception
//   monitor thread.
int diagnostics_init_unhandled_exception_monitor() {
    int retval = 0;

    // Initialize the fallback critical section in case we fail to create the
    //   unhandled exception monitor.
    InitializeCriticalSection(&csExceptionMonitorFallback);


    // Create a mutex that can be used to put any thread that has thrown
    //   an unhandled exception to sleep.
    hExceptionMonitorHalt = CreateMutex(NULL, FALSE, NULL);
    if (!hExceptionMonitorHalt) {
        fprintf(
            stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionMonitorHalt failed, GLE %lu\n", GetLastError()
        );
    }

    // The following event is thrown by the exception monitoring thread
    //   right before it waits for the hExceptionDetectedEvent event.
    hExceptionMonitorStartedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (!hExceptionMonitorStartedEvent) {
        fprintf(
            stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionMonitorStartedEvent failed, GLE %lu\n", GetLastError()
        );
    }

    // The following event is thrown by a thread that has experienced an
    //   unhandled exception after storing its exception record but before
    //   it attempts to acquire the halt mutex.
    hExceptionDetectedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (!hExceptionDetectedEvent) {
        fprintf(
            stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionDetectedEvent failed, GLE %lu\n", GetLastError()
        );
    }

    // Create an event that we can use to shutdown the unhandled exception
    //   monitoring thread.
    hExceptionQuitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (!hExceptionQuitEvent) {
        fprintf(
            stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionQuitEvent failed, GLE %lu\n", GetLastError()
        );
    }
    hExceptionQuitFinishedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (!hExceptionQuitFinishedEvent) {
        fprintf(
            stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionQuitFinishedEvent failed, GLE %lu\n", GetLastError()
        );
    }

    // Create the thread that is going to monitor any unhandled exceptions
    // NOTE: Only attempt to create the thread if all the thread sync objects
    //   have been created.
    if (hExceptionMonitorHalt && hExceptionDetectedEvent && hExceptionQuitEvent && hExceptionQuitFinishedEvent) {
        hExceptionMonitorThread = (HANDLE)_beginthreadex(
            NULL,
            0,
            diagnostics_unhandled_exception_monitor,
            0,
            0,
            &uiExceptionMonitorThreadId
        );
        if (!hExceptionMonitorThread) {
            fprintf(
                stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionMonitorThread failed, errno %d\n", errno
            );
        }
    }

    if (!hExceptionMonitorThread) {
        fprintf(
            stderr, "WARNING: BOINC Windows Runtime Debugger has been disabled.\n"
        );
        retval = ERR_THREAD;
    } else {

        // Wait until the exception monitor is ready for business.
        //
        WaitForSingleObject(hExceptionMonitorStartedEvent, INFINITE);
        CloseHandle(hExceptionMonitorStartedEvent);

    }

    return retval;
}


// Shutdown the unhandled exception monitoring thread and cleanup any
//   of the in memory structures.
int diagnostics_finish_unhandled_exception_monitor() {

    // Begin the cleanup process by means of shutting down the
    //   message monitoring thread.
    SetEvent(hExceptionQuitEvent);

    // Wait until it is message monitoring thread is shutdown before
    //   cleaning up the structure since we'll need to acquire the
    //   MessageMonitorSync mutex.
    WaitForSingleObject(hExceptionQuitFinishedEvent, INFINITE);

    // Cleanup the handles
    if (hExceptionDetectedEvent) CloseHandle(hExceptionDetectedEvent);
    if (hExceptionQuitEvent) CloseHandle(hExceptionQuitEvent);
    if (hExceptionQuitFinishedEvent) CloseHandle(hExceptionQuitFinishedEvent);
    if (hExceptionMonitorHalt) CloseHandle(hExceptionMonitorHalt);
    if (hExceptionMonitorThread) CloseHandle(hExceptionMonitorThread);

    // Cleanup the fallback critical section.
    DeleteCriticalSection(&csExceptionMonitorFallback);

    return 0;
}


// Dump crash header information
//
int diagnostics_unhandled_exception_dump_banner() {
    char szDate[64];
    char szTime[64];

    strdate(szDate);
    strtime(szTime);

    fprintf(stderr, "\n\n");
    fprintf(stderr, "********************\n");
    fprintf(stderr, "\n\n");
    fprintf(stderr, "BOINC Windows Runtime Debugger Version %s\n", BOINC_VERSION_STRING);
    fprintf(stderr, "\n\n");
    fprintf(stderr, "Dump Timestamp    : %s %s\n", szDate, szTime);
    if (diagnostics_is_flag_set(BOINC_DIAG_BOINCAPPLICATION)) {
        fprintf(stderr, "Install Directory : %s\n", diagnostics_get_boinc_install_dir());
        fprintf(stderr, "Data Directory    : %s\n", diagnostics_get_boinc_dir());
        fprintf(stderr, "Project Symstore  : %s\n", diagnostics_get_symstore());
    }

    return 0;
}

// Capture the foreground window details for future use.
//
int diagnostics_capture_foreground_window(PBOINC_WINDOWCAPTURE window_info) {
    DWORD dwType;
    DWORD dwSize;
    DWORD dwCaptureForegroundWindow;


    // Initialize structure variables.
	safe_strcpy(window_info->window_name, "");
	safe_strcpy(window_info->window_class, "");
    window_info->hwnd = 0;
    window_info->window_process_id = 0;
    window_info->window_thread_id = 0;


    // Check the registry to see if we are aloud to capture the foreground
    //   window data. Many people were concerned about privacy issues.
    //
    // We'll turn it off by default, but keep it around just in case we need
    //   it.
    //
    dwCaptureForegroundWindow = 0;
    dwType = REG_DWORD;
    dwSize = sizeof(dwCaptureForegroundWindow);
    diagnostics_get_registry_value(
        "CaptureForegroundWindow",
        &dwType,
        &dwSize,
        (LPBYTE)&dwCaptureForegroundWindow
    );


    if (dwCaptureForegroundWindow) {
        window_info->hwnd = GetForegroundWindow();

        window_info->window_thread_id = GetWindowThreadProcessId(
            window_info->hwnd,
            &window_info->window_process_id
        );

	    // Only query the window text from windows in a different process space.
	    //   All threads that might have windows are suspended in this process
	    //   space and attempting to get the window text will deadlock the exception
	    //   handler.
	    if (window_info->window_process_id != GetCurrentProcessId()) {
		    GetWindowTextA(
			    window_info->hwnd,
			    window_info->window_name,
			    sizeof(window_info->window_name)
		    );

		    GetClassNameA(
			    window_info->hwnd,
			    window_info->window_class,
			    sizeof(window_info->window_class)
		    );
	    }
    }

    return 0;
}


// Dump the foreground window details to stderr.
//
int diagnostics_foreground_window_dump(PBOINC_WINDOWCAPTURE window_info) {

    fprintf(
        stderr,
        "*** Foreground Window Data ***\n"
        "    Window Name      : %s\n"
        "    Window Class     : %s\n"
        "    Window Process ID: %lu\n"
        "    Window Thread ID : %lu\n\n",
        window_info->window_name,
        window_info->window_class,
        window_info->window_process_id,
        window_info->window_thread_id
    );

    return 0;
}


// Dump the captured information for a the current process.
//
int diagnostics_dump_process_information() {
    // Header
    fprintf(
        stderr,
        "*** Dump of the Process Statistics: ***\n\n"
    );

    // I/O Counters
    fprintf(
        stderr,
        "- I/O Operations Counters -\n"
        "Read: %llu, Write: %llu, Other %llu\n"
        "\n"
        "- I/O Transfers Counters -\n"
        "Read: %llu, Write: %llu, Other %llu\n"
        "\n",
        diagnostics_process.io_counters.ReadOperationCount,
        diagnostics_process.io_counters.WriteOperationCount,
        diagnostics_process.io_counters.OtherOperationCount,
        diagnostics_process.io_counters.ReadTransferCount,
        diagnostics_process.io_counters.WriteTransferCount,
        diagnostics_process.io_counters.OtherTransferCount
    );

    // VM Counters
    fprintf(
        stderr,
        "- Paged Pool Usage -\n"
        "QuotaPagedPoolUsage: %" PRIz "u, QuotaPeakPagedPoolUsage: %" PRIz "u\n"
        "QuotaNonPagedPoolUsage: %" PRIz "u, QuotaPeakNonPagedPoolUsage: %" PRIz "u\n"
        "\n"
        "- Virtual Memory Usage -\n"
        "VirtualSize: %" PRIz "u, PeakVirtualSize: %" PRIz "u\n"
        "\n"
        "- Pagefile Usage -\n"
        "PagefileUsage: %" PRIz "u, PeakPagefileUsage: %" PRIz "u\n"
        "\n"
        "- Working Set Size -\n"
        "WorkingSetSize: %" PRIz "u, PeakWorkingSetSize: %" PRIz "u, PageFaultCount: %lu\n"
        "\n",
        diagnostics_process.vm_counters.QuotaPagedPoolUsage,
        diagnostics_process.vm_counters.QuotaPeakPagedPoolUsage,
        diagnostics_process.vm_counters.QuotaNonPagedPoolUsage,
        diagnostics_process.vm_counters.QuotaPeakNonPagedPoolUsage,
        diagnostics_process.vm_counters.VirtualSize,
        diagnostics_process.vm_counters.PeakVirtualSize,
        diagnostics_process.vm_counters.PagefileUsage,
        diagnostics_process.vm_counters.PeakPagefileUsage,
        diagnostics_process.vm_counters.WorkingSetSize,
        diagnostics_process.vm_counters.PeakWorkingSetSize,
        diagnostics_process.vm_counters.PageFaultCount
    );

    return 0;
}


// Dump the captured information for a given thread.
//
int diagnostics_dump_thread_information(BOINC_THREADLISTENTRY *pThreadEntry) {
    std::string strStatusExtra;

    if (pThreadEntry->crash_state == StateWait) {
        strStatusExtra += "Wait Reason: ";
        strStatusExtra += diagnostics_format_thread_wait_reason(pThreadEntry->crash_wait_reason);
    } else {
        strStatusExtra += "Base Priority: ";
        strStatusExtra += diagnostics_format_thread_priority(pThreadEntry->crash_base_priority);
        strStatusExtra += ", ";
        strStatusExtra += "Priority: ";
        strStatusExtra += diagnostics_format_thread_priority(pThreadEntry->crash_priority);
    }

    fprintf(
        stderr,
        "*** Dump of thread ID %lu (state: %s): ***\n\n"
        "- Information -\n"
        "Status: %s, "
        "Kernel Time: %.3f, "
        "User Time: %.3f, "
        "Wait Time: %lu\n"
        "\n",
        pThreadEntry->thread_id,
        diagnostics_format_thread_state(pThreadEntry->crash_state),
        strStatusExtra.c_str(),
        pThreadEntry->crash_kernel_time,
        pThreadEntry->crash_user_time,
        pThreadEntry->crash_wait_time
    );

    return 0;
}


// Provide a generic way to format exceptions
//
int diagnostics_dump_generic_exception(const char* exception_desc, DWORD exception_code, PVOID exception_address) {
    fprintf(
        stderr,
        "Reason: %s (0x%lx) at address 0x%p\n\n",
        exception_desc,
        exception_code,
        exception_address
    );
    return 0;
}

// Dump the exception code record to stderr in a human readable form.
//
int diagnostics_dump_exception_record(PEXCEPTION_POINTERS pExPtrs) {
    char           status[256];
    char           substatus[256];
    char           message[1024];
    PVOID          exception_address = pExPtrs->ExceptionRecord->ExceptionAddress;
    DWORD          exception_code = pExPtrs->ExceptionRecord->ExceptionCode;
#ifdef HAVE_DELAYIMP_H
    PDelayLoadInfo delay_load_info = NULL;
#endif

    // Print unhandled exception banner
    fprintf(stderr, "- Unhandled Exception Record -\n");

    switch (exception_code) {
#ifdef HAVE_DELAYIMP_H
        case VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND):
            delay_load_info = (PDelayLoadInfo)pExPtrs->ExceptionRecord->ExceptionInformation[0];
            fprintf(
                stderr,
                "Delay Load Failure: Attempting to load '%s' failed.\n\n",
                delay_load_info->szDll
            );
            break;
        case VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND):
            delay_load_info = (PDelayLoadInfo)pExPtrs->ExceptionRecord->ExceptionInformation[0];
            fprintf(
                stderr,
                "Delay Load Failure: Attempting to find '%s' in '%s' failed.\n\n",
                delay_load_info->dlp.szProcName,
                delay_load_info->szDll
            );
            break;
#endif
        case 0xC0000135:                     // STATUS_DLL_NOT_FOUND
        case 0xC0000139:                     // STATUS_ENTRYPOINT_NOT_FOUND
        case 0xC0000142:                     // STATUS_DLL_INIT_FAILED
        case 0xC0000143:                     // STATUS_MISSING_SYSTEMFILE
            fprintf(stderr, "%s\n\n", windows_format_error_string(exception_code, message, sizeof(message)));
            break;
        case 0xE06D7363:
            diagnostics_dump_generic_exception("(C++ Exception)", exception_code, exception_address);
            break;
        case EXCEPTION_ACCESS_VIOLATION:
            safe_strcpy(status, "Access Violation");
            safe_strcpy(substatus, "");
            if (pExPtrs->ExceptionRecord->NumberParameters == 2) {
                switch(pExPtrs->ExceptionRecord->ExceptionInformation[0]) {
                case EXCEPTION_READ_FAULT:
                    snprintf(substatus, sizeof(substatus),
                        "read attempt from address 0x%p",
                        (void*)pExPtrs->ExceptionRecord->ExceptionInformation[1]
                    );
                    break;
                case EXCEPTION_WRITE_FAULT:
                    snprintf(substatus, sizeof(substatus),
                        "write attempt to address 0x%p",
                        (void*)pExPtrs->ExceptionRecord->ExceptionInformation[1]
                    );
                    break;
                case EXCEPTION_EXECUTE_FAULT:
                    snprintf(substatus, sizeof(substatus),
                        "execute attempt at address 0x%p",
                        (void*)pExPtrs->ExceptionRecord->ExceptionInformation[1]
                    );
                    break;
                }
            }
            fprintf(stderr,
                "Reason: %s (0x%lx) at address 0x%p %s\n\n",
                status, exception_code, exception_address, substatus
            );
            break;
        case EXCEPTION_DATATYPE_MISALIGNMENT:
            diagnostics_dump_generic_exception("Data Type Misalignment", exception_code, exception_address);
            break;
        case EXCEPTION_BREAKPOINT:
            diagnostics_dump_generic_exception("Breakpoint Encountered", exception_code, exception_address);
            break;
        case EXCEPTION_SINGLE_STEP:
            diagnostics_dump_generic_exception("Single Instruction Executed", exception_code, exception_address);
            break;
        case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
            diagnostics_dump_generic_exception("Array Bounds Exceeded", exception_code, exception_address);
            break;
        case EXCEPTION_FLT_DENORMAL_OPERAND:
            diagnostics_dump_generic_exception("Float Denormal Operand", exception_code, exception_address);
            break;
        case EXCEPTION_FLT_DIVIDE_BY_ZERO:
            diagnostics_dump_generic_exception("Divide by Zero", exception_code, exception_address);
            break;
        case EXCEPTION_FLT_INEXACT_RESULT:
            diagnostics_dump_generic_exception("Float Inexact Result", exception_code, exception_address);
            break;
        case EXCEPTION_FLT_INVALID_OPERATION:
            diagnostics_dump_generic_exception("Float Invalid Operation", exception_code, exception_address);
            break;
        case EXCEPTION_FLT_OVERFLOW:
            diagnostics_dump_generic_exception("Float Overflow", exception_code, exception_address);
            break;
        case EXCEPTION_FLT_STACK_CHECK:
            diagnostics_dump_generic_exception("Float Stack Check", exception_code, exception_address);
            break;
        case EXCEPTION_FLT_UNDERFLOW:
            diagnostics_dump_generic_exception("Float Underflow", exception_code, exception_address);
            break;
        case EXCEPTION_INT_DIVIDE_BY_ZERO:
            diagnostics_dump_generic_exception("Integer Divide by Zero", exception_code, exception_address);
            break;
        case EXCEPTION_INT_OVERFLOW:
            diagnostics_dump_generic_exception("Integer Overflow", exception_code, exception_address);
            break;
        case EXCEPTION_PRIV_INSTRUCTION:
            diagnostics_dump_generic_exception("Privileged Instruction", exception_code, exception_address);
            break;
        case EXCEPTION_IN_PAGE_ERROR:
            diagnostics_dump_generic_exception("In Page Error", exception_code, exception_address);
            break;
        case EXCEPTION_ILLEGAL_INSTRUCTION:
            diagnostics_dump_generic_exception("Illegal Instruction", exception_code, exception_address);
            break;
        case EXCEPTION_NONCONTINUABLE_EXCEPTION:
            diagnostics_dump_generic_exception("Noncontinuable Exception", exception_code, exception_address);
            break;
        case EXCEPTION_STACK_OVERFLOW:
            diagnostics_dump_generic_exception("Stack Overflow", exception_code, exception_address);
            break;
        case EXCEPTION_INVALID_DISPOSITION:
            diagnostics_dump_generic_exception("Invalid Disposition", exception_code, exception_address);
            break;
        case EXCEPTION_GUARD_PAGE:
            diagnostics_dump_generic_exception("Guard Page Violation", exception_code, exception_address);
            break;
        case EXCEPTION_INVALID_HANDLE:
            diagnostics_dump_generic_exception("Invalid Handle", exception_code, exception_address);
            break;
        case CONTROL_C_EXIT:
            diagnostics_dump_generic_exception("Ctrl+C Exit", exception_code, exception_address);
            break;
        default:
            diagnostics_dump_generic_exception("Unknown exception", exception_code, exception_address);
            break;
    }

    return 0;
}


// Priority is given to the worker threads exception code, and then the
//   graphics thread.  If neither of those two threw the exception grab
//   the exception code of the thread that did.
UINT diagnostics_determine_exit_code() {
    UINT   uiReturn = 0;
    UINT   uiIndex = 0;
    size_t size = 0;

    // Any thread will do at this point
    size = diagnostics_threads.size();
    for (uiIndex = 0; uiIndex < size; uiIndex++) {
        if (diagnostics_threads[uiIndex]->crash_exception_record) {
            uiReturn =
                diagnostics_threads[uiIndex]->crash_exception_record->ExceptionRecord->ExceptionCode;
        }
    }

    return uiReturn;
}


UINT WINAPI diagnostics_unhandled_exception_monitor(LPVOID /* lpParameter */) {
    DWORD        dwEvent = (DWORD)NULL;
    BOOL         bContinue = TRUE;
    BOOL         bDebuggerInitialized = FALSE;
    HANDLE       hEvents[2];
    unsigned int i;
    CONTEXT      c;
    BOINC_WINDOWCAPTURE window_info;
    BOINC_THREADLISTENTRY *pThreadEntry = NULL;
    const size_t boinc_install_dir_len = strlen(diagnostics_get_boinc_install_dir());

    // We should not suspend our crash dump thread.
    diagnostics_set_thread_exempt_suspend();

    // Acquire the mutex that will keep all the threads that throw an exception
    //   at bay until we are ready to deal with them.
    WaitForSingleObject(hExceptionMonitorHalt, INFINITE);

    // Which events do we want to wait for?
    hEvents[0] = hExceptionQuitEvent;
    hEvents[1] = hExceptionDetectedEvent;

    // Notify the initialization thread that initialization is complete and now
    //  we are waiting for an exception event.
    SetEvent(hExceptionMonitorStartedEvent);

    while (bContinue) {
        dwEvent = WaitForMultipleObjects(
            2,           // number of objects in array
            hEvents,     // array of objects
            FALSE,       // wait for any
            INFINITE     // wait
        );
        switch(dwEvent) {
            // hExceptionQuitEvent was signaled.
            case WAIT_OBJECT_0 + 0:

                // We are shutting down, so let's clean up and exit.
                bContinue = false;

                break;

            // hExceptionDetectedEvent was signaled.
            case WAIT_OBJECT_0 + 1:
#ifdef _DEBUG
                if (diagnostics_is_flag_set(BOINC_DIAG_MEMORYLEAKCHECKENABLED)) {
                    CLEAR_CRT_DEBUG_FIELD(_CRTDBG_LEAK_CHECK_DF);
                }
                if (diagnostics_is_flag_set(BOINC_DIAG_HEAPCHECKENABLED)) {
                    CLEAR_CRT_DEBUG_FIELD(_CRTDBG_CHECK_ALWAYS_DF);
                    CLEAR_CRT_DEBUG_FIELD(_CRTDBG_CHECK_EVERY_1024_DF);
                }
#endif // _DEBUG


                // Enumerate through all the threads so we have a complete list of what we need to dump.
                diagnostics_update_thread_list();

                // Get any data that will be needed later but will cause a deadlock if called after
                //   the other threads are suspended.
                diagnostics_capture_foreground_window(&window_info);

                // Wait for the ThreadListSync mutex before writing updates
                WaitForSingleObject(hThreadListSync, INFINITE);

                // Dump some basic stuff about runtime debugger version and date
                diagnostics_unhandled_exception_dump_banner();

#if !defined(__CYGWIN__) && !defined(_M_ARM) && !defined(_M_ARM64)
                // Kickstart the debugger extensions, look for the debugger files
                //   in the install directory if it is defined, otherwise look
                //   in the data directory.
                if (0 != boinc_install_dir_len) {
 	                bDebuggerInitialized = !DebuggerInitialize(
                        diagnostics_get_boinc_install_dir(),
                        diagnostics_get_symstore(),
                        diagnostics_is_proxy_enabled(),
                        diagnostics_get_proxy()
                    );
                } else {
 	                bDebuggerInitialized = !DebuggerInitialize(
                        diagnostics_get_boinc_dir(),
                        diagnostics_get_symstore(),
                        diagnostics_is_proxy_enabled(),
                        diagnostics_get_proxy()
                    );
                }

                // Dump any useful information
                if (bDebuggerInitialized) DebuggerDisplayDiagnostics();
#endif
                // Dump the process statistics
                diagnostics_dump_process_information();

                // Dump the other threads stack.
                for (i=0; i<diagnostics_threads.size(); i++) {
                    pThreadEntry = diagnostics_threads[i];
                    if (pThreadEntry->thread_id && !pThreadEntry->crash_suspend_exempt) {

                        diagnostics_dump_thread_information(pThreadEntry);

                        // Dump the exception record
                        if (pThreadEntry->crash_exception_record) {
                            diagnostics_dump_exception_record(
                                pThreadEntry->crash_exception_record
                            );
                        }

                        if (diagnostics_is_flag_set(BOINC_DIAG_DUMPCALLSTACKENABLED)) {
#if !defined(__CYGWIN__) && !defined(_M_ARM) && !defined(_M_ARM64)
                            if (bDebuggerInitialized) {
                                if (pThreadEntry->crash_exception_record ) {
                                    StackwalkFilter(
                                        pThreadEntry->crash_exception_record,
                                        EXCEPTION_EXECUTE_HANDLER
                                    );
                                } else {
                                    // Suspend thread before extracting the contexts,
                                    //   otherwise it'll be trash.
                                    SuspendThread(pThreadEntry->thread_handle);

                                    // Get the thread context
                                    memset(&c, 0, sizeof(CONTEXT));
                                    c.ContextFlags = CONTEXT_FULL;
				                    GetThreadContext(pThreadEntry->thread_handle, &c);

                                    StackwalkThread(
                                        pThreadEntry->thread_handle,
                                        &c
                                    );
                                }
                            }
#else
                            fprintf(stderr, "Warning: Callstack dumps are not supported on CYGWIN\n");
#endif
                        }
                        fprintf(stderr, "\n");
                    }
                }

                diagnostics_message_monitor_dump();

                diagnostics_foreground_window_dump(&window_info);

                fprintf(stderr, "Exiting...\n");

                // Release the Mutex
                ReleaseMutex(hThreadListSync);

                // Force terminate the app letting BOINC know an exception has occurred.
                if (diagnostics_is_aborted_via_gui()) {
                    TerminateProcess(GetCurrentProcess(), (UINT)ERR_ABORTED_VIA_GUI);
                } else {
                    TerminateProcess(GetCurrentProcess(), diagnostics_determine_exit_code());
                }

                break;
        }
    }

    // Notify the calling thread that the message monitoring thread is
    //   finished.
    SetEvent(hExceptionQuitFinishedEvent);
    return 0;
}


static int no_reset[SIGRTMAX+1];
static int no_ignore[SIGRTMAX+1];
static int setup_arrays=0;


void setup_no_reset() {
  no_reset[SIGILL]=1;
#ifdef SIGTRAP
  no_reset[SIGTRAP]=1;
#endif
#ifdef SIGPRIV
  no_reset[SIGPRIV]=1;
#endif
  no_reset[SIGINT]=1;
};


void setup_no_ignore() {
#ifdef SIGKILL
  no_ignore[SIGKILL]=1;
#endif
#ifdef SIGSTOP
  no_ignore[SIGSTOP]=1;
#endif
  no_ignore[SIGSEGV]=1;
};


LONG pass_to_signal_handler(int signum) {
    void (*handler)(int);

    if (!setup_arrays) {
        setup_arrays=1;
        setup_no_ignore();
        setup_no_reset();
    }

    // Are we using the default signal handler?
    // If so return to the exception handler.
    handler=signal(signum,SIG_DFL);
    if (handler==SIG_DFL) {
       return EXCEPTION_CONTINUE_SEARCH;
    }

    // Should we ignore this signal?
    if (handler==SIG_IGN) {
        signal(signum,handler);
        // Are we allowed to?
        if (!no_ignore[signum]) {
            // Yes? Attempt to ignore the exception.
            return EXCEPTION_CONTINUE_EXECUTION;
        } else {
            return EXCEPTION_CONTINUE_SEARCH;
        }
    }

    // Call our signal handler, this probably won't return...
    handler(signum);

    // if it does, reset the signal handler if appropriate.
    if (no_reset[signum]) signal(signum,handler);

    // try to continue execution
    return EXCEPTION_CONTINUE_EXECUTION;
}


// Allow apps to install signal handlers for some exceptions that bypass
// the boinc diagnostics.  This translates the Windows exceptions into
// standard signals.
LONG diagnostics_check_signal_handlers(PEXCEPTION_POINTERS pExPtrs) {
    switch (pExPtrs->ExceptionRecord->ExceptionCode) {
      case CONTROL_C_EXIT:
                                       return pass_to_signal_handler(SIGINT);
      case EXCEPTION_BREAKPOINT:
      case EXCEPTION_SINGLE_STEP:
#ifdef SIGTRAP
                                       return pass_to_signal_handler(SIGTRAP);
#else
                                       break;
#endif
      case EXCEPTION_FLT_DENORMAL_OPERAND:
      case EXCEPTION_FLT_DIVIDE_BY_ZERO:
      case EXCEPTION_FLT_INEXACT_RESULT:
      case EXCEPTION_FLT_INVALID_OPERATION:
      case EXCEPTION_FLT_OVERFLOW:
      case EXCEPTION_FLT_UNDERFLOW:
                                       {
                                         LONG rv=pass_to_signal_handler(SIGFPE);
                                         /* MS claims ignoring an FP signal
                                          * results in an unknown FP state.
                                          * Does an _fpreset() help?
                                          */
                                         if (rv != EXCEPTION_CONTINUE_SEARCH)
                                             _fpreset();
                                         return rv;
                                       }
      case EXCEPTION_INT_DIVIDE_BY_ZERO:
      case EXCEPTION_INT_OVERFLOW:
                                       return pass_to_signal_handler(SIGFPE);
      case EXCEPTION_PRIV_INSTRUCTION:
#ifdef SIGPRIV
                                       return pass_to_signal_handler(SIGPRIV);
                                       // nobreak
#endif
      case EXCEPTION_ILLEGAL_INSTRUCTION:
                                       return pass_to_signal_handler(SIGILL);
                                       // nobreak
      case EXCEPTION_DATATYPE_MISALIGNMENT:
#ifdef SIGBUS
                                       return pass_to_signal_handler(SIGBUS);
                                       // nobreak
#endif
      case EXCEPTION_STACK_OVERFLOW:
      case EXCEPTION_ACCESS_VIOLATION:
      case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
      case EXCEPTION_IN_PAGE_ERROR:
                                       return pass_to_signal_handler(SIGSEGV);
                                       // nobreak
      default:                         break;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}


// Let the unhandled exception monitor take care of logging the exception data.
//   Store the exception pointers and then singal the exception monitor to start
//   partying on the data.
LONG CALLBACK boinc_catch_signal(PEXCEPTION_POINTERS pExPtrs) {

    // Check whether somone has installed a standard C signal handler to
    // handle this exception.
    if (diagnostics_check_signal_handlers(pExPtrs) == EXCEPTION_CONTINUE_EXECUTION) {
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    fprintf( stderr, "\n\n");
    fprintf( stderr, "Unhandled Exception Detected...\n\n");

    // Dump what we know about...
    diagnostics_dump_exception_record(pExPtrs);

    if (hExceptionMonitorThread) {

        // Engage the BOINC Windows Runtime Debugger and dump as much diagnostic
        //   data as possible.
        //
        fprintf( stderr, "Engaging BOINC Windows Runtime Debugger...\n\n");

        // Store the exception record pointers.
        diagnostics_set_thread_exception_record(pExPtrs);

        // Wake the unhandled exception monitor up to process the exception.
        SetEvent(hExceptionDetectedEvent);

        // Go to sleep waiting for something this thread will never see.
        WaitForSingleObject(hExceptionMonitorHalt, INFINITE);

    } else {

        // This is a really bad place to be.  The unhandled exception monitor wasn't
        //   created, so we need to bail out as quickly as possible.
        //
        fprintf( stderr, "BOINC Windows Runtime Debugger not configured, terminating application...\n");

        // Enter the critical section in case multiple threads decide to try and blow
        //   chunks at the same time.  Let the OS decide who gets to determine what
        //   error code we return.
        EnterCriticalSection(&csExceptionMonitorFallback);

        TerminateProcess(GetCurrentProcess(), pExPtrs->ExceptionRecord->ExceptionCode);

        LeaveCriticalSection(&csExceptionMonitorFallback);
    }

    // We won't make it to this point, but make the compiler happy anyway.
    return EXCEPTION_CONTINUE_SEARCH;
}
