// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 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 . // Stuff related to catching SEH exceptions, monitoring threads, and trapping // debugger messages; used by both core client and by apps. #if defined(_WIN32) && !defined(__STDWX_H__) #include "boinc_win.h" #elif defined(_WIN32) && defined(__STDWX_H__) #include "stdwx.h" #endif #if defined(_MSC_VER) || defined(__MINGW32__) #define snprintf _snprintf #define strdate _strdate #define strtime _strtime #endif #ifndef __CYGWIN32__ #include "stackwalker_win.h" #endif #include "version.h" #include "diagnostics.h" #include "diagnostics_win.h" #include "error_numbers.h" #include "str_util.h" #include "util.h" // NtQuerySystemInformation typedef NTSTATUS (WINAPI *tNTQSI)( ULONG SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength ); // IsDebuggerPresent typedef BOOL (WINAPI *tIDP)(); // CreateToolhelp32Snapshot typedef HANDLE (WINAPI *tCT32S)(DWORD dwFlags, DWORD dwProcessID); // Thread32First typedef BOOL (WINAPI *tT32F)(HANDLE hSnapshot, LPTHREADENTRY32 lpte); // Thread32Next typedef BOOL (WINAPI *tT32N)(HANDLE hSnapshot, LPTHREADENTRY32 lpte); // OpenThread typedef HANDLE (WINAPI *tOT)(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId); // 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. typedef struct _BOINC_PROCESSENTRY { DWORD process_id; VM_COUNTERS vm_counters; IO_COUNTERS io_counters; } BOINC_PROCESSENTRY, *PBOINC_PROCESSENTRY; 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 nessassary // to dump backtraces for all threads during an abort or // crash. This is platform specific in nature since it // depends on the OS datatypes. typedef struct _BOINC_THREADLISTENTRY { DWORD thread_id; HANDLE thread_handle; BOOL crash_suspend_exempt; FLOAT crash_kernel_time; FLOAT crash_user_time; FLOAT 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]; } BOINC_THREADLISTENTRY, *PBOINC_THREADLISTENTRY; static std::vector diagnostics_threads; static HANDLE hThreadListSync; // Initialize the thread list entry. int diagnostics_init_thread_entry(PBOINC_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.0; entry->crash_priority = 0; entry->crash_base_priority = 0; entry->crash_state = 0; entry->crash_wait_reason = 0; entry->crash_exception_record = NULL; strncpy(entry->crash_message, "", sizeof(entry->crash_message)); 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 syncronize data access // to the global thread list. hThreadListSync = CreateMutex(NULL, TRUE, NULL); if (!hThreadListSync) { fprintf( stderr, "diagnostics_init_thread_list(): Creating hThreadListSync failed, GLE %d\n", GetLastError() ); retval = GetLastError(); } else { size = diagnostics_threads.size(); for (i=0; ithread_id) { pThread = diagnostics_threads[uiIndex]; } } } return pThread; } // Enumerate the running threads in the process space and add them to // the list. This is the most compatible implementation. int diagnostics_update_thread_list_9X() { HANDLE hThreadSnap = INVALID_HANDLE_VALUE; HANDLE hThread = NULL; HMODULE hKernel32Lib = NULL; PBOINC_THREADLISTENTRY pThreadEntry = NULL; tCT32S pCT32S = NULL; tT32F pT32F = NULL; tT32N pT32N = NULL; tOT pOT = NULL; THREADENTRY32 te32; // Which version of the data structure are we using. te32.dwSize = sizeof(te32); // Dynamically link to the proper function pointers. hKernel32Lib = GetModuleHandleA("kernel32.dll"); pCT32S = (tCT32S) GetProcAddress( hKernel32Lib, "CreateToolhelp32Snapshot" ); pT32F = (tT32F) GetProcAddress( hKernel32Lib, "Thread32First" ); pT32N = (tT32N) GetProcAddress( hKernel32Lib, "Thread32Next" ); pOT = (tOT) GetProcAddress( hKernel32Lib, "OpenThread" ); if (!pCT32S || !pT32F || !pT32N) { return ERROR_NOT_SUPPORTED; } // Take a snapshot of all running threads hThreadSnap = pCT32S(TH32CS_SNAPTHREAD, 0); if( hThreadSnap == INVALID_HANDLE_VALUE ) { return GetLastError(); } // Retrieve information about the first thread, // and exit if unsuccessful if( !pT32F( hThreadSnap, &te32 ) ) { CloseHandle( hThreadSnap ); return GetLastError(); } // Wait for the ThreadListSync mutex before writing updates WaitForSingleObject(hThreadListSync, INFINITE); // Now walk the thread list of the system, // and display information about each thread // associated with the specified process do { if( te32.th32OwnerProcessID == GetCurrentProcessId() ) { pThreadEntry = diagnostics_find_thread_entry(te32.th32ThreadID); if (!pThreadEntry) { pThreadEntry = new BOINC_THREADLISTENTRY; diagnostics_init_thread_entry(pThreadEntry); pThreadEntry->thread_id = te32.th32ThreadID; if (pOT) { hThread = pOT( THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID ); pThreadEntry->thread_handle = hThread; } diagnostics_threads.push_back(pThreadEntry); } } } while( pT32N(hThreadSnap, &te32 ) ); // Release the Mutex ReleaseMutex(hThreadListSync); CloseHandle(hThreadSnap); return 0; } // 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) { retval = ERROR_NOT_ENOUGH_MEMORY; } Status = pNTQSI( SystemProcessAndThreadInformation, *ppBuffer, *pcbBuffer, pcbBuffer ); if (Status == STATUS_INFO_LENGTH_MISMATCH) { HeapFree(hHeap, (DWORD)NULL, *ppBuffer); *pcbBuffer *= 2; } else if (!NT_SUCCESS(Status)) { HeapFree(hHeap, (DWORD)NULL, *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 NT 4.0 based machines. This also // includes additional information which can be logged during a crash // event. int diagnostics_update_thread_list_NT() { DWORD dwCurrentProcessId = GetCurrentProcessId(); HANDLE hThread = NULL; PBOINC_THREADLISTENTRY pThreadEntry = NULL; ULONG cbBuffer = 32*1024; // 32k initial buffer PVOID pBuffer = NULL; PSYSTEM_PROCESSES_NT4 pProcesses = NULL; PSYSTEM_THREADS pThread = NULL; UINT uiSystemIndex = 0; HMODULE hKernel32Lib; tOT pOT = NULL; // Dynamically link to the proper function pointers. hKernel32Lib = GetModuleHandleA("kernel32.dll"); pOT = (tOT) GetProcAddress( hKernel32Lib, "OpenThread" ); // 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_NT4)pBuffer; do { // Okay, found the current procceses 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; // Enumerate the threads for(uiSystemIndex = 0; uiSystemIndex < pProcesses->ThreadCount; uiSystemIndex++) { pThread = &pProcesses->Threads[uiSystemIndex]; pThreadEntry = diagnostics_find_thread_entry(pThread->ClientId.UniqueThread); if (pThreadEntry) { pThreadEntry->crash_kernel_time = (FLOAT)pThread->KernelTime.QuadPart; pThreadEntry->crash_user_time = (FLOAT)pThread->UserTime.QuadPart; pThreadEntry->crash_wait_time = (FLOAT)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 { if (pOT) { hThread = pOT( THREAD_ALL_ACCESS, FALSE, pThread->ClientId.UniqueThread ); } pThreadEntry = new BOINC_THREADLISTENTRY; diagnostics_init_thread_entry(pThreadEntry); pThreadEntry->thread_id = pThread->ClientId.UniqueThread; pThreadEntry->thread_handle = hThread; pThreadEntry->crash_kernel_time = (FLOAT)pThread->KernelTime.QuadPart; pThreadEntry->crash_user_time = (FLOAT)pThread->UserTime.QuadPart; pThreadEntry->crash_wait_time = (FLOAT)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_NT4)(((LPBYTE)pProcesses) + pProcesses->NextEntryDelta); } while (pProcesses); // Release resources if (hThreadListSync) ReleaseMutex(hThreadListSync); if (pBuffer) HeapFree(GetProcessHeap(), (DWORD)NULL, pBuffer); return 0; } // 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_XP() { DWORD dwCurrentProcessId = GetCurrentProcessId(); HANDLE hThread = NULL; PBOINC_THREADLISTENTRY pThreadEntry = NULL; ULONG cbBuffer = 32*1024; // 32k initial buffer PVOID pBuffer = NULL; PSYSTEM_PROCESSES pProcesses = NULL; PSYSTEM_THREADS pThread = NULL; UINT uiSystemIndex = 0; HMODULE hKernel32Lib; tOT pOT = NULL; // Dynamically link to the proper function pointers. hKernel32Lib = GetModuleHandleA("kernel32.dll"); pOT = (tOT) GetProcAddress( hKernel32Lib, "OpenThread" ); // 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 procceses 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(pThread->ClientId.UniqueThread); if (pThreadEntry) { pThreadEntry->crash_kernel_time = (FLOAT)pThread->KernelTime.QuadPart; pThreadEntry->crash_user_time = (FLOAT)pThread->UserTime.QuadPart; pThreadEntry->crash_wait_time = (FLOAT)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 { if (pOT) { hThread = pOT( THREAD_ALL_ACCESS, FALSE, pThread->ClientId.UniqueThread ); } pThreadEntry = new BOINC_THREADLISTENTRY; diagnostics_init_thread_entry(pThreadEntry); pThreadEntry->thread_id = pThread->ClientId.UniqueThread; pThreadEntry->thread_handle = hThread; pThreadEntry->crash_kernel_time = (FLOAT)pThread->KernelTime.QuadPart; pThreadEntry->crash_user_time = (FLOAT)pThread->UserTime.QuadPart; pThreadEntry->crash_wait_time = (FLOAT)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(), (DWORD)NULL, pBuffer); return 0; } // Determine which update thread list function to call based on OS // version. int diagnostics_update_thread_list() { int retval = 0; // Detect platform information OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx(&osvi); switch(osvi.dwPlatformId) { case VER_PLATFORM_WIN32_WINDOWS: // Win95, Win98, WinME retval = diagnostics_update_thread_list_9X(); break; case VER_PLATFORM_WIN32_NT: switch(osvi.dwMajorVersion) { case 4: // WinNT 4.0 retval = diagnostics_update_thread_list_NT(); break; case 5: // Win2k, WinXP, Win2k3 retval = diagnostics_update_thread_list_XP(); break; case 6: if (osvi.dwMinorVersion == 0) { // WinVista retval = diagnostics_update_thread_list_XP(); } else { // In cases where we do not know if the interfaces have // changed from the ones we know about, just default to // the most compatible implementation. retval = diagnostics_update_thread_list_9X(); } break; default: // In cases where we do not know if the interfaces have // changed from the ones we know about, just default to // the most compatible implementation. retval = diagnostics_update_thread_list_9X(); break; } break; } return retval; } // 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; PBOINC_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; PBOINC_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; PBOINC_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(char* message) { HANDLE hThread; PBOINC_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 // char* diagnostics_format_thread_state(int thread_state) { switch(thread_state) { case ThreadStateInitialized: return "Initialized"; case ThreadStateReady: return "Ready"; case ThreadStateRunning: return "Running"; case ThreadStateStandby: return "Standby"; case ThreadStateTerminated: return "Terminated"; case ThreadStateWaiting: return "Waiting"; case ThreadStateTransition: return "Transition"; default: return "Unknown"; } return ""; } // Translate the thread wait reason into a human readable form. // // See: http://support.microsoft.com/?kbid=837372 // 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"; default: return "Unknown"; } return ""; } // 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 // 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"; default: return "Unknown"; } return ""; } // 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 // 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"; default: return "Unknown"; } return ""; } // Provide a mechinism 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 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; HMODULE hKernel32Lib; tIDP pIDP = NULL; 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 syncronize data access // to the global thread list. hMessageMonitorSync = CreateMutex(NULL, TRUE, NULL); if (!hMessageMonitorSync) { fprintf( stderr, "diagnostics_init_message_monitor(): Creating hMessageMonitorSync failed, GLE %d\n", GetLastError() ); } // Clear out any previous messages. for (i=0; itimestamp), 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; } // 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 seperate exceptions thrown from different // threads. // // This structure is used to keep track of stuff nessassary // 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 %d\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 %d\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 aquire the halt mutex. hExceptionDetectedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!hExceptionDetectedEvent) { fprintf( stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionDetectedEvent failed, GLE %d\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 %d\n", GetLastError() ); } hExceptionQuitFinishedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!hExceptionQuitFinishedEvent) { fprintf( stderr, "diagnostics_init_unhandled_exception_monitor(): Creating hExceptionQuitFinishedEvent failed, GLE %d\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); } 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 aquire 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. strcpy(window_info->window_name, ""); 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: %x\n" " Window Thread ID : %x\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: %d, Write: %d, Other %d\n" "\n" "- I/O Transfers Counters -\n" "Read: %d, Write: %d, Other %d\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: %d, QuotaPeakPagedPoolUsage: %d\n" "QuotaNonPagedPoolUsage: %d, QuotaPeakNonPagedPoolUsage: %d\n" "\n" "- Virtual Memory Usage -\n" "VirtualSize: %d, PeakVirtualSize: %d\n" "\n" "- Pagefile Usage -\n" "PagefileUsage: %d, PeakPagefileUsage: %d\n" "\n" "- Working Set Size -\n" "WorkingSetSize: %d, PeakWorkingSetSize: %d, PageFaultCount: %d\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(PBOINC_THREADLISTENTRY pThreadEntry) { std::string strStatusExtra; if (pThreadEntry->crash_state == ThreadStateWaiting) { strStatusExtra += "Wait Reason: "; strStatusExtra += diagnostics_format_thread_wait_reason(pThreadEntry->crash_wait_reason); strStatusExtra += ", "; } else { strStatusExtra += "Base Priority: "; strStatusExtra += diagnostics_format_thread_priority(pThreadEntry->crash_base_priority); strStatusExtra += ", "; strStatusExtra += "Priority: "; strStatusExtra += diagnostics_format_thread_priority(pThreadEntry->crash_priority); strStatusExtra += ", "; } fprintf( stderr, "*** Dump of thread ID %d (state: %s): ***\n\n" "- Information -\n" "Status: %s, " "Kernel Time: %f, " "User Time: %f, " "Wait Time: %f\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(char* exception_desc, DWORD exception_code, PVOID exception_address) { fprintf( stderr, "Reason: %s (0x%x) 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; PDelayLoadInfo delay_load_info = NULL; // Print unhandled exception banner fprintf(stderr, "- Unhandled Exception Record -\n"); switch (exception_code) { 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; 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("Out Of Memory (C++ Exception)", exception_code, exception_address); break; case EXCEPTION_ACCESS_VIOLATION: strcpy(status, "Access Violation"); if (pExPtrs->ExceptionRecord->NumberParameters == 2) { switch(pExPtrs->ExceptionRecord->ExceptionInformation[0]) { case 0: // read attempt sprintf(substatus, "read attempt to address 0x%8.8X", pExPtrs->ExceptionRecord->ExceptionInformation[1]); break; case 1: // write attempt sprintf(substatus, "write attempt to address 0x%8.8X", pExPtrs->ExceptionRecord->ExceptionInformation[1]); break; } } fprintf(stderr, "Reason: %s (0x%x) 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; PBOINC_THREADLISTENTRY pThreadEntry = NULL; // We should not suspend our crash dump thread. diagnostics_set_thread_exempt_suspend(); // Aquire 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 lets cleanup 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(); #ifndef __CYGWIN__ // 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 != strlen(diagnostics_get_boinc_install_dir())) { 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; ithread_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)) { #ifndef __CYGWIN__ 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; } // Starting with Visual Studio 2005 the C Runtime Library has really started to // enforce parameter validation. Problem is that the parameter validation code // uses its own structured exception handler and terminates without writing // any useful output to stderr. Microsoft has created a hook an application // developer can use to get more debugging information which is the purpose // of this function. When an invalid parameter is passed to the C Runtime // library this function will write whatever trace information it can and // then throw a breakpoint exception to dump all the rest of the useful // information. void boinc_catch_signal_invalid_parameter( const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t /* pReserved */ ) { fprintf( stderr, "ERROR: Invalid parameter detected in function %s. File: %s Line: %d\n", function, file, line ); fprintf( stderr, "ERROR: Expression: %s\n", expression ); // Cause a Debug Breakpoint. DebugBreak(); }