diff options
author | dotnet-bot <dotnet-bot@microsoft.com> | 2015-01-30 14:14:42 -0800 |
---|---|---|
committer | dotnet-bot <dotnet-bot@microsoft.com> | 2015-01-30 14:14:42 -0800 |
commit | ef1e2ab328087c61a6878c1e84f4fc5d710aebce (patch) | |
tree | dee1bbb89e9d722e16b0d1485e3cdd1b6c8e2cfa /src/vm/dwreport.cpp | |
download | coreclr-ef1e2ab328087c61a6878c1e84f4fc5d710aebce.tar.gz coreclr-ef1e2ab328087c61a6878c1e84f4fc5d710aebce.tar.bz2 coreclr-ef1e2ab328087c61a6878c1e84f4fc5d710aebce.zip |
Initial commit to populate CoreCLR repo
[tfs-changeset: 1407945]
Diffstat (limited to 'src/vm/dwreport.cpp')
-rw-r--r-- | src/vm/dwreport.cpp | 3285 |
1 files changed, 3285 insertions, 0 deletions
diff --git a/src/vm/dwreport.cpp b/src/vm/dwreport.cpp new file mode 100644 index 0000000000..210e08240c --- /dev/null +++ b/src/vm/dwreport.cpp @@ -0,0 +1,3285 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// +// FILE: dwreport.cpp +// + +// + +// +// ============================================================================ + +#include "common.h" + +#include "dwreport.h" +#include "dwbucketmanager.hpp" +#include <cordbpriv.h> +#include "field.h" +#include <msodwwrap.h> +#include <shlobj.h> +#include "dbginterface.h" +#include <sha1.h> +#include <winver.h> +#include "dlwrap.h" +#include "eemessagebox.h" +#include "eventreporter.h" +#include "utilcode.h" +#include "../dlls/mscorrc/resource.h" // for resource ids + +#include "imagehlp.h" + +#ifdef FEATURE_UEF_CHAINMANAGER +// This is required to register our UEF callback with the UEF chain manager +#include <mscoruefwrapper.h> +#endif // FEATURE_UEF_CHAINMANAGER + +EFaultRepRetVal DoReportFault(EXCEPTION_POINTERS * pExceptionInfo); + +// Should the CLR use Watson to report fatal errors and unhandled exceptions? +static BOOL g_watsonErrorReportingEnabled = FALSE; + +// Variables to control launching Watson only once, but making all threads wait for that single launch to finish. +LONG g_watsonAlreadyLaunched = 0; // Used to note that another thread has done Watson. + +#if !defined(FEATURE_UEF_CHAINMANAGER) +HandleHolder g_hWatsonCompletionEvent = NULL; // Used to signal that Watson has finished. +#endif // FEATURE_UEF_CHAINMANAGER + +const WCHAR kErrorReportingPoliciesKey[] = W("SOFTWARE\\Policies\\Microsoft\\PCHealth\\ErrorReporting"); +const WCHAR kErrorReportingKey[] = W("SOFTWARE\\Microsoft\\PCHealth\\ErrorReporting"); + +const WCHAR kShowUIValue[] = W("ShowUI"); +const WCHAR kForceQueueModeValue[] = W("ForceQueueMode"); +const WCHAR kDoReportValue[] = W("DoReport"); +const WCHAR kAllOrNoneValue[] = W("AllOrNone"); +const WCHAR kIncludeMSAppsValue[] = W("IncludeMicrosoftApps"); +const WCHAR kIncludeWindowsAppsValue[] = W("IncludeWindowsApps"); +const WCHAR kExclusionListKey[] = W("SOFTWARE\\Microsoft\\PCHealth\\ErrorReporting\\ExclusionList"); +const WCHAR kInclusionListKey[] = W("SOFTWARE\\Microsoft\\PCHealth\\ErrorReporting\\InclusionList"); +const WCHAR kExclusionListSubKey[] = W("\\ExclusionList"); +const WCHAR kInclusionListSubKey[] = W("\\InclusionList"); + + +// Default values for various registry keys +const DWORD kDefaultShowUIValue = 1; +const DWORD kDefaultForceQueueModeValue = 0; +const DWORD kDefaultDoReportValue = 1; +const DWORD kDefaultAllOrNoneValue = 1; +const DWORD kDefaultExclusionValue = 0; +const DWORD kDefaultInclusionValue = 0; +const DWORD kDefaultIncludeMSAppsValue = 1; +const DWORD kDefaultIncludeWindowsAppsValue = 1; + +// Default value for the default debugger and auto debugger attach settings. +const BOOL kDefaultDebuggerIsWatson = FALSE; +const BOOL kDefaultAutoValue = FALSE; + +// When debugging the watson process itself, the faulting process will spin +// waiting for Watson to signal various events. If these waits time out, the +// faulting process will go ahead and exit, which is sub-optimal if you need to +// inspect the faulting process with the debugger at the same time. In debug +// builds, use a longer wait time, since watson may be stopped under the +// debugger for a while. + +#ifdef _DEBUG +const DWORD kDwWaitTime = DW_TIMEOUT_VALUE * 1000; +#else +const DWORD kDwWaitTime = DW_TIMEOUT_VALUE; +#endif + +#ifdef _TARGET_X86_ + const DWORD kWatsonRegKeyOptions = 0; +#else + const DWORD kWatsonRegKeyOptions = KEY_WOW64_32KEY; +#endif + +const WCHAR kWatsonPath[] = WATSON_INSTALLED_REG_SUBPATH; +#if defined(_TARGET_X86_) +const WCHAR kWatsonValue[] = WATSON_INSTALLED_REG_VAL; +#else +const WCHAR kWatsonValue[] = WATSON_INSTALLED_REG_VAL_IA64; +#endif +const WCHAR* kWatsonImageNameOnLonghorn = W("\\dw20.exe"); + +typedef HMODULE (*AcquireLibraryHandleFn)(LPCWSTR); + +template <AcquireLibraryHandleFn AcquireLibraryHandleFnPtr, bool RequiresFree> +class SimpleModuleHolder +{ +private: + HMODULE hModule; + +public: + SimpleModuleHolder(LPCWSTR moduleName) + { + hModule = AcquireLibraryHandleFnPtr(moduleName); + } + + ~SimpleModuleHolder() + { + if (RequiresFree && hModule) + { + CLRFreeLibrary(hModule); + } + } + + operator HMODULE() { return hModule; } +}; + +#ifndef FEATURE_CORESYSTEM +#define WER_MODULE_NAME_W WINDOWS_KERNEL32_DLLNAME_W +typedef SimpleModuleHolder<WszGetModuleHandle, false> WerModuleHolder; +#else +#define WER_MODULE_NAME_W W("api-ms-win-core-windowserrorreporting-l1-1-0.dll") +typedef SimpleModuleHolder<CLRLoadLibrary, true> WerModuleHolder; +#endif + +//------------------------------------------------------------------------------ +// Description +// Indicate if Watson is enabled +// +// Parameters +// None +// +// Returns +// TRUE -- Yes, Watson is enabled. +// FALSE -- No, it's not. +//------------------------------------------------------------------------------ +BOOL IsWatsonEnabled() +{ + LIMITED_METHOD_CONTRACT; + return g_watsonErrorReportingEnabled; +} + +//------------------------------------------------------------------------------ +// Description +// Initializes watson global critsec and event. Records whether run via +// managed .exe. +// +// Parameters +// fFlags -- the COINITIEE flags used to start the runtime. +// +// Returns +// TRUE -- always +//------------------------------------------------------------------------------ +BOOL InitializeWatson(COINITIEE fFlags) +{ + LIMITED_METHOD_CONTRACT; + + // Watson is enabled for all SKUs + g_watsonErrorReportingEnabled = TRUE; + + LOG((LF_EH, LL_INFO10, "InitializeWatson: %s\n", g_watsonErrorReportingEnabled ? "enabled" : "disabled")); + + if (!IsWatsonEnabled()) + { + return TRUE; + } + +#if defined(FEATURE_UEF_CHAINMANAGER) + return TRUE; +#else + // Create the event that all-but-the-first threads will wait on (the first thread + // will set the event when Watson is done.) + g_hWatsonCompletionEvent = WszCreateEvent(NULL, TRUE /*manual reset*/, FALSE /*initial state*/, NULL); + return (g_hWatsonCompletionEvent != NULL); +#endif // FEATURE_UEF_CHAINMANAGER + +} // BOOL InitializeWatson() + +//------------------------------------------------------------------------------ +// Description +// Register out-of-process Watson callbacks provided in DAC dll for WIN7 or later +// +// Parameters +// None +// +// Returns +// None +// +// Note: In Windows 7, the OS will take over the job of error reporting, and so most +// of our watson code should not be used. In such cases, we will however still need +// to provide some services to windows error reporting, such as computing bucket +// parameters for a managed unhandled exception. +//------------------------------------------------------------------------------ +BOOL RegisterOutOfProcessWatsonCallbacks() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(RunningOnWin7()); + } + CONTRACTL_END; + + WCHAR wszDACName[] = MAIN_DAC_MODULE_NAME_W W(".dll"); + WCHAR wszDACPath[MAX_PATH]; + DWORD dwSize = 0; + + if ((FAILED(::GetCORSystemDirectoryInternal(wszDACPath, NumItems(wszDACPath), &dwSize))) || + (wcscat_s(wszDACPath, _countof(wszDACPath), wszDACName) != 0)) + { + return FALSE; + } + + WerModuleHolder hWerModule(WER_MODULE_NAME_W); + +#ifdef FEATURE_CORESYSTEM + if ((hWerModule == NULL) && !RunningOnWin8()) + { + // If we are built for CoreSystemServer, but are running on Windows 7, we need to look elsewhere + hWerModule = WerModuleHolder(W("Kernel32.dll")); + } +#endif + + if (hWerModule == NULL) + { + _ASSERTE(!"failed to get WER module handle"); + return FALSE; + } + + typedef HRESULT (WINAPI * WerRegisterRuntimeExceptionModuleFnPtr)(PCWSTR, PDWORD); + WerRegisterRuntimeExceptionModuleFnPtr pFnWerRegisterRuntimeExceptionModule; + + pFnWerRegisterRuntimeExceptionModule = (WerRegisterRuntimeExceptionModuleFnPtr) + GetProcAddress(hWerModule, "WerRegisterRuntimeExceptionModule"); + + _ASSERTE(pFnWerRegisterRuntimeExceptionModule != NULL); + if (pFnWerRegisterRuntimeExceptionModule == NULL) + { + return FALSE; + } + + HRESULT hr = (*pFnWerRegisterRuntimeExceptionModule)(wszDACPath, (PDWORD)g_pMSCorEE); + if (FAILED(hr)) + { + STRESS_LOG0(LF_STARTUP, + LL_ERROR, + "WATSON support: failed to register DAC dll with WerRegisterRuntimeExceptionModule"); + +#ifdef FEATURE_CORESYSTEM + // For CoreSys we *could* be running on a platform that doesn't have Watson proper + // (the APIs might exist but they just fail). + // WerRegisterRuntimeExceptionModule may return E_NOIMPL. + return TRUE; +#else // FEATURE_CORESYSTEM + _ASSERTE(! "WATSON support: failed to register DAC dll with WerRegisterRuntimeExceptionModule"); + return FALSE; +#endif // FEATURE_CORESYSTEM + } + + STRESS_LOG0(LF_STARTUP, + LL_INFO100, + "WATSON support: registered DAC dll with WerRegisterRuntimeExceptionModule"); + return TRUE; +} + +//------------------------------------------------------------------------------ +// CreateWatsonSharedMemory +// +// Description +// +// Creates a shared memory block for communication with Watson +// +// Parameters +// hWatsonSharedMemory -- [out] The handle to the watson shared memory. +// ppWatsonSharedMemory -- [out] A pointer to the Watson shared memory. +// Returns +// S_OK -- if the function complete normally. +// FALSE -- otherwise +// Exceptions +// None +//------------------------------------------------------------------------------ +HRESULT CreateWatsonSharedMemory(HANDLE* hWatsonSharedMemory, + DWSharedMem** ppWatsonSharedMemory); + +//------------------------------------------------------------------------------ +// Description +// Alerts the host that the thread is leaving the runtime, and sleeps +// waiting for an object to be signalled +// +// Parameters +// handle -- the handle to wait on +// timeout -- the length of time to wait +// +// Returns +// DWORD -- The return value from WaitForSingleObject +// +// Exceptions +// None +// +// Notes +// winwrap.h prevents us from using SetEvent by including +// #define SetEvent Dont_Use_SetEvent +// This is because using SetEvent within the runtime will result in poor +// interaction with any sort of host process (e.g. SQL). We can use the +// SetEvent/WaitForSingleObject primitives as long as we do some other work to +// make sure the host understands. +//------------------------------------------------------------------------------ +#undef SetEvent +DWORD ClrWaitForSingleObject(HANDLE handle, DWORD timeout) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + MODE_ANY; + } + CONTRACTL_END; + + CONTRACT_VIOLATION(ThrowsViolation); + + LeaveRuntimeHolder holder(reinterpret_cast< size_t >(WaitForSingleObject)); + return WaitForSingleObject(handle, timeout); +} // DWORD ClrWaitForSingleObject() + +//------------------------------------------------------------------------------ +// Helper class to set an event in destructor -- allows setting an event on the +// way out of a function. +// +// Used to synchronize multiple threads with unhandled exceptions -- only the +// first will run Watson, and all the rest will wait on the first one to be +// done. +//------------------------------------------------------------------------------ +class SettingEventHolder +{ +public: + SettingEventHolder(HANDLE &event) : m_event(event), m_bSetIt(FALSE) { LIMITED_METHOD_CONTRACT; } + ~SettingEventHolder() { LIMITED_METHOD_CONTRACT; if (m_bSetIt && m_event) SetEvent(m_event); } + void EnableSetting() { LIMITED_METHOD_CONTRACT; m_bSetIt = TRUE; } + DWORD DoWait(DWORD timeout=INFINITE_TIMEOUT) { WRAPPER_NO_CONTRACT; return m_event ? ClrWaitForSingleObject(m_event, timeout) : 0; } + +private: + HANDLE m_event; // The event to set + BOOL m_bSetIt; // If true, set event in destructor. +}; + +HRESULT DwGetFileVersionInfo( + __in_z LPCWSTR wszFilePath, + USHORT& major, + USHORT& minor, + USHORT& build, + USHORT& revision) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + major = minor = build = revision = 0; + ULARGE_INTEGER appVersion = { 0, 0 }; + + HRESULT result = GetFileVersion(wszFilePath, &appVersion); + if (SUCCEEDED(result)) + { + major = (appVersion.HighPart & 0xFFFF0000) >> 16; + minor = appVersion.HighPart & 0x0000FFFF; + build = (appVersion.LowPart & 0xFFFF0000) >> 16; + revision = appVersion.LowPart & 0x0000FFFF; + } + + return result; +} + +enum MicrosoftAppTypes +{ + MicrosoftAppTypesNone = 0, + MicrosoftAppTypesWindows = 0x1, + MicrosoftAppTypesOther = 0x2 +}; + +inline void SetMSFTApp(DWORD &AppType) { LIMITED_METHOD_CONTRACT; AppType |= MicrosoftAppTypesOther; } +inline void SetMSFTWindowsApp(DWORD &AppType) { LIMITED_METHOD_CONTRACT; AppType |= MicrosoftAppTypesWindows; } + +inline BOOL IsMSFTApp(DWORD AppType) { LIMITED_METHOD_CONTRACT; return (AppType & MicrosoftAppTypesOther) ? TRUE : FALSE; } +inline BOOL IsMSFTWindowsApp(DWORD AppType) { LIMITED_METHOD_CONTRACT; return (AppType & MicrosoftAppTypesWindows) ? TRUE : FALSE; } + + +//------------------------------------------------------------------------------ +// Description +// Determine if the application is a Microsoft application. +// +// Parameters +// wszFilePath Path to a file to exctract the information from +// pAppTypes [out] Put MicrosoftAppTypes here. +// +// Returns +// S_OK If the function succeede +// E_XXXX Failure result. +// +// Exceptions +// None +//------------------------------------------------------------------------------ +HRESULT DwCheckCompany( // S_OK or error. + __in_z LPWSTR wszFilePath, // Path to the executable. + DWORD* pAppTypes) // Non-microsoft, microsoft, microsoft windows. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // + // Note that this code is equivalent to FusionGetFileVersionInfo, found in fusion\asmcache\asmcache.cpp + // + + HRESULT hr = S_OK; // result of some operation + DWORD dwHandle = 0; + DWORD bufSize = 0; // Size of allocation for VersionInfo. + DWORD ret; + + // Avoid confusion + *pAppTypes = MicrosoftAppTypesNone; + + // Find the buffer size for the version info structure we need to create + EX_TRY + { + bufSize = GetFileVersionInfoSizeW(wszFilePath, &dwHandle); + if (!bufSize) + { + hr = HRESULT_FROM_GetLastErrorNA(); + } + } + EX_CATCH + { + hr = E_OUTOFMEMORY; + } + EX_END_CATCH(SwallowAllExceptions); + if (!bufSize) + { + return hr; + } + + // Allocate the buffer for the version info structure + // _alloca() can't return NULL -- raises STATUS_STACK_OVERFLOW. + BYTE* pVersionInfoBuffer = reinterpret_cast< BYTE* >(_alloca(bufSize)); + + // Extract the version information blob. The version information + // contains much more than the actual item of interest. + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = GetFileVersionInfoW(wszFilePath, dwHandle, bufSize, pVersionInfoBuffer); + + if (!ret) + { + return HRESULT_FROM_GetLastErrorNA(); + } + } + + // Extract the actual CompanyName and compare it to "Microsoft" and + // "MicrosoftWindows" + + // Get the language and codepage for the version info. + UINT size = 0; + struct + { + WORD language; + WORD codePage; + }* translation; + + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = VerQueryValueW(pVersionInfoBuffer, W("\\VarFileInfo\\Translation"), + reinterpret_cast< void **>(&translation), &size); + + if (!ret || size == 0) + { + return HRESULT_FROM_GetLastErrorNA(); + } + } + + // Build the query key for the language-specific company name resource. + WCHAR buf[64]; //----+----1----+----2----+----3----+----4 + _snwprintf_s(buf, NumItems(buf), _TRUNCATE, W("\\StringFileInfo\\%04x%04x\\CompanyName"), + translation->language, translation->codePage); + + // Get the company name. + WCHAR *name; + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = VerQueryValueW(pVersionInfoBuffer, buf, + reinterpret_cast< void** >(&name), &size); + } + + // If there is company name info, check it. + if (ret != 0 && size != 0 && wcsstr(name, W("Microsoft"))) + { + SetMSFTApp(*pAppTypes); + } + + + // Now build the query key for the language-specific product name resource. + _snwprintf_s(buf, NumItems(buf), _TRUNCATE, W("\\StringFileInfo\\%04x%04x\\ProductName"), + translation->language, translation->codePage); + + // Get the product name. + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = VerQueryValueW(pVersionInfoBuffer, buf, + reinterpret_cast< void** >(&name), &size); + } + + // If there is product name info, check it. + if (ret != 0 && size != 0 && wcsstr(name, W("Microsoft\x0ae Windows\x0ae"))) + { + SetMSFTWindowsApp(*pAppTypes); + } + + return S_OK; + +} // HRESULT DwCheckCompany() + + +//------------------------------------------------------------------------------ +// Description +// Read the description from the resource section. +// +// Parameters +// wszFilePath Path to a file from which to extract the description +// pBuf [out] Put description here. +// cchBuf [in] Size of buf, wide chars. +// +// Returns +// The number of characters stored. Zero if error or no description. +// +// Exceptions +// None +//------------------------------------------------------------------------------ +int DwGetAppDescription( // Number of characters written. + __in_z LPWSTR wszFilePath, // Path to the executable. + __inout_ecount(cchBuf) WCHAR *pBuf, // Put description here. + int cchBuf) // Size of buf, wide chars. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + DWORD dwHandle = 0; + DWORD bufSize = 0; // Size of allocation for VersionInfo. + DWORD ret; + + // Find the buffer size for the version info structure we need to create + EX_TRY + { + bufSize = GetFileVersionInfoSizeW(wszFilePath, &dwHandle); + } + EX_CATCH + { + bufSize = 0; + } + EX_END_CATCH(SwallowAllExceptions); + + if (!bufSize) + { + return 0; + } + + // Allocate the buffer for the version info structure + // _alloca() can't return NULL -- raises STATUS_STACK_OVERFLOW. + BYTE* pVersionInfoBuffer = reinterpret_cast< BYTE* >(_alloca(bufSize)); + + // Extract the version information blob. The version information + // contains much more than the actual item of interest. + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = GetFileVersionInfoW(wszFilePath, dwHandle, bufSize, pVersionInfoBuffer); + } + + if (!ret) + { + return 0; + } + + // Extract the description. + + // Get the language and codepage for the version info. + UINT size = 0; + struct + { + WORD language; + WORD codePage; + }* translation; + + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = VerQueryValueW(pVersionInfoBuffer, W("\\VarFileInfo\\Translation"), + reinterpret_cast< void **>(&translation), &size); + } + + if (!ret || size == 0) + { + return 0; + } + + // Build the query key for the language-specific file description resource. + WCHAR buf[64]; //----+----1----+----2----+----3----+----4----+ + _snwprintf_s(buf, NumItems(buf), _TRUNCATE, W("\\StringFileInfo\\%04x%04x\\FileDescription"), + translation->language, translation->codePage); + + // Get the file description. + WCHAR* fileDescription; + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = VerQueryValueW(pVersionInfoBuffer, buf, + reinterpret_cast< void** >(&fileDescription), &size); + } + + // If the call failed, or there is no file description, done. + if (!ret || size == 0) + { + return 0; + } + + // If the description is a single space, ignore it. + if (wcscmp(fileDescription, W(" ")) == 0) + { + return 0; + } + + // Copy back the description. + size = (int)size > cchBuf-1 ? cchBuf-1 : size; + wcsncpy_s(pBuf, cchBuf, fileDescription, size); + + return size; +} // int DwGetAppDescription() + +//------------------------------------------------------------------------------ +// Description +// Extract the assembly version from an executable. +// +// Parameters +// wszFilePath Path to a file to exctract the version information from +// pBuf [out] Put version here. +// cchBuf Size of pBuf, in wide chars. +// +// Returns +// Count of characters stored. +// +// Exceptions +// None +//------------------------------------------------------------------------------ +int DwGetAssemblyVersion( // Number of characters written. + __in_z LPWSTR wszFilePath, // Path to the executable. + __inout_ecount(cchBuf) WCHAR *pBuf, // Put description here. + int cchBuf) // Size of buf, wide chars. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + DWORD dwHandle = 0; + DWORD bufSize = 01; // Size of allocation for VersionInfo. + DWORD ret; + + // Find the buffer size for the version info structure we need to create + EX_TRY + { + bufSize = GetFileVersionInfoSizeW(wszFilePath, &dwHandle); + } + EX_CATCH + { + bufSize = 0; + } + EX_END_CATCH(SwallowAllExceptions); + + if (!bufSize) + { + return 0; + } + + // Allocate the buffer for the version info structure + // _alloca() can't return NULL -- raises STATUS_STACK_OVERFLOW. + BYTE* pVersionInfoBuffer = reinterpret_cast< BYTE* >(_alloca(bufSize)); + + // Extract the version information blob. The version information + // contains much more than the actual item of interest. + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of GetFileVersionInfoW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = GetFileVersionInfoW(wszFilePath, dwHandle, bufSize, pVersionInfoBuffer); + } + + if (!ret) + { + return 0; + } + + // Extract the description. + + // Get the language and codepage for the version info. + UINT size = 0; + struct + { + WORD language; + WORD codePage; + }* translation; + + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of VerQueryValueW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = VerQueryValueW(pVersionInfoBuffer, W("\\VarFileInfo\\Translation"), + reinterpret_cast< void **>(&translation), &size); + } + + if (ret == 0 || size == 0) + { + return 0; + } + + // Build the query key for the language-specific assembly version resource. + WCHAR buf[64]; //----+----1----+----2----+----3----+----4----+ + _snwprintf_s(buf, NumItems(buf), _TRUNCATE, W("\\StringFileInfo\\%04x%04x\\Assembly Version"), + translation->language, translation->codePage); + + // Get the assembly version. + WCHAR* assemblyVersion; + { + // If the previoud GetFileVersionInfoSizeW succeeds, version.dll has been loaded + // in the process, and delay load of VerQueryValueW will not throw. + CONTRACT_VIOLATION(ThrowsViolation); + ret = VerQueryValueW(pVersionInfoBuffer, buf, + reinterpret_cast< void** >(&assemblyVersion), &size); + } + + // If the call failed, or there is no assembly version, done. + if (ret == 0 || size == 0) + { + return 0; + } + + // If the assembly version is a single space, ignore it. + if (wcscmp(assemblyVersion, W(" ")) == 0) + { + return 0; + } + + // Copy back the assembly version. + size = (int)size > cchBuf-1 ? cchBuf-1 : size; + wcsncpy_s(pBuf, cchBuf, assemblyVersion, size); + + return size; +} // int DwGetAssemblyVersion() + + + +//------------------------------------------------------------------------------ +// CLRWatsonHelper class +// +// Certain registry keys affect the behavior of watson. In particulary, they +// control +// o whether or not a Watson report should result in UI popups +// o which debugger should be used to JIT attach to the faulting process +// o whether error reports should be sent at all. +// This class is a holder for static functions that access these registry keys +// to determine the proper settings. +// +//------------------------------------------------------------------------------ +class CLRWatsonHelper +{ +public: + enum WHDebugAction + { + WHDebug_InvalidValue, + WHDebug_AutoLaunch, + WHDebug_AskToLaunch, + WHDebug_DontLaunch + } m_debugAction; + + enum WHReportAction + { + WHReport_InvalidValue, + WHReport_AutoQueue, + WHReport_AskToSend, + WHReport_DontSend + } m_reportAction; + + enum WHDialogAction + { + WHDialog_InvalidValue, + WHDialog_OkToPopup, + WHDialog_DontPopup + } m_dialogAction; + + CLRWatsonHelper() + : m_debugAction(WHDebug_InvalidValue), + m_reportAction(WHReport_InvalidValue), + m_dialogAction(WHDialog_InvalidValue) + { LIMITED_METHOD_CONTRACT; } + + void Init(BOOL bIsManagedFault, TypeOfReportedError tore); + + // Does the current interactive USER have sufficient permissions to + // launch Watson or a debugger against this PROCESS? + BOOL CurrentUserHasSufficientPermissions(); + + // Should a debugger automatically, or should the user be queried for a debugger? + BOOL ShouldDebug(); + + // Should a managed debugger be launched, without even asking? + BOOL ShouldAutoAttach(); + + // Should Watson include a "Debug" button? + BOOL ShouldOfferDebug(); + + // Should a Watson report be generated? + BOOL ShouldReport(); + + // Should there be a popup? Possibly with only "quit"? + BOOL ShouldShowUI(); + + // If a Watson report is generated, should it be auto-queued? + // (vs asking the user what to do about it) + BOOL ShouldQueueReport(); + +private: + // Looks in HKCU/Software/Policies/Microsoft/PCHealth/ErrorReporting + // then in HKLM/ " " " " " + // then in HKCU/SOftware/Microsoft/PCHealth/ErrorReporting + // then in HKLM/ " " " " + static int GetPCHealthConfigLong( // Return value from registry or default. + LPCWSTR szName, // Name of value to get. + int iDefault); // Default value to return if not found. + + // Like above, but searches for a subkey with the given value. + static BOOL GetPCHealthConfigSubKeyLong(// Return value from registry or default. + LPCWSTR szSubKey, // Name of the subkey. + LPCWSTR szName, // Name of value to get. + int iDefault, // Default value to return if not found. + DWORD *pValue); // Put value here. + + void AssertValid() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_debugAction != WHDebug_InvalidValue); + _ASSERTE(m_reportAction != WHReport_InvalidValue); + _ASSERTE(m_dialogAction != WHDialog_InvalidValue); + } + +}; // class CLRWatsonHelper + +//------------------------------------------------------------------------------ +// Description +// Initialization for watson helper class. +// +// Parameters +// bIsManagedFault - true if EXCEPTION_COMPLUS or fault from jitted code. +// - false otherwise +// +// +// Notes: +// - Launches and Pops always happen to the same session in which the +// process is running. +// - This function computes what actions should happen, but doesn't do any. +// +// This routine returns which actions should be taken given the current registry +// settings and environment. It implements the following matrix: +// +// <<-- AutoLaunch -->> +// TRUE FALSE +// Interactive process A3 B2 +// Non-interactive process A3 C1 +// +// Action codes: +// A - Auto attach debugger +// B - Ask to attach debugger +// C - Don't attach debugger +// +// 1 - Auto Queue Watson report +// 2 - Ask to Send Watson report +// 3 - Don't send Watson report +// +// +// CLRWatsonHelper::Init +//------------------------------------------------------------------------------ +void CLRWatsonHelper::Init( + BOOL bIsManagedFault, // Is the fault in question from managed code? + TypeOfReportedError tore) // What sort of error is this? +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Initialize returned values + WHDebugAction tmpDebugAction = WHDebug_InvalidValue; + WHReportAction tmpReportAction = WHReport_InvalidValue; + WHDialogAction tmpDialogAction = WHDialog_InvalidValue; + + // First run the matrix, then later provide the over-rides + BOOL fRunningInteractive = RunningInteractive(); + + if (fRunningInteractive) + { + // Interactive services and interactive apps running as LocalSystem are considered non-interactive + // so that we don't display any UI for them. Note that we should check the process token (and not the + // thread token if the thread is impersonating a user) to determine if the app is running as LocalSystem. + // This is because Watson displays UI for us and Watson is run by calling CreateProcess. CreateProcess + // always creates child processes using the process token. + + BOOL fLocalSystemOrService; + if (RunningAsLocalSystemOrService(fLocalSystemOrService) != ERROR_SUCCESS) + { + // Err on the side of caution; treat the app as non-interactive + fRunningInteractive = FALSE; + } + else if (fLocalSystemOrService) + { + fRunningInteractive = FALSE; + } + } + + BOOL bAutoLaunch = FALSE; + SString ssDummy; + + GetDebuggerSettingInfo(ssDummy, &bAutoLaunch); + + if (bAutoLaunch) + { + tmpDebugAction = WHDebug_AutoLaunch; + tmpReportAction = WHReport_DontSend; + tmpDialogAction = WHDialog_DontPopup; + } + else + { + if (fRunningInteractive) + { + tmpDebugAction = WHDebug_AskToLaunch; + tmpReportAction = WHReport_AskToSend; + tmpDialogAction = WHDialog_OkToPopup; + } + else + { + // Non-interactive process + tmpDebugAction = WHDebug_DontLaunch; + tmpReportAction = WHReport_AutoQueue; + tmpDialogAction = WHDialog_DontPopup; + } + } + + // If this is a breakpoint, never send a report. + if (tore.IsBreakpoint()) + tmpReportAction = WHReport_DontSend; + + // Store off the results. + m_debugAction = tmpDebugAction; + m_reportAction = tmpReportAction; + m_dialogAction = tmpDialogAction; + + // Done. Log some stuff in debug mode. + #if defined(_DEBUG) + { + char *(rda[]) = {"InvalidValue", "AutoDebug", "AskToDebug", "DontDebug"}; + char *(rwa[]) = {"InvalidValue", "AutoQueue", "AskToSend", "DontSend"}; + char *(rdlga[]) = {"InvalidValue", "OkToPopup", "DontPopup"}; + LOG((LF_EH, LL_INFO100, "CLR Watson: debug action: %s\n", rda[m_debugAction])); + LOG((LF_EH, LL_INFO100, "CLR Watson: report action: %s\n", rwa[m_reportAction])); + LOG((LF_EH, LL_INFO100, "CLR Watson: dialog action: %s\n", rdlga[m_dialogAction])); + #define LB(expr) LOG((LF_EH, LL_INFO100, "CLR Watson: " #expr ": %s\n", ((expr) ? "true" : "false") )) + LB(CurrentUserHasSufficientPermissions()); + LB(ShouldDebug()); + LB(ShouldAutoAttach()); + LB(ShouldOfferDebug()); + LB(ShouldReport()); + LB(ShouldQueueReport()); + #undef LB + } + #endif + +} // void CLRWatsonHelper::Init() + + +//------------------------------------------------------------------------------ +// CurrentUserHasSufficientPermissions +// +// Determines if the user logged in has the correct permissions to launch Watson. +// +// Parameters: +// None. +// +// Returns: +// TRUE if the user has sufficient permissions, else FALSE +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::CurrentUserHasSufficientPermissions() +{ + // TODO! Implement! + return TRUE; +} // BOOL CLRWatsonHelper::CurrentUserHasSufficientPermissions() + + + +//------------------------------------------------------------------------------ +// Description +// Determines whether we will show Watson at all. +// +// Parameters +// none +// +// Returns +// TRUE -- If Watson should show UI. +// FALSE -- Otherwise +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::ShouldShowUI() +{ + WRAPPER_NO_CONTRACT; + + AssertValid(); + + return (m_dialogAction == WHDialog_OkToPopup); +} // BOOL CLRWatsonHelper::ShouldShowUI() + +//------------------------------------------------------------------------------ +// Description +// Determines whether a debugger will (or may be) launched. True if there +// is an auto-launch debugger, or if we will ask the user. +// +// Parameters +// none +// +// Returns +// TRUE -- If a debugger might be attached. +// FALSE -- Otherwise +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::ShouldDebug() +{ + LIMITED_METHOD_CONTRACT; + + return ShouldOfferDebug() || ShouldAutoAttach(); +} // BOOL CLRWatsonHelper::ShouldDebug() + +//------------------------------------------------------------------------------ +// Description +// Determines whether or not the Debug button should be present in the +// Watson dialog +// +// Parameters +// none +// +// Returns +// TRUE -- if the Debug button should be displayed +// FALSE -- otherwise +// +// Notes +// This means "is there an appropriate debugger registered for auto attach?" +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::ShouldOfferDebug() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + AssertValid(); + + // Permission check. + if (!CurrentUserHasSufficientPermissions()) + { + return FALSE; + } + + // Check based on DbgJitDebugLaunchSetting & interactivity. + if (m_debugAction != WHDebug_AskToLaunch) + { + // Don't ask the user about debugging. Do or don't debug; but don't ask. + return FALSE; + } + + SString ssDebuggerString; + GetDebuggerSettingInfo(ssDebuggerString, NULL); + + // If there is no debugger installed, don't offer to debug, since we can't. + if (ssDebuggerString.IsEmpty()) + { + return FALSE; + } + + return TRUE; + +} // BOOL CLRWatsonHelper::ShouldOfferDebug() + +//------------------------------------------------------------------------------ +// +// ShouldAutoAttach +// +// Description +// Determines whether or not a debugger should be launched +// automatically, without prompting the user. +// +// Parameters +// None. +// +// Returns +// TRUE -- If a debugger should be auto-attached. +// FALSE -- Otherwise +// +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::ShouldAutoAttach() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + AssertValid(); + + // Permissions check. + if (!CurrentUserHasSufficientPermissions()) + { + return FALSE; + } + + return (m_debugAction == WHDebug_AutoLaunch); +} // BOOL CLRWatsonHelper::ShouldAutoAttach() + + +//------------------------------------------------------------------------------ +// Description +// Returns whether a Watson report should be generated. +// +// Parameters +// none +// +// Returns +// TRUE - a Watson report should be generated (with a minidump). +// FALSE - don't generate a report. +// +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::ShouldReport() +{ + WRAPPER_NO_CONTRACT; + + AssertValid(); + + // If we queue or ask, we should generate. + return (m_reportAction == WHReport_AutoQueue) || (m_reportAction == WHReport_AskToSend); + +} // BOOL CLRWatsonHelper::ShouldReport() + + +//------------------------------------------------------------------------------ +// Description +// If a Watson report is generated, returns whether it should be auto-queued. +// (vs asking the user what to do about it) +// +// Parameters +// none +// +// Returns +// TRUE - any Watson report should be be queued. +// FALSE - any Watson report is posed to the user for "send" or "don't send". +// +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::ShouldQueueReport() +{ + WRAPPER_NO_CONTRACT; + + AssertValid(); + + // If we queue a report. + return (m_reportAction == WHReport_AutoQueue); + +} // BOOL CLRWatsonHelper::ShouldQueueReport() + +//------------------------------------------------------------------------------ +// Description +// Reads a PCHealth configuration LONG value from the registry. +// +// Parameters +// szName -- name of the value +// iDefault -- default value, if not found +// +// Returns +// The value read, or default if no value found. +// +// Exceptions +// None +// +// NOtes: +// Looks in HKCU/Software/Policies/Microsoft/PCHealth/ErrorReporting +// then in HKLM/ " " " " " +// then in HKCU/SOftware/Microsoft/PCHealth/ErrorReporting +// then in HKLM/ " " " " +//------------------------------------------------------------------------------ +int CLRWatsonHelper::GetPCHealthConfigLong( // Return value from registry or default. + LPCTSTR szName, // Name of value to get. + int iDefault) // Default value to return if not found. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + long iValue; // Actual value from registry. + + // Try HKCR policy key + if (GetRegistryLongValue(HKEY_CURRENT_USER, kErrorReportingPoliciesKey, szName, &iValue, FALSE)) + return iValue; + + // Try HKLM policy key + if (GetRegistryLongValue(HKEY_LOCAL_MACHINE, kErrorReportingPoliciesKey, szName, &iValue, FALSE)) + return iValue; + + // Try HKCR key + if (GetRegistryLongValue(HKEY_CURRENT_USER, kErrorReportingKey, szName, &iValue, FALSE)) + return iValue; + + // Try HKLM key + if (GetRegistryLongValue(HKEY_LOCAL_MACHINE, kErrorReportingKey, szName, &iValue, FALSE)) + return iValue; + + // None of them had value -- return default. + return iDefault; +} // long CLRWatsonHelper::GetPCHealthConfigLong() + +//------------------------------------------------------------------------------ +// Description +// Reads a PCHealth configuration LONG value from the registry, from a +// given subkey. +// +// Parameters +// szSubKey -- name of the subkey. +// szName -- name of the value +// iDefault -- default value, if not found +// pValue -- put value here. +// +// Returns +// TRUE - a value was found in the registry +// FALSE - no value found. +// +// Exceptions +// None +// +// NOtes: +// Looks in HKCU/Software/Policies/Microsoft/PCHealth/ErrorReporting +// then in HKLM/ " " " " " +// then in HKCU/SOftware/Microsoft/PCHealth/ErrorReporting +// then in HKLM/ " " " " +//------------------------------------------------------------------------------ +BOOL CLRWatsonHelper::GetPCHealthConfigSubKeyLong( // Return value from registry or default. + LPCWSTR szSubKey, // Name of the subkey. + LPCWSTR szName, // Name of value to get. + int iDefault, // Default value to return if not found. + DWORD *pValue) // Put the value (registry or default) here. +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + long iValue; // Actual value from registry. + + // Only one thread will *ever* enter this function, so it is safe to use a static + // buffer. We know the the longest strings we will want to catenate. Size + // the buffer appropriately, and we're set. + static WCHAR rcBuf[lengthof(kErrorReportingPoliciesKey) + lengthof(kInclusionListSubKey) + 3]; + + _ASSERT( (wcslen(kErrorReportingPoliciesKey) + wcslen(szSubKey) + 1) < lengthof(rcBuf)); + + // Try HKCR policy key + wcscpy_s(rcBuf, COUNTOF(rcBuf), kErrorReportingPoliciesKey); + wcsncat_s(rcBuf, COUNTOF(rcBuf), szSubKey, lengthof(rcBuf)-wcslen(rcBuf)-1); + + if (GetRegistryLongValue(HKEY_CURRENT_USER, rcBuf, szName, &iValue, FALSE)) + { + *pValue = iValue; + return TRUE; + } + + // Try the HKLM policy key + if (GetRegistryLongValue(HKEY_LOCAL_MACHINE, rcBuf, szName, &iValue, FALSE)) + { + *pValue = iValue; + return TRUE; + } + + // Try HKCR key + wcscpy_s(rcBuf, COUNTOF(rcBuf), kErrorReportingKey); + wcsncat_s(rcBuf, COUNTOF(rcBuf), szSubKey, lengthof(rcBuf)-wcslen(rcBuf)-1); + + if (GetRegistryLongValue(HKEY_CURRENT_USER, rcBuf, szName, &iValue, FALSE)) + { + *pValue = iValue; + return TRUE; + } + + // Try HKLM key + if (GetRegistryLongValue(HKEY_LOCAL_MACHINE, rcBuf, szName, &iValue, FALSE)) + { + *pValue = iValue; + return TRUE; + } + + // None of them had value -- return default. + *pValue = iDefault; + return FALSE; +} // long CLRWatsonHelper::GetPCHealthConfigLong() + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +HRESULT CreateWatsonSharedMemory( + HANDLE *hWatsonSharedMemory, + DWSharedMem **ppWatsonSharedMemory) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Watson needs to inherit the shared memory block, so we have to set up + // security attributes to make that happens. + SECURITY_ATTRIBUTES securityAttributes; + memset(&securityAttributes, 0, sizeof(securityAttributes)); + securityAttributes.nLength = sizeof(securityAttributes); + securityAttributes.bInheritHandle = TRUE; + + _ASSERTE(NULL != hWatsonSharedMemory); + _ASSERTE(NULL != ppWatsonSharedMemory); + + *hWatsonSharedMemory = NULL; + *ppWatsonSharedMemory = NULL; + + // In cases where we have to return form this function with a failure, we + // need to clean up the handle. Use a holder to take care of that for us. + HandleHolder hTemp = + WszCreateFileMapping(INVALID_HANDLE_VALUE, + &securityAttributes, + PAGE_READWRITE, + 0, + sizeof(DWSharedMem), + NULL); + + if (hTemp == NULL) + { + return HRESULT_FROM_GetLastErrorNA(); + } + + DWSharedMem* pTemp = + static_cast< DWSharedMem* >(CLRMapViewOfFile(hTemp, + FILE_MAP_ALL_ACCESS, + 0, + 0, + sizeof(DWSharedMem))); + + if (NULL == pTemp) + { + return HRESULT_FROM_GetLastErrorNA(); + } + + memset(pTemp, 0, sizeof(DWSharedMem)); + *hWatsonSharedMemory = hTemp; + *ppWatsonSharedMemory = pTemp; + + // We're ready to exit normally and pass the IPC block's handle back to our + // caller, so we don't want to close it. + hTemp.SuppressRelease(); + + return S_OK; +} // HRESULT CreateWatsonSharedMemory() + + + +const WCHAR* kWatsonImageNameOnVista = W("\\dw20.exe"); + +//------------------------------------------------------------------------------ +// Description +// A helper function to launch the Watson process and wait for it to +// complete +// Parameters +// hWatsonSharedMemory +// Handle to the shared memory block to pass to Watson. This handle +// must be inheritable. +// hEventAlive +// hEventDone +// hMutex +// Returns +// true - If watson executed normally +// false - if watson was unable to launch, reported an error, or +// appeared to hang/crash +//------------------------------------------------------------------------------ +BOOL RunWatson( + HANDLE hWatsonSharedMemory, + HANDLE hEventAlive, + HANDLE hEventDone, + HANDLE hMutex) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(!RunningOnWin7()); + } + CONTRACTL_END; + + // Since we're doing our own error reporting, we don't want to pop up the + // OS Watson dialog/GPF Dialog. Supress it now. + + PROCESS_INFORMATION processInformation; + STARTUPINFOW startupInfo; + memset(&startupInfo, 0, sizeof(STARTUPINFOW)); + startupInfo.cb = sizeof(STARTUPINFOW); + + + WCHAR watsonAppName[MAX_PATH]; + WCHAR watsonCommandLine[MAX_PATH+1]; + + { +#if !defined(FEATURE_CORECLR) + // Use the version of DW20.exe that lives in the system directory. + DWORD ret; + + if (FAILED(GetCORSystemDirectoryInternal(watsonAppName, NumItems(watsonAppName), &ret))) + { + return false; + } + if (wcsncat_s(watsonAppName, NumItems(watsonAppName), kWatsonImageNameOnVista, _TRUNCATE) != 0) + { + return false; + } +#else // FEATURE_CORECLR + HKEYHolder hKey; + // Look for key \\HKLM\Software\Microsoft\PCHealth\ErrorReporting\DW\Installed" + DWORD ret = WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, + kWatsonPath, + 0, + KEY_READ | kWatsonRegKeyOptions, + &hKey); + + if (ERROR_SUCCESS != ret) + { + return false; + } + + + // Look in ...\DW\Installed for dw0200 (dw0201 on ia64). This will be + // the full path to the executable. + DWORD size = NumItems(watsonAppName); + ret = WszRegQueryValueEx(hKey, + kWatsonValue, + NULL, + NULL, + reinterpret_cast< LPBYTE >(watsonAppName), + &size); + + + if (ERROR_SUCCESS != ret) + { + return false; + } +#endif // ! FEATURE_CORECLR + + _snwprintf_s(watsonCommandLine, + NumItems(watsonCommandLine)-1, + _TRUNCATE, + W("dw20.exe -x -s %lu"), + PtrToUlong(hWatsonSharedMemory)); + watsonCommandLine[NumItems(watsonCommandLine) - 1] = W('\0'); + } + + + { + BOOL ret = WszCreateProcess(watsonAppName, + watsonCommandLine, + NULL, + NULL, + TRUE, + NULL, + NULL, + NULL, + &startupInfo, + &processInformation); + + if (FALSE == ret) + { + // + // Watson failed to start up. + // + // This can happen if e.g. Watson wasn't installed on the machine. + // + HRESULT hr = HRESULT_FROM_GetLastErrorNA(); + return false; + } + + } + + // Wait for watson to finish. + // + // This code was more-or-less pasted directly out of the test app for + // watson, found at + // + // \\redist\redist\Watson\dw20_latest\neutral\retail\0\testcrash.cpp + + // These handles need to live until we're done waiting for the watson + // process to finish execution. + HandleHolder hProcess(processInformation.hProcess); + HandleHolder hThread(processInformation.hThread); + + + BOOL watsonSignalledCompletion = FALSE, bDWRunning = TRUE; + + while (bDWRunning) + { + if (WAIT_OBJECT_0 == ClrWaitForSingleObject(hEventAlive, + kDwWaitTime)) + { + // Okay, Watson's still pinging us; see if it's finished. + if (WAIT_OBJECT_0 == ClrWaitForSingleObject(hEventDone, 1)) + { + bDWRunning = FALSE; + watsonSignalledCompletion = TRUE; + } + + // If watson is finished (i.e. has signaled hEventDone), + // bDWRunning is false and we'll fall out of the loop. If + // watson isn't finished, we'll go back to waiting for the + // next ping on hEventAlive + continue; + } + + Thread::BeginThreadAffinity(); + // we timed-out waiting for DW to respond. + DWORD dw = WaitForSingleObject(hMutex, DW_TIMEOUT_VALUE); + + if (WAIT_TIMEOUT == dw) + { + // either DW's hung or crashed, we must carry on. Let watson + // no that we're giving up on watson, in case it comes back + // from the hang. + SetEvent(hEventDone); + bDWRunning = FALSE; + } + else if (WAIT_ABANDONED == dw) + { + // The mutex was abandoned, which means Watson crashed on + // us. + bDWRunning = FALSE; + + ReleaseMutex(hMutex); + } + else + { + // Check one last time to see if Watson has woken up. + if (WAIT_OBJECT_0 != ClrWaitForSingleObject(hEventAlive, 1)) + { + // Nope. hasn't woken up. Give up on Watson + SetEvent(hEventDone); + bDWRunning = FALSE; + } + else + { + // Oh, it HAS woken up! See if it's finished as well. + if (WAIT_OBJECT_0 == ClrWaitForSingleObject(hEventDone, 1)) + { + bDWRunning = FALSE; + watsonSignalledCompletion = TRUE; + } + } + + ReleaseMutex(hMutex); + } + Thread::EndThreadAffinity(); + } + + // Go ahead and bail if Watson didn't exit for some reason. + if (!watsonSignalledCompletion) + { + return FALSE; + } + + // We're now done with hProcess and hThread, it's safe to let the + // HandleHolders destroy them now. + // + // We don't need to wait for the Watson process to exit; once it's signalled + // "hEventDone" it's safe to assume that Watson will not try communicating + // with us anymore and we have succeeded. + return true; +} // BOOL RunWatson() + + +// +// Constants used to control various aspects of Watson's behavior. +// + + +// Flags controlling the minidump Watson creates. +const DWORD kMiniDumpType = MiniDumpNormal; +const DWORD kThreadWriteFlags = ThreadWriteThread | ThreadWriteContext | ThreadWriteStack; +const DWORD kModuleWriteFlags = ModuleWriteModule; // | ModuleWriteDataSeg ? + + + +// Reporting. The defaults are fine here +const DWORD kReportingFlags = 0; + +// +// Enable these flags if the report should be queued (i.e., if no UI should be +// shown, but a report should still be sent). +// + +// Enable these flags are for bfDWRFlags +const DWORD kQueuingReportingFlags = fDwrForceToAdminQueue | fDwrIgnoreHKCU; + +// Enable these flags in the bfDWUFlags field +const DWORD kQueuingUIFlags = fDwuNoEventUI; + +// +// No reporting flags. Enable these flags if an error report should not be sent. +// + +// Enable these flags in bfDWRFlags if a report is not to be sent. +const DWORD kNoReportFlags = fDwrNeverUpload; + + +// UI Flags +// +// We need to use the light plea, since we may be reporting faults for +// Non-Microsoft software (if some random 3rd party app throws an exception, we +// can't really promise that their error report will be used to fix the +// problem). +// +const DWORD kUIFlags = fDwuDenySuspend | fDwuShowFeedbackLink; + +// Exception mode flags. By default, the "restart" and "recover" buttons are +// checked. We need to turn that behavior off. We also need to use the +// minidump API to gather the heap dump, in order to get a managed-aware +// minidump. Finally, release the dumping thread before doing the cabbing +// for performance reasons. +const DWORD kExceptionModeFlags = fDweDefaultQuit | fDweGatherHeapAsMdmp | fDweReleaseBeforeCabbing; + +// "Miscellaneous" flags. These flags are only used by Office. +const DWORD kMiscFlags = 0; + +// Flags to control which buttons are available on the Watson dialog. +// +// We will only display the "Send Error Report" and "Don't Send" buttons +// available -- we're not going to make the "restart" or "recover" checkboxes +// available by default. +const DWORD kOfferFlags = msoctdsQuit; + +//------------------------------------------------------------------------------ +// Description +// Returns the IP of the instruction that caused the exception to occur. +// For managed exceptions this may not match the Exceptions contained in +// the exception record. +// +// Parameters +// pExceptionRecord -- the SEH exception for the current exception +// pThread -- Pointer to Thread object of faulting thread +// +// Returns +// The IP that caused the exception to occur +// +// Exceptions +// None +//------------------------------------------------------------------------------ +UINT_PTR GetIPOfThrowSite( + EXCEPTION_RECORD* pExceptionRecord, + Thread *pThread) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // If we can't determine a better value, use the exception record's exception address. + UINT_PTR rslt = reinterpret_cast< UINT_PTR >(pExceptionRecord->ExceptionAddress); + + // If it is not a managed exception, use the IP from the exception. + if (!IsComPlusException(pExceptionRecord)) + return rslt; + + // Get the thread object, from which we'll try to get the managed exception object. + if (NULL == pThread) + { // If there's no managed thread, use the IP from the exception. + return rslt; + } + + // Retrieve any stack trace from the managed exception. If there is a stack + // trace, it will start with the topmost (lowest address, newest) managed + // code, which is what we want. + GCX_COOP(); + OBJECTREF throwable = pThread->GetThrowable(); + + // If there was no managed code on the stack and we are on 64-bit, then we won't have propagated + // the LastThrownObject into the Throwable yet. + if (throwable == NULL) + throwable = pThread->LastThrownObject(); + + _ASSERTE(throwable != NULL); + _ASSERTE(IsException(throwable->GetMethodTable())); + + // If the last thrown object is of type Exception, get the stack trace. + if (throwable != NULL) + { + // Get the BYTE[] containing the stack trace. + StackTraceArray traceData; + ((EXCEPTIONREF)throwable)->GetStackTrace(traceData); + + GCPROTECT_BEGIN(traceData); + // Grab the first non-zero, if there is one. + for (size_t ix = 0; ix < traceData.Size(); ++ix) + { + if (traceData[ix].ip) + { + rslt = traceData[ix].ip; + break; + } + } + GCPROTECT_END(); + } + + return rslt; +} // UINT_PTR GetIPOfThrowSite() + +//------------------------------------------------------------------------------ +// Description +// Given a wchar string, returns true if any of the individual characters are unicode. +// Else returns false (which implies the string could be losslessly converted to ascii) +// +// Input +// wsz -- The string to check. +// +// Returns +// true -- if the string contained any non-ascii characters, +// false -- otherwise. +// +//------------------------------------------------------------------------------ +BOOL ContainsUnicodeChars(__in_z LPCWSTR wsz) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(wsz != NULL); + + while (NULL != *wsz) + { + if (!iswascii(*wsz)) + { + return TRUE; + } + ++wsz; + } + return FALSE; +} // BOOL ContainsUnicodeChars() + +//------------------------------------------------------------------------------ +// Description +// Builds the GenericMode bucket parameters for a managed Watson dump. +// +// Parameters +// tore -- type of error being reported +// pThread -- Pointer to Thread object of faulting thread +// ip -- Where the exception was thrown. +// pGenericModeBlock -- Where to build the buckets +// exception -- the throwable +// +// Returns +// S_OK if all there is a valid managed exception to report on and +// Watson buckets were initialized successfully +// S_FALSE if there is no managed exception to report on +// E_OUTOFMEMORY if we ran out of memory while filling out the buckets +// +// Notes +// (pGenericModeBlock->fInited == TRUE) <=> (result = S_OK) +// The original contract of this method required that both of these conditions +// had to be checked independently and it has caused us some grief. +// See Dev10 bug 833350. +//------------------------------------------------------------------------------ +HRESULT GetManagedBucketParametersForIp( + TypeOfReportedError tore, + Thread * pThread, + UINT_PTR ip, + GenericModeBlock * pGenericModeBlock, + OBJECTREF * pThrowable) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + // Avoid confusion and stale data. + memset(pGenericModeBlock, 0, sizeof(GenericModeBlock)); + + // If the exception is not from managed code, then return S_FALSE. There is + // no more bucket data we can fill out. + if (ip == 0) + { + LOG((LF_EH, LL_INFO1000, "GetManagedBucketParametersForIP: ip == 0, returning\n")); + return S_FALSE; + } + + PCODE currentPC = PCODE(ip); + + if (!ExecutionManager::IsManagedCode(currentPC)) + { + // If there's no code manager for the location of the exception, then we + // should just treat this exception like an unmanaged exception. We are + // probably inside of mscorwks + // + // Note that while there may be an actual managed exception that + // occured, we can live without the managed bucket parameters. For + // exceptions coming from within mscorwks.dll, the native bucket + // parameters will do just fine. + + LOG((LF_EH, LL_INFO1000, "GetManagedBucketParametersForIP: IsManagedCode(%p) == FALSE, returning\n", currentPC)); + return S_FALSE; + } + + WatsonBucketType bucketType = GetWatsonBucketType(); +#ifndef FEATURE_CORECLR + if (bucketType == MoCrash) + { + MoCrashBucketParamsManager moCrashManager(pGenericModeBlock, tore, currentPC, pThread, pThrowable); + moCrashManager.PopulateBucketParameters(); + } + else +#endif // !FEATURE_CORECLR + { +#ifdef FEATURE_WINDOWSPHONE + _ASSERTE(bucketType == WinPhoneCrash); + WinPhoneBucketParamsManager winphoneManager(pGenericModeBlock, tore, currentPC, pThread, pThrowable); + winphoneManager.PopulateBucketParameters(); +#else + // if we default to CLR20r3 then let's assert that the bucketType is correct + _ASSERTE(bucketType == CLR20r3); + CLR20r3BucketParamsManager clr20r3Manager(pGenericModeBlock, tore, currentPC, pThread, pThrowable); + clr20r3Manager.PopulateBucketParameters(); +#endif // FEATURE_WINDOWSPHONE + } + + // At this point we have a valid managed exception, so the GMB should get + // filled out. If we set this to TRUE and there isn't a managed exception, + // Watson will get confused and not report the full unmanaged data. + pGenericModeBlock->fInited = TRUE; + + return S_OK; +} // HRESULT GetManagedBucketParametersForIp() + +//------------------------------------------------------------------------------ +// Description +// Builds the GenericMode bucket parameters for a managed Watson dump. +// +// Parameters +// ip -- the managed ip where the fault occurred. +// tore -- the type of reportederror +// pThread -- the thread point with the exception +// +// Returns +// Allocated GenericModeBlock or null. +// +// Notes +// This will attempt to allocate a new GenericModeBlock, and, if +// successful, will fill it with the GenericMode parameters for +// a managed Watson dump. This is intended to be used in places where +// the information is about to be lost. +// +// This function is called from elsewhere in the runtime. +//------------------------------------------------------------------------------ +void* GetBucketParametersForManagedException(UINT_PTR ip, TypeOfReportedError tore, Thread * pThread, OBJECTREF * pThrowable) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if (!IsWatsonEnabled()) + { + return NULL; + } + + // Set up an empty GenericModeBlock to hold the bucket parameters. + GenericModeBlock *pgmb = new (nothrow) GenericModeBlock; + if (pgmb == NULL) + return NULL; + + // Try to get BucketParameters. + HRESULT hr = GetManagedBucketParametersForIp(tore, pThread, ip, pgmb, pThrowable); + + // If it didn't succeed, delete the GenericModeBlock. Note that hr could be S_FALSE, and that still + // means the buckets aren't initialized. + if (hr != S_OK) + { + delete pgmb; + pgmb = NULL; + } + + return pgmb; +} // void* GetBucketParametersForManagedException() + +//------------------------------------------------------------------------------ +// Description +// Frees the GenericModeBlock allocated by GetBucketParametersForManagedException. +// +// Parameters +// pgmb -- the allocated GenericModeBlock. +// +// Returns +// nothing. +//------------------------------------------------------------------------------ +void FreeBucketParametersForManagedException(void *pgmb) +{ + WRAPPER_NO_CONTRACT; + + if (!IsWatsonEnabled()) + { + _ASSERTE(pgmb == NULL); + return; + } + + if (pgmb) + delete pgmb; +} // void FreeBucketParametersForManagedException() + + +//------------------------------------------------------------------------------ +// Description +// Retrieves or builds the GenericMode bucket parameters for a managed +// Watson dump. +// +// Parameters +// pExceptionRecord -- Information regarding the exception +// pGenericModeBlock -- Where to build the buckets +// tore -- type of error being reported +// pThread -- Pointer to Thread object of faulting thread +// +// Returns +// S_OK or error code. +// +// Notes +// If there is a saved GenericModeBlock on the thread object's ExceptionState +// that will be used. Otherwise, a new block is created. +//------------------------------------------------------------------------------ +HRESULT RetrieveManagedBucketParameters( + EXCEPTION_RECORD *pExceptionRecord, + GenericModeBlock *pGenericModeBlock, + TypeOfReportedError tore, + Thread *pThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_INTOLERANT; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + +#if defined(PRESERVE_WATSON_ACROSS_CONTEXTS) + GenericModeBlock *pBuckets = NULL; + +#ifdef FEATURE_CORECLR + // On CoreCLR, Watson may not be enabled. Thus, we should + // skip this. + if (IsWatsonEnabled()) +#endif // FEATURE_CORECLR + { + if (pThread != NULL) + { + // Try to get the buckets from the UE Watson Bucket Tracker + pBuckets = reinterpret_cast<GenericModeBlock*>(pThread->GetExceptionState()->GetUEWatsonBucketTracker()->RetrieveWatsonBuckets()); + if ((pBuckets == NULL) && (pThread->GetExceptionState()->GetCurrentExceptionTracker() != NULL)) + { + // If we didnt find the buckets in the UE Watson bucket tracker, then + // try to look them up in the current exception's watson tracker if + // an exception tracker exists. + pBuckets = reinterpret_cast<GenericModeBlock*>(pThread->GetExceptionState()->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->RetrieveWatsonBuckets()); + } + } + } + + // See if the thread has some managed bucket parameters stashed away... + if (pBuckets != NULL) + { // Yes it does, so copy them to the output buffer. + LOG((LF_EH, LL_INFO100, "Watson: RetrieveManagedBucketParameters returning stashed parameters (%p)\n", pBuckets)); + *pGenericModeBlock = *pBuckets; + +#if defined(_DEBUG) + LOG((LF_EH, LL_INFO100, "Watson b 1: %S\n", pGenericModeBlock->wzP1)); + LOG((LF_EH, LL_INFO100, " b 2: %S\n", pGenericModeBlock->wzP2)); + LOG((LF_EH, LL_INFO100, " b 3: %S\n", pGenericModeBlock->wzP3)); + LOG((LF_EH, LL_INFO100, " b 4: %S\n", pGenericModeBlock->wzP4)); + LOG((LF_EH, LL_INFO100, " b 5: %S\n", pGenericModeBlock->wzP5)); + LOG((LF_EH, LL_INFO100, " b 6: %S\n", pGenericModeBlock->wzP6)); + LOG((LF_EH, LL_INFO100, " b 7: %S\n", pGenericModeBlock->wzP7)); + LOG((LF_EH, LL_INFO100, " b 8: %S\n", pGenericModeBlock->wzP8)); + LOG((LF_EH, LL_INFO100, " b 9: %S\n", pGenericModeBlock->wzP9)); +#endif + } + else +#endif + { // No stashed bucket parameters, so get them from the exception. + UINT_PTR ip = 0; + if (pExceptionRecord != NULL) + { + // This function is called from functions that have NOTHROW/GC_NOTRIGGER + // contracts (in particular EEPolicy::HandleFatalError). Because that + // function always passes pExceptionInfo as NULL, we will never actually + // reach the potentially throwing code. + // + CONTRACT_VIOLATION(ThrowsViolation | GCViolation); + + ip = GetIPOfThrowSite(pExceptionRecord, pThread); + } + + hr = GetManagedBucketParametersForIp(tore, pThread, ip, pGenericModeBlock, NULL); + } + + return hr; +} // HRESULT RetrieveManagedBucketParameters() + +//------------------------------------------------------------------------------ +// Description +// Helper to get Watson bucket parameters, for the DebugManager interface. +// +// Parameters +// pParams -- Fill the parameters here. +// +// Returns +// S_OK -- Parameters filled in. +// S_FALSE -- No current exception. +// error -- Some error occurred. +// +// Note: +// This function is exposed via the hosting interface. +//------------------------------------------------------------------------------ +HRESULT GetBucketParametersForCurrentException( + BucketParameters *pParams) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_NOT_MAINLINE; + } + CONTRACTL_END; + + HRESULT hr; + GenericModeBlock gmb; + + // Make sure this is (or at least has been) a managed thread. + Thread *pThread = GetThread(); + if (pThread == NULL) + { // Not the greatest error, but we don't expect to be called on a unmanaged thread. + return E_UNEXPECTED; + } + + if (!IsWatsonEnabled()) + { + return E_NOTIMPL; + } + + // And make sure there is a current exception. + ThreadExceptionState* pExState = pThread->GetExceptionState(); + if (!pExState->IsExceptionInProgress()) + return S_FALSE; + + // Make sure we're not in the second pass. + if (pExState->GetFlags()->UnwindHasStarted()) + { // unwind indicates the second pass, so quit + return S_FALSE; + } + + EXCEPTION_RECORD *pExceptionRecord = pExState->GetExceptionRecord(); + + // Try to get the parameters... + hr = RetrieveManagedBucketParameters(pExceptionRecord, &gmb, TypeOfReportedError::UnhandledException, pThread); + + // ... and if successful, copy to the output block. If the return value is + // S_FALSE then it wasn't a managed exception and we should not copy the data in + // S_OK is the only success value that has inited the data + if (hr == S_OK) + { + // Event type name. + wcsncpy_s(pParams->pszEventTypeName, COUNTOF(pParams->pszEventTypeName), gmb.wzEventTypeName, _TRUNCATE); + + // Buckets. Mind the 1-based vs 0-based. + wcsncpy_s(pParams->pszParams[0], COUNTOF(pParams->pszParams[0]), gmb.wzP1, _TRUNCATE); + wcsncpy_s(pParams->pszParams[1], COUNTOF(pParams->pszParams[1]), gmb.wzP2, _TRUNCATE); + wcsncpy_s(pParams->pszParams[2], COUNTOF(pParams->pszParams[2]), gmb.wzP3, _TRUNCATE); + wcsncpy_s(pParams->pszParams[3], COUNTOF(pParams->pszParams[3]), gmb.wzP4, _TRUNCATE); + wcsncpy_s(pParams->pszParams[4], COUNTOF(pParams->pszParams[4]), gmb.wzP5, _TRUNCATE); + wcsncpy_s(pParams->pszParams[5], COUNTOF(pParams->pszParams[5]), gmb.wzP6, _TRUNCATE); + wcsncpy_s(pParams->pszParams[6], COUNTOF(pParams->pszParams[6]), gmb.wzP7, _TRUNCATE); + wcsncpy_s(pParams->pszParams[7], COUNTOF(pParams->pszParams[7]), gmb.wzP8, _TRUNCATE); + wcsncpy_s(pParams->pszParams[8], COUNTOF(pParams->pszParams[8]), gmb.wzP9, _TRUNCATE); + wcsncpy_s(pParams->pszParams[9], COUNTOF(pParams->pszParams[9]), gmb.wzP10, _TRUNCATE); + + // All good. + pParams->fInited = TRUE; + } + + return hr; +} // HRESULT GetBucketParametersForCurrentException() + + +//------------------------------------------------------------------------------ +// Description +// +// Parameters +// pExceptionInfo -- information about the exception that caused the error. +// If the error is not the result of an exception, pass NULL for this +// parameter +// tore -- Information about the fault +// pThread -- Thread object for faulting thread, could be NULL +// dwThreadID -- OS Thread ID for faulting thread +// +// Returns +// FaultReportResult -- enumeration indicating the +// FaultReportResultAbort -- if Watson could not execute normally +// FaultReportResultDebug -- if Watson executed normally, and the user +// chose to debug the process +// FaultReportResultQuit -- if Watson executed normally, and the user +// chose to end the process (e.g. pressed "Send Error Report" or +// "Don't Send"). +// +// Exceptions +// None. +//------------------------------------------------------------------------------ +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +FaultReportResult DoFaultReportWorker( // Was Watson attempted, successful? Run debugger? + EXCEPTION_POINTERS *pExceptionInfo, // Information about the fault. + TypeOfReportedError tore, // What sort of error is this? + Thread *pThread, // Thread object for faulting thread, could be NULL + DWORD dwThreadID) // OS Thread ID for faulting thread +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(!RunningOnWin7()); + + LOG((LF_EH, LL_INFO100, "DoFaultReportWorker: at sp %p ...\n", GetCurrentSP())); + + if (!IsWatsonEnabled()) + { + return FaultReportResultQuit; + } + +#if !defined(FEATURE_UEF_CHAINMANAGER) + // If we've already tried to report a Watson crash once, we don't really + // want to pester the user about this exception. This can occur in certain + // pathological programs. + // For events other than user breakpoint, we only want to report once. + // For user breakpoints, report whenever the thread wants to. + if (!tore.IsUserBreakpoint()) + { + // If Watson already launched (say, on another thread)... + if (FastInterlockCompareExchange(&g_watsonAlreadyLaunched, 1, 0) != 0) + { + // wait until Watson process is completed + ClrWaitForSingleObject(g_hWatsonCompletionEvent, INFINITE_TIMEOUT); + return FaultReportResultQuit; + } + } +#endif // FEATURE_UEF_CHAINMANAGER + + // Assume an unmanaged fault until we determine otherwise. + BOOL bIsManagedFault = FALSE; + + // IF we don't have an ExceptionInfo, what does that mean? + if (pExceptionInfo) + { + if (IsExceptionFromManagedCode(pExceptionInfo->ExceptionRecord)) + { + // This is a managed fault. + bIsManagedFault = TRUE; + } + } + + // Figure out what we should do. + CLRWatsonHelper policy; + policy.Init(bIsManagedFault, tore); + + if (policy.ShouldAutoAttach()) + { + return FaultReportResultDebug; + } + + // Is there anything for Watson to do? (Either report, or ask about debugging?) + if ((!policy.ShouldReport()) && (!policy.ShouldOfferDebug()) && (!policy.ShouldShowUI())) + { + // Hmm ... we're not supposed to report anything or pop up a dialog. In + // this case, we can stop right now. + return FaultReportResultQuit; + } + + HANDLE hWatsonSharedMemory; + DWSharedMem *pWatsonSharedMemory; + { + HRESULT hr = CreateWatsonSharedMemory(&hWatsonSharedMemory, + &pWatsonSharedMemory); + if (FAILED(hr)) + { + return FaultReportResultAbort; + } + } + + // Some basic bookkeeping for Watson + pWatsonSharedMemory->dwSize = sizeof(DWSharedMem); + pWatsonSharedMemory->dwVersion = DW_CURRENT_VERSION; + pWatsonSharedMemory->pid = GetCurrentProcessId(); + pWatsonSharedMemory->tid = dwThreadID; + _snwprintf_s(pWatsonSharedMemory->wzEventLogSource, + NumItems(pWatsonSharedMemory->wzEventLogSource), + _TRUNCATE, + W(".NET Runtime %0d.%0d Error Reporting"), + VER_MAJORVERSION, + VER_MINORVERSION); + pWatsonSharedMemory->eip = (pExceptionInfo) ? reinterpret_cast< DWORD_PTR >(pExceptionInfo->ExceptionRecord->ExceptionAddress) : NULL; + + // If we set exception pointers, the debugger will automatically do a .ecxr on them. SO, + // don't set the pointers unless it really is an exception and we have a + // a good context record + if (tore.IsException() || + (tore.IsFatalError() && pExceptionInfo && pExceptionInfo->ContextRecord && + (pExceptionInfo->ContextRecord->ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL) + ) + { + pWatsonSharedMemory->pep = pExceptionInfo; + } + else + { + pWatsonSharedMemory->pep = NULL; + } + + // Handles to kernel objects that Watson uses. + // + // We're expecting these handles to be valid until the Watson child process + // has run to completion. Make sure these holders stay in scope until after + // the call to RunWatson + + HandleHolder hEventDone(NULL), + hEventNotifyDone(NULL), + hEventAlive(NULL), + hMutex(NULL), + hProc(NULL), + sharedMemoryHolder(hWatsonSharedMemory); + { + // SECURITY_ATTRIBUTES so the handles can be inherited (by Watson). + SECURITY_ATTRIBUTES securityAttributes = + { sizeof(SECURITY_ATTRIBUTES), NULL, true }; + + hEventDone = WszCreateEvent(&securityAttributes, FALSE, FALSE, NULL); + if (hEventDone == NULL) + { + LOG((LF_EH, LL_INFO100, "CLR Watson: WszCreateEvent returned error, GetLastError(): %#x\n", GetLastError())); + return FaultReportResultAbort; + } + pWatsonSharedMemory->hEventDone = hEventDone; + + + hEventNotifyDone = WszCreateEvent(&securityAttributes, FALSE, FALSE, NULL); + if (hEventNotifyDone == NULL) + { + LOG((LF_EH, LL_INFO100, "CLR Watson: WszCreateEvent returned error, GetLastError(): %#x\n", GetLastError())); + return FaultReportResultAbort; + } + pWatsonSharedMemory->hEventNotifyDone = hEventNotifyDone; + + + hEventAlive = WszCreateEvent(&securityAttributes, FALSE, FALSE, NULL); + if (hEventAlive == NULL) + { + LOG((LF_EH, LL_INFO100, "CLR Watson: WszCreateEvent returned error, GetLastError(): %#x\n", GetLastError())); + return FaultReportResultAbort; + } + pWatsonSharedMemory->hEventAlive = hEventAlive; + + + hMutex = WszCreateMutex(&securityAttributes, FALSE, NULL); + if (hMutex == NULL) + { + LOG((LF_EH, LL_INFO100, "CLR Watson: WszCreateEvent returned error, GetLastError(): %#x\n", GetLastError())); + return FaultReportResultAbort; + } + pWatsonSharedMemory->hMutex = hMutex; + } + + // During error reporting we need to do dump collection, freeze threads inside the process, read memory blocks + // (if you register memory), read stuff from the PEB, create remote threads for recovery. So it needs quite a + // lot of permissions; we end up with PROCESS_ALL_ACCESS to satisfy all required permissions. + hProc = OpenProcess(PROCESS_ALL_ACCESS, + TRUE, + pWatsonSharedMemory->pid); + if (hProc == NULL) + { + LOG((LF_EH, LL_INFO100, "CLR Watson: OpenProcess returned error, GetLastError(): %#x\n", GetLastError())); + return FaultReportResultAbort; + } + + pWatsonSharedMemory->hProc = hProc; + + + // Flags to control reporting, queuing, etc. + DWORD reportingFlags = kReportingFlags; // 0 + DWORD uiFlags = kUIFlags; // fDwuDenySuspend | fDwuShowFeedbackLink + DWORD dwEflags = kExceptionModeFlags; // fDweDefaultQuit | fDweGatherHeapAsMdmp + + // Reporting flags... + if (policy.ShouldQueueReport()) + { // If we should queue a report, + // turn on kQueueingReportingFlags, which is fDwrForceToAdminQueue | fDwrIgnoreHKCU + reportingFlags |= kQueuingReportingFlags; + } + else + if (!policy.ShouldReport()) + { // We shouldn't report at all, + // turn on kNoReportFlags, which is fDwrNeverUpload, which means "don't report" + reportingFlags |= kNoReportFlags; + } + else + { + // Ask to report. + } + + // Offer flags... + DWORD offerFlags = kOfferFlags; // msoctdsQuit + if (policy.ShouldOfferDebug()) + { // Turn on msoctdsDebug, which adds "Debug" button. + offerFlags |= msoctdsDebug; + } + else + { // No debug, so ignore aeDebug + dwEflags |= fDweIgnoreAeDebug; + } + + // UI flags... + if (policy.ShouldQueueReport() && !policy.ShouldOfferDebug()) + { // Queue report headlessly. Turn on kQueueingUIFlags, which is fDwuNoEventUI. + uiFlags |= kQueuingUIFlags; + } + + pWatsonSharedMemory->bfmsoctdsOffer = offerFlags; // From above + pWatsonSharedMemory->bfDWRFlags = reportingFlags; // From above + pWatsonSharedMemory->bfDWUFlags = uiFlags; // From above + pWatsonSharedMemory->bfDWEFlags = dwEflags; // From above + pWatsonSharedMemory->bfDWMFlags = kMiscFlags; // 0 + + // We're going to rely on Watson's default localization behavior. + pWatsonSharedMemory->lcidUI = 0; + + // By default, Watson will terminate the process after snapping a + // minidump. Notify & LetRun flags disable that. + pWatsonSharedMemory->bfmsoctdsNotify = msoctdsNull; + pWatsonSharedMemory->bfmsoctdsLetRun = offerFlags; + + { + DWORD dwRet = WszGetModuleFileName(NULL, + pWatsonSharedMemory->wzModuleFileName, + NumItems(pWatsonSharedMemory->wzModuleFileName)); + _ASSERTE(0 != dwRet); + if (0 == dwRet) + { + LOG((LF_EH, LL_INFO100, "CLR Watson: WszGetModuleFileName returned error, GetLastError(): %#x\n", GetLastError())); + return FaultReportResultAbort; + } + } + + // We're going capture the same minidump information for all modules, so set wzDotDataDlls to "*" + if (sizeof(DW_ALLMODULES) <= sizeof(pWatsonSharedMemory->wzDotDataDlls)) + { + memcpy(pWatsonSharedMemory->wzDotDataDlls, DW_ALLMODULES, sizeof(DW_ALLMODULES)); + } + else + { + // Assert, but go on + _ASSERTE(sizeof(DW_ALLMODULES) <= sizeof(pWatsonSharedMemory->wzDotDataDlls)); + pWatsonSharedMemory->wzDotDataDlls[0] = 0; + } + + // UI Customization + // + // The only UI customization we perform is to set the App Name. Currently we + // do this just by using the executable name. + // + { + WCHAR buf[_MAX_PATH]; // Buffer for path for description. + WCHAR *pName = buf; // Pointer to filename or description. + int size; // Size of description. + HMODULE hModule; // Handle to module. + DWORD result; // Return code + + // Get module name. + hModule = WszGetModuleHandle(NULL); + result = WszGetModuleFileName(hModule, buf, NumItems(buf)); + + if (result == 0) + { // Couldn't get module name. This should never happen. + wcscpy_s(buf, COUNTOF(buf), W("<<unknown>>")); + } + else + { // re-use the buf for pathname and description. + size = DwGetAppDescription(buf, buf, NumItems(buf)); + + // If the returned size was zero, buf wasn't changed, and still contains the path. + // find just the filename part. + if (size == 0) + { // Look for final '\' + pName = wcsrchr(buf, W('\\')); + // If found, skip it; if not, point to full name. + pName = pName ? pName+1 : buf; + } + } + + wcsncpy_s(pWatsonSharedMemory->uib.wzGeneral_AppName, + COUNTOF(pWatsonSharedMemory->uib.wzGeneral_AppName), + pName, + _TRUNCATE); + + // For breakpoint, need to customize the "We're sorry..." message + if (tore.IsBreakpoint()) + { + LCID lcid = 0; + // Get the message. + StackSString sszMain_Intro_Bold; + StackSString sszMain_Intro_Reg; + EX_TRY + { + sszMain_Intro_Bold.LoadResource(CCompRC::Debugging, IDS_WATSON_DEBUG_BREAK_INTRO_BOLD); + sszMain_Intro_Reg.LoadResource(CCompRC::Debugging, IDS_WATSON_DEBUG_BREAK_INTRO_REG); + // Try to determine the language used for the above resources + // At the moment this OS call is a heuristic which should match most of the time. But the + // CLR is starting to support languages that don't even have LCIDs, so this may not always + // be correct (and there may be NO LCID we can pass to watson). Long term, the correct fix + // here is to get out of the game of making watson policy / UI decisions. This is happening + // for Windows 7. + lcid = GetThreadLocale(); + } + EX_CATCH + { + // Just don't customize. + } + EX_END_CATCH(SwallowAllExceptions) + + // If we were able to get a string, set it. + if (sszMain_Intro_Reg.GetCount() > 0) + { + // Instead of "<app.exe> has encountered an error and nees to close...", say + // "<app.exe> has encountered a user-defined breakpoint." + wcsncpy_s(pWatsonSharedMemory->uib.wzMain_Intro_Bold, COUNTOF(pWatsonSharedMemory->uib.wzMain_Intro_Bold), sszMain_Intro_Bold, _TRUNCATE); + // Instead of "If you were in the middle of something...", say + // "A breakpoint in an application indicates a program error..." + wcsncpy_s(pWatsonSharedMemory->uib.wzMain_Intro_Reg, COUNTOF(pWatsonSharedMemory->uib.wzMain_Intro_Reg), sszMain_Intro_Reg, _TRUNCATE); + + pWatsonSharedMemory->bfDWUFlags = fDwuDenySuspend; + + pWatsonSharedMemory->lcidUI = lcid; + } + } + + } + + // Get the bucket parameters. + switch (tore.GetType()) + { + case TypeOfReportedError::NativeThreadUnhandledException: + // Let Watson provide the buckets for a native thread. + break; + case TypeOfReportedError::UnhandledException: + case TypeOfReportedError::FatalError: + case TypeOfReportedError::UserBreakpoint: + case TypeOfReportedError::NativeBreakpoint: + // For managed exception or exceptions that come from managed code, we get the managed bucket parameters, + // which will be displayed in the "details" section on any UI. + // + // Otherwise, use the unmanaged IP to bucket. + if (bIsManagedFault) + { + RetrieveManagedBucketParameters(pExceptionInfo?pExceptionInfo->ExceptionRecord:NULL, &pWatsonSharedMemory->gmb, tore, pThread); + } + break; + default: + _ASSERTE(!"Unexpected TypeOfReportedException"); + break; + } + + // dwThisThreadExFlags and dwOtherThreadExFlags are only used on IA64. + CustomMinidumpBlock cmb = + { + TRUE, // fCustomMinidump + kMiniDumpType, // dwMinidumpType : MiniDumpNormal + FALSE, // fOnlyThisThread + kThreadWriteFlags, // dwThisThreadFlags : ThreadWriteThread | ThreadWriteContext | ThreadWriteStack + kThreadWriteFlags, // dwOtherThreadFlags + 0, // dwThisThreadExFlags + 0, // dwOtherThreadExFlags + kModuleWriteFlags, // dwPreferredModuleFlags + kModuleWriteFlags // dwOtherModuleFlags. + }; + + pWatsonSharedMemory->cmb = cmb; + + // At this point, the IPC block is all ready to go + BOOL result = false; + // There are two calls to RunWatson below. We want the second call to execute iff + // secondInvocation is true. + BOOL secondInvocation = true; + + + EX_TRY + { + bool fRunWatson = false; +#if defined(_TARGET_X86_) + bool fGuardPagePresent = false; + + // There is an unfortunate side effect of calling ReadProcessMemory() out-of-process on IA64 WOW. + // On all platforms (IA64 native & WOW64, AMD64 native & WOW64, and x86 native), if we call + // ReadProcessMemory() out-of-process on a page with PAGE_GUARD protection, the read operation + // fails as expected. However, on IA64 WOW64 only, the PAGE_GUARD protection is removed after + // the read operation. Even IA64 native preserves the PAGE_GUARD protection. + // See VSW 451447 for more information. + if ((pThread != NULL) && pThread->DetermineIfGuardPagePresent()) + { + fGuardPagePresent = true; + } +#endif // _TARGET_X86_ + + if (secondInvocation) + { + fRunWatson = true; + result = RunWatson(hWatsonSharedMemory, + pWatsonSharedMemory->hEventAlive, + pWatsonSharedMemory->hEventDone, + pWatsonSharedMemory->hMutex); + } + +#if defined(_TARGET_X86_) + if (fRunWatson && fGuardPagePresent) + { + // This shouldn't cause a problem because guard pages are present in the first place. + _ASSERTE(pThread != NULL); + pThread->RestoreGuardPage(); + } +#endif // _TARGET_X86_ + } + EX_CATCH + { + // We couldn't wait around for watson to execute for some reason. + result = false; + } + EX_END_CATCH(SwallowAllExceptions) + + // It's now safe to close all the synchronization and process handles. + + if (!result) + { + // Hmmm ... watson couldn't execute correctly. + return FaultReportResultAbort; + } + + LOG((LF_EH, LL_INFO100, "CLR Watson: returned 0x%x\n", pWatsonSharedMemory->msoctdsResult)); + + // If user clicked "Debug" + if (msoctdsDebug == pWatsonSharedMemory->msoctdsResult) + { + return FaultReportResultDebug; + } + + // No debugging, successful completion. + return FaultReportResultQuit; +} // FaultReportResult DoFaultReportWorker() +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +class WatsonThreadData { + public: + + WatsonThreadData(EXCEPTION_POINTERS *pExc, TypeOfReportedError t, Thread* pThr, DWORD dwID, FaultReportResult res) + : pExceptionInfo(pExc) + , tore(t) + , pThread(pThr) + , dwThreadID(dwID) + , result(res) + { + } + + EXCEPTION_POINTERS *pExceptionInfo; // Information about the exception, NULL if the error is not caused by an exception + TypeOfReportedError tore; // Information about the fault + Thread* pThread; // Thread object for faulting thread, could be NULL + DWORD dwThreadID; // OS Thread ID for faulting thread + FaultReportResult result; // Result of invoking Watson +}; + +class WatsonSOExceptionAddress { + public: + + WatsonSOExceptionAddress() + { + m_SystemMethod = NULL; + m_UserMethod = NULL; + } + + SLOT m_SystemMethod; // IP in the first method on the stack which is in a system module + SLOT m_UserMethod; // IP in the first method on the stack which is in a non-system module +}; + +//------------------------------------------------------------------------------ +// Description +// This function is the stack walk callback for a thread that hit a soft SO (i.e., a SO caused by a +// failed stack probe). +// +// Parameters +// pCf -- A pointer to the current CrawlFrame +// data - A pointer to WatsonSOExceptionAddress instance +// +// Returns: +// SWA_ABORT to stop the stack crawl +// SWA_CONTINUE to continue crawling the stack +// +// Exceptions +// None. +//------------------------------------------------------------------------------ +StackWalkAction WatsonSOStackCrawlCallback(CrawlFrame* pCf, void* pParam) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pParam != NULL); + WatsonSOExceptionAddress *pData = (WatsonSOExceptionAddress *) pParam; + + SLOT ip; + + if (pCf->IsFrameless()) + { + ip = (PBYTE)GetControlPC(pCf->GetRegisterSet()); + } + else + { + ip = (SLOT) pCf->GetFrame()->GetIP(); + } + + MethodDesc *pMD = pCf->GetFunction(); + + if (pMD != NULL) + { + if (pMD->GetModule()->IsSystem()) + { + if (pData->m_SystemMethod == NULL) + { + pData->m_SystemMethod = ip; + } + return SWA_CONTINUE; + } + else + { + _ASSERTE(pData->m_UserMethod == NULL); + pData->m_UserMethod = ip; + return SWA_ABORT; + } + } + else + { + return SWA_CONTINUE; + } + +}// WatsonSOCrawlCallBack + +//------------------------------------------------------------------------------ +// Description +// Wrapper function for DoFaultReport. This function is called for SOs. +// It sets up the ExceptionInfo appropriately for soft SOs (caused by +// failed stack probes) before callign DoFaultReport. +// +// Parameters +// pParam -- A pointer to a WatsonThreadData instance +// +// Exceptions +// None. +//------------------------------------------------------------------------------ +DWORD WINAPI DoFaultReportWorkerCallback(LPVOID pParam) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pParam != NULL); + + WatsonThreadData* pData = (WatsonThreadData*) pParam; + + EXCEPTION_POINTERS ExceptionInfo; + EXCEPTION_RECORD ExceptionRecord; + PEXCEPTION_POINTERS pExceptionInfo = pData->pExceptionInfo; + + if (IsSOExceptionCode(pExceptionInfo->ExceptionRecord->ExceptionCode)) + { + EX_TRY + { + if (ShouldLogInEventLog()) + { + EventReporter reporter(EventReporter::ERT_StackOverflow); + reporter.Report(); + } + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + } + + SetupThread(); + + GCX_COOP(); + + if (pData->pThread != NULL && pExceptionInfo != NULL && + pExceptionInfo->ContextRecord == NULL && + pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW && + pExceptionInfo->ExceptionRecord->ExceptionAddress == 0) + { + // In the case of a soft SO on a managed thread, we set the ExceptionAddress to one of the following + // + // 1. The first method on the stack that is in a non-system module. + // 2. Failing that, the first method on the stack that is in a system module + + CONTEXT ContextRecord; + memset(&ContextRecord, 0, sizeof(CONTEXT)); + + ExceptionInfo.ContextRecord = &ContextRecord; // To display the "Send" button, dw20 wants a non-NULL pointer + ExceptionRecord = *(pExceptionInfo->ExceptionRecord); + ExceptionInfo.ExceptionRecord = &ExceptionRecord; + pExceptionInfo = &ExceptionInfo; + + WatsonSOExceptionAddress WatsonExceptionAddresses; + + pData->pThread->StackWalkFrames( + WatsonSOStackCrawlCallback, + &WatsonExceptionAddresses, + FUNCTIONSONLY|ALLOW_ASYNC_STACK_WALK); + + if (WatsonExceptionAddresses.m_UserMethod != NULL) + { + pExceptionInfo->ExceptionRecord->ExceptionAddress = WatsonExceptionAddresses.m_UserMethod; + } + else if (WatsonExceptionAddresses.m_SystemMethod != NULL) + { + pExceptionInfo->ExceptionRecord->ExceptionAddress = WatsonExceptionAddresses.m_SystemMethod; + } + + } + + pData->result = DoFaultReportWorker( + pExceptionInfo, + pData->tore, + pData->pThread, + pData->dwThreadID); + + + return 0; + +} // void DoFaultReportFavorWorker() + +DWORD WINAPI ResetWatsonBucketsCallbackForStackOverflow(LPVOID pParam) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + PRECONDITION(IsWatsonEnabled()); + PRECONDITION(RunningOnWin7()); + PRECONDITION(pParam != NULL); + } + CONTRACTL_END; + + // ThreadStore lock could be already taken (SO during GC) so we skip creating a managed thread and get a hardcoded exception name. + // If we wanted to get the exception name from OBJECTREF we would have to switch to GC_COOP mode and be on a managed thread. + + ResetWatsonBucketsParams * pRWBP = reinterpret_cast<ResetWatsonBucketsParams *>(pParam); + Thread * pThread = pRWBP->m_pThread; + PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker(); + _ASSERTE(pUEWatsonBucketTracker != NULL); + + UINT_PTR ip = reinterpret_cast<UINT_PTR>(pRWBP->pExceptionRecord->ExceptionAddress); + pUEWatsonBucketTracker->SaveIpForWatsonBucket(ip); + pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::StackOverflowException, pThread, NULL); + if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL) + { + pUEWatsonBucketTracker->ClearWatsonBucketDetails(); + } + + return 0; +} + +//------------------------------------------------------------------------------ +// Description +// This function is called by the Debugger thread in response to a favor +// posted to it by the faulting thread. The faulting thread uses the +// Debugger thread to reset Watson buckets in the case of stack overflows. +// Since the debugger thread doesn't have a managed Thread object, +// it cannot be directly used to call ResetWatsonBucketsFavorWorker. +// Instead, this function spawns a worker thread and waits for it to complete. +// +// Parameters +// pParam -- A pointer to a ResetWatsonBucketsParams instance +// +// Exceptions +// None. +//------------------------------------------------------------------------------ +void ResetWatsonBucketsFavorWorker(void * pParam) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(IsWatsonEnabled()); + PRECONDITION(RunningOnWin7()); + PRECONDITION(pParam != NULL); + } + CONTRACTL_END; + + HANDLE hThread = NULL; + DWORD dwThreadId; + + hThread = ::CreateThread(NULL, 0, ResetWatsonBucketsCallbackForStackOverflow, pParam, 0, &dwThreadId); + if (hThread != NULL) + { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + } + + return; +} + + +//------------------------------------------------------------------------------ +// Description +// This function is called by the Debugger thread in response to a favor +// posted to it by the faulting thread. The faulting thread uses the +// Debugger thread to invoke Watson in the case of stack overflows. +// Since the debugger thread doesn't have a managed Thread object, +// it cannot be directly used to call DoFaultReport. Instead, this function +// spawns a worker thread and waits for it to complete. +// +// Parameters +// pParam -- A pointer to a WatsonThreadData instance +// +// Exceptions +// None. +//------------------------------------------------------------------------------ +void DoFaultReportFavorWorker(void* pParam) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pParam != NULL); + + HANDLE hThread = NULL; + DWORD dwThreadId; + + hThread = ::CreateThread(NULL, 0, DoFaultReportWorkerCallback, pParam, 0, &dwThreadId); + if (hThread != NULL) + { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + } + + return; + +} // void DoFaultReportFavorWorker() + +//---------------------------------------------------------------------------- +// CreateThread() callback to invoke native Watson or put up our fake Watson +// dialog depending on m_fDoReportFault value. +// +// The output is a FaultReport* value communicated by setting +// pFaultReportInfo->m_result. The DWORD function return value +// is unused. +//---------------------------------------------------------------------------- +static DWORD WINAPI DoFaultReportCreateThreadCallback(LPVOID pFaultReportInfoAsVoid) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_FORBID_FAULT; + + + // We are allowed to ignore OOM's here as FaultReport() is merely a notification of + // an unhandled exception. If we can't do the report, that's just too bad. + FAULT_NOT_FATAL(); + + LOG((LF_EH, LL_INFO100, "DoFaultReport: at sp %p ...\n", GetCurrentSP())); + + FaultReportInfo *pFaultReportInfo = (FaultReportInfo*)pFaultReportInfoAsVoid; + EXCEPTION_POINTERS *pExceptionInfo = pFaultReportInfo->m_pExceptionInfo; + + if (pFaultReportInfo->m_fDoReportFault) + { + pFaultReportInfo->m_faultRepRetValResult = DoReportFault(pExceptionInfo); + } + else + { + int res = EEMessageBoxCatastrophicWithCustomizedStyle( + IDS_DEBUG_UNHANDLEDEXCEPTION, + IDS_DEBUG_SERVICE_CAPTION, + MB_OKCANCEL | MB_ICONEXCLAMATION, + TRUE, + GetCurrentProcessId(), + GetCurrentProcessId(), + pFaultReportInfo->m_threadid, + pFaultReportInfo->m_threadid + ); + if (res == IDOK) + { + pFaultReportInfo->m_faultReportResult = FaultReportResultQuit; + } + else + { + pFaultReportInfo->m_faultReportResult = FaultReportResultDebug; + } + } + + return 0; +} + + +//---------------------------------------------------------------------------- +// Favor callback for the debugger thread. +//---------------------------------------------------------------------------- +VOID WINAPI DoFaultReportDoFavorCallback(LPVOID pFaultReportInfoAsVoid) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; + STATIC_CONTRACT_FORBID_FAULT; + + + // Since the debugger thread doesn't allow ordinary New's which our stuff + // indirectly calls, it cannot be directly used to call DoFaultReport. Instead, this function + // spawns a worker thread and waits for it to complete. + + HANDLE hThread = NULL; + DWORD dwThreadId; + + hThread = ::CreateThread(NULL, 0, DoFaultReportCreateThreadCallback, pFaultReportInfoAsVoid, 0, &dwThreadId); + if (hThread != NULL) + { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + } +} + + + +//------------------------------------------------------------------------------ +// Description +// +// Parameters +// pExceptionInfo -- information about the exception that caused the error. +// If the error is not the result of an exception, pass NULL for this +// parameter +// tore -- Information about the fault +// Returns +// FaultReportResult -- enumeration indicating the +// FaultReportResultAbort -- if Watson could not execute normally +// FaultReportResultDebug -- if Watson executed normally, and the user +// chose to debug the process +// FaultReportResultQuit -- if Watson executed normally, and the user +// chose to end the process (e.g. pressed "Send Error Report" or +// "Don't Send"). +// +// Exceptions +// None. +//------------------------------------------------------------------------------ +FaultReportResult DoFaultReport( // Was Watson attempted, successful? Run debugger? + EXCEPTION_POINTERS *pExceptionInfo, // Information about the fault. + TypeOfReportedError tore) // What sort of error is this? +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(!RunningOnWin7()); + + LOG((LF_EH, LL_INFO100, "DoFaultReport: at sp %p ...\n", GetCurrentSP())); + + Thread *pThread = GetThread(); + +#ifdef FEATURE_CORECLR + // If watson isn't available (eg. in Silverlight), then use a simple dialog box instead + if (!IsWatsonEnabled()) + { + if (!pThread) + { + return FaultReportResultAbort; + } + + // Since the StackOverflow handler also calls us, we must keep our stack budget + // to a minimum. Thus, we will launch a thread to do the actual work. + FaultReportInfo fri; + fri.m_fDoReportFault = FALSE; + fri.m_pExceptionInfo = pExceptionInfo; + fri.m_threadid = GetCurrentThreadId(); + // DoFaultCreateThreadReportCallback will overwrite this - if it doesn't, we'll assume it failed. + fri.m_faultReportResult = FaultReportResultAbort; + + GCX_PREEMP(); + + + if (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_STACK_OVERFLOW) + { + DoFaultReportCreateThreadCallback(&fri); + } + else + { + // Stack overflow case - we don't have enough stack on our own thread so let the debugger + // helper thread do the work. + if (!g_pDebugInterface || FAILED(g_pDebugInterface->RequestFavor(DoFaultReportDoFavorCallback, &fri))) + { + // If we can't initialize the debugger helper thread or we are running on the debugger helper + // thread, give it up. We don't have enough stack space. + + } + } + + return fri.m_faultReportResult; + } +#endif // FEATURE_CORECLR + +#ifdef FEATURE_UEF_CHAINMANAGER + if (g_pUEFManager && !tore.IsUserBreakpoint()) + { + IWatsonSxSManager * pWatsonSxSManager = g_pUEFManager->GetWastonSxSManagerInstance(); + + // Has Watson report been triggered? + if (pWatsonSxSManager->HasWatsonBeenTriggered()) + { + LOG((LF_EH, LL_INFO100, "DoFaultReport: Watson has been triggered.")); + LeaveRuntimeHolderNoThrow holder(reinterpret_cast< size_t >(WaitForSingleObject)); + pWatsonSxSManager->WaitForWatsonSxSCompletionEvent(); + return FaultReportResultQuit; + } + // The unhandled exception is thrown by the current runtime. + else if (IsExceptionFromManagedCode(pExceptionInfo->ExceptionRecord)) + { + // Is the current runtime allowed to report Watson? + if (!pWatsonSxSManager->IsCurrentRuntimeAllowedToReportWatson()) + { + LOG((LF_EH, LL_INFO100, "DoFaultReport: Watson is reported by another runtime.")); + LeaveRuntimeHolderNoThrow holder(reinterpret_cast< size_t >(WaitForSingleObject)); + pWatsonSxSManager->WaitForWatsonSxSCompletionEvent(); + return FaultReportResultQuit; + } + } + // The unhandled exception is thrown by another runtime in the process. + else if (pWatsonSxSManager->IsExceptionClaimed(pExceptionInfo->ExceptionRecord)) + { + LOG((LF_EH, LL_INFO100, "DoFaultReport: Watson will be reported by another runtime.\n")); + return FaultReportResultQuit; + } + // The unhandled exception is thrown by native code. + else + { + // Is the current runtime allowed to report Watson? + if (!pWatsonSxSManager->IsCurrentRuntimeAllowedToReportWatson()) + { + LOG((LF_EH, LL_INFO100, "DoFaultReport: Watson is reported by another runtime.")); + LeaveRuntimeHolderNoThrow holder(reinterpret_cast< size_t >(WaitForSingleObject)); + pWatsonSxSManager->WaitForWatsonSxSCompletionEvent(); + return FaultReportResultQuit; + } + } + } +#endif // FEATURE_UEF_CHAINMANAGER + + // Check if the current thread has the permission to open a process handle of the current process. + // If not, the current thread may have been impersonated, we have to launch Watson from a new thread as in SO case. + BOOL fOpenProcessFailed = FALSE; + if (pExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_STACK_OVERFLOW) + { + HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId()); + fOpenProcessFailed = hProcess == NULL; + } + + if ((pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW) || fOpenProcessFailed) + { + + WatsonThreadData* pData = new(nothrow) WatsonThreadData( + pExceptionInfo, + tore, + pThread, + GetCurrentThreadId(), + FaultReportResultAbort); // default result + + if (pData == NULL) + { + return FaultReportResultAbort; + } + + GCX_PREEMP(); + + if (!g_pDebugInterface || + FAILED(g_pDebugInterface->RequestFavor(DoFaultReportFavorWorker, pData))) + { + // If we can't initialize the debugger helper thread or we are running on the debugger helper + // thread, return without invoking Watson. We don't have enough stack space. + + delete pData; + return FaultReportResultAbort; + } + + FaultReportResult ret = pData->result; + delete pData; + return ret; + } + + return DoFaultReportWorker(pExceptionInfo, tore, GetThread(), GetCurrentThreadId()); +} // FaultReportResult DoFaultReport() + +// look at the type of the contract failure. if it's a precondition then we want to blame the caller +// of the method that originated the ContractException not just the first non-contract runtime frame. +// if this isn't a ContractException then we default to Invariant which won't skip the extra frame. +ContractFailureKind GetContractFailureKind(OBJECTREF obj) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(obj != NULL); + } + CONTRACTL_END; + + PTR_MethodTable pMT = obj->GetMethodTable(); + + if (MscorlibBinder::IsException(pMT, kContractException)) + return CONTRACTEXCEPTIONREF(obj)->GetContractFailureKind(); + + // there are cases where the code contracts rewriter will use a ContractException + // type that's compiled into the user's assembly. if we get here then this is + // one of those cases. we will make a best guess if this is a ContractException + // so that we can return the value in the _Kind field. + + // NOTE: this really isn't meant to be a general-purpose solution for identifying ContractException types. + // we're making a few assumptions here since we're being called in context of WER bucket parameter generation. + + // just return anything that isn't precondition so that an extra frame won't be skipped. + ContractFailureKind result = CONTRACT_FAILURE_INVARIANT; + + // first compare the exception name. + PTR_MethodTable pContractExceptionMT = MscorlibBinder::GetClassIfExist(CLASS__CONTRACTEXCEPTION); + _ASSERTE(pContractExceptionMT); + + if (pContractExceptionMT) + { + LPCUTF8 contractExceptionNamespace = NULL; + LPCUTF8 contractExceptionName = pContractExceptionMT->GetFullyQualifiedNameInfo(&contractExceptionNamespace); + _ASSERTE(contractExceptionName); + + LPCUTF8 incomingExceptionNamespace = NULL; + LPCUTF8 incomingExceptionName = pMT->GetFullyQualifiedNameInfo(&incomingExceptionNamespace); + _ASSERTE(incomingExceptionName); + + // NOTE: we can't compare the namespaces since sometimes it comes back as an empty string + if (contractExceptionName && incomingExceptionName && strcmp(incomingExceptionName, contractExceptionName) == 0) + { + WORD requiredNumFields = pContractExceptionMT->GetNumInstanceFields(); + WORD numFields = pMT->GetNumInstanceFields(); + + // now see if this exception object has the required number of fields + if (numFields == requiredNumFields) + { + // getting closer, now look for all three fields on ContractException + const int requiredFieldMatches = 3; + + PTR_EEClass pEEClass = pMT->GetClass_NoLogging(); + + PTR_FieldDesc pFD = pEEClass->GetFieldDescList(); + PTR_FieldDesc pFDEnd = pFD + numFields; + PTR_FieldDesc pKindFD = NULL; + + int numMatchedFields = 0; + while ((pFD < pFDEnd) && (numMatchedFields != requiredFieldMatches)) + { + CorElementType fieldType = pFD->GetFieldType(); + if (fieldType == ELEMENT_TYPE_I4) + { + // found the _Kind field + LPCUTF8 name = NULL; + HRESULT hr = pFD->GetName_NoThrow(&name); + if (SUCCEEDED(hr) && name && (strcmp(name, "_Kind") == 0)) + { + // found the _Kind field, remember this FieldDesc in case we have a match + pKindFD = pFD; + ++numMatchedFields; + } + } + else if (fieldType == ELEMENT_TYPE_CLASS) + { + LPCUTF8 name = NULL; + HRESULT hr = pFD->GetName_NoThrow(&name); + if (SUCCEEDED(hr) && name && ((strcmp(name, "_UserMessage") == 0) || (strcmp(name, "_Condition") == 0))) + { + // found another matching field + ++numMatchedFields; + } + } + + ++pFD; + } + + if (numMatchedFields == requiredFieldMatches) + { + _ASSERTE(pKindFD != NULL); + ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); + pKindFD->GetInstanceField(obj, reinterpret_cast<void*>(&result)); + } + } + } + } + + return result; +} |