diff options
Diffstat (limited to 'src/debug/ee/rcthread.cpp')
-rw-r--r-- | src/debug/ee/rcthread.cpp | 2209 |
1 files changed, 2209 insertions, 0 deletions
diff --git a/src/debug/ee/rcthread.cpp b/src/debug/ee/rcthread.cpp new file mode 100644 index 0000000000..7e6f1ae304 --- /dev/null +++ b/src/debug/ee/rcthread.cpp @@ -0,0 +1,2209 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// File: RCThread.cpp +// + +// +// Runtime Controller Thread +// +//***************************************************************************** + +#include "stdafx.h" + + +#include "securitywrapper.h" +#include <aclapi.h> +#include <hosting.h> + +#include "ipcmanagerinterface.h" +#include "eemessagebox.h" +#include "genericstackprobe.h" + +#ifndef SM_REMOTESESSION +#define SM_REMOTESESSION 0x1000 +#endif + +#include <limits.h> + +#ifdef _DEBUG +// Declare statics +EEThreadId DebuggerRCThread::s_DbgHelperThreadId; +#endif + +// +// Constructor +// +DebuggerRCThread::DebuggerRCThread(Debugger * pDebugger) + : m_debugger(pDebugger), + m_pDCB(NULL), + m_thread(NULL), + m_run(true), + m_threadControlEvent(NULL), + m_helperThreadCanGoEvent(NULL), + m_fDetachRightSide(false) +{ + CONTRACTL + { + SO_INTOLERANT; + WRAPPER(THROWS); + GC_NOTRIGGER; + CONSTRUCTOR_CHECK; + } + CONTRACTL_END; + + _ASSERTE(pDebugger != NULL); + + for( int i = 0; i < IPC_TARGET_COUNT;i++) + { + m_rgfInitRuntimeOffsets[i] = true; + } + + // Initialize this here because we Destroy it in the DTOR. + // Note that this function can't fail. +} + + +// +// Destructor. Cleans up all of the open handles the RC thread uses. +// This expects that the RC thread has been stopped and has terminated +// before being called. +// +DebuggerRCThread::~DebuggerRCThread() +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + DESTRUCTOR_CHECK; + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO1000, "DebuggerRCThread::~DebuggerRCThread\n")); + + // We explicitly leak the debugger object on shutdown. See Debugger::StopDebugger for details. + _ASSERTE(!"RCThread dtor should not be called."); +} + + + +//--------------------------------------------------------------------------------------- +// +// Close the IPC events associated with a debugger connection +// +// Notes: +// The only IPC connection supported is OOP. +// +//--------------------------------------------------------------------------------------- +void DebuggerRCThread::CloseIPCHandles() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if( m_pDCB != NULL) + { + m_pDCB->m_rightSideProcessHandle.Close(); + } +} + +//----------------------------------------------------------------------------- +// Helper to get the proper decorated name +// Caller ensures that pBufSize is large enough. We'll assert just to check, +// but no runtime failure. +// pBuf - the output buffer to write the decorated name in +// cBufSizeInChars - the size of the buffer in characters, including the null. +// pPrefx - The undecorated name of the event. +//----------------------------------------------------------------------------- +void GetPidDecoratedName(__out_z __in_ecount(cBufSizeInChars) WCHAR * pBuf, + int cBufSizeInChars, + const WCHAR * pPrefix) +{ + LIMITED_METHOD_CONTRACT; + + DWORD pid = GetCurrentProcessId(); + + GetPidDecoratedName(pBuf, cBufSizeInChars, pPrefix, pid); +} + + + + +//----------------------------------------------------------------------------- +// Simple wrapper to create win32 events. +// This helps make DebuggerRCThread::Init pretty, beccause we +// create lots of events there. +// These will either: +// 1) Create/Open and return an event +// 2) or throw an exception. +// @todo - should these be CLREvents? ClrCreateManualEvent / ClrCreateAutoEvent +//----------------------------------------------------------------------------- +HANDLE CreateWin32EventOrThrow( + LPSECURITY_ATTRIBUTES lpEventAttributes, + EEventResetType eType, + BOOL bInitialState +) +{ + CONTRACT(HANDLE) + { + THROWS; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(lpEventAttributes, NULL_OK)); + POSTCONDITION(RETVAL != NULL); + } + CONTRACT_END; + + HANDLE h = NULL; + h = WszCreateEvent(lpEventAttributes, (BOOL) eType, bInitialState, NULL); + + if (h == NULL) + ThrowLastError(); + + RETURN h; +} + +//----------------------------------------------------------------------------- +// Open an event. Another helper for DebuggerRCThread::Init +//----------------------------------------------------------------------------- +HANDLE OpenWin32EventOrThrow( + DWORD dwDesiredAccess, + BOOL bInheritHandle, + LPCWSTR lpName +) +{ + CONTRACT(HANDLE) + { + THROWS; + GC_NOTRIGGER; + POSTCONDITION(RETVAL != NULL); + } + CONTRACT_END; + + HANDLE h = WszOpenEvent( + dwDesiredAccess, + bInheritHandle, + lpName + ); + if (h == NULL) + ThrowLastError(); + + RETURN h; +} + +//----------------------------------------------------------------------------- +// Holder for IPC SecurityAttribute +//----------------------------------------------------------------------------- +IPCHostSecurityAttributeHolder::IPCHostSecurityAttributeHolder(DWORD pid) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + m_pSA = NULL; + +#ifdef FEATURE_IPCMAN + HRESULT hr = CCLRSecurityAttributeManager::GetHostSecurityAttributes(&m_pSA); + IfFailThrow(hr); + + _ASSERTE(m_pSA != NULL); +#endif // FEATURE_IPCMAN +} + +SECURITY_ATTRIBUTES * IPCHostSecurityAttributeHolder::GetHostSA() +{ + LIMITED_METHOD_CONTRACT; + return m_pSA; +} + + +IPCHostSecurityAttributeHolder::~IPCHostSecurityAttributeHolder() +{ + LIMITED_METHOD_CONTRACT; + +#ifdef FEATURE_IPCMAN + CCLRSecurityAttributeManager::DestroyHostSecurityAttributes(m_pSA); +#endif // FEATURE_IPCMAN +} + + +//--------------------------------------------------------------------------------------- +// +// Init +// +// Initialize the IPC block. +// +// Arguments: +// hRsea - Handle to Right-Side Event Available event. +// hRser - Handle to Right-Side Event Read event. +// hLsea - Handle to Left-Side Event Available event. +// hLser - Handle to Left-Side Event Read event. +// hLsuwe - Handle to Left-Side unmanaged wait event. +// +// Notes: +// The Init method works since there are no virtual functions - don't add any virtual functions without +// changing this! +// We assume ownership of the handles as soon as we're called; regardless of our success. +// On failure, we throw. +// Initialization of the debugger control block occurs partly on the left side and partly on +// the right side. This initialization occurs in parallel, so it's unsafe to make assumptions about +// the order in which the fields will be initialized. +// +// +//--------------------------------------------------------------------------------------- +HRESULT DebuggerIPCControlBlock::Init( + HANDLE hRsea, + HANDLE hRser, + HANDLE hLsea, + HANDLE hLser, + HANDLE hLsuwe +) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // NOTE this works since there are no virtual functions - don't add any without changing this! + // Although we assume the IPC block is zero-initialized by the OS upon creation, we still need to clear + // the memory here to protect ourselves from DOS attack. One scenario is when a malicious debugger + // pre-creates a bogus IPC block. This means that our synchronization scheme won't work in DOS + // attack scenarios, but we will be messed up anyway. + // WARNING!!! m_DCBSize is used as a semaphore and is set to non-zero to signal that initialization of the + // WARNING!!! DCB is complete. if you remove the below memset be sure to initialize m_DCBSize to zero in the ctor! + memset( this, 0, sizeof( DebuggerIPCControlBlock) ); + + // Setup version checking info. + m_verMajor = VER_PRODUCTBUILD; + m_verMinor = VER_PRODUCTBUILD_QFE; + +#ifdef _DEBUG + m_checkedBuild = true; +#else + m_checkedBuild = false; +#endif + m_bHostingInFiber = false; + + // Are we in fiber mode? In Whidbey, we do not support launch a fiber mode process + // nor do we support attach to a fiber mode process. + // + if (g_CORDebuggerControlFlags & DBCF_FIBERMODE) + { + m_bHostingInFiber = true; + } + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif + // Copy RSEA and RSER into the control block. + if (!m_rightSideEventAvailable.SetLocal(hRsea)) + { + ThrowLastError(); + } + + if (!m_rightSideEventRead.SetLocal(hRser)) + { + ThrowLastError(); + } + + if (!m_leftSideUnmanagedWaitEvent.SetLocal(hLsuwe)) + { + ThrowLastError(); + } +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } +#endif // !FEATURE_DBGIPC_TRANSPORT_VM + + + // Mark the debugger special thread list as not dirty, empty and null. + m_specialThreadListDirty = false; + m_specialThreadListLength = 0; + m_specialThreadList = NULL; + + m_shutdownBegun = false; + + return S_OK; +} + +#ifdef FEATURE_IPCMAN +extern CCLRSecurityAttributeManager s_CLRSecurityAttributeManager; +#endif // FEATURE_IPCMAN + + +void DebuggerRCThread::WatchForStragglers(void) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(m_threadControlEvent != NULL); + LOG((LF_CORDB,LL_INFO100000, "DRCT::WFS:setting event to watch " + "for stragglers\n")); + + SetEvent(m_threadControlEvent); +} + +//--------------------------------------------------------------------------------------- +// +// Init sets up all the objects that the RC thread will need to run. +// +// +// Return Value: +// S_OK on success. May also throw. +// +// Assumptions: +// Called during startup, even if we're not debugging. +// +// +//--------------------------------------------------------------------------------------- +HRESULT DebuggerRCThread::Init(void) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + PRECONDITION(!ThisIsHelperThreadWorker()); // initialized by main thread + } + CONTRACTL_END; + + + LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThreadInit called\n")); + + DWORD dwStatus; + if (m_debugger == NULL) + { + ThrowHR(E_INVALIDARG); + } + + // Init should only be called once. + if (g_pRCThread != NULL) + { + ThrowHR(E_FAIL); + } + + g_pRCThread = this; + + m_favorData.Init(); // throws + + + // Create the thread control event. + m_threadControlEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); + + // Create the helper thread can go event. + m_helperThreadCanGoEvent = CreateWin32EventOrThrow(NULL, kManualResetEvent, TRUE); + + m_pDCB = new(nothrow) DebuggerIPCControlBlock; + + // Don't fail out because the shared memory failed to create +#if _DEBUG + if (m_pDCB == NULL) + { + LOG((LF_CORDB, LL_INFO10000, + "DRCT::I: Failed to get Debug IPC block.\n")); + } +#endif // _DEBUG + + HRESULT hr; + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif + IPCHostSecurityAttributeHolder sa(GetCurrentProcessId()); + + // Create the events that the thread will need to receive events + // from the out of process piece on the right side. + // We will not fail out if CreateEvent fails for RSEA or RSER. Because + // the worst case is that debugger cannot attach to debuggee. + // + HandleHolder rightSideEventAvailable(WszCreateEvent(sa.GetHostSA(), (BOOL) kAutoResetEvent, FALSE, NULL)); + + // Security fix: + // We need to check the last error to see if the event was precreated or not + // If so, we need to release the handle right now. + // + dwStatus = GetLastError(); + if (dwStatus == ERROR_ALREADY_EXISTS) + { + // clean up the handle now + rightSideEventAvailable.Clear(); + } + + HandleHolder rightSideEventRead(WszCreateEvent(sa.GetHostSA(), (BOOL) kAutoResetEvent, FALSE, NULL)); + + // Security fix: + // We need to check the last error to see if the event was precreated or not + // If so, we need to release the handle right now. + // + dwStatus = GetLastError(); + if (dwStatus == ERROR_ALREADY_EXISTS) + { + // clean up the handle now + rightSideEventRead.Clear(); + } + + + HandleHolder leftSideUnmanagedWaitEvent(CreateWin32EventOrThrow(NULL, kManualResetEvent, FALSE)); + + // Copy RSEA and RSER into the control block only if shared memory is created without error. + if (m_pDCB) + { + // Since Init() gets ownership of handles as soon as it's called, we can + // release our ownership now. + rightSideEventAvailable.SuppressRelease(); + rightSideEventRead.SuppressRelease(); + leftSideUnmanagedWaitEvent.SuppressRelease(); + + // NOTE: initialization of the debugger control block occurs partly on the left side and partly on + // the right side. This initialization occurs in parallel, so it's unsafe to make assumptions about + // the order in which the fields will be initialized. + hr = m_pDCB->Init(rightSideEventAvailable, + rightSideEventRead, + NULL, + NULL, + leftSideUnmanagedWaitEvent); + + _ASSERTE(SUCCEEDED(hr)); // throws on error. + } + +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } + else + { + if (m_pDCB) + { + hr = m_pDCB->Init(NULL, NULL, NULL, NULL, NULL); + _ASSERTE(SUCCEEDED(hr)); // throws on error. + } + } +#endif + + if(m_pDCB) + { + // We have to ensure that most of the runtime offsets for the out-of-proc DCB are initialized right away. This is + // needed to support certian races during an interop attach. Since we can't know whether an interop attach will ever + // happen or not, we are forced to do this now. Note: this is really too early, as some data structures haven't been + // initialized yet! + hr = EnsureRuntimeOffsetsInit(IPC_TARGET_OUTOFPROC); + _ASSERTE(SUCCEEDED(hr)); // throw on error + + // Note: we have to mark that we need the runtime offsets re-initialized for the out-of-proc DCB. This is because + // things like the patch table aren't initialized yet. Calling NeedRuntimeOffsetsReInit() ensures that this happens + // before we really need the patch table. + NeedRuntimeOffsetsReInit(IPC_TARGET_OUTOFPROC); + + m_pDCB->m_helperThreadStartAddr = (void *) DebuggerRCThread::ThreadProcStatic; + m_pDCB->m_helperRemoteStartAddr = (void *) DebuggerRCThread::ThreadProcRemote; + m_pDCB->m_leftSideProtocolCurrent = CorDB_LeftSideProtocolCurrent; + m_pDCB->m_leftSideProtocolMinSupported = CorDB_LeftSideProtocolMinSupported; + + LOG((LF_CORDB, LL_INFO10, + "DRCT::I: version info: %d.%d.%d current protocol=%d, min protocol=%d\n", + m_pDCB->m_verMajor, + m_pDCB->m_verMinor, + m_pDCB->m_checkedBuild, + m_pDCB->m_leftSideProtocolCurrent, + m_pDCB->m_leftSideProtocolMinSupported)); + + // Left-side always creates helper-thread. + // @dbgtodo inspection - by end of V3, LS will never create helper-thread :) + m_pDCB->m_rightSideShouldCreateHelperThread = false; + + // m_DCBSize is used as a semaphore to indicate that the DCB is fully initialized. + // let's ensure that it's updated after all the other fields. + MemoryBarrier(); + m_pDCB->m_DCBSize = sizeof(DebuggerIPCControlBlock); + } + + return S_OK; +} + +#ifndef FEATURE_PAL + +// This function is used to verify the security descriptor on an event +// matches our expectation to prevent attack. This should be called when +// we opened an event by name and assumed that the RS creates the event. +// That means the event's dacl should match our default policy - current user +// and admin. It can be narrower. By default, the DACL looks like the debugger +// process user, debuggee user, and admin. +// +HRESULT DebuggerRCThread::VerifySecurityOnRSCreatedEvents( + HANDLE sse, + HANDLE lsea, + HANDLE lser) +{ + + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + STRESS_LOG0(LF_CORDB,LL_INFO1000,"DRCT::VerifySecurityOnRSCreatedEvents\n"); + + if (lsea == NULL || lser == NULL) + { + // no valid handle, does not need to verify. + // The caller will close the handles + return E_FAIL; + } + + HRESULT hr = S_OK; + + SIZE_T i; + ACCESS_ALLOWED_ACE *pAllowAceSSE = NULL; + ACCESS_ALLOWED_ACE *pAllowAceLSEA = NULL; + ACCESS_ALLOWED_ACE *pAllowAceLSER = NULL; + + + EX_TRY + { + // Get security descriptors for the handles. + Win32SecurityDescriptor sdSSE; + sdSSE.InitFromHandle(sse); + + Win32SecurityDescriptor sdLSEA; + sdLSEA.InitFromHandle(lsea); + + Win32SecurityDescriptor sdLSER; + sdLSER.InitFromHandle(lser); + + + + + // Make sure all 3 have the same creator + // We've already verifed in CreateSetupSyncEvent that the SSE's owner is in the DACL. + if (!Sid::Equals(sdSSE.GetOwner(), sdLSEA.GetOwner()) || + !Sid::Equals(sdSSE.GetOwner(), sdLSER.GetOwner())) + { + // Not equal! return now with failure code. + STRESS_LOG1(LF_CORDB,LL_INFO1000,"DRCT::VSORSCE failed on EqualSid - 0x%08x\n", hr); + ThrowHR(E_FAIL); + } + + // DACL_SECURITY_INFORMATION + // Now verify the DACL. It should be only two of them at most. One of them is the + // target process SID. + Dacl daclSSE = sdSSE.GetDacl(); + Dacl daclLSEA = sdLSEA.GetDacl(); + Dacl daclLSER = sdLSER.GetDacl(); + + + // Now all of these three ACL should be alike. There should be at most two of entries + // there. One if the debugger process's SID and one if debuggee sid. + if ((daclSSE.GetAceCount() != 1) && (daclSSE.GetAceCount() != 2)) + { + ThrowHR(E_FAIL); + } + + + // All of the ace count should equal for all events. + if ((daclSSE.GetAceCount() != daclLSEA.GetAceCount()) || + (daclSSE.GetAceCount() != daclLSER.GetAceCount())) + { + ThrowHR(E_FAIL); + } + + // Now check the ACE inside.These should be all equal + for (i = 0; i < daclSSE.GetAceCount(); i++) + { + ACE_HEADER *pAce; + + // Get the ace from the SSE + pAce = daclSSE.GetAce(i); + if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE) + { + ThrowHR(E_FAIL); + } + pAllowAceSSE = (ACCESS_ALLOWED_ACE*)pAce; + + // Get the ace from LSEA + pAce = daclLSEA.GetAce(i); + if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE) + { + ThrowHR(E_FAIL); + } + pAllowAceLSEA = (ACCESS_ALLOWED_ACE*)pAce; + + // This is the SID + // We can call EqualSid on this pAllowAce->SidStart + if (EqualSid((PSID)&(pAllowAceSSE->SidStart), (PSID)&(pAllowAceLSEA->SidStart)) == FALSE) + { + // ACE not equal. Fail out. + ThrowHR(E_FAIL); + } + + // Get the ace from LSER + pAce = daclLSER.GetAce(i); + if (pAce->AceType != ACCESS_ALLOWED_ACE_TYPE) + { + ThrowHR(E_FAIL); + } + pAllowAceLSER = (ACCESS_ALLOWED_ACE*)pAce; + + if (EqualSid((PSID)&(pAllowAceSSE->SidStart), (PSID)&(pAllowAceLSER->SidStart)) == FALSE) + { + // ACE not equal. Fail out. + ThrowHR(E_FAIL); + } + } // end for loop. + + + // The last ACE should be target process. That is it should be + // our process's sid! + // + if (pAllowAceLSER == NULL) + { + ThrowHR(E_FAIL);; // fail if we don't have the ACE. + } + { + SidBuffer sbCurrentProcess; + sbCurrentProcess.InitFromProcess(GetCurrentProcessId()); + if (!Sid::Equals(sbCurrentProcess.GetSid(), (PSID)&(pAllowAceLSER->SidStart))) + { + ThrowHR(E_FAIL); + } + } + } + EX_CATCH + { + // If we threw an exception, then the verification failed. + hr = E_FAIL; + } + EX_END_CATCH(RethrowTerminalExceptions); + + if (FAILED(hr)) + { + STRESS_LOG1(LF_CORDB,LL_INFO1000,"DRCT::VSORSCE failed with - 0x%08x\n", hr); + } + + return hr; +} + +#endif // FEATURE_PAL + +//--------------------------------------------------------------------------------------- +// +// Setup the Runtime Offsets struct. +// +// Arguments: +// pDebuggerIPCControlBlock - Pointer to the debugger's portion of the IPC +// block, which this routine will write into the offsets of various parts of +// the runtime. +// +// Return Value: +// S_OK on success. +// +//--------------------------------------------------------------------------------------- +HRESULT DebuggerRCThread::SetupRuntimeOffsets(DebuggerIPCControlBlock * pDebuggerIPCControlBlock) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + // Allocate the struct if needed. We just fill in any existing one. + DebuggerIPCRuntimeOffsets * pDebuggerRuntimeOffsets = pDebuggerIPCControlBlock->m_pRuntimeOffsets; + + if (pDebuggerRuntimeOffsets == NULL) + { + // Perhaps we should preallocate this. This is the only allocation + // that would force SendIPCEvent to throw an exception. It'd be very + // nice to have + CONTRACT_VIOLATION(ThrowsViolation); + pDebuggerRuntimeOffsets = new DebuggerIPCRuntimeOffsets(); + _ASSERTE(pDebuggerRuntimeOffsets != NULL); // throws on oom + } + + // Fill out the struct. +#ifdef FEATURE_INTEROP_DEBUGGING + pDebuggerRuntimeOffsets->m_genericHijackFuncAddr = Debugger::GenericHijackFunc; + // Set flares - these only exist for interop debugging. + pDebuggerRuntimeOffsets->m_signalHijackStartedBPAddr = (void*) SignalHijackStartedFlare; + pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffStartBPAddr = (void*) ExceptionForRuntimeHandoffStartFlare; + pDebuggerRuntimeOffsets->m_excepForRuntimeHandoffCompleteBPAddr = (void*) ExceptionForRuntimeHandoffCompleteFlare; + pDebuggerRuntimeOffsets->m_signalHijackCompleteBPAddr = (void*) SignalHijackCompleteFlare; + pDebuggerRuntimeOffsets->m_excepNotForRuntimeBPAddr = (void*) ExceptionNotForRuntimeFlare; + pDebuggerRuntimeOffsets->m_notifyRSOfSyncCompleteBPAddr = (void*) NotifyRightSideOfSyncCompleteFlare; + +#if !defined(FEATURE_CORESYSTEM) + // Grab the address of RaiseException in kernel32 because we have to play some games with exceptions + // that are generated there (just another reason why mixed mode debugging is shady). See bug 476768. + HMODULE hModule = WszGetModuleHandle(W("kernel32.dll")); + _ASSERTE(hModule != NULL); + PREFAST_ASSUME(hModule != NULL); + pDebuggerRuntimeOffsets->m_raiseExceptionAddr = GetProcAddress(hModule, "RaiseException"); + _ASSERTE(pDebuggerRuntimeOffsets->m_raiseExceptionAddr != NULL); + hModule = NULL; +#else + pDebuggerRuntimeOffsets->m_raiseExceptionAddr = NULL; +#endif +#endif // FEATURE_INTEROP_DEBUGGING + + pDebuggerRuntimeOffsets->m_pPatches = DebuggerController::GetPatchTable(); + pDebuggerRuntimeOffsets->m_pPatchTableValid = (BOOL*)DebuggerController::GetPatchTableValidAddr(); + pDebuggerRuntimeOffsets->m_offRgData = DebuggerPatchTable::GetOffsetOfEntries(); + pDebuggerRuntimeOffsets->m_offCData = DebuggerPatchTable::GetOffsetOfCount(); + pDebuggerRuntimeOffsets->m_cbPatch = sizeof(DebuggerControllerPatch); + pDebuggerRuntimeOffsets->m_offAddr = offsetof(DebuggerControllerPatch, address); + pDebuggerRuntimeOffsets->m_offOpcode = offsetof(DebuggerControllerPatch, opcode); + pDebuggerRuntimeOffsets->m_cbOpcode = sizeof(PRD_TYPE); + pDebuggerRuntimeOffsets->m_offTraceType = offsetof(DebuggerControllerPatch, trace.type); + pDebuggerRuntimeOffsets->m_traceTypeUnmanaged = TRACE_UNMANAGED; + + // @dbgtodo inspection - this should all go away or be obtained from DacDbi Primitives. + g_pEEInterface->GetRuntimeOffsets(&pDebuggerRuntimeOffsets->m_TLSIndex, + &pDebuggerRuntimeOffsets->m_TLSIsSpecialIndex, + &pDebuggerRuntimeOffsets->m_TLSCantStopIndex, + &pDebuggerRuntimeOffsets->m_TLSIndexOfPredefs, + &pDebuggerRuntimeOffsets->m_EEThreadStateOffset, + &pDebuggerRuntimeOffsets->m_EEThreadStateNCOffset, + &pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledOffset, + &pDebuggerRuntimeOffsets->m_EEThreadPGCDisabledValue, + &pDebuggerRuntimeOffsets->m_EEThreadDebuggerWordOffset, + &pDebuggerRuntimeOffsets->m_EEThreadFrameOffset, + &pDebuggerRuntimeOffsets->m_EEThreadMaxNeededSize, + &pDebuggerRuntimeOffsets->m_EEThreadSteppingStateMask, + &pDebuggerRuntimeOffsets->m_EEMaxFrameValue, + &pDebuggerRuntimeOffsets->m_EEThreadDebuggerFilterContextOffset, + &pDebuggerRuntimeOffsets->m_EEThreadCantStopOffset, + &pDebuggerRuntimeOffsets->m_EEFrameNextOffset, + &pDebuggerRuntimeOffsets->m_EEIsManagedExceptionStateMask); + +#ifndef FEATURE_IMPLICIT_TLS + _ASSERTE((pDebuggerRuntimeOffsets->m_TLSIndexOfPredefs != 0) || !"CExecutionEngine::TlsIndex is not initialized yet"); +#endif + + // Remember the struct in the control block. + pDebuggerIPCControlBlock->m_pRuntimeOffsets = pDebuggerRuntimeOffsets; + + return S_OK; +} + +struct DebugFilterParam +{ + DebuggerIPCEvent *event; +}; + +// Filter called when we throw an exception while Handling events. +static LONG _debugFilter(LPEXCEPTION_POINTERS ep, PVOID pv) +{ + LOG((LF_CORDB, LL_INFO10, + "Unhandled exception in Debugger::HandleIPCEvent\n")); + + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + +#if defined(_DEBUG) || !defined(FEATURE_CORESYSTEM) + DebuggerIPCEvent *event = ((DebugFilterParam *)pv)->event; + + DWORD pid = GetCurrentProcessId(); + DWORD tid = GetCurrentThreadId(); + + DebuggerIPCEventType type = (DebuggerIPCEventType) (event->type & DB_IPCE_TYPE_MASK); +#endif _DEBUG || !FEATURE_CORESYSTEM + + // We should never AV here. In a debug build, throw up an assert w/ lots of useful (private) info. +#ifdef _DEBUG + { + // We can't really use SStrings on the helper thread; though if we're at this point, we've already died. + // So go ahead and risk it and use them anyways. + SString sStack; + StackScratchBuffer buffer; + GetStackTraceAtContext(sStack, ep->ContextRecord); + const CHAR *string = NULL; + + EX_TRY + { + string = sStack.GetANSI(buffer); + } + EX_CATCH + { + string = "*Could not retrieve stack*"; + } + EX_END_CATCH(RethrowTerminalExceptions); + + CONSISTENCY_CHECK_MSGF(false, + ("Unhandled exception on the helper thread.\nEvent=%s(0x%x)\nCode=0x%0x, Ip=0x%p, .cxr=%p, .exr=%p.\n pid=0x%x (%d), tid=0x%x (%d).\n-----\nStack of exception:\n%s\n----\n", + IPCENames::GetName(type), type, + ep->ExceptionRecord->ExceptionCode, GetIP(ep->ContextRecord), ep->ContextRecord, ep->ExceptionRecord, + pid, pid, tid, tid, + string)); + } +#endif + +// this message box doesn't work well on coresystem... we actually get in a recursive exception handling loop +#ifndef FEATURE_CORESYSTEM + // We took an AV on the helper thread. This is a catastrophic situation so we can + // simply call the EE's catastrophic message box to display the error. + EEMessageBoxCatastrophic( + IDS_DEBUG_UNHANDLEDEXCEPTION_IPC, IDS_DEBUG_SERVICE_CAPTION, + type, + ep->ExceptionRecord->ExceptionCode, + GetIP(ep->ContextRecord), + pid, pid, tid, tid); +#endif + + // For debugging, we can change the behavior by manually setting eax. + // EXCEPTION_EXECUTE_HANDLER=1, EXCEPTION_CONTINUE_SEARCH=0, EXCEPTION_CONTINUE_EXECUTION=-1 + return EXCEPTION_CONTINUE_SEARCH; +} + +#ifdef _DEBUG +// Tracking to ensure that we don't call New() for the normal (non interop-safe heap) +// on the helper thread. We also can't do a normal allocation when we have hard +// suspended any other thread (since it could hold the OS heap lock). + +// TODO: this probably belongs in the EE itself, not here in the debugger stuff. + +void AssertAllocationAllowed() +{ +#ifdef USE_INTEROPSAFE_HEAP + // Don't forget to preserve error status! + DWORD err = GetLastError(); + + // We can mark certain + if (g_DbgSuppressAllocationAsserts == 0) + { + + // if we have hard suspended any threads. We want to assert as it could cause deadlock + // since those suspended threads may hold the OS heap lock + if (g_fEEStarted) { + _ASSERTE (!EEAllocationDisallowed()); + } + + // Can't call IsDbgHelperSpecialThread() here b/c that changes program state. + // So we use our + if (DebuggerRCThread::s_DbgHelperThreadId.IsSameThread()) + { + // In case assert allocates, bump up the 'OK' counter to avoid an infinite recursion. + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + _ASSERTE(false || !"New called on Helper Thread"); + + } + } + SetLastError(err); +#endif +} +#endif + + +//--------------------------------------------------------------------------------------- +// +// Primary function of the Runtime Controller thread. First, we let +// the Debugger Interface know that we're up and running. Then, we run +// the main loop. +// +//--------------------------------------------------------------------------------------- +void DebuggerRCThread::ThreadProc(void) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_TRIGGERS; // Debugger::SuspendComplete can trigger GC + + // Although we're the helper thread, we haven't set it yet. + DISABLED(PRECONDITION(ThisIsHelperThreadWorker())); + + INSTANCE_CHECK; + } + CONTRACTL_END; + + STRESS_LOG_RESERVE_MEM (0); + // This message actually serves a purpose (which is why it is always run) + // The Stress log is run during hijacking, when other threads can be suspended + // at arbitrary locations (including when holding a lock that NT uses to serialize + // all memory allocations). By sending a message now, we insure that the stress + // log will not allocate memory at these critical times an avoid deadlock. + { + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + STRESS_LOG0(LF_CORDB|LF_ALWAYS, LL_ALWAYS, "Debugger Thread spinning up\n"); + + // Call this to force creation of the TLS slots on helper-thread. + IsDbgHelperSpecialThread(); + } + +#ifdef _DEBUG + // Track the helper thread. + s_DbgHelperThreadId.SetThreadId(); +#endif + CantAllocHolder caHolder; + + +#ifdef _DEBUG + // Cause wait in the helper thread startup. This lets us test against certain races. + // 1 = 6 sec. (shorter than Poll) + // 2 = 12 sec (longer than Poll). + // 3 = infinite - never comes up. + static int fDelayHelper = -1; + + if (fDelayHelper == -1) + { + fDelayHelper = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgDelayHelper); + } + + if (fDelayHelper) + { + DWORD dwSleep = 6000; + + switch(fDelayHelper) + { + case 1: dwSleep = 6000; break; + case 2: dwSleep = 12000; break; + case 3: dwSleep = INFINITE; break; + } + + ClrSleepEx(dwSleep, FALSE); + } +#endif + + LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread spinning up...\n")); + + // In case the shared memory is not initialized properly, it will be noop + if (m_pDCB == NULL) + { + return; + } + + // Lock the debugger before spinning up. + Debugger::DebuggerLockHolder debugLockHolder(m_debugger); + + if (m_pDCB->m_helperThreadId != 0) + { + // someone else has created a helper thread, we're outta here + // the most likely scenario here is that there was some kind of + // race between remotethread creation and localthread creation + + LOG((LF_CORDB, LL_EVERYTHING, "Second debug helper thread creation detected, thread will safely suicide\n")); + // dbgLockHolder goes out of scope - implicit Release + return; + } + + // this thread took the lock and there is no existing m_helperThreadID therefore + // this *IS* the helper thread and nobody else can be the helper thread + + // the handle was created by the Start method + _ASSERTE(m_thread != NULL); + +#ifdef _DEBUG + // Make sure that we have the proper permissions. + { + DWORD dwWaitResult = WaitForSingleObject(m_thread, 0); + _ASSERTE(dwWaitResult == WAIT_TIMEOUT); + } +#endif + + // Mark that we're the true helper thread. Now that we've marked + // this, no other threads will ever become the temporary helper + // thread. + m_pDCB->m_helperThreadId = GetCurrentThreadId(); + + LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: helper thread id is 0x%x helperThreadId\n", + m_pDCB->m_helperThreadId)); + + // If there is a temporary helper thread, then we need to wait for + // it to finish being the helper thread before we can become the + // helper thread. + if (m_pDCB->m_temporaryHelperThreadId != 0) + { + LOG((LF_CORDB, LL_INFO1000, + "DRCT::TP: temporary helper thread 0x%x is in the way, " + "waiting...\n", + m_pDCB->m_temporaryHelperThreadId)); + + debugLockHolder.Release(); + + // Wait for the temporary helper thread to finish up. + DWORD dwWaitResult = WaitForSingleObject(m_helperThreadCanGoEvent, INFINITE); + (void)dwWaitResult; //prevent "unused variable" error from GCC + + LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: done waiting for temp help to finish up.\n")); + + _ASSERTE(dwWaitResult == WAIT_OBJECT_0); + _ASSERTE(m_pDCB->m_temporaryHelperThreadId==0); + } + else + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::TP: no temp help in the way...\n")); + + debugLockHolder.Release(); + } + + // Run the main loop as the true helper thread. + MainLoop(); +} + +void DebuggerRCThread::RightSideDetach(void) +{ + _ASSERTE( m_fDetachRightSide == false ); + m_fDetachRightSide = true; +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif + CloseIPCHandles(); +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } +#endif // !FEATURE_DBGIPC_TRANSPORT_VM +} + +// +// These defines control how many times we spin while waiting for threads to sync and how often. Note its higher in +// debug builds to allow extra time for threads to sync. +// +#define CorDB_SYNC_WAIT_TIMEOUT 20 // 20ms + +#ifdef _DEBUG +#define CorDB_MAX_SYNC_SPIN_COUNT (10000 / CorDB_SYNC_WAIT_TIMEOUT) // (10 seconds) +#else +#define CorDB_MAX_SYNC_SPIN_COUNT (3000 / CorDB_SYNC_WAIT_TIMEOUT) // (3 seconds) +#endif + +// +// NDPWhidbey issue 10749 - Due to a compiler change for vc7.1, +// Don't inline this function! +// PAL_TRY allocates space on the stack and so can not be used within a loop, +// else we'll slowly leak stack space w/ each interation and get an overflow. +// So make this its own function to enforce that we free the stack space between +// iterations. +// +bool HandleIPCEventWrapper(Debugger* pDebugger, DebuggerIPCEvent *e) +{ + struct Param : DebugFilterParam + { + Debugger* pDebugger; + bool wasContinue; + } param; + param.event = e; + param.pDebugger = pDebugger; + param.wasContinue = false; + PAL_TRY(Param *, pParam, ¶m) + { + pParam->wasContinue = pParam->pDebugger->HandleIPCEvent(pParam->event); + } + PAL_EXCEPT_FILTER(_debugFilter) + { + LOG((LF_CORDB, LL_INFO10, "Unhandled exception caught in Debugger::HandleIPCEvent\n")); + } + PAL_ENDTRY + + return param.wasContinue; +} + +bool DebuggerRCThread::HandleRSEA() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + if (g_pEEInterface->GetThread() != NULL) { GC_TRIGGERS; } else { GC_NOTRIGGER; } + PRECONDITION(ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO10000, "RSEA from out of process (right side)\n")); + DebuggerIPCEvent * e; +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif + // Make room for any Right Side event on the stack. + BYTE buffer[CorDBIPC_BUFFER_SIZE]; + e = (DebuggerIPCEvent *) buffer; + + // If the RSEA is signaled, then handle the event from the Right Side. + memcpy(e, GetIPCEventReceiveBuffer(), CorDBIPC_BUFFER_SIZE); +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } + else + { + // Be sure to fetch the event into the official receive buffer since some event handlers assume it's there + // regardless of the the event buffer pointer passed to them. + e = GetIPCEventReceiveBuffer(); + g_pDbgTransport->GetNextEvent(e, CorDBIPC_BUFFER_SIZE); + } +#endif // !FEATURE_DBGIPC_TRANSPOPRT + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + if(!useTransport) + { +#endif + // If no reply is required, then let the Right Side go since we've got a copy of the event now. + _ASSERTE(!e->asyncSend || !e->replyRequired); + + if (!e->replyRequired && !e->asyncSend) + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: no reply required, letting Right Side go.\n")); + + BOOL succ = SetEvent(m_pDCB->m_rightSideEventRead); + + if (!succ) + CORDBDebuggerSetUnrecoverableWin32Error(m_debugger, 0, true); + } +#ifdef LOGGING + else if (e->asyncSend) + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: async send.\n")); + else + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: reply required, holding Right Side...\n")); +#endif +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } +#endif // FEATURE_DBGIPC_TRANSPORT_VM + + // Pass the event to the debugger for handling. Returns true if the event was a Continue event and we can + // stop looking for stragglers. We wrap this whole thing in an exception handler to help us debug faults. + bool wasContinue = false; + + wasContinue = HandleIPCEventWrapper(m_debugger, e); + + return wasContinue; +} + +//--------------------------------------------------------------------------------------- +// +// Main loop of the Runtime Controller thread. It waits for IPC events +// and dishes them out to the Debugger object for processing. +// +// Some of this logic is copied in Debugger::VrpcToVls +// +//--------------------------------------------------------------------------------------- +void DebuggerRCThread::MainLoop() +{ + // This function can only be called on native Debugger helper thread. + // + + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + + PRECONDITION(m_thread != NULL); + PRECONDITION(ThisIsHelperThreadWorker()); + PRECONDITION(IsDbgHelperSpecialThread()); // Can only be called on native debugger helper thread + PRECONDITION((!ThreadStore::HoldingThreadStore()) || g_fProcessDetach); + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: running main loop\n")); + + // Anbody doing helper duty is in a can't-stop range, period. + // Our helper thread is already in a can't-stop range, so this is particularly useful for + // threads doing helper duty. + CantStopHolder cantStopHolder; + + HANDLE rghWaitSet[DRCT_COUNT_FINAL]; + +#ifdef _DEBUG + DWORD dwSyncSpinCount = 0; +#endif + + // We start out just listening on RSEA and the thread control event... + unsigned int cWaitCount = DRCT_COUNT_INITIAL; + DWORD dwWaitTimeout = INFINITE; + rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; + rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif // !FEATURE_DBGIPC_TRANSPORT_VM + rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable; +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } + else + { + rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); + } +#endif // !FEATURE_DBGIPC_TRANSPORT_VM + + CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH + + // Lock holder. Don't take it yet. We take lock on this when we succeeded suspended runtime. + // We will release the lock later when continue happens and runtime resumes + Debugger::DebuggerLockHolder debugLockHolderSuspended(m_debugger, false); + + while (m_run) + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n")); + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif // FEATURE_DBGIPC_TRANSPORT_VM + // If there is a debugger attached, wait on its handle, too... + if ((cWaitCount == DRCT_COUNT_INITIAL) && + m_pDCB->m_rightSideProcessHandle.ImportToLocalProcess() != NULL) + { + _ASSERTE((cWaitCount + 1) == DRCT_COUNT_FINAL); + rghWaitSet[DRCT_DEBUGGER_EVENT] = m_pDCB->m_rightSideProcessHandle; + cWaitCount = DRCT_COUNT_FINAL; + } +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } +#endif // FEATURE_DBGIPC_TRANSPORT_VM + + if (m_fDetachRightSide) + { + m_fDetachRightSide = false; + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + if(!useTransport) + { +#endif + _ASSERTE(cWaitCount == DRCT_COUNT_FINAL); + _ASSERTE((cWaitCount - 1) == DRCT_COUNT_INITIAL); + + rghWaitSet[DRCT_DEBUGGER_EVENT] = NULL; + cWaitCount = DRCT_COUNT_INITIAL; +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + } +#endif // !FEATURE_DBGIPC_TRANSPORT_VM + } + + // Wait for an event from the Right Side. + DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE); + + if (!m_run) + { + continue; + } + + + if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT) + { + // If the handle of the right side process is signaled, then we've lost our controlling debugger. We + // terminate this process immediatley in such a case. + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: terminating this process. Right Side has exited.\n")); + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + EEPOLICY_HANDLE_FATAL_ERROR(0); + _ASSERTE(!"Should never reach this point."); + } + else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL) + { + // execute the callback set by DoFavor() + FAVORCALLBACK fpCallback = GetFavorFnPtr(); + // We never expect the callback to be null unless some other component + // wrongly signals our event (see DD 463807). + // In case we messed up, we will not set the FavorReadEvent and will hang favor requesting thread. + if (fpCallback) + { + (*fpCallback)(GetFavorData()); + SetEvent(GetFavorReadEvent()); + } + } + else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA) + { + bool fWasContinue = HandleRSEA(); + + if (fWasContinue) + { + + // If they called continue, then we must have released the TSL. + _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach); + + // Let's release the lock here since runtime is resumed. + debugLockHolderSuspended.Release(); + + // This debugger thread shoud not be holding debugger locks anymore + _ASSERTE(!g_pDebugger->ThreadHoldsLock()); +#ifdef _DEBUG + // Always reset the syncSpinCount to 0 on a continue so that we have the maximum number of possible + // spins the next time we need to sync. + dwSyncSpinCount = 0; +#endif + + if (dwWaitTimeout != INFINITE) + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: don't check for stragglers due to continue.\n")); + + dwWaitTimeout = INFINITE; + } + + } + } + else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT) + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: straggler event set.\n")); + + Debugger::DebuggerLockHolder debugLockHolder(m_debugger); + // Make sure that we're still synchronizing... + if (m_debugger->IsSynchronizing()) + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: dropping the timeout.\n")); + + dwWaitTimeout = CorDB_SYNC_WAIT_TIMEOUT; + + // + // Skip waiting the first time and just give it a go. Note: Implicit + // release of the lock, because we are leaving its scope. + // + goto LWaitTimedOut; + } +#ifdef LOGGING + else + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: told to wait, but not syncing anymore.\n")); +#endif + // dbgLockHolder goes out of scope - implicit Release + } + else if (dwWaitResult == WAIT_TIMEOUT) + { + +LWaitTimedOut: + + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: wait timed out.\n")); + + // Debugger::DebuggerLockHolder debugLockHolder(m_debugger); + // Explicitly get the lock here since we try to check to see if + // have suspended. We will release the lock if we are not suspended yet. + // + debugLockHolderSuspended.Acquire(); + + // We should still be synchronizing, otherwise we would not have timed out. + _ASSERTE(m_debugger->IsSynchronizing()); + + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML:: sweeping the thread list.\n")); + +#ifdef _DEBUG + // If we fail to suspend the CLR, don't bother waiting for a BVT to timeout, + // fire up an assert up now. + // Threads::m_DebugWillSyncCount+1 is the number of outstanding threads. + // We're trying to suspend any thread w/ TS_DebugWillSync set. + if (dwSyncSpinCount++ > CorDB_MAX_SYNC_SPIN_COUNT) + { + _ASSERTE_MSG(false, "Timeout trying to suspend CLR for debugging. Possibly a deadlock.\n"\ + "You can ignore this assert to continue waiting\n"); + dwSyncSpinCount = 0; + } +#endif + + // Don't call Sweep if we're doing helper thread duty. + // If we're doing helper thread duty, then we already Suspended the CLR, and we already hold the TSL. + bool fSuspended; + { + // SweepThreadsForDebug() may call new!!! ARGG!!! + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + fSuspended = g_pEEInterface->SweepThreadsForDebug(false); + } + + if (fSuspended) + { + STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: wait set empty after sweep.\n"); + + // There are no more threads to wait for, so go ahead and send the sync complete event. + m_debugger->SuspendComplete(); + dwWaitTimeout = INFINITE; + + // Note: we hold the thread store lock now and debugger lock... + + // We also hold debugger lock the whole time that Runtime is stopped. We will release the debugger lock + // when we receive the Continue event that resumes the runtime. + + _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + } + else + { + // If we're doing helper thread duty, then we expect to have been suspended already. + // And so the sweep should always succeed. + STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: threads still syncing after sweep.\n"); + debugLockHolderSuspended.Release(); + } + // debugLockHolderSuspended does not go out of scope. It has to be either released explicitly on the line above or + // we intend to hold the lock till we hit continue event. + + } + } + + STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::ML:: Exiting.\n"); +} + +//--------------------------------------------------------------------------------------- +// +// Main loop of the temporary Helper thread. It waits for IPC events +// and dishes them out to the Debugger object for processing. +// +// Notes: +// When we enter here, we are holding debugger lock and thread store lock. +// The debugger lock was SuppressRelease in DoHelperThreadDuty. The continue event +// that we are waiting for will trigger the corresponding release. +// +// IMPORTANT!!! READ ME!!!! +// This MainLoop is similiar to MainLoop function above but simplified to deal with only +// some scenario. So if you change here, you should look at MainLoop to see if same change is +// required. +//--------------------------------------------------------------------------------------- +void DebuggerRCThread::TemporaryHelperThreadMainLoop() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + + + // If we come in here, this managed thread is trying to do helper thread duty. + // It should be holding the debugger lock!!! + // + PRECONDITION(m_debugger->ThreadHoldsLock()); + PRECONDITION((ThreadStore::HoldingThreadStore()) || g_fProcessDetach); + PRECONDITION(ThisIsTempHelperThread()); + } + CONTRACTL_END; + + STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: Doing helper thread duty, running main loop.\n"); + // Anbody doing helper duty is in a can't-stop range, period. + // Our helper thread is already in a can't-stop range, so this is particularly useful for + // threads doing helper duty. + CantStopHolder cantStopHolder; + + HANDLE rghWaitSet[DRCT_COUNT_FINAL]; + +#ifdef _DEBUG + DWORD dwSyncSpinCount = 0; +#endif + + // We start out just listening on RSEA and the thread control event... + unsigned int cWaitCount = DRCT_COUNT_INITIAL; + DWORD dwWaitTimeout = INFINITE; + rghWaitSet[DRCT_CONTROL_EVENT] = m_threadControlEvent; + rghWaitSet[DRCT_FAVORAVAIL] = GetFavorAvailableEvent(); +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif + rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable; +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } + else + { + rghWaitSet[DRCT_RSEA] = g_pDbgTransport->GetIPCEventReadyEvent(); + } +#endif // !FEATURE_DBGIPC_TRANSPORT_VM + + CONTRACT_VIOLATION(ThrowsViolation);// HndCreateHandle throws, and this loop is not backstopped by any EH + + while (m_run) + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::ML: waiting for event.\n")); + + // Wait for an event from the Right Side. + DWORD dwWaitResult = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE); + + if (!m_run) + { + continue; + } + + + if (dwWaitResult == WAIT_OBJECT_0 + DRCT_DEBUGGER_EVENT) + { + // If the handle of the right side process is signaled, then we've lost our controlling debugger. We + // terminate this process immediatley in such a case. + LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML: terminating this process. Right Side has exited.\n")); + + TerminateProcess(GetCurrentProcess(), 0); + _ASSERTE(!"Should never reach this point."); + } + else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_FAVORAVAIL) + { + // execute the callback set by DoFavor() + (*GetFavorFnPtr())(GetFavorData()); + + SetEvent(GetFavorReadEvent()); + } + else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_RSEA) + { + // @todo: + // We are only interested in dealing with Continue event here... + // Once we remove the HelperThread duty, this will just go away. + // + bool fWasContinue = HandleRSEA(); + + if (fWasContinue) + { + // If they called continue, then we must have released the TSL. + _ASSERTE(!ThreadStore::HoldingThreadStore() || g_fProcessDetach); + +#ifdef _DEBUG + // Always reset the syncSpinCount to 0 on a continue so that we have the maximum number of possible + // spins the next time we need to sync. + dwSyncSpinCount = 0; +#endif + + // HelperThread duty is finished. We have got a Continue message + goto LExit; + } + } + else if (dwWaitResult == WAIT_OBJECT_0 + DRCT_CONTROL_EVENT) + { + LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: straggler event set.\n")); + + // Make sure that we're still synchronizing... + _ASSERTE(m_debugger->IsSynchronizing()); + LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: dropping the timeout.\n")); + + dwWaitTimeout = CorDB_SYNC_WAIT_TIMEOUT; + + // + // Skip waiting the first time and just give it a go. Note: Implicit + // release of the lock, because we are leaving its scope. + // + goto LWaitTimedOut; + } + else if (dwWaitResult == WAIT_TIMEOUT) + { + +LWaitTimedOut: + + LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: wait timed out.\n")); + + // We should still be synchronizing, otherwise we would not have timed out. + _ASSERTE(m_debugger->IsSynchronizing()); + + LOG((LF_CORDB, LL_INFO1000, "DRCT::THTML:: sweeping the thread list.\n")); + +#ifdef _DEBUG + // If we fail to suspend the CLR, don't bother waiting for a BVT to timeout, + // fire up an assert up now. + // Threads::m_DebugWillSyncCount+1 is the number of outstanding threads. + // We're trying to suspend any thread w/ TS_DebugWillSync set. + if (dwSyncSpinCount++ > CorDB_MAX_SYNC_SPIN_COUNT) + { + _ASSERTE(false || !"Timeout trying to suspend CLR for debugging. Possibly a deadlock. " + "You can ignore this assert to continue waiting\n"); + dwSyncSpinCount = 0; + } +#endif + + STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: wait set empty after sweep.\n"); + + // We are holding Debugger lock (Look at the SuppressRelease on the DoHelperThreadDuty) + // The debugger lock will be released on the Continue event which we will then + // exit the loop. + + // There are no more threads to wait for, so go ahead and send the sync complete event. + m_debugger->SuspendComplete(); + dwWaitTimeout = INFINITE; + + // Note: we hold the thread store lock now and debugger lock... + _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + + } + } + +LExit: + + STRESS_LOG0(LF_CORDB, LL_INFO1000, "DRCT::THTML:: Exiting.\n"); +} + + + +// +// This is the thread's real thread proc. It simply calls to the +// thread proc on the RCThread object. +// +/*static*/ DWORD WINAPI DebuggerRCThread::ThreadProcRemote(LPVOID) +{ + // We just wrap create a local thread and we're outta here + WRAPPER_NO_CONTRACT; + + ClrFlsSetThreadType(ThreadType_DbgHelper); + + LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcRemote called\n")); +#ifdef _DEBUG + dbgOnly_IdentifySpecialEEThread(); +#endif + + // this method can be called both by a local createthread or a remote create thread + // so we must use the g_RCThread global to find the (unique!) this pointer + // we cannot count on the parameter. + + DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread; + + // This remote thread is created by the debugger process + // and so its ACLs will reflect permissions for the user running + // the debugger. If this process is running in the context of a + // different user then this (the now running) process will not be + // able to do operations on that (remote) thread. + // + // To avoid this problem, if we are the remote thread, then + // we simply launch a new, local, thread right here and let + // the remote thread die. This new thread is created the same + // way as always, and since it is created by this process + // this process will be able to synchronize with it and so forth + + t->Start(); // this thread is remote, we must start a new thread + + return 0; +} + +// +// This is the thread's real thread proc. It simply calls to the +// thread proc on the RCThread object. +// +/*static*/ DWORD WINAPI DebuggerRCThread::ThreadProcStatic(LPVOID) +{ + // We just wrap the instance method DebuggerRCThread::ThreadProc + WRAPPER_NO_CONTRACT; + + BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD_FORCE_SO(); + + ClrFlsSetThreadType(ThreadType_DbgHelper); + + LOG((LF_CORDB, LL_EVERYTHING, "ThreadProcStatic called\n")); + +#ifdef _DEBUG + dbgOnly_IdentifySpecialEEThread(); +#endif + + // We commit the thread's entire stack to ensure we're robust in low memory conditions. If we can't commit the + // stack, then we can't let the CLR continue to function. + BOOL fSuccess = Thread::CommitThreadStack(NULL); + + if (!fSuccess) + { + STRESS_LOG0(LF_GC, LL_ALWAYS, "Thread::CommitThreadStack failed.\n"); + _ASSERTE(!"Thread::CommitThreadStack failed."); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_STACKOVERFLOW); + } + + DebuggerRCThread* t = (DebuggerRCThread*)g_pRCThread; + + t->ThreadProc(); // this thread is local, go and become the helper + + END_SO_INTOLERANT_CODE; + + return 0; +} + +RCThreadLazyInit * DebuggerRCThread::GetLazyData() +{ + return g_pDebugger->GetRCThreadLazyData(); +} + + +// +// Start actually creates and starts the RC thread. It waits for the thread +// to come up and perform initial synchronization with the Debugger +// Interface before returning. +// +HRESULT DebuggerRCThread::Start(void) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread::Start called...\n")); + + DWORD helperThreadId; + + if (m_thread != NULL) + { + LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread::Start declined to start another helper thread...\n")); + return S_OK; + } + + Debugger::DebuggerLockHolder debugLockHolder(m_debugger); + + if (m_thread == NULL) + { + // Create suspended so that we can sniff the tid before the thread actually runs. + // This may not be before the native thread-create event, but should be before everything else. + // Note: strange as it may seem, the Right Side depends on us + // using CreateThread to create the helper thread here. If you + // ever change this to some other thread creation routine, you + // need to update the logic in process.cpp where we discover the + // helper thread on CREATE_THREAD_DEBUG_EVENTs... + m_thread = CreateThread(NULL, 0, DebuggerRCThread::ThreadProcStatic, + NULL, CREATE_SUSPENDED, &helperThreadId ); + + if (m_thread == NULL) + { + LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread failed, err=%d\n", GetLastError())); + hr = HRESULT_FROM_GetLastError(); + + } + else + { + LOG((LF_CORDB, LL_EVERYTHING, "DebuggerRCThread start was successful, id=%d\n", helperThreadId)); + } + + // This gets published immediately. + DebuggerIPCControlBlock* dcb = GetDCB(); + PREFIX_ASSUME(dcb != NULL); + dcb->m_realHelperThreadId = helperThreadId; + +#ifdef _DEBUG + // Record the OS Thread ID for debugging purposes. + m_DbgHelperThreadOSTid = helperThreadId ; +#endif + + if (m_thread != NULL) + { + ResumeThread(m_thread); + } + + } + + // unlock debugger lock is implied. + + return hr; +} + + +//--------------------------------------------------------------------------------------- +// +// Stop causes the RC thread to stop receiving events and exit. +// It does not wait for it to exit before returning (hence "AsyncStop" instead of "Stop"). +// +// Return Value: +// Always S_OK at the moment. +// +//--------------------------------------------------------------------------------------- +HRESULT DebuggerRCThread::AsyncStop(void) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + +#ifdef _TARGET_X86_ + PRECONDITION(!ThisIsHelperThreadWorker()); +#else + PRECONDITION(!ThisIsHelperThreadWorker()); +#endif + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + m_run = FALSE; + + // We need to get the helper thread out of its wait loop. So ping the thread-control event. + // (Don't ping RSEA since that event should be used only for IPC communication). + // Don't bother waiting for it to exit. + SetEvent(this->m_threadControlEvent); + + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// This method checks that the runtime offset has been loaded, and if not, loads it. +// +//--------------------------------------------------------------------------------------- +HRESULT inline DebuggerRCThread::EnsureRuntimeOffsetsInit(IpcTarget ipcTarget) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + if (m_rgfInitRuntimeOffsets[ipcTarget] == true) + { + hr = SetupRuntimeOffsets(m_pDCB); + _ASSERTE(SUCCEEDED(hr)); // throws on failure + + // RuntimeOffsets structure is setup. + m_rgfInitRuntimeOffsets[ipcTarget] = false; + } + + return hr; +} + +// +// Call this function to tell the rc thread that we need the runtime offsets re-initialized at the next avaliable time. +// +void DebuggerRCThread::NeedRuntimeOffsetsReInit(IpcTarget i) +{ + LIMITED_METHOD_CONTRACT; + + m_rgfInitRuntimeOffsets[i] = true; +} + +//--------------------------------------------------------------------------------------- +// +// Send an debug event to the Debugger. This may be either a notification +// or a reply to a debugger query. +// +// Arguments: +// iTarget - which connection. This must be IPC_TARGET_OUTOFPROC. +// +// Return Value: +// S_OK on success +// +// Notes: +// SendIPCEvent is used by the Debugger object to send IPC events to +// the Debugger Interface. It waits for acknowledgement from the DI +// before returning. +// +// This assumes that the event send buffer has been properly +// filled in. All it does it wake up the DI and let it know that its +// safe to copy the event out of this process. +// +// This function may block indefinitely if the controlling debugger +// suddenly went away. +// +// @dbgtodo inspection - this is all a nop around SendRawEvent! +// +//--------------------------------------------------------------------------------------- +HRESULT DebuggerRCThread::SendIPCEvent() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; // duh, we're in preemptive.. + + if (ThisIsHelperThreadWorker()) + { + // When we're stopped, the helper could actually be contracted as either mode-cooperative + // or mode-preemptive! + // If we're the helper thread, we're only sending events while we're stopped. + // Our callers will be mode-cooperative, so call this mode_cooperative to avoid a bunch + // of unncessary contract violations. + MODE_COOPERATIVE; + } + else + { + // Managed threads sending debug events should always be in preemptive mode. + MODE_PREEMPTIVE; + } + + + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + + // one right side + _ASSERTE(m_debugger->ThreadHoldsLock()); + + HRESULT hr = S_OK; + + // All the initialization is already done in code:DebuggerRCThread.Init, + // so we can just go ahead and send the event. + + DebuggerIPCEvent* pManagedEvent = GetIPCEventSendBuffer(); + + STRESS_LOG2(LF_CORDB, LL_INFO1000, "D::SendIPCEvent %s to outofproc appD 0x%x,\n", + IPCENames::GetName(pManagedEvent->type), + VmPtrToCookie(pManagedEvent->vmAppDomain)); + + // increase the debug counter + DbgLog((DebuggerIPCEventType)(pManagedEvent->type & DB_IPCE_TYPE_MASK)); + + g_pDebugger->SendRawEvent(pManagedEvent); + + return hr; +} + +// +// Return true if the helper thread is up & running +// +bool DebuggerRCThread::IsRCThreadReady() +{ + LIMITED_METHOD_CONTRACT; + + if (GetDCB() == NULL) + { + return false; + } + + int idHelper = GetDCB()->m_helperThreadId; + + // The simplest check. If the threadid isn't set, we're not ready. + if (idHelper == 0) + { + LOG((LF_CORDB, LL_EVERYTHING, "DRCT::IsReady - Helper not ready since DCB says id = 0.\n")); + return false; + } + + // a more subtle check. It's possible the thread was up, but then + // an bad call to ExitProcess suddenly terminated the helper thread, + // leaving the threadid still non-0. So check the actual thread object + // and make sure it's still around. + int ret = WaitForSingleObject(m_thread, 0); + LOG((LF_CORDB, LL_EVERYTHING, "DRCT::IsReady - wait(0x%x)=%d, GetLastError() = %d\n", m_thread, ret, GetLastError())); + + if (ret != WAIT_TIMEOUT) + { + return false; + } + + return true; +} + + +HRESULT DebuggerRCThread::ReDaclEvents(PSECURITY_DESCRIPTOR pSecurityDescriptor) +{ + LIMITED_METHOD_CONTRACT; + +#ifndef FEATURE_PAL + if (m_pDCB != NULL) + { + if (m_pDCB->m_rightSideEventAvailable) + { + if (SetKernelObjectSecurity(m_pDCB->m_rightSideEventAvailable, + DACL_SECURITY_INFORMATION, + pSecurityDescriptor) == 0) + { + // failed! + return HRESULT_FROM_GetLastError(); + } + } + if (m_pDCB->m_rightSideEventRead) + { + if (SetKernelObjectSecurity(m_pDCB->m_rightSideEventRead, + DACL_SECURITY_INFORMATION, + pSecurityDescriptor) == 0) + { + // failed! + return HRESULT_FROM_GetLastError(); + } + } + } +#endif // FEATURE_PAL + + return S_OK; +} + + +// +// A normal thread may hit a stack overflow and so we want to do +// any stack-intensive work on the Helper thread so that we don't +// use up the grace memory. +// Note that DoFavor will block until the fp is executed +// +void DebuggerRCThread::DoFavor(FAVORCALLBACK fp, void * pData) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_TRIGGERS; + + PRECONDITION(!ThisIsHelperThreadWorker()); + +#ifdef PREFAST + // Prefast issue + // error C2664: 'CHECK CheckPointer(TypeHandle,IsNullOK)' : cannot convert parameter 1 from + // 'DebuggerRCThread::FAVORCALLBACK' to 'TypeHandle' +#else + PRECONDITION(CheckPointer(fp)); + PRECONDITION(CheckPointer(pData, NULL_OK)); +#endif + } + CONTRACTL_END; + + // We are being called on managed thread only. + // + + // We'll have problems if another thread comes in and + // deletes the RCThread object on us while we're in this call. + if (IsRCThreadReady()) + { + // If the helper thread calls this, we deadlock. + // (Since we wait on an event that only the helper thread sets) + _ASSERTE(GetRCThreadId() != GetCurrentThreadId()); + + // Only lock if we're waiting on the helper thread. + // This should be the only place the FavorLock is used. + // Note this is never called on the helper thread. + CrstHolder ch(GetFavorLock()); + + SetFavorFnPtr(fp, pData); + + // Our main message loop operating on the Helper thread will + // pickup that event, call the fp, and set the Read event + SetEvent(GetFavorAvailableEvent()); + + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Waiting on FavorReadEvent for favor 0x%08x\n", fp)); + + // Wait for either the FavorEventRead to be set (which means that the favor + // was executed by the helper thread) or the helper thread's handle (which means + // that the helper thread exited without doing the favor, so we should do it) + // + // Note we are assuming that there's only 2 ways the helper thread can exit: + // 1) Someone calls ::ExitProcess, killing all threads. That will kill us too, so we're "ok". + // 2) Someone calls Stop(), causing the helper to exit gracefully. That's ok too. The helper + // didn't execute the Favor (else the FREvent would have been set first) and so we can. + // + // Beware of problems: + // 1) If the helper can block, we may deadlock. + // 2) If the helper can exit magically (or if we change the Wait to include a timeout) , + // the helper thread may have not executed the favor, partially executed the favor, + // or totally executed the favor but not yet signaled the FavorReadEvent. We don't + // know what it did, so we don't know what we can do; so we're in an unstable state. + + const HANDLE waitset [] = { GetFavorReadEvent(), m_thread }; + + // the favor worker thread will require a transition to cooperative mode in order to complete its work and we will + // wait for the favor to complete before terminating the process. if there is a GC in progress the favor thread + // will be blocked and if the thread requesting the favor is in cooperative mode we'll deadlock, so we switch to + // preemptive mode before waiting for the favor to complete (see Dev11 72349). + GCX_PREEMP(); + + DWORD ret = WaitForMultipleObjectsEx( + NumItems(waitset), + waitset, + FALSE, + INFINITE, + FALSE + ); + + DWORD wn = (ret - WAIT_OBJECT_0); + if (wn == 0) // m_FavorEventRead + { + // Favor was executed, nothing to do here. + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - favor 0x%08x finished, ret = %d\n", fp, ret)); + } + else + { + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - lost helper thread during wait, " + "doing favor 0x%08x on current thread\n", fp)); + + // Since we have no timeout, we shouldn't be able to get an error on the wait, + // but just in case ... + _ASSERTE(ret != WAIT_FAILED); + _ASSERTE((wn == 1) && !"DoFavor - unexpected return from WFMO"); + + // Thread exited without doing favor, so execute it on our thread. + // If we're here because of a stack overflow, this may push us over the edge, + // but there's nothing else we can really do + (*fp)(pData); + + ResetEvent(GetFavorAvailableEvent()); + } + + // m_fpFavor & m_pFavorData are meaningless now. We could set them + // to NULL, but we may as well leave them as is to leave a trail. + + } + else + { + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - helper thread not ready, " + "doing favor 0x%08x on current thread\n", fp)); + // If helper isn't ready yet, go ahead and execute the favor + // on the callee's space + (*fp)(pData); + } + + // Drop a log message so that we know if we survived a stack overflow or not + LOG((LF_CORDB, LL_INFO10000, "DRCT::DF - Favor 0x%08x completed successfully\n", fp)); +} + + +// +// SendIPCReply simply indicates to the Right Side that a reply to a +// two-way event is ready to be read and that the last event sent from +// the Right Side has been fully processed. +// +// NOTE: this assumes that the event receive buffer has been properly +// filled in. All it does it wake up the DI and let it know that its +// safe to copy the event out of this process. +// +HRESULT DebuggerRCThread::SendIPCReply() +{ + HRESULT hr = S_OK; + +#ifdef LOGGING + DebuggerIPCEvent* event = GetIPCEventReceiveBuffer(); + + LOG((LF_CORDB, LL_INFO10000, "D::SIPCR: replying with %s.\n", + IPCENames::GetName(event->type))); +#endif + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + DWORD useTransport = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgUseTransport); + if(!useTransport) + { +#endif + BOOL succ = SetEvent(m_pDCB->m_rightSideEventRead); + if (!succ) + { + hr = CORDBDebuggerSetUnrecoverableWin32Error(m_debugger, 0, false); + } +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + } + else + { + hr = g_pDbgTransport->SendEvent(GetIPCEventReceiveBuffer()); + if (FAILED(hr)) + { + m_debugger->UnrecoverableError(hr, + 0, + __FILE__, + __LINE__, + false); + } + } +#endif // FEATURE_DBGIPC_TRANSPORT_VM + + return hr; +} + +// +// EarlyHelperThreadDeath handles the case where the helper +// thread has been ripped out from underneath of us by +// ExitProcess or TerminateProcess. These calls are bad, whacking +// all threads except the caller in the process. This can happen, for +// instance, when an app calls ExitProcess. All threads are wacked, +// the main thread calls all DLL main's, and the EE starts shutting +// down in its DLL main with the helper thread terminated. +// +void DebuggerRCThread::EarlyHelperThreadDeath(void) +{ + LOG((LF_CORDB, LL_INFO10000, "DRCT::EHTD\n")); + + // If we ever spun up a thread... + if (m_thread != NULL && m_pDCB) + { + Debugger::DebuggerLockHolder debugLockHolder(m_debugger); + + m_pDCB->m_helperThreadId = 0; + + LOG((LF_CORDB, LL_INFO10000, "DRCT::EHTD helperThreadId\n")); + // dbgLockHolder goes out of scope - implicit Release + } +} + |