mirror of https://github.com/BOINC/boinc.git
1894 lines
67 KiB
C++
1894 lines
67 KiB
C++
// 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;
|
|
}
|