text: //
  // 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_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)
      // 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();
      }
  #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)
  
      if (m_pDCB)
      {
          hr = m_pDCB->Init(NULL, NULL, NULL, NULL, NULL);
          _ASSERTE(SUCCEEDED(hr)); // throws on error.
      }
  #else //FEATURE_DBGIPC_TRANSPORT_VM 
  
      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.
      }
  #endif //FEATURE_DBGIPC_TRANSPORT_VM 
  
      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.IsCurrentThread())
          {
              // 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.SetToCurrentThread();
  #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)
      CloseIPCHandles();
  #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, &param)
      {
          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)
      // 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);
  #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 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
  #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)
      rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable;
  #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)
          // 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;
          }
  #endif // !FEATURE_DBGIPC_TRANSPORT_VM
  
  
          if (m_fDetachRightSide)
          {
              m_fDetachRightSide = false;
  
  #if !defined(FEATURE_DBGIPC_TRANSPORT_VM)
              _ASSERTE(cWaitCount == DRCT_COUNT_FINAL);
              _ASSERTE((cWaitCount - 1) == DRCT_COUNT_INITIAL);
  
              rghWaitSet[DRCT_DEBUGGER_EVENT] = NULL;
              cWaitCount = DRCT_COUNT_INITIAL;
  #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)
      rghWaitSet[DRCT_RSEA] = m_pDCB->m_rightSideEventAvailable;
  #else //FEATURE_DBGIPC_TRANSPORT_VM 
      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)
      BOOL succ = SetEvent(m_pDCB->m_rightSideEventRead);
      if (!succ)
      {
          hr = CORDBDebuggerSetUnrecoverableWin32Error(m_debugger, 0, false);
      }
  #else // !FEATURE_DBGIPC_TRANSPORT_VM
      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
      }
  }
  
  