From 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Wed, 23 Nov 2016 19:09:09 +0900 Subject: Imported Upstream version 1.1.0 --- src/debug/ee/debugger.cpp | 17073 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 17073 insertions(+) create mode 100644 src/debug/ee/debugger.cpp (limited to 'src/debug/ee/debugger.cpp') diff --git a/src/debug/ee/debugger.cpp b/src/debug/ee/debugger.cpp new file mode 100644 index 0000000000..a06811c817 --- /dev/null +++ b/src/debug/ee/debugger.cpp @@ -0,0 +1,17073 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +//***************************************************************************** +// File: debugger.cpp +// + +// +// Debugger runtime controller routines. +// +//***************************************************************************** + +#include "stdafx.h" +#include "debugdebugger.h" +#include "ipcmanagerinterface.h" +#include "../inc/common.h" +#include "perflog.h" +#include "eeconfig.h" // This is here even for retail & free builds... +#include "../../dlls/mscorrc/resource.h" + +#ifdef FEATURE_REMOTING +#include "remoting.h" +#endif + +#include "context.h" +#include "vars.hpp" +#include +#include "ilformatter.h" +#include "typeparse.h" +#include "debuginfostore.h" +#include "generics.h" +#include "../../vm/security.h" +#include "../../vm/methoditer.h" +#include "../../vm/encee.h" +#include "../../vm/dwreport.h" +#include "../../vm/eepolicy.h" +#include "../../vm/excep.h" +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) +#include "dbgtransportsession.h" +#endif // FEATURE_DBGIPC_TRANSPORT_VM + +#ifdef TEST_DATA_CONSISTENCY +#include "datatest.h" +#endif // TEST_DATA_CONSISTENCY + +#if defined(FEATURE_CORECLR) +#include "dbgenginemetrics.h" +#endif // FEATURE_CORECLR + +#include "../../vm/rejit.h" + +#include "threadsuspend.h" + +class CCLRSecurityAttributeManager; +extern CCLRSecurityAttributeManager s_CLRSecurityAttributeManager; + + +#ifdef DEBUGGING_SUPPORTED + +#ifdef _DEBUG +// Reg key. We can set this and then any debugger-lazy-init code will assert. +// This helps track down places where we're caching in debugger stuff in a +// non-debugger scenario. +bool g_DbgShouldntUseDebugger = false; +#endif + + +/* ------------------------------------------------------------------------ * + * Global variables + * ------------------------------------------------------------------------ */ + +GPTR_IMPL(Debugger, g_pDebugger); +GPTR_IMPL(EEDebugInterface, g_pEEInterface); +SVAL_IMPL_INIT(BOOL, Debugger, s_fCanChangeNgenFlags, TRUE); + +bool g_EnableSIS = false; + + +#ifndef DACCESS_COMPILE + +DebuggerRCThread *g_pRCThread = NULL; + +#ifndef _PREFAST_ +// Do some compile time checking on the events in DbgIpcEventTypes.h +// No one ever calls this. But the compiler should still compile it, +// and that should be sufficient. +void DoCompileTimeCheckOnDbgIpcEventTypes() +{ + _ASSERTE(!"Don't call this function. It just does compile time checking\n"); + + // We use the C_ASSERT macro here to get a compile-time assert. + + // Make sure we don't have any duplicate numbers. + // The switch statements in the main loops won't always catch this + // since we may not switch on all events. + + // store Type-0 in const local vars, so we can use them for bounds checking + // Create local vars with the val from Type1 & Type2. If there are any + // collisions, then the variables' names will collide at compile time. + #define IPC_EVENT_TYPE0(type, val) const int e_##type = val; + #define IPC_EVENT_TYPE1(type, val) int T_##val; T_##val = 0; + #define IPC_EVENT_TYPE2(type, val) int T_##val; T_##val = 0; + #include "dbgipceventtypes.h" + #undef IPC_EVENT_TYPE2 + #undef IPC_EVENT_TYPE1 + #undef IPC_EVENT_TYPE0 + + // Ensure that all identifiers are unique and are matched with + // integer values. + #define IPC_EVENT_TYPE0(type, val) int T2_##type; T2_##type = val; + #define IPC_EVENT_TYPE1(type, val) int T2_##type; T2_##type = val; + #define IPC_EVENT_TYPE2(type, val) int T2_##type; T2_##type = val; + #include "dbgipceventtypes.h" + #undef IPC_EVENT_TYPE2 + #undef IPC_EVENT_TYPE1 + #undef IPC_EVENT_TYPE0 + + // Make sure all values are subset of the bits specified by DB_IPCE_TYPE_MASK + #define IPC_EVENT_TYPE0(type, val) + #define IPC_EVENT_TYPE1(type, val) C_ASSERT((val & e_DB_IPCE_TYPE_MASK) == val); + #define IPC_EVENT_TYPE2(type, val) C_ASSERT((val & e_DB_IPCE_TYPE_MASK) == val); + #include "dbgipceventtypes.h" + #undef IPC_EVENT_TYPE2 + #undef IPC_EVENT_TYPE1 + #undef IPC_EVENT_TYPE0 + + // Make sure that no value is DB_IPCE_INVALID_EVENT + #define IPC_EVENT_TYPE0(type, val) + #define IPC_EVENT_TYPE1(type, val) C_ASSERT(val != e_DB_IPCE_INVALID_EVENT); + #define IPC_EVENT_TYPE2(type, val) C_ASSERT(val != e_DB_IPCE_INVALID_EVENT); + #include "dbgipceventtypes.h" + #undef IPC_EVENT_TYPE2 + #undef IPC_EVENT_TYPE1 + #undef IPC_EVENT_TYPE0 + + // Make sure first-last values are well structured. + static_assert_no_msg(e_DB_IPCE_RUNTIME_FIRST < e_DB_IPCE_RUNTIME_LAST); + static_assert_no_msg(e_DB_IPCE_DEBUGGER_FIRST < e_DB_IPCE_DEBUGGER_LAST); + + // Make sure that event ranges don't overlap. + // This check is simplified because L->R events come before R<-L + static_assert_no_msg(e_DB_IPCE_RUNTIME_LAST < e_DB_IPCE_DEBUGGER_FIRST); + + + // Make sure values are in the proper ranges + // Type1 should be in the Runtime range, Type2 in the Debugger range. + #define IPC_EVENT_TYPE0(type, val) + #define IPC_EVENT_TYPE1(type, val) C_ASSERT((e_DB_IPCE_RUNTIME_FIRST <= val) && (val < e_DB_IPCE_RUNTIME_LAST)); + #define IPC_EVENT_TYPE2(type, val) C_ASSERT((e_DB_IPCE_DEBUGGER_FIRST <= val) && (val < e_DB_IPCE_DEBUGGER_LAST)); + #include "dbgipceventtypes.h" + #undef IPC_EVENT_TYPE2 + #undef IPC_EVENT_TYPE1 + #undef IPC_EVENT_TYPE0 + + // Make sure that events are in increasing order + // It's ok if the events skip numbers. + // This is a more specific check than the range check above. + + /* Expands to look like this: + const bool f = ( + first <= + 10) && (10 < + 11) && (11 < + 12) && (12 < + last) + static_assert_no_msg(f); + */ + + const bool f1 = ( + (e_DB_IPCE_RUNTIME_FIRST <= + #define IPC_EVENT_TYPE0(type, val) + #define IPC_EVENT_TYPE1(type, val) val) && (val < + #define IPC_EVENT_TYPE2(type, val) + #include "dbgipceventtypes.h" + #undef IPC_EVENT_TYPE2 + #undef IPC_EVENT_TYPE1 + #undef IPC_EVENT_TYPE0 + e_DB_IPCE_RUNTIME_LAST) + ); + static_assert_no_msg(f1); + + const bool f2 = ( + (e_DB_IPCE_DEBUGGER_FIRST <= + #define IPC_EVENT_TYPE0(type, val) + #define IPC_EVENT_TYPE1(type, val) + #define IPC_EVENT_TYPE2(type, val) val) && (val < + #include "dbgipceventtypes.h" + #undef IPC_EVENT_TYPE2 + #undef IPC_EVENT_TYPE1 + #undef IPC_EVENT_TYPE0 + e_DB_IPCE_DEBUGGER_LAST) + ); + static_assert_no_msg(f2); + +} // end checks +#endif // _PREFAST_ + +//----------------------------------------------------------------------------- +// Ctor for AtSafePlaceHolder +AtSafePlaceHolder::AtSafePlaceHolder(Thread * pThread) +{ + _ASSERTE(pThread != NULL); + if (!g_pDebugger->IsThreadAtSafePlace(pThread)) + { + m_pThreadAtUnsafePlace = pThread; + g_pDebugger->IncThreadsAtUnsafePlaces(); + } + else + { + m_pThreadAtUnsafePlace = NULL; + } +} + +//----------------------------------------------------------------------------- +// Dtor for AtSafePlaceHolder +AtSafePlaceHolder::~AtSafePlaceHolder() +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// Returns true if this adjusted the unsafe counter +bool AtSafePlaceHolder::IsAtUnsafePlace() +{ + return m_pThreadAtUnsafePlace != NULL; +} + +//----------------------------------------------------------------------------- +// Clear the holder. +// Notes: +// This can be called multiple times. +// Calling this makes the dtor a nop. +void AtSafePlaceHolder::Clear() +{ + if (m_pThreadAtUnsafePlace != NULL) + { + // The thread is still at an unsafe place. + // We're clearing the flag to avoid the Dtor() calling DecThreads again. + m_pThreadAtUnsafePlace = NULL; + g_pDebugger->DecThreadsAtUnsafePlaces(); + } +} + +//----------------------------------------------------------------------------- +// Is the guard page missing on this thread? +// Should only be called for managed threads handling a managed exception. +// If we're handling a stack overflow (ie, missing guard page), then another +// stack overflow will instantly terminate the process. In that case, do stack +// intensive stuff on the helper thread (which has lots of stack space). Only +// problem is that if the faulting thread has a lock, the helper thread may +// get stuck. +// Serves as a hint whether we want to do a favor on the +// faulting thread (preferred) or the helper thread (if low stack). +// See whidbey issue 127436. +//----------------------------------------------------------------------------- +bool IsGuardPageGone() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + Thread * pThread = g_pEEInterface->GetThread(); + + // We're not going to be called for a unmanaged exception. + // Should always have a managed thread, but just in case something really + // crazy happens, it's not worth an AV. (since this is just being used as a hint) + if (pThread == NULL) + { + return false; + } + + // Don't use pThread->IsGuardPageGone(), it's not accurate here. + bool fGuardPageGone = (pThread->DetermineIfGuardPagePresent() == FALSE); + LOG((LF_CORDB, LL_INFO1000000, "D::IsGuardPageGone=%d\n", fGuardPageGone)); + return fGuardPageGone; +} + + +// This is called from AppDomainEnumerationIPCBlock::Lock and Unlock +void BeginThreadAffinityHelper() +{ + WRAPPER_NO_CONTRACT; + + Thread::BeginThreadAffinity(); +} +void EndThreadAffinityHelper() +{ + WRAPPER_NO_CONTRACT; + Thread::EndThreadAffinity(); +} + +//----------------------------------------------------------------------------- +// LSPTR_XYZ is a type-safe wrapper around an opaque reference type XYZ in the left-side. +// But TypeHandles are value-types that can't be directly converted into a pointer. +// Thus converting between LSPTR_XYZ and TypeHandles requires some extra glue. +// The following conversions are valid: +// LSPTR_XYZ <--> XYZ* (via Set/UnWrap methods) +// TypeHandle <--> void* (via AsPtr() and FromPtr()). +// so we can't directly convert between LSPTR_TYPEHANDLE and TypeHandle. +// We must do: TypeHandle <--> void* <--> XYZ <--> LSPTR_XYZ +// So LSPTR_TYPEHANDLE is actually for TypeHandleDummyPtr, and then we unsafe cast +// that to a void* to use w/ AsPtr() and FromPtr() to convert to TypeHandles. +// @todo- it would be nice to have these happen automatically w/ Set & UnWrap. +//----------------------------------------------------------------------------- + +// helper class to do conversion above. +class TypeHandleDummyPtr +{ +private: + TypeHandleDummyPtr() { }; // should never actually create this. + void * data; +}; + +// Convert: VMPTR_TYPEHANDLE --> TypeHandle +TypeHandle GetTypeHandle(VMPTR_TypeHandle ptr) +{ + return TypeHandle::FromPtr(ptr.GetRawPtr()); +} + +// Convert: TypeHandle --> LSPTR_TYPEHANDLE +VMPTR_TypeHandle WrapTypeHandle(TypeHandle th) +{ + return VMPTR_TypeHandle::MakePtr(reinterpret_cast (th.AsPtr())); +} + +extern void WaitForEndOfShutdown(); + + +// Get the Canary structure which can sniff if the helper thread is safe to run. +HelperCanary * Debugger::GetCanary() +{ + return g_pRCThread->GetCanary(); +} + +// IMPORTANT!!!!! +// Do not call Lock and Unlock directly. Because you might not unlock +// if exception takes place. Use DebuggerLockHolder instead!!! +// Only AcquireDebuggerLock can call directly. +// +void Debugger::DoNotCallDirectlyPrivateLock(void) +{ + WRAPPER_NO_CONTRACT; + + LOG((LF_CORDB,LL_INFO10000, "D::Lock aquire attempt by 0x%x\n", + GetCurrentThreadId())); + + // Debugger lock is larger than both Controller & debugger-data locks. + // So we should never try to take the D lock if we hold either of the others. + + + // Lock becomes no-op in late shutdown. + if (g_fProcessDetach) + { + return; + } + + + // + // If the debugger has been disabled by the runtime, this means that it should block + // all threads that are trying to travel thru the debugger. We do this by blocking + // threads as they try and take the debugger lock. + // + if (m_fDisabled) + { + __SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING); + _ASSERTE (!"Can not reach here"); + } + + m_mutex.Enter(); + + // + // If we were blocked on the lock and the debugging facilities got disabled + // while we were waiting, release the lock and park this thread. + // + if (m_fDisabled) + { + m_mutex.Leave(); + __SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING); + _ASSERTE (!"Can not reach here"); + } + + // + // Now check if we are in a shutdown case... + // + Thread * pThread; + bool fIsCooperative; + BEGIN_GETTHREAD_ALLOWED; + pThread = g_pEEInterface->GetThread(); + fIsCooperative = (pThread != NULL) && (pThread->PreemptiveGCDisabled()); + END_GETTHREAD_ALLOWED; + if (m_fShutdownMode && !fIsCooperative) + { + // The big fear is that some other random thread will take the debugger-lock and then block on something else, + // and thus prevent the helper/finalizer threads from taking the debugger-lock in shutdown scenarios. + // + // If we're in shutdown mode, then some locks (like the Thread-Store-Lock) get special semantics. + // Only helper / finalizer / shutdown threads can actually take these locks. + // Other threads that try to take them will just get parked and block forever. + // This is ok b/c the only threads that need to run at this point are the Finalizer and Helper threads. + // + // We need to be in preemptive to block for shutdown, so we don't do this block in Coop mode. + // Fortunately, it's safe to take this lock in coop mode because we know the thread can't block + // on anything interesting because we're in a GC-forbid region (see crst flags). + m_mutex.ReleaseAndBlockForShutdownIfNotSpecialThread(); + } + + + +#ifdef _DEBUG + _ASSERTE(m_mutexCount >= 0); + + if (m_mutexCount>0) + { + if (pThread) + { + // mamaged thread + _ASSERTE(m_mutexOwner == GetThreadIdHelper(pThread)); + } + else + { + // unmanaged thread + _ASSERTE(m_mutexOwner == GetCurrentThreadId()); + } + } + + m_mutexCount++; + if (pThread) + { + m_mutexOwner = GetThreadIdHelper(pThread); + } + else + { + // unmanaged thread + m_mutexOwner = GetCurrentThreadId(); + } + + if (m_mutexCount == 1) + { + LOG((LF_CORDB,LL_INFO10000, "D::Lock aquired by 0x%x\n", m_mutexOwner)); + } +#endif + +} + +// See comment above. +// Only ReleaseDebuggerLock can call directly. +void Debugger::DoNotCallDirectlyPrivateUnlock(void) +{ + WRAPPER_NO_CONTRACT; + + // Controller lock is "smaller" than debugger lock. + + + if (!g_fProcessDetach) + { +#ifdef _DEBUG + if (m_mutexCount == 1) + LOG((LF_CORDB,LL_INFO10000, "D::Unlock released by 0x%x\n", + m_mutexOwner)); + + if(0 == --m_mutexCount) + m_mutexOwner = 0; + + _ASSERTE( m_mutexCount >= 0); +#endif + m_mutex.Leave(); + + // + // If the debugger has been disabled by the runtime, this means that it should block + // all threads that are trying to travel thru the debugger. We do this by blocking + // threads also as they leave the debugger lock. + // + if (m_fDisabled) + { + __SwitchToThread(INFINITE, CALLER_LIMITS_SPINNING); + _ASSERTE (!"Can not reach here"); + } + + } +} + +#ifdef TEST_DATA_CONSISTENCY + +// --------------------------------------------------------------------------------- +// Implementations for DataTest member functions +// --------------------------------------------------------------------------------- + +// Send an event to the RS to signal that it should test to determine if a crst is held. +// This is for testing purposes only. +// Arguments: +// input: pCrst - the lock to test +// fOkToTake - true iff the LS does NOT currently hold the lock +// output: none +// Notes: The RS will throw if the lock is held. The code that tests the lock will catch the +// exception and assert if throwing was not the correct thing to do (determined via the +// boolean). See the case for DB_IPCE_TEST_CRST in code:CordbProcess::RawDispatchEvent. +// +void DataTest::SendDbgCrstEvent(Crst * pCrst, bool fOkToTake) +{ + DebuggerIPCEvent * pLockEvent = g_pDebugger->m_pRCThread->GetIPCEventSendBuffer(); + + g_pDebugger->InitIPCEvent(pLockEvent, DB_IPCE_TEST_CRST); + + pLockEvent->TestCrstData.vmCrst.SetRawPtr(pCrst); + pLockEvent->TestCrstData.fOkToTake = fOkToTake; + + g_pDebugger->SendRawEvent(pLockEvent); + +} // DataTest::SendDbgCrstEvent + +// Send an event to the RS to signal that it should test to determine if a SimpleRWLock is held. +// This is for testing purposes only. +// Arguments: +// input: pRWLock - the lock to test +// fOkToTake - true iff the LS does NOT currently hold the lock +// output: none +// Note: The RS will throw if the lock is held. The code that tests the lock will catch the +// exception and assert if throwing was not the correct thing to do (determined via the +// boolean). See the case for DB_IPCE_TEST_RWLOCK in code:CordbProcess::RawDispatchEvent. +// +void DataTest::SendDbgRWLockEvent(SimpleRWLock * pRWLock, bool okToTake) +{ + DebuggerIPCEvent * pLockEvent = g_pDebugger->m_pRCThread->GetIPCEventSendBuffer(); + + g_pDebugger->InitIPCEvent(pLockEvent, DB_IPCE_TEST_RWLOCK); + + pLockEvent->TestRWLockData.vmRWLock.SetRawPtr(pRWLock); + pLockEvent->TestRWLockData.fOkToTake = okToTake; + + g_pDebugger->SendRawEvent(pLockEvent); +} // DataTest::SendDbgRWLockEvent + +// Takes a series of locks in various ways and signals the RS to test the locks at interesting +// points to ensure we reliably detect when the LS holds a lock. If in the course of inspection, the +// DAC needs to execute a code path where the LS holds a lock, we assume that the locked data is in +// an inconsistent state. In this situation, we don't want to report information about this data, so +// we throw an exception. +// This is for testing purposes only. +// +// Arguments: none +// Return Value: none +// Notes: See code:CordbProcess::RawDispatchEvent for the RS part of this test and code:Debugger::Startup +// for the LS invocation of the test. +// The environment variable TestDataConsistency must be set to 1 to make this test run. +void DataTest::TestDataSafety() +{ + const bool okToTake = true; + + SendDbgCrstEvent(&m_crst1, okToTake); + { + CrstHolder ch1(&m_crst1); + SendDbgCrstEvent(&m_crst1, !okToTake); + { + CrstHolder ch2(&m_crst2); + SendDbgCrstEvent(&m_crst2, !okToTake); + SendDbgCrstEvent(&m_crst1, !okToTake); + } + SendDbgCrstEvent(&m_crst2, okToTake); + SendDbgCrstEvent(&m_crst1, !okToTake); + } + SendDbgCrstEvent(&m_crst1, okToTake); + + { + SendDbgRWLockEvent(&m_rwLock, okToTake); + SimpleReadLockHolder readLock(&m_rwLock); + SendDbgRWLockEvent(&m_rwLock, okToTake); + } + SendDbgRWLockEvent(&m_rwLock, okToTake); + { + SimpleWriteLockHolder readLock(&m_rwLock); + SendDbgRWLockEvent(&m_rwLock, !okToTake); + } + +} // DataTest::TestDataSafety + +#endif // TEST_DATA_CONSISTENCY + +#if _DEBUG +static DebugEventCounter g_debugEventCounter; +static int g_iDbgRuntimeCounter[DBG_RUNTIME_MAX]; +static int g_iDbgDebuggerCounter[DBG_DEBUGGER_MAX]; + +void DoAssertOnType(DebuggerIPCEventType event, int count) +{ + WRAPPER_NO_CONTRACT; + + // check to see if we need fire the assertion or not. + if ((event & 0x0300) == 0x0100) + { + // use the Runtime array + if (g_iDbgRuntimeCounter[event & 0x00ff] == count) + { + char tmpStr[256]; + sprintf(tmpStr, "%s == %d, break now!", + IPCENames::GetName(event), count); + + // fire the assertion + DbgAssertDialog(__FILE__, __LINE__, tmpStr); + } + } + // check to see if we need fire the assertion or not. + else if ((event & 0x0300) == 0x0200) + { + // use the Runtime array + if (g_iDbgDebuggerCounter[event & 0x00ff] == count) + { + char tmpStr[256]; + sprintf(tmpStr, "%s == %d, break now!", + IPCENames::GetName(event), count); + + // fire the assertion + DbgAssertDialog(__FILE__, __LINE__, tmpStr); + } + } + +} +void DbgLogHelper(DebuggerIPCEventType event) +{ + WRAPPER_NO_CONTRACT; + + switch (event) + { +// we don't need to handle event type 0 +#define IPC_EVENT_TYPE0(type, val) +#define IPC_EVENT_TYPE1(type, val) case type: {\ + g_debugEventCounter.m_iDebugCount_##type++; \ + DoAssertOnType(type, g_debugEventCounter.m_iDebugCount_##type); \ + break; \ + } +#define IPC_EVENT_TYPE2(type, val) case type: { \ + g_debugEventCounter.m_iDebugCount_##type++; \ + DoAssertOnType(type, g_debugEventCounter.m_iDebugCount_##type); \ + break; \ + } +#include "dbgipceventtypes.h" +#undef IPC_EVENT_TYPE2 +#undef IPC_EVENT_TYPE1 +#undef IPC_EVENT_TYPE0 + default: + break; + } +} +#endif // _DEBUG + + + + + + + + + +/* ------------------------------------------------------------------------ * + * DLL export routine + * ------------------------------------------------------------------------ */ + +Debugger *CreateDebugger(void) +{ + Debugger *pDebugger = NULL; + + EX_TRY + { + pDebugger = new (nothrow) Debugger(); + } + EX_CATCH + { + if (pDebugger != NULL) + { + delete pDebugger; + pDebugger = NULL; + } + } + EX_END_CATCH(RethrowTerminalExceptions); + + return pDebugger; +} + +// +// CorDBGetInterface is exported to the Runtime so that it can call +// the Runtime Controller. +// +extern "C"{ +HRESULT __cdecl CorDBGetInterface(DebugInterface** rcInterface) +{ + CONTRACT(HRESULT) + { + NOTHROW; // use HRESULTS instead + GC_NOTRIGGER; + POSTCONDITION(FAILED(RETVAL) || (rcInterface == NULL) || (*rcInterface != NULL)); + } + CONTRACT_END; + + HRESULT hr = S_OK; + + if (rcInterface != NULL) + { + if (g_pDebugger == NULL) + { + LOG((LF_CORDB, LL_INFO10, + "CorDBGetInterface: initializing debugger.\n")); + + g_pDebugger = CreateDebugger(); + TRACE_ALLOC(g_pDebugger); + + if (g_pDebugger == NULL) + hr = E_OUTOFMEMORY; + } + + *rcInterface = g_pDebugger; + } + + RETURN hr; +} +} + +//----------------------------------------------------------------------------- +// Send a pre-init IPC event and block. +// We assume the IPC event has already been initialized. There's nothing special +// here; it just used the standard formula for sending an IPC event to the RS. +// This should match up w/ the description in SENDIPCEVENT_BEGIN. +//----------------------------------------------------------------------------- +void Debugger::SendSimpleIPCEventAndBlock() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + // BEGIN will acquire the lock (END will release it). While blocking, the + // debugger may have detached though, so we need to check for that. + _ASSERTE(ThreadHoldsLock()); + + if (CORDebuggerAttached()) + { + m_pRCThread->SendIPCEvent(); + + // Stop all Runtime threads + this->TrapAllRuntimeThreads(); + } +} + +//----------------------------------------------------------------------------- +// Get context from a thread in managed code. +// See header for exact semantics. +//----------------------------------------------------------------------------- +CONTEXT * GetManagedStoppedCtx(Thread * pThread) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pThread != NULL); + + // We may be stopped or live. + + // If we're stopped at an interop-hijack, we'll have a filter context, + // but we'd better not be redirected for a managed-suspension hijack. + if (pThread->GetInteropDebuggingHijacked()) + { + _ASSERTE(!ISREDIRECTEDTHREAD(pThread)); + return NULL; + } + + // Check if we have a filter ctx. This should only be for managed-code. + // We're stopped at some exception (likely an int3 or single-step). + // Can't have both filter ctx + redirected ctx. + CONTEXT *pCtx = g_pEEInterface->GetThreadFilterContext(pThread); + if (pCtx != NULL) + { + _ASSERTE(!ISREDIRECTEDTHREAD(pThread)); + return pCtx; + } + + if (ISREDIRECTEDTHREAD(pThread)) + { + pCtx = GETREDIRECTEDCONTEXT(pThread); + _ASSERTE(pCtx != NULL); + return pCtx; + } + + // Not stopped somewhere in managed code. + return NULL; +} + +//----------------------------------------------------------------------------- +// See header for exact semantics. +// Never NULL. (Caller guarantees this is active.) +//----------------------------------------------------------------------------- +CONTEXT * GetManagedLiveCtx(Thread * pThread) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(pThread != NULL); + + // We should never be on the helper thread, we should only be inspecting our own thread. + // We're in some Controller's Filter after hitting an exception. + // We're not stopped. + //_ASSERTE(!g_pDebugger->IsStopped()); <-- @todo - this fires, need to find out why. + _ASSERTE(GetThread() == pThread); + + CONTEXT *pCtx = g_pEEInterface->GetThreadFilterContext(pThread); + + // Note that we may be in a M2U hijack. So we can't assert !pThread->GetInteropDebuggingHijacked() + _ASSERTE(!ISREDIRECTEDTHREAD(pThread)); + _ASSERTE(pCtx); + + return pCtx; +} + +// Attempt to validate a GC handle. +HRESULT ValidateGCHandle(OBJECTHANDLE oh) +{ + // The only real way to do this is to Enumerate all GC handles in the handle table. + // That's too expensive. So we'll use a similar workaround that we use in ValidateObject. + // This will err on the side off returning True for invalid handles. + + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + EX_TRY + { + // Use AVInRuntimeImplOkHolder. + AVInRuntimeImplOkayHolder AVOkay; + + // This may throw if the Object Handle is invalid. + Object * objPtr = *((Object**) oh); + + // NULL is certinally valid... + if (objPtr != NULL) + { + if (!objPtr->ValidateObjectWithPossibleAV()) + { + LOG((LF_CORDB, LL_INFO10000, "GAV: object methodtable-class invariant doesn't hold.\n")); + hr = E_INVALIDARG; + goto LExit; + } + } + + LExit: ; + } + EX_CATCH + { + LOG((LF_CORDB, LL_INFO10000, "GAV: exception indicated ref is bad.\n")); + hr = E_INVALIDARG; + } + EX_END_CATCH(SwallowAllExceptions); + + return hr; +} + + +// Validate an object. Returns E_INVALIDARG or S_OK. +HRESULT ValidateObject(Object *objPtr) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + EX_TRY + { + // Use AVInRuntimeImplOkHolder. + AVInRuntimeImplOkayHolder AVOkay; + + // NULL is certinally valid... + if (objPtr != NULL) + { + if (!objPtr->ValidateObjectWithPossibleAV()) + { + LOG((LF_CORDB, LL_INFO10000, "GAV: object methodtable-class invariant doesn't hold.\n")); + hr = E_INVALIDARG; + goto LExit; + } + } + + LExit: ; + } + EX_CATCH + { + LOG((LF_CORDB, LL_INFO10000, "GAV: exception indicated ref is bad.\n")); + hr = E_INVALIDARG; + } + EX_END_CATCH(SwallowAllExceptions); + + return hr; +} // ValidateObject + + +#ifdef FEATURE_DBGIPC_TRANSPORT_VM +void +ShutdownTransport() +{ + if (g_pDbgTransport != NULL) + { + g_pDbgTransport->Shutdown(); + g_pDbgTransport = NULL; + } +} + +void +AbortTransport() +{ + if (g_pDbgTransport != NULL) + { + g_pDbgTransport->AbortConnection(); + } +} +#endif // FEATURE_DBGIPC_TRANSPORT_VM + + +/* ------------------------------------------------------------------------ * + * Debugger routines + * ------------------------------------------------------------------------ */ + +// +// a Debugger object represents the global state of the debugger program. +// + +// +// Constructor & Destructor +// + +/****************************************************************************** + * + ******************************************************************************/ +Debugger::Debugger() + : + m_fLeftSideInitialized(FALSE), +#ifdef _DEBUG + m_mutexCount(0), +#endif //_DEBUG + m_pRCThread(NULL), + m_trappingRuntimeThreads(FALSE), + m_stopped(FALSE), + m_unrecoverableError(FALSE), + m_ignoreThreadDetach(FALSE), + m_pMethodInfos(NULL), + m_mutex(CrstDebuggerMutex, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)), +#ifdef _DEBUG + m_mutexOwner(0), + m_tidLockedForEventSending(0), +#endif //_DEBUG + m_threadsAtUnsafePlaces(0), + m_jitAttachInProgress(FALSE), + m_attachingForManagedEvent(FALSE), + m_launchingDebugger(FALSE), + m_userRequestedDebuggerLaunch(FALSE), + m_LoggingEnabled(TRUE), + m_pAppDomainCB(NULL), + m_dClassLoadCallbackCount(0), + m_pModules(NULL), + m_RSRequestedSync(FALSE), + m_sendExceptionsOutsideOfJMC(TRUE), + m_pIDbgThreadControl(NULL), + m_forceNonInterceptable(FALSE), + m_pLazyData(NULL), + m_defines(_defines) +{ + CONTRACTL + { + SO_INTOLERANT; + WRAPPER(THROWS); + WRAPPER(GC_TRIGGERS); + CONSTRUCTOR_CHECK; + } + CONTRACTL_END; + + m_fShutdownMode = false; + m_fDisabled = false; + m_rgHijackFunction = NULL; + +#ifdef _DEBUG + InitDebugEventCounting(); +#endif + + m_processId = GetCurrentProcessId(); + + // Initialize these in ctor because we free them in dtor. + // And we can't set them to some safe uninited value (like NULL). + + + + //------------------------------------------------------------------------------ + // Metadata data structure version numbers + // + // 1 - initial state of the layouts ( .Net 4.5.2 ) + // + // as data structure layouts change, add a new version number + // and comment the changes + m_mdDataStructureVersion = 1; + +} + +/****************************************************************************** + * + ******************************************************************************/ +Debugger::~Debugger() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + DESTRUCTOR_CHECK; + SO_INTOLERANT; + } + CONTRACTL_END; + + // We explicitly leak the debugger object on shutdown. See Debugger::StopDebugger for details. + _ASSERTE(!"Debugger dtor should not be called."); +} + +#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX) +typedef void (*PFN_HIJACK_FUNCTION) (void); + +// Given the start address and the end address of a function, return a MemoryRange for the function. +inline MemoryRange GetMemoryRangeForFunction(PFN_HIJACK_FUNCTION pfnStart, PFN_HIJACK_FUNCTION pfnEnd) +{ + PCODE pfnStartAddress = (PCODE)GetEEFuncEntryPoint(pfnStart); + PCODE pfnEndAddress = (PCODE)GetEEFuncEntryPoint(pfnEnd); + return MemoryRange(dac_cast(pfnStartAddress), (pfnEndAddress - pfnStartAddress)); +} + +// static +MemoryRange Debugger::s_hijackFunction[kMaxHijackFunctions] = + {GetMemoryRangeForFunction(ExceptionHijack, ExceptionHijackEnd), + GetMemoryRangeForFunction(RedirectedHandledJITCaseForGCThreadControl_Stub, + RedirectedHandledJITCaseForGCThreadControl_StubEnd), + GetMemoryRangeForFunction(RedirectedHandledJITCaseForDbgThreadControl_Stub, + RedirectedHandledJITCaseForDbgThreadControl_StubEnd), + GetMemoryRangeForFunction(RedirectedHandledJITCaseForUserSuspend_Stub, + RedirectedHandledJITCaseForUserSuspend_StubEnd), + GetMemoryRangeForFunction(RedirectedHandledJITCaseForYieldTask_Stub, + RedirectedHandledJITCaseForYieldTask_StubEnd) +#if defined(HAVE_GCCOVER) && defined(_TARGET_AMD64_) + , + GetMemoryRangeForFunction(RedirectedHandledJITCaseForGCStress_Stub, + RedirectedHandledJITCaseForGCStress_StubEnd) +#endif // HAVE_GCCOVER && _TARGET_AMD64_ + }; +#endif // FEATURE_HIJACK && !PLATFORM_UNIX + +// Save the necessary information for the debugger to recognize an IP in one of the thread redirection +// functions. +void Debugger::InitializeHijackFunctionAddress() +{ +#if defined(FEATURE_HIJACK) && !defined(PLATFORM_UNIX) + // Advertise hijack address for the DD Hijack primitive + m_rgHijackFunction = Debugger::s_hijackFunction; +#endif // FEATURE_HIJACK && !PLATFORM_UNIX +} + +// For debug-only builds, we'll have a debugging feature to count +// the number of ipc events and break on a specific number. +// Initialize the stuff to do that. +void Debugger::InitDebugEventCounting() +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; +#ifdef _DEBUG + // initialize the debug event counter structure to zero + memset(&g_debugEventCounter, 0, sizeof(DebugEventCounter)); + memset(&g_iDbgRuntimeCounter, 0, DBG_RUNTIME_MAX*sizeof(int)); + memset(&g_iDbgDebuggerCounter, 0, DBG_DEBUGGER_MAX*sizeof(int)); + + // retrieve the possible counter for break point + LPWSTR wstrValue = NULL; + // The string value is of the following format + // =Count;=Count;....; + // The string must end with ; + if ((wstrValue = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DebuggerBreakPoint)) != NULL) + { + LPSTR strValue; + int cbReq; + cbReq = WszWideCharToMultiByte(CP_UTF8, 0, wstrValue,-1, 0,0, 0,0); + + strValue = new (nothrow) char[cbReq+1]; + // This is a debug only thingy, if it fails, not worth taking + // down the process. + if (strValue == NULL) + return; + + + // now translate the unicode to ansi string + WszWideCharToMultiByte(CP_UTF8, 0, wstrValue, -1, strValue, cbReq+1, 0,0); + char *szEnd = (char *)strchr(strValue, ';'); + char *szStart = strValue; + while (szEnd != NULL) + { + // Found a key value + char *szNameEnd = strchr(szStart, '='); + int iCount; + DebuggerIPCEventType eventType; + if (szNameEnd != NULL) + { + // This is a well form key + *szNameEnd = '\0'; + *szEnd = '\0'; + + // now szStart is the key name null terminated. Translate the counter into integer. + iCount = atoi(szNameEnd+1); + if (iCount != 0) + { + eventType = IPCENames::GetEventType(szStart); + + if (eventType < DB_IPCE_DEBUGGER_FIRST) + { + // use the runtime one + g_iDbgRuntimeCounter[eventType & 0x00ff] = iCount; + } + else if (eventType < DB_IPCE_DEBUGGER_LAST) + { + // use the debugger one + g_iDbgDebuggerCounter[eventType & 0x00ff] = iCount; + } + else + _ASSERTE(!"Unknown Event Type"); + } + } + szStart = szEnd + 1; + // try to find next key value + szEnd = (char *)strchr(szStart, ';'); + } + + // free the ansi buffer + delete [] strValue; + REGUTIL::FreeConfigString(wstrValue); + } +#endif // _DEBUG +} + + +// This is a notification from the EE it's about to go to fiber mode. +// This is given *before* it actually goes to fiber mode. +HRESULT Debugger::SetFiberMode(bool isFiberMode) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + // Notifications from EE never come on helper worker. + PRECONDITION(!ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + + Thread * pThread = ::GetThread(); + + m_pRCThread->m_pDCB->m_bHostingInFiber = isFiberMode; + + // If there is a debugger already attached, then we have a big problem. As of V2.0, the debugger + // does not support debugging processes with fibers in them. We set the unrecoverable state to + // indicate that we're in a bad state now. The debugger will notice this, and take appropiate action. + if (isFiberMode && CORDebuggerAttached()) + { + LOG((LF_CORDB, LL_INFO10, "Thread has entered fiber mode while debugger attached.\n")); + + EX_TRY + { + // We send up a MDA for two reasons: 1) we want to give the user some chance to see what went wrong, + // and 2) we want to get the Right Side to notice that we're in an unrecoverable error state now. + + SString szName(W("DebuggerFiberModeNotSupported")); + SString szDescription; + szDescription.LoadResource(CCompRC::Debugging, MDARC_DEBUGGER_FIBER_MODE_NOT_SUPPORTED); + SString szXML(W("")); + + // Sending any debug event will be a GC violation. + // However, if we're enabling fiber-mode while a debugger is attached, we're already doomed. + // Deadlocks and AVs are just around the corner. A Gc-violation is the least of our worries. + // We want to at least notify the debugger at all costs. + CONTRACT_VIOLATION(GCViolation); + + // As soon as we set unrecoverable error in the LS, the RS will pick it up and basically shut down. + // It won't dispatch any events. So we fire the MDA first, and then set unrecoverable error. + SendMDANotification(pThread, &szName, &szDescription, &szXML, (CorDebugMDAFlags) 0, FALSE); + + CORDBDebuggerSetUnrecoverableError(this, CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS, false); + + // Fire the MDA again just to force the RS to sniff the LS and pick up that we're in an unrecoverable error. + // No harm done from dispatching an MDA twice. And + SendMDANotification(pThread, &szName, &szDescription, &szXML, (CorDebugMDAFlags) 0, FALSE); + + } + EX_CATCH + { + LOG((LF_CORDB, LL_INFO10, "Error sending MDA regarding fiber mode.\n")); + } + EX_END_CATCH(SwallowAllExceptions); + } + + return S_OK; +} + +// Checks if the MethodInfos table has been allocated, and if not does so. +// Throw on failure, so we always return +HRESULT Debugger::CheckInitMethodInfoTable() +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (m_pMethodInfos == NULL) + { + DebuggerMethodInfoTable *pMethodInfos = NULL; + + EX_TRY + { + pMethodInfos = new (interopsafe) DebuggerMethodInfoTable(); + } + EX_CATCH + { + pMethodInfos = NULL; + } + EX_END_CATCH(RethrowTerminalExceptions); + + + if (pMethodInfos == NULL) + { + return E_OUTOFMEMORY; + } + + if (InterlockedCompareExchangeT(&m_pMethodInfos, pMethodInfos, NULL) != NULL) + { + DeleteInteropSafe(pMethodInfos); + } + } + + return S_OK; +} + +// Checks if the m_pModules table has been allocated, and if not does so. +HRESULT Debugger::CheckInitModuleTable() +{ + CONTRACT(HRESULT) + { + NOTHROW; + GC_NOTRIGGER; + POSTCONDITION(m_pModules != NULL); + } + CONTRACT_END; + + if (m_pModules == NULL) + { + DebuggerModuleTable *pModules = new (interopsafe, nothrow) DebuggerModuleTable(); + + if (pModules == NULL) + { + RETURN (E_OUTOFMEMORY); + } + + if (InterlockedCompareExchangeT(&m_pModules, pModules, NULL) != NULL) + { + DeleteInteropSafe(pModules); + } + } + + RETURN (S_OK); +} + +// Checks if the m_pModules table has been allocated, and if not does so. +HRESULT Debugger::CheckInitPendingFuncEvalTable() +{ + CONTRACT(HRESULT) + { + NOTHROW; + GC_NOTRIGGER; + POSTCONDITION(GetPendingEvals() != NULL); + } + CONTRACT_END; + +#ifndef DACCESS_COMPILE + + if (GetPendingEvals() == NULL) + { + DebuggerPendingFuncEvalTable *pPendingEvals = new (interopsafe, nothrow) DebuggerPendingFuncEvalTable(); + + if (pPendingEvals == NULL) + { + RETURN(E_OUTOFMEMORY); + } + + // Since we're setting, we need an LValue and not just an accessor. + if (InterlockedCompareExchangeT(&(GetLazyData()->m_pPendingEvals), pPendingEvals, NULL) != NULL) + { + DeleteInteropSafe(pPendingEvals); + } + } +#endif + + RETURN (S_OK); +} + + +#ifdef _DEBUG_DMI_TABLE +// Returns the number of (official) entries in the table +ULONG DebuggerMethodInfoTable::CheckDmiTable(void) +{ + LIMITED_METHOD_CONTRACT; + + ULONG cApparant = 0; + ULONG cOfficial = 0; + + if (NULL != m_pcEntries) + { + DebuggerMethodInfoEntry *dcp; + int i = 0; + while (i++ pFD != 0 && + dcp->pFD != (MethodDesc*)0xcdcdcdcd && + dcp->mi != NULL) + { + cApparant++; + + _ASSERTE( dcp->pFD == dcp->mi->m_fd ); + LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Entry:0x%p mi:0x%p\nPrevs:\n", + dcp, dcp->mi)); + DebuggerMethodInfo *dmi = dcp->mi->m_prevMethodInfo; + + while(dmi != NULL) + { + LOG((LF_CORDB, LL_INFO1000, "\t0x%p\n", dmi)); + dmi = dmi->m_prevMethodInfo; + } + dmi = dcp->mi->m_nextMethodInfo; + + LOG((LF_CORDB, LL_INFO1000, "Nexts:\n", dmi)); + while(dmi != NULL) + { + LOG((LF_CORDB, LL_INFO1000, "\t0x%p\n", dmi)); + dmi = dmi->m_nextMethodInfo; + } + + LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:DONE\n", + dcp, dcp->mi)); + } + } + + if (m_piBuckets == 0) + { + LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT: The table is officially empty!\n")); + return cOfficial; + } + + LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Looking for official entries:\n")); + + ULONG iNext = m_piBuckets[0]; + ULONG iBucket = 1; + HASHENTRY *psEntry = NULL; + while (TRUE) + { + while (iNext != UINT32_MAX) + { + cOfficial++; + + psEntry = EntryPtr(iNext); + dcp = ((DebuggerMethodInfoEntry *)psEntry); + + LOG((LF_CORDB, LL_INFO1000, "\tEntry:0x%p mi:0x%p @idx:0x%x @bucket:0x%x\n", + dcp, dcp->mi, iNext, iBucket)); + + iNext = psEntry->iNext; + } + + // Advance to the next bucket. + if (iBucket < m_iBuckets) + iNext = m_piBuckets[iBucket++]; + else + break; + } + + LOG((LF_CORDB, LL_INFO1000, "DMIT::CDT:Finished official entries: ****************")); + } + + return cOfficial; +} +#endif // _DEBUG_DMI_TABLE + + +//--------------------------------------------------------------------------------------- +// +// Class constructor for DebuggerEval. This is the supporting data structure for +// func-eval tracking. +// +// Arguments: +// pContext - The context to return to when done with this eval. +// pEvalInfo - Contains all the important information, such as parameters, type args, method. +// fInException - TRUE if the thread for the eval is currently in an exception notification. +// +DebuggerEval::DebuggerEval(CONTEXT * pContext, DebuggerIPCE_FuncEvalInfo * pEvalInfo, bool fInException) +{ + WRAPPER_NO_CONTRACT; + + // Allocate the breakpoint instruction info in executable memory. + m_bpInfoSegment = new (interopsafeEXEC, nothrow) DebuggerEvalBreakpointInfoSegment(this); + + // This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16 + // so that we can have a breakpoint instruction in any slot in the bundle. + m_bpInfoSegment->m_breakpointInstruction[0] = 0x16; +#if defined(_TARGET_ARM_) + USHORT *bp = (USHORT*)&m_bpInfoSegment->m_breakpointInstruction; + *bp = CORDbg_BREAK_INSTRUCTION; +#endif // _TARGET_ARM_ + m_thread = pEvalInfo->vmThreadToken.GetRawPtr(); + m_evalType = pEvalInfo->funcEvalType; + m_methodToken = pEvalInfo->funcMetadataToken; + m_classToken = pEvalInfo->funcClassMetadataToken; + + // Note: we can't rely on just the DebuggerModule* or AppDomain* because the AppDomain + // could get unloaded between now and when the funceval actually starts. So we stash an + // AppDomain ID which is safe to use after the AD is unloaded. It's only safe to + // use the DebuggerModule* after we've verified the ADID is still valid (i.e. by entering that domain). + m_debuggerModule = g_pDebugger->LookupOrCreateModule(pEvalInfo->vmDomainFile); + + if (m_debuggerModule == NULL) + { + // We have no associated code. + _ASSERTE((m_evalType == DB_IPCE_FET_NEW_STRING) || (m_evalType == DB_IPCE_FET_NEW_ARRAY)); + + // We'll just do the creation in whatever domain the thread is already in. + // It's conceivable that we might want to allow the caller to specify a specific domain, but + // ICorDebug provides the debugger with no was to specify the domain. + m_appDomainId = m_thread->GetDomain()->GetId(); + } + else + { + m_appDomainId = m_debuggerModule->GetAppDomain()->GetId(); + } + + m_funcEvalKey = pEvalInfo->funcEvalKey; + m_argCount = pEvalInfo->argCount; + m_targetCodeAddr = NULL; + m_stringSize = pEvalInfo->stringSize; + m_arrayRank = pEvalInfo->arrayRank; + m_genericArgsCount = pEvalInfo->genericArgsCount; + m_genericArgsNodeCount = pEvalInfo->genericArgsNodeCount; + m_successful = false; + m_argData = NULL; + memset(m_result, 0, sizeof(m_result)); + m_md = NULL; + m_resultType = TypeHandle(); + m_aborting = FE_ABORT_NONE; + m_aborted = false; + m_completed = false; + m_evalDuringException = fInException; + m_rethrowAbortException = false; + m_retValueBoxing = Debugger::NoValueTypeBoxing; + m_requester = (Thread::ThreadAbortRequester)0; + m_vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr(); + + // Copy the thread's context. + if (pContext == NULL) + { + memset(&m_context, 0, sizeof(m_context)); + } + else + { + memcpy(&m_context, pContext, sizeof(m_context)); + } +} + +//--------------------------------------------------------------------------------------- +// +// This constructor is only used when setting up an eval to re-abort a thread. +// +// Arguments: +// pContext - The context to return to when done with this eval. +// pThread - The thread to re-abort. +// requester - The type of abort to throw. +// +DebuggerEval::DebuggerEval(CONTEXT * pContext, Thread * pThread, Thread::ThreadAbortRequester requester) +{ + WRAPPER_NO_CONTRACT; + + // Allocate the breakpoint instruction info in executable memory. + m_bpInfoSegment = new (interopsafeEXEC, nothrow) DebuggerEvalBreakpointInfoSegment(this); + + // This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16 + // so that we can have a breakpoint instruction in any slot in the bundle. + m_bpInfoSegment->m_breakpointInstruction[0] = 0x16; + m_thread = pThread; + m_evalType = DB_IPCE_FET_RE_ABORT; + m_methodToken = mdMethodDefNil; + m_classToken = mdTypeDefNil; + m_debuggerModule = NULL; + m_funcEvalKey = RSPTR_CORDBEVAL::NullPtr(); + m_argCount = 0; + m_stringSize = 0; + m_arrayRank = 0; + m_genericArgsCount = 0; + m_genericArgsNodeCount = 0; + m_successful = false; + m_argData = NULL; + m_targetCodeAddr = NULL; + memset(m_result, 0, sizeof(m_result)); + m_md = NULL; + m_resultType = TypeHandle(); + m_aborting = FE_ABORT_NONE; + m_aborted = false; + m_completed = false; + m_evalDuringException = false; + m_rethrowAbortException = false; + m_retValueBoxing = Debugger::NoValueTypeBoxing; + m_requester = requester; + + if (pContext == NULL) + { + memset(&m_context, 0, sizeof(m_context)); + } + else + { + memcpy(&m_context, pContext, sizeof(m_context)); + } +} + + +#ifdef _DEBUG +// Thread proc for interop stress coverage. Have an unmanaged thread +// that just loops throwing native exceptions. This can test corner cases +// such as getting an native exception while the runtime is synced. +DWORD WINAPI DbgInteropStressProc(void * lpParameter) +{ + LIMITED_METHOD_CONTRACT; + + int i = 0; + int zero = 0; + + + // This will ensure that the compiler doesn't flag our 1/0 exception below at compile-time. + if (lpParameter != NULL) + { + zero = 1; + } + + // Note that this thread is a non-runtime thread. So it can't take any CLR locks + // or do anything else that may block the helper thread. + // (Log statements take CLR locks). + while(true) + { + i++; + + if ((i % 10) != 0) + { + // Generate an in-band event. + PAL_CPP_TRY + { + // Throw a handled exception. Don't use an AV since that's pretty special. + *(int*)lpParameter = 1 / zero; + } + PAL_CPP_CATCH_ALL + { + } + PAL_CPP_ENDTRY + } + else + { + // Generate the occasional oob-event. + WszOutputDebugString(W("Ping from DbgInteropStressProc")); + } + + // This helps parallelize if we have a lot of threads, and keeps us from + // chewing too much CPU time. + ClrSleepEx(2000,FALSE); + ClrSleepEx(GetRandomInt(1000), FALSE); + } + + return 0; +} + +// ThreadProc that does everything in a can't stop region. +DWORD WINAPI DbgInteropCantStopStressProc(void * lpParameter) +{ + WRAPPER_NO_CONTRACT; + + // This will mark us as a can't stop region. + ClrFlsSetThreadType (ThreadType_DbgHelper); + + return DbgInteropStressProc(lpParameter); +} + +// Generate lots of OOB events. +DWORD WINAPI DbgInteropDummyStressProc(void * lpParameter) +{ + LIMITED_METHOD_CONTRACT; + + ClrSleepEx(1,FALSE); + return 0; +} + +DWORD WINAPI DbgInteropOOBStressProc(void * lpParameter) +{ + WRAPPER_NO_CONTRACT; + + int i = 0; + while(true) + { + i++; + if (i % 10 == 1) + { + // Create a dummy thread. That generates 2 oob events + // (1 for create, 1 for destroy) + DWORD id; + ::CreateThread(NULL, 0, DbgInteropDummyStressProc, NULL, 0, &id); + } + else + { + // Generate the occasional oob-event. + WszOutputDebugString(W("OOB ping from ")); + } + + ClrSleepEx(3000, FALSE); + } + + return 0; +} + +// List of the different possible stress procs. +LPTHREAD_START_ROUTINE g_pStressProcs[] = +{ + DbgInteropOOBStressProc, + DbgInteropCantStopStressProc, + DbgInteropStressProc +}; +#endif + + +DebuggerHeap * Debugger::GetInteropSafeHeap() +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Lazily initialize our heap. + if (!m_heap.IsInit()) + { + _ASSERTE(!"InteropSafe Heap should have already been initialized in LazyInit"); + + // Just in case we miss it in retail, convert to OOM here: + ThrowOutOfMemory(); + } + + return &m_heap; +} + +DebuggerHeap * Debugger::GetInteropSafeHeap_NoThrow() +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Lazily initialize our heap. + if (!m_heap.IsInit()) + { + _ASSERTE(!"InteropSafe Heap should have already been initialized in LazyInit"); + + // Just in case we miss it in retail, convert to OOM here: + return NULL; + } + return &m_heap; +} + +DebuggerHeap * Debugger::GetInteropSafeExecutableHeap() +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Lazily initialize our heap. + if (!m_executableHeap.IsInit()) + { + _ASSERTE(!"InteropSafe Executable Heap should have already been initialized in LazyInit"); + + // Just in case we miss it in retail, convert to OOM here: + ThrowOutOfMemory(); + } + + return &m_executableHeap; +} + +DebuggerHeap * Debugger::GetInteropSafeExecutableHeap_NoThrow() +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Lazily initialize our heap. + if (!m_executableHeap.IsInit()) + { + _ASSERTE(!"InteropSafe Executable Heap should have already been initialized in LazyInit"); + + // Just in case we miss it in retail, convert to OOM here: + return NULL; + } + return &m_executableHeap; +} + +//--------------------------------------------------------------------------------------- +// +// Notify potential debugger that the runtime has started up +// +// +// Assumptions: +// Called during startup path +// +// Notes: +// If no debugger is attached, this does nothing. +// +//--------------------------------------------------------------------------------------- +void Debugger::RaiseStartupNotification() +{ + // Right-side will read this field from OOP via DAC-primitive to determine attach or launch case. + // We do an interlocked increment to gaurantee this is an atomic memory write, and to ensure + // that it's flushed from any CPU cache into memory. + InterlockedIncrement(&m_fLeftSideInitialized); + +#ifndef FEATURE_DBGIPC_TRANSPORT_VM + // If we are remote debugging, don't send the event now if a debugger is not attached. No one will be + // listening, and we will fail. However, we still want to initialize the variable above. + DebuggerIPCEvent startupEvent; + InitIPCEvent(&startupEvent, DB_IPCE_LEFTSIDE_STARTUP, NULL, VMPTR_AppDomain::NullPtr()); + + SendRawEvent(&startupEvent); + + // RS will set flags from OOP while we're stopped at the event if it wants to attach. +#endif // FEATURE_DBGIPC_TRANSPORT_VM +} + + +//--------------------------------------------------------------------------------------- +// +// Sends a raw managed debug event to the debugger. +// +// Arguments: +// pManagedEvent - managed debug event +// +// +// Notes: +// This can be called even if a debugger is not attached. +// The entire process will get frozen by the debugger once we send. The debugger +// needs to resume the process. It may detach as well. +// See code:IsEventDebuggerNotification for decoding this event. These methods must stay in sync. +// The debugger process reads the events via code:CordbProcess.CopyManagedEventFromTarget. +// +//--------------------------------------------------------------------------------------- +void Debugger::SendRawEvent(const DebuggerIPCEvent * pManagedEvent) +{ +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + HRESULT hr = g_pDbgTransport->SendDebugEvent(const_cast(pManagedEvent)); + + if (FAILED(hr)) + { + _ASSERTE(!"Failed to send debugger event"); + + STRESS_LOG1(LF_CORDB, LL_INFO1000, "D::SendIPCEvent Error on Send with 0x%x\n", hr); + UnrecoverableError(hr, + 0, + FILE_DEBUG, + LINE_DEBUG, + false); + + // @dbgtodo Mac - what can we do here? + } +#else + // We get to send an array of ULONG_PTRs as data with the notification. + // The debugger can then use ReadProcessMemory to read through this array. + ULONG_PTR rgData [] = { + CLRDBG_EXCEPTION_DATA_CHECKSUM, + (ULONG_PTR) g_pMSCorEE, + (ULONG_PTR) pManagedEvent + }; + + // If no debugger attached, then don't bother raising a 1st-chance exception because nobody will sniff it. + // @dbgtodo iDNA: in iDNA case, the recorder may sniff it. + if (!IsDebuggerPresent()) + { + return; + } + + // + // Physically send the event via an OS Exception. We're using exceptions as a notification + // mechanism on top of the OS native debugging pipeline. + // @dbgtodo cross-plat - this needs to be cross-plat. + // + EX_TRY + { + const DWORD dwFlags = 0; // continuable (eg, Debugger can continue GH) + RaiseException(CLRDBG_NOTIFICATION_EXCEPTION_CODE, dwFlags, NumItems(rgData), rgData); + + // If debugger continues "GH" (DBG_CONTINUE), then we land here. + // This is the expected path for a well-behaved ICorDebug debugger. + } + EX_CATCH + { + // If no debugger is attached, or if the debugger continues "GN" (DBG_EXCEPTION_NOT_HANDLED), then we land here. + // A naive (not-ICorDebug aware) native-debugger won't handle the exception and so land us here. + // We may also get here if a debugger detaches at the Exception notification + // (and thus implicitly continues GN). + } + EX_END_CATCH(SwallowAllExceptions); +#endif // FEATURE_DBGIPC_TRANSPORT_VM +} + +//--------------------------------------------------------------------------------------- +// Send a createProcess event to give the RS a chance to do SetDesiredNGENFlags +// +// Arguments: +// pDbgLockHolder - lock holder. +// +// Assumptions: +// Lock is initially held. This will toggle the lock to send an IPC event. +// This will start a synchronization. +// +// Notes: +// In V2, this also gives the RS a chance to intialize the IPC protocol. +// Spefically, this needs to be sent before the LS can send a sync-complete. +//--------------------------------------------------------------------------------------- +void Debugger::SendCreateProcess(DebuggerLockHolder * pDbgLockHolder) +{ + pDbgLockHolder->Release(); + + // Encourage helper thread to spin up so that we're in a consistent state. + PollWaitingForHelper(); + + // we don't need to use SENDIPCEVENT_BEGIN/END macros that perform the debug-suspend aware checks, + // as this code executes on the startup path... + SENDIPCEVENT_RAW_BEGIN(pDbgLockHolder); + + // Send a CreateProcess event. + // @dbgtodo pipeline - eliminate these reasons for needing a CreateProcess event (part of pipeline feature crew) + // This will let the RS know that the IPC block is up + ready, and then the RS can read it. + // The RS will then update the DCB with enough information so that we can send the sync-complete. + // (such as letting us know whether we're interop-debugging or not). + DebuggerIPCEvent event; + InitIPCEvent(&event, DB_IPCE_CREATE_PROCESS, NULL, VMPTR_AppDomain::NullPtr()); + SendRawEvent(&event); + + // @dbgtodo inspection- it doesn't really make sense to sync on a CreateProcess. We only have 1 thread + // in the CLR and we know exactly what state we're in and we can ensure that we're synchronized. + // For V3,RS should be able to treat a CreateProcess like a synchronized. + // Remove this in V3 as we make SetDesiredNgenFlags operate OOP. + TrapAllRuntimeThreads(); + + // Must have a thread object so that we ensure that we will actually block here. + // This ensures the debuggee is actually stopped at startup, and + // this gives the debugger a chance to call SetDesiredNGENFlags before we + // set s_fCanChangeNgenFlags to FALSE. + _ASSERTE(GetThread() != NULL); + SENDIPCEVENT_RAW_END; + + pDbgLockHolder->Acquire(); +} + +#if defined(FEATURE_CORECLR) && !defined(FEATURE_PAL) + +HANDLE g_hContinueStartupEvent = NULL; + +CLR_ENGINE_METRICS g_CLREngineMetrics = { + sizeof(CLR_ENGINE_METRICS), + CorDebugVersion_4_0, + &g_hContinueStartupEvent}; + + +bool IsTelestoDebugPackInstalled() +{ + RegKeyHolder hKey; + if (ERROR_SUCCESS != WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &hKey)) + return false; + + bool debugPackInstalled = false; + + DWORD cbValue = 0; + + if (ERROR_SUCCESS == WszRegQueryValueEx(hKey, CLRConfig::EXTERNAL_DbgPackShimPath, NULL, NULL, NULL, &cbValue)) + { + if (cbValue != 0) + { + debugPackInstalled = true; + } + } + + // RegCloseKey called by holder + return debugPackInstalled; +} + +#define StartupNotifyEventNamePrefix W("TelestoStartupEvent_") +const int cchEventNameBufferSize = sizeof(StartupNotifyEventNamePrefix)/sizeof(WCHAR) + 8; // + hex DWORD (8). NULL terminator is included in sizeof(StartupNotifyEventNamePrefix) +HANDLE OpenStartupNotificationEvent() +{ + DWORD debuggeePID = GetCurrentProcessId(); + WCHAR szEventName[cchEventNameBufferSize]; + swprintf_s(szEventName, cchEventNameBufferSize, StartupNotifyEventNamePrefix W("%08x"), debuggeePID); + + return WszOpenEvent(EVENT_ALL_ACCESS, FALSE, szEventName); +} + +void NotifyDebuggerOfTelestoStartup() +{ + // Create the continue event first so that we guarantee that any + // enumeration of this process will get back a valid continue event + // the instant we signal the startup notification event. + + CONSISTENCY_CHECK(NULL == g_hContinueStartupEvent); + g_hContinueStartupEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); + CONSISTENCY_CHECK(INVALID_HANDLE_VALUE != g_hContinueStartupEvent); // we reserve this value for error conditions in EnumerateCLRs + + HANDLE startupEvent = OpenStartupNotificationEvent(); + if (startupEvent != NULL) + { + // signal notification event + SetEvent(startupEvent); + CloseHandle(startupEvent); + startupEvent = NULL; + + // wait on continue startup event + // The debugger may attach to us while we're blocked here. + WaitForSingleObject(g_hContinueStartupEvent, INFINITE); + } + + CloseHandle(g_hContinueStartupEvent); + g_hContinueStartupEvent = NULL; +} + +#endif // FEATURE_CORECLR && !FEATURE_PAL + +//--------------------------------------------------------------------------------------- +// +// Initialize Left-Side debugger object +// +// Return Value: +// S_OK on successs. May also throw. +// +// Assumptions: +// This is called in the startup path. +// +// Notes: +// Startup initializes any necessary debugger objects, including creating +// and starting the Runtime Controller thread. Once the RC thread is started +// and we return successfully, the Debugger object can expect to have its +// event handlers called. +// +//--------------------------------------------------------------------------------------- +HRESULT Debugger::Startup(void) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + _ASSERTE(g_pEEInterface != NULL); + +#if defined(FEATURE_CORECLR) && !defined(FEATURE_PAL) + if (IsWatsonEnabled() || IsTelestoDebugPackInstalled()) + { + // Iff the debug pack is installed, then go through the telesto debugging pipeline. + LOG((LF_CORDB, LL_INFO10, "Debugging service is enabled because debug pack is installed or Watson support is enabled)\n")); + + // This may block while an attach occurs. + NotifyDebuggerOfTelestoStartup(); + } + else + { + // On Windows, it's actually safe to finish the initialization here even without the debug pack. + // However, doing so causes a perf regression because we used to bail out early if the debug + // pack is not installed. + // + // Unlike Windows, we can't continue executing this function if the debug pack is not installed. + // The transport requires the debug pack to be present. Otherwise it'll raise a fatal error. + return S_FALSE; + } +#endif // FEATURE_CORECLR && !FEATURE_PAL + + { + DebuggerLockHolder dbgLockHolder(this); + + // Stubs in Stacktraces are always enabled. + g_EnableSIS = true; + + // We can get extra Interop-debugging test coverage by having some auxillary unmanaged + // threads running and throwing debug events. Keep these stress procs separate so that + // we can focus on certain problem areas. + #ifdef _DEBUG + + g_DbgShouldntUseDebugger = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNoDebugger) != 0; + + + // Creates random thread procs. + DWORD dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreads); + DWORD dwId; + DWORD i; + + if (dwRegVal > 0) + { + for (i = 0; i < dwRegVal; i++) + { + int iProc = GetRandomInt(NumItems(g_pStressProcs)); + LPTHREAD_START_ROUTINE pStartRoutine = g_pStressProcs[iProc]; + ::CreateThread(NULL, 0, pStartRoutine, NULL, 0, &dwId); + LOG((LF_CORDB, LL_INFO1000, "Created random thread (%d) with tid=0x%x\n", i, dwId)); + } + } + + dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsIB); + if (dwRegVal > 0) + { + for (i = 0; i < dwRegVal; i++) + { + ::CreateThread(NULL, 0, DbgInteropStressProc, NULL, 0, &dwId); + LOG((LF_CORDB, LL_INFO1000, "Created extra thread (%d) with tid=0x%x\n", i, dwId)); + } + } + + dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsCantStop); + if (dwRegVal > 0) + { + for (i = 0; i < dwRegVal; i++) + { + ::CreateThread(NULL, 0, DbgInteropCantStopStressProc, NULL, 0, &dwId); + LOG((LF_CORDB, LL_INFO1000, "Created extra thread 'can't-stop' (%d) with tid=0x%x\n", i, dwId)); + } + } + + dwRegVal = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgExtraThreadsOOB); + if (dwRegVal > 0) + { + for (i = 0; i < dwRegVal; i++) + { + ::CreateThread(NULL, 0, DbgInteropOOBStressProc, NULL, 0, &dwId); + LOG((LF_CORDB, LL_INFO1000, "Created extra thread OOB (%d) with tid=0x%x\n", i, dwId)); + } + } + #endif + + #ifdef FEATURE_PAL + PAL_InitializeDebug(); + #endif // FEATURE_PAL + + // Lazily initialize the interop-safe heap + + // Must be done before the RC thread is initialized. + // @dbgtodo - In V2, LS was lazily initialized; but was eagerly pre-initialized if launched by debugger. + // (This was for perf reasons). But we don't want Launch vs. Attach checks in the LS, so we now always + // init. As we move more to OOP, this init will become cheaper. + { + LazyInit(); + DebuggerController::Initialize(); + } + + InitializeHijackFunctionAddress(); + + // Create the runtime controller thread, a.k.a, the debug helper thread. + // Don't use the interop-safe heap b/c we don't want to lazily create it. + m_pRCThread = new DebuggerRCThread(this); + _ASSERTE(m_pRCThread != NULL); // throws on oom + TRACE_ALLOC(m_pRCThread); + + hr = m_pRCThread->Init(); + _ASSERTE(SUCCEEDED(hr)); // throws on error + + #if defined(FEATURE_DBGIPC_TRANSPORT_VM) + // Create transport session and initialize it. + g_pDbgTransport = new DbgTransportSession(); + hr = g_pDbgTransport->Init(m_pRCThread->GetDCB(), m_pAppDomainCB); + if (FAILED(hr)) + { + ShutdownTransport(); + ThrowHR(hr); + } + #ifdef FEATURE_PAL + PAL_SetShutdownCallback(AbortTransport); + #endif // FEATURE_PAL + #endif // FEATURE_DBGIPC_TRANSPORT_VM + + RaiseStartupNotification(); + + // Also initialize the AppDomainEnumerationIPCBlock + #if !defined(FEATURE_IPCMAN) || defined(FEATURE_DBGIPC_TRANSPORT_VM) + m_pAppDomainCB = new (nothrow) AppDomainEnumerationIPCBlock(); + #else + m_pAppDomainCB = g_pIPCManagerInterface->GetAppDomainBlock(); + #endif + + if (m_pAppDomainCB == NULL) + { + LOG((LF_CORDB, LL_INFO100, "D::S: Failed to get AppDomain IPC block from IPCManager.\n")); + ThrowHR(E_FAIL); + } + + hr = InitAppDomainIPC(); + _ASSERTE(SUCCEEDED(hr)); // throws on error. + + // See if we need to spin up the helper thread now, rather than later. + DebuggerIPCControlBlock* pIPCControlBlock = m_pRCThread->GetDCB(); + (void)pIPCControlBlock; //prevent "unused variable" error from GCC + + _ASSERTE(pIPCControlBlock != NULL); + _ASSERTE(!pIPCControlBlock->m_rightSideShouldCreateHelperThread); + { + // Create the win32 thread for the helper and let it run free. + hr = m_pRCThread->Start(); + + // convert failure to exception as with old contract + if (FAILED(hr)) + { + ThrowHR(hr); + } + + LOG((LF_CORDB, LL_EVERYTHING, "Start was successful\n")); + } + + #ifdef TEST_DATA_CONSISTENCY + // if we have set the environment variable TestDataConsistency, run the data consistency test. + // See code:DataTest::TestDataSafety for more information + if ((g_pConfig != NULL) && (g_pConfig->TestDataConsistency() == true)) + { + DataTest dt; + dt.TestDataSafety(); + } + #endif + } + +#ifdef FEATURE_PAL + // Signal the debugger (via dbgshim) and wait until it is ready for us to + // continue. This needs to be outside the lock and after the transport is + // initialized. + PAL_NotifyRuntimeStarted(); +#endif // FEATURE_PAL + + // We don't bother changing this process's permission. + // A managed debugger will have the SE_DEBUG permission which will allow it to open our process handle, + // even if we're a guest account. + + return hr; +} + +//--------------------------------------------------------------------------------------- +// Finishes startup once we have a Thread object. +// +// Arguments: +// pThread - the current thread. Must be non-null +// +// Notes: +// Most debugger initialization is done in code:Debugger.Startup, +// However, debugger can't block on synchronization without a Thread object, +// so sending IPC events must wait until after we have a thread object. +//--------------------------------------------------------------------------------------- +HRESULT Debugger::StartupPhase2(Thread * pThread) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + // Must have a thread so that we can block + _ASSERTE(pThread != NULL); + + DebuggerLockHolder dbgLockHolder(this); + + // @dbgtodo - This may need to change when we remove SetupSyncEvent... + // If we're launching, then sync now so that the RS gets an early chance to dispatch the CreateProcess event. + // This is especially important b/c certain portions of the ICorDebugAPI (like setting ngen flags) are only + // valid during the CreateProcess callback in the launch case. + // We need to send the callback early enough so those APIs can set the flags before they're actually used. + // We also ensure the debugger is actually attached. + if (SUCCEEDED(hr) && CORDebuggerAttached()) + { + StartCanaryThread(); + SendCreateProcess(&dbgLockHolder); // toggles lock + } + + // After returning from debugger startup we assume that the runtime might start using the NGEN flags to make + // binding decisions. From now on the debugger can not influence NGEN binding policy + // Use volatile store to guarantee make the value visible to the DAC (the store can be optimized out otherwise) + VolatileStoreWithoutBarrier(&s_fCanChangeNgenFlags, FALSE); + + // Must release the lock (which would be done at the end of this method anyways) so that + // the helper thread can do the jit-attach. + dbgLockHolder.Release(); + + +#ifdef _DEBUG + // Give chance for stress harnesses to launch a managed debugger when a managed app starts up. + // This lets us run a set of managed apps under a debugger. + if (!CORDebuggerAttached()) + { + #define DBG_ATTACH_ON_STARTUP_ENV_VAR W("COMPlus_DbgAttachOnStartup") + PathString temp; + // We explicitly just check the env because we don't want a switch this invasive to be global. + DWORD fAttach = WszGetEnvironmentVariable(DBG_ATTACH_ON_STARTUP_ENV_VAR, temp) > 0; + + if (fAttach) + { + // Remove the env var from our process so that the debugger we spin up won't inherit it. + // Else, if the debugger is managed, we'll have an infinite recursion. + BOOL fOk = WszSetEnvironmentVariable(DBG_ATTACH_ON_STARTUP_ENV_VAR, NULL); + + if (fOk) + { + // We've already created the helper thread (which can service the attach request) + // So just do a normal jit-attach now. + + SString szName(W("DebuggerStressStartup")); + SString szDescription(W("MDA used for debugger-stress scenario. This is fired to trigger a jit-attach") + W("to allow us to attach a debugger to any managed app that starts up.") + W("This MDA is only fired when the 'DbgAttachOnStartup' COM+ knob/reg-key is set on checked builds.")); + SString szXML(W("See the description")); + + SendMDANotification( + NULL, // NULL b/c we don't have a thread yet + &szName, + &szDescription, + &szXML, + ((CorDebugMDAFlags) 0 ), + TRUE // this will force the jit-attach + ); + } + } + } +#endif + + + return hr; +} + + +//--------------------------------------------------------------------------------------- +// +// Public entrypoint into the debugger to force the lazy data to be initialized at a +// controlled point in time. This is useful for those callers into the debugger (e.g., +// ETW rundown) that know they will need the lazy data initialized but cannot afford to +// have it initialized unpredictably or inside a lock. +// +// This may be called more than once, and will know to initialize the lazy data only +// once. +// + +void Debugger::InitializeLazyDataIfNecessary() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + if (!HasLazyData()) + { + DebuggerLockHolder lockHolder(this); + LazyInit(); // throws + } +} + + +/****************************************************************************** +Lazy initialize stuff once we know we are debugging. +This reduces the startup cost in the non-debugging case. + +We can do this at a bunch of random strategic places. + ******************************************************************************/ + +HRESULT Debugger::LazyInitWrapper() +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + // Do lazy initialization now. + EX_TRY + { + LazyInit(); // throws on errors. + } + EX_CATCH + { + Exception *_ex = GET_EXCEPTION(); + hr = _ex->GetHR(); + STRESS_LOG1(LF_CORDB, LL_ALWAYS, "LazyInit failed w/ hr:0x%08x\n", hr); + } + EX_END_CATCH(SwallowAllExceptions); + + return hr; +} + +void Debugger::LazyInit() +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + PRECONDITION(ThreadHoldsLock()); // ensure we're serialized, requires GC_NOTRIGGER + + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + // Have knob that catches places where we lazy init. + _ASSERTE(!g_DbgShouldntUseDebugger); + + // If we're already init, then bail. + if (m_pLazyData != NULL) + { + return; + } + + + + + // Lazily create our heap. + HRESULT hr = m_heap.Init(FALSE); + IfFailThrow(hr); + + hr = m_executableHeap.Init(TRUE); + IfFailThrow(hr); + + m_pLazyData = new (interopsafe) DebuggerLazyInit(); + _ASSERTE(m_pLazyData != NULL); // throws on oom. + + m_pLazyData->Init(); + +} + +HelperThreadFavor::HelperThreadFavor() : + m_fpFavor(NULL), + m_pFavorData(NULL), + m_FavorReadEvent(NULL), + m_FavorLock(CrstDebuggerFavorLock, CRST_DEFAULT), + m_FavorAvailableEvent(NULL) +{ +} + +void HelperThreadFavor::Init() +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + // Create events for managing favors. + m_FavorReadEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); + m_FavorAvailableEvent = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); +} + + + +DebuggerLazyInit::DebuggerLazyInit() : + m_pPendingEvals(NULL), + // @TODO: a-meicht + // Major clean up needed for giving the right flag + // There are cases where DebuggerDataLock is taken by managed thread and unmanaged trhead is also trying to take it. + // It could cause deadlock if we toggle GC upon taking lock. + // Unfortunately UNSAFE_COOPGC is not enough. There is a code path in Jit comipling that we are in GC Preemptive + // enabled. workaround by orring the unsafe_anymode flag. But we really need to do proper clean up. + // + // NOTE: If this ever gets fixed, you should replace CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT + // with appropriate contracts at each site. + // + m_DebuggerDataLock(CrstDebuggerJitInfo, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)), + m_CtrlCMutex(NULL), + m_exAttachEvent(NULL), + m_exUnmanagedAttachEvent(NULL), + m_DebuggerHandlingCtrlC(NULL) +{ +} + +void DebuggerLazyInit::Init() +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + // Caller ensures this isn't double-called. + + // This event is only used in the unmanaged attach case. We must mark this event handle as inheritable. + // Otherwise, the unmanaged debugger won't be able to notify us. + // + // Note that PAL currently doesn't support specifying the security attributes when creating an event, so + // unmanaged attach for unhandled exceptions is broken on PAL. + SECURITY_ATTRIBUTES* pSA = NULL; + SECURITY_ATTRIBUTES secAttrib; + secAttrib.nLength = sizeof(secAttrib); + secAttrib.lpSecurityDescriptor = NULL; + secAttrib.bInheritHandle = TRUE; + + pSA = &secAttrib; + + // Create some synchronization events... + // these events stay signaled all the time except when an attach is in progress + m_exAttachEvent = CreateWin32EventOrThrow(NULL, kManualResetEvent, TRUE); + m_exUnmanagedAttachEvent = CreateWin32EventOrThrow(pSA, kManualResetEvent, TRUE); + + m_CtrlCMutex = CreateWin32EventOrThrow(NULL, kAutoResetEvent, FALSE); + m_DebuggerHandlingCtrlC = FALSE; + + // Let the helper thread lazy init stuff too. + m_RCThread.Init(); +} + + +DebuggerLazyInit::~DebuggerLazyInit() +{ + { + USHORT cBlobs = m_pMemBlobs.Count(); + void **rgpBlobs = m_pMemBlobs.Table(); + + for (int i = 0; i < cBlobs; i++) + { + g_pDebugger->ReleaseRemoteBuffer(rgpBlobs[i], false); + } + } + + if (m_pPendingEvals) + { + DeleteInteropSafe(m_pPendingEvals); + m_pPendingEvals = NULL; + } + + if (m_CtrlCMutex != NULL) + { + CloseHandle(m_CtrlCMutex); + } + + if (m_exAttachEvent != NULL) + { + CloseHandle(m_exAttachEvent); + } + + if (m_exUnmanagedAttachEvent != NULL) + { + CloseHandle(m_exUnmanagedAttachEvent); + } +} + + +// +// RequestFavor gets the debugger helper thread to call a function. It's +// typically called when the current thread can't call the function directly, +// e.g, there isn't enough stack space. +// +// RequestFavor can be called in stack-overflow scenarios and thus explicitly +// avoids any lazy initialization. +// It blocks until the favor callback completes. +// +// Parameters: +// fp - a non-null Favour callback function +// pData - the parameter passed to the favor callback function. This can be any value. +// +// Return values: +// S_OK if the function succeeds, else a failure HRESULT +// + +HRESULT Debugger::RequestFavor(FAVORCALLBACK fp, void * pData) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_TRIGGERS; + PRECONDITION(fp != NULL); + } + CONTRACTL_END; + + if (m_pRCThread == NULL || + m_pRCThread->GetRCThreadId() == GetCurrentThreadId()) + { + // Since favors are only used internally, we know that the helper should alway be up and ready + // to handle them. Also, since favors can be used in low-stack scenarios, there's not any + // extra initialization needed for them. + _ASSERTE(!"Helper not initialized for favors."); + return E_UNEXPECTED; + } + + m_pRCThread->DoFavor(fp, pData); + return S_OK; +} + +/****************************************************************************** +// Called to set the interface that the Runtime exposes to us. + ******************************************************************************/ +void Debugger::SetEEInterface(EEDebugInterface* i) +{ + LIMITED_METHOD_CONTRACT; + + // @@@ + + // Implements DebugInterface API + + g_pEEInterface = i; + +} + + +/****************************************************************************** +// Called to shut down the debugger. This stops the RC thread and cleans +// the object up. + ******************************************************************************/ +void Debugger::StopDebugger(void) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Leak almost everything on process exit. The OS will clean it up anyways and trying to + // clean it up ourselves is just one more place we may AV / deadlock. + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + ShutdownTransport(); +#endif // FEATURE_DBGIPC_TRANSPORT_VM + + // Ping the helper thread to exit. This will also prevent the helper from servicing new requests. + if (m_pRCThread != NULL) + { + m_pRCThread->AsyncStop(); + } + + // Also clean up the AppDomain stuff since this is cross-process. + TerminateAppDomainIPC (); + + // + // Tell the VM to clear out all references to the debugger before we start cleaning up, + // so that nothing will reference (accidentally) through the partially cleaned up debugger. + // + // NOTE: we cannot clear out g_pDebugger before the delete call because the + // stuff in delete (particularly deleteinteropsafe) needs to look at it. + // + g_pEEInterface->ClearAllDebugInterfaceReferences(); + g_pDebugger = NULL; +} + + +/* ------------------------------------------------------------------------ * + * JIT Interface routines + * ------------------------------------------------------------------------ */ + + +/****************************************************************************** + * + ******************************************************************************/ +DebuggerMethodInfo *Debugger::CreateMethodInfo(Module *module, mdMethodDef md) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + + PRECONDITION(HasDebuggerDataLock()); + } + CONTRACTL_END; + + + // @todo perf: creating these on the heap is slow. We should use a + // pool and create them out of there since we never free them + // until the AD is unloaded. + // + DebuggerMethodInfo *mi = new (interopsafe) DebuggerMethodInfo(module, md); + _ASSERTE(mi != NULL); // throws on oom error + + TRACE_ALLOC(mi); + + LOG((LF_CORDB, LL_INFO100000, "D::CreateMethodInfo module=%p, token=0x%08x, info=%p\n", + module, md, mi)); + + // + // Lock a mutex when changing the table. + // + //@TODO : _ASSERTE(EnC); + HRESULT hr; + hr =InsertToMethodInfoList(mi); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_EVERYTHING, "IAHOL Failed!!\n")); + DeleteInteropSafe(mi); + return NULL; + } + return mi; + +} + + + + + +/****************************************************************************** +// void Debugger::JITComplete(): JITComplete is called by +// the jit interface when the JIT completes, successfully or not. +// +// MethodDesc* fd: MethodDesc of the code that's been JITted +// BYTE* newAddress: The address of that the method begins at. +// If newAddress is NULL then the JIT failed. Remember that this +// gets called before the start address of the MethodDesc gets set, +// and so methods like GetFunctionAddress & GetFunctionSize won't work. +// +// @Todo If we're passed 0 for the newAddress param, the jit has been +// cancelled & should be undone. + ******************************************************************************/ +void Debugger::JITComplete(MethodDesc* fd, TADDR newAddress) +{ + + CONTRACTL + { + SO_INTOLERANT; + THROWS; + PRECONDITION(!HasDebuggerDataLock()); + PRECONDITION(newAddress != NULL); + CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + +#ifdef _TARGET_ARM_ + newAddress = newAddress|THUMB_CODE; +#endif + + // @@@ + // Can be called on managed thread only + // This API Implements DebugInterface + + if (CORDebuggerAttached()) + { + // Populate the debugger's cache of DJIs. Normally we can do this lazily, + // the only reason we do it here is b/c the MethodDesc is not yet officially marked as "jitted", + // and so we can't lazily create it yet. Furthermore, the binding operations may need the DJIs. + // + // This also gives the debugger a chance to know if new JMC methods are coming. + DebuggerMethodInfo * dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef()); + if (dmi == NULL) + { + goto Exit; + } + DebuggerJitInfo * ji = dmi->CreateInitAndAddJitInfo(fd, newAddress); + + // Bind any IL patches to the newly jitted native code. + HRESULT hr; + hr = MapAndBindFunctionPatches(ji, fd, (CORDB_ADDRESS_TYPE *)newAddress); + _ASSERTE(SUCCEEDED(hr)); + } + + LOG((LF_CORDB, LL_EVERYTHING, "JitComplete completed successfully\n")); + +Exit: + ; +} + +/****************************************************************************** +// Get the number of fixed arguments to a function, i.e., the explicit args and the "this" pointer. +// This does not include other implicit arguments or varargs. This is used to compute a variable ID +// (see comment in CordbJITILFrame::ILVariableToNative for more detail) +// fVarArg is not used when this is called by Debugger::GetAndSendJITInfo, thus it has a default value. +// The return value is not used when this is called by Debugger::getVars. + ******************************************************************************/ +SIZE_T Debugger::GetArgCount(MethodDesc *fd,BOOL *fVarArg /* = NULL */) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Create a MetaSig for the given method's sig. (Easier than + // picking the sig apart ourselves.) + PCCOR_SIGNATURE pCallSig; + DWORD cbCallSigSize; + + fd->GetSig(&pCallSig, &cbCallSigSize); + + if (pCallSig == NULL) + { + // Sig should only be null if the image is corrupted. (Even for lightweight-codegen) + // We expect the jit+verifier to catch this, so that we never land here. + // But just in case ... + CONSISTENCY_CHECK_MSGF(false, ("Corrupted image, null sig.(%s::%s)", fd->m_pszDebugClassName, fd->m_pszDebugMethodName)); + return 0; + } + + MetaSig msig(pCallSig, cbCallSigSize, g_pEEInterface->MethodDescGetModule(fd), NULL, MetaSig::sigMember); + + // Get the arg count. + UINT32 NumArguments = msig.NumFixedArgs(); + + // Account for the 'this' argument. + if (!(g_pEEInterface->MethodDescIsStatic(fd))) + NumArguments++; + + // Is this a VarArg's function? + if (msig.IsVarArg() && fVarArg != NULL) + { + *fVarArg = true; + } + + return NumArguments; +} + +#endif // #ifndef DACCESS_COMPILE + + + + + +/****************************************************************************** + DebuggerJitInfo * Debugger::GetJitInfo(): GetJitInfo + will return a pointer to a DebuggerJitInfo. If the DJI + doesn't exist, or it does exist, but the method has actually + been pitched (and the caller wants pitched methods filtered out), + then we'll return NULL. + + Note: This will also create a DMI for if one does not exist for this DJI. + + MethodDesc* fd: MethodDesc for the method we're interested in. + CORDB_ADDRESS_TYPE * pbAddr: Address within the code, to indicate which + version we want. If this is NULL, then we want the + head of the DebuggerJitInfo list, whether it's been + JITted or not. + ******************************************************************************/ + + +// Get a DJI from an address. +DebuggerJitInfo *Debugger::GetJitInfoFromAddr(TADDR addr) +{ + WRAPPER_NO_CONTRACT; + + MethodDesc *fd; + fd = g_pEEInterface->GetNativeCodeMethodDesc(addr); + _ASSERTE(fd); + + return GetJitInfo(fd, (const BYTE*) addr, NULL); +} + +// Get a DJI for a Native MD (MD for a native function). +// In the EnC scenario, the MethodDesc refers to the most recent method. +// This is very dangerous since there may be multiple versions alive at the same time. +// This will give back the wrong DJI if we're lookikng for a stale method desc. +// @todo - can a caller possibly use this correctly? +DebuggerJitInfo *Debugger::GetLatestJitInfoFromMethodDesc(MethodDesc * pMethodDesc) +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(pMethodDesc != NULL); + // We'd love to assert that we're jitted; but since this may be in the JitComplete + // callback path, we can't be sure. + + return GetJitInfoWorker(pMethodDesc, NULL, NULL); +} + + +DebuggerJitInfo *Debugger::GetJitInfo(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo ) +{ + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_NOTRIGGER; + PRECONDITION(!g_pDebugger->HasDebuggerDataLock()); + } + CONTRACTL_END; + + // Address should be non-null and in range of MethodDesc. This lets us tell which EnC version. + _ASSERTE(pbAddr != NULL); + + return GetJitInfoWorker(fd, pbAddr, pMethInfo); + +} + +// Internal worker to GetJitInfo. Doesn't validate parameters. +DebuggerJitInfo *Debugger::GetJitInfoWorker(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo) +{ + + DebuggerMethodInfo *dmi = NULL; + DebuggerJitInfo *dji = NULL; + + // If we have a null MethodDesc - we're not going to get a jit-info. Do this check once at the top + // rather than littered throughout the rest of this function. + if (fd == NULL) + { + LOG((LF_CORDB, LL_EVERYTHING, "Debugger::GetJitInfo, addr=0x%p - null fd - returning null\n", pbAddr)); + return NULL; + } + else + { + CONSISTENCY_CHECK_MSGF(!fd->IsWrapperStub(), ("Can't get Jit-info for wrapper MDesc,'%s'", fd->m_pszDebugMethodName)); + } + + // The debugger doesn't track Lightweight-codegen methods b/c they have no metadata. + if (fd->IsDynamicMethod()) + { + return NULL; + } + + + // initialize our out param + if (pMethInfo) + { + *pMethInfo = NULL; + } + + LOG((LF_CORDB, LL_EVERYTHING, "Debugger::GetJitInfo called\n")); + // CHECK_DJI_TABLE_DEBUGGER; + + // Find the DJI via the DMI + // + // One way to improve the perf, both in terms of memory usage, number of allocations + // and lookup speeds would be to have the first JitInfo inline in the MethodInfo + // struct. After all, we never want to have a MethodInfo in the table without an + // associated JitInfo, and this should bring us back very close to the old situation + // in terms of perf. But correctness comes first, and perf later... + // CHECK_DMI_TABLE; + dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef()); + + if (dmi == NULL) + { + // If we can't create the DMI, we won't be able to create the DJI. + return NULL; + } + + + // This may take the lock and lazily create an entry, so we do it up front. + dji = dmi->GetLatestJitInfo(fd); + + + DebuggerDataLockHolder debuggerDataLockHolder(this); + + // Note the call to GetLatestJitInfo() will lazily create the first DJI if we don't already have one. + for (; dji != NULL; dji = dji->m_prevJitInfo) + { + if (PTR_TO_TADDR(dji->m_fd) == PTR_HOST_TO_TADDR(fd)) + { + break; + } + } + LOG((LF_CORDB, LL_INFO1000, "D::GJI: for md:0x%x (%s::%s), got dmi:0x%x.\n", + fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName, + dmi)); + + + + + // Log stuff - fd may be null; so we don't want to AV in the log. + + LOG((LF_CORDB, LL_INFO1000, "D::GJI: for md:0x%x (%s::%s), got dmi:0x%x, dji:0x%x, latest dji:0x%x, latest fd:0x%x, prev dji:0x%x\n", + fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName, + dmi, dji, (dmi ? dmi->GetLatestJitInfo_NoCreate() : 0), + ((dmi && dmi->GetLatestJitInfo_NoCreate()) ? dmi->GetLatestJitInfo_NoCreate()->m_fd:0), + (dji?dji->m_prevJitInfo:0))); + + if ((dji != NULL) && (pbAddr != NULL)) + { + dji = dji->GetJitInfoByAddress(pbAddr); + + // XXX Microsoft - dac doesn't support stub tracing + // so this just results in not-impl exceptions. +#ifndef DACCESS_COMPILE + if (dji == NULL) //may have been given address of a thunk + { + LOG((LF_CORDB,LL_INFO1000,"Couldn't find a DJI by address 0x%p, " + "so it might be a stub or thunk\n", pbAddr)); + TraceDestination trace; + + g_pEEInterface->TraceStub((const BYTE *)pbAddr, &trace); + + if ((trace.GetTraceType() == TRACE_MANAGED) && (pbAddr != (const BYTE *)trace.GetAddress())) + { + LOG((LF_CORDB,LL_INFO1000,"Address thru thunk" + ": 0x%p\n", trace.GetAddress())); + dji = GetJitInfo(fd, dac_cast(trace.GetAddress())); + } +#ifdef LOGGING + else + { + _ASSERTE(trace.GetTraceType() != TRACE_UNJITTED_METHOD || + (fd == trace.GetMethodDesc())); + LOG((LF_CORDB,LL_INFO1000,"Address not thunked - " + "must be to unJITted method, or normal managed " + "method lacking a DJI!\n")); + } +#endif //LOGGING + } +#endif // #ifndef DACCESS_COMPILE + } + + if (pMethInfo) + { + *pMethInfo = dmi; + } + + // DebuggerDataLockHolder out of scope - release implied + + return dji; +} + +DebuggerMethodInfo *Debugger::GetOrCreateMethodInfo(Module *pModule, mdMethodDef token) +{ + CONTRACTL + { + SO_INTOLERANT; + SUPPORTS_DAC; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DebuggerMethodInfo *info = NULL; + + // When dump debugging, we don't expect to have a lock, + // nor would it be useful for anything. + ALLOW_DATATARGET_MISSING_MEMORY( + // In case we don't have already, take it now. + DebuggerDataLockHolder debuggerDataLockHolder(this); + ); + + if (m_pMethodInfos != NULL) + { + info = m_pMethodInfos->GetMethodInfo(pModule, token); + } + + // dac checks ngen'ed image content first, so + // if we didn't find information it doesn't exist. +#ifndef DACCESS_COMPILE + if (info == NULL) + { + info = CreateMethodInfo(pModule, token); + + LOG((LF_CORDB, LL_INFO1000, "D::GOCMI: created DMI for mdToken:0x%x, dmi:0x%x\n", + token, info)); + } +#endif // #ifndef DACCESS_COMPILE + + + if (info == NULL) + { + // This should only happen in an oom scenario. It would be nice to throw here. + STRESS_LOG2(LF_CORDB, LL_EVERYTHING, "OOM - Failed to allocate DJI (0x%p, 0x%x)\n", pModule, token); + } + + // DebuggerDataLockHolder out of scope - release implied + return info; +} + + +#ifndef DACCESS_COMPILE + +/****************************************************************************** + * GetILToNativeMapping returns a map from IL offsets to native + * offsets for this code. An array of COR_PROF_IL_TO_NATIVE_MAP + * structs will be returned, and some of the ilOffsets in this array + * may be the values specified in CorDebugIlToNativeMappingTypes. + ******************************************************************************/ +HRESULT Debugger::GetILToNativeMapping(MethodDesc *pMD, ULONG32 cMap, + ULONG32 *pcMap, COR_DEBUG_IL_TO_NATIVE_MAP map[]) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + } + CONTRACTL_END; + +#ifdef PROFILING_SUPPORTED + // At this point, we're pulling in the debugger. + if (!HasLazyData()) + { + DebuggerLockHolder lockHolder(this); + LazyInit(); // throws + } + + // Get the JIT info by functionId. + + // This function is unsafe to use during EnC because the MethodDesc doesn't tell + // us which version is being requested. + // However, this function is only used by the profiler, and you can't profile with EnC, + // which means that getting the latest jit-info is still correct. +#if defined(PROFILING_SUPPORTED) + _ASSERTE(CORProfilerPresent()); +#endif // PROFILING_SUPPORTED + + DebuggerJitInfo *pDJI = GetLatestJitInfoFromMethodDesc(pMD); + + // Dunno what went wrong + if (pDJI == NULL) + return (E_FAIL); + + // If they gave us space to copy into... + if (map != NULL) + { + // Only copy as much as either they gave us or we have to copy. + ULONG32 cpyCount = min(cMap, pDJI->GetSequenceMapCount()); + + // Read the map right out of the Left Side. + if (cpyCount > 0) + ExportILToNativeMap(cpyCount, + map, + pDJI->GetSequenceMap(), + pDJI->m_sizeOfCode); + } + + // Return the true count of entries + if (pcMap) + { + *pcMap = pDJI->GetSequenceMapCount(); + } + + return (S_OK); +#else + return E_NOTIMPL; +#endif +} + + +//--------------------------------------------------------------------------------------- +// +// This is morally the same as GetILToNativeMapping, except the output is in a different +// format, to better facilitate sending the ETW ILToNativeMap events. +// +// Arguments: +// pMD - MethodDesc whose IL-to-native map will be returned +// cMapMax - Max number of map entries to return. Although +// this function handles the allocation of the returned +// array, the caller still wants to limit how big this +// can get, since ETW itself has limits on how big +// events can get +// pcMap - [out] Number of entries returned in each output parallel array (next +// two parameters). +// prguiILOffset - [out] Array of IL offsets. This function allocates, caller must free. +// prguiNativeOffset - [out] Array of the starting native offsets that correspond +// to each (*prguiILOffset)[i]. This function allocates, +// caller must free. +// +// Return Value: +// HRESULT indicating success or failure. +// +// Notes: +// * This function assumes lazy data has already been initialized (in order to +// ensure that this doesn't trigger or take the large debugger mutex). So +// callers must guarantee they call InitializeLazyDataIfNecessary() first. +// * Either this function fails, and (*prguiILOffset) & (*prguiNativeOffset) will be +// untouched OR this function succeeds and (*prguiILOffset) & (*prguiNativeOffset) +// will both be non-NULL, set to the parallel arrays this function allocated. +// * If this function returns success, then the caller must free (*prguiILOffset) and +// (*prguiNativeOffset) +// * (*prguiILOffset) and (*prguiNativeOffset) are parallel arrays, such that +// (*prguiILOffset)[i] corresponds to (*prguiNativeOffset)[i] for each 0 <= i < *pcMap +// * If EnC is enabled, this function will return the IL-to-native mapping for the latest +// EnC version of the function. This may not be what the profiler wants, but EnC +// + ETW-map events is not a typical combination, and this is consistent with +// other ETW events like JittingStarted or MethodLoad, which also fire multiple +// events for the same MethodDesc (each time it's EnC'd), with each event +// corresponding to the most recent EnC version at the time. +// + +HRESULT Debugger::GetILToNativeMappingIntoArrays( + MethodDesc * pMD, + USHORT cMapMax, + USHORT * pcMap, + UINT ** prguiILOffset, + UINT ** prguiNativeOffset) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(pcMap != NULL); + _ASSERTE(prguiILOffset != NULL); + _ASSERTE(prguiNativeOffset != NULL); + + // Any caller of GetILToNativeMappingIntoArrays had better call + // InitializeLazyDataIfNecessary first! + _ASSERTE(HasLazyData()); + + // Get the JIT info by functionId. + + DebuggerJitInfo * pDJI = GetLatestJitInfoFromMethodDesc(pMD); + + // Dunno what went wrong + if (pDJI == NULL) + return E_FAIL; + + ULONG32 cMap = min(cMapMax, pDJI->GetSequenceMapCount()); + DebuggerILToNativeMap * rgMapInt = pDJI->GetSequenceMap(); + + NewArrayHolder rguiILOffsetTemp = new (nothrow) UINT[cMap]; + if (rguiILOffsetTemp == NULL) + return E_OUTOFMEMORY; + + NewArrayHolder rguiNativeOffsetTemp = new (nothrow) UINT[cMap]; + if (rguiNativeOffsetTemp == NULL) + return E_OUTOFMEMORY; + + for (ULONG32 iMap=0; iMap < cMap; iMap++) + { + rguiILOffsetTemp[iMap] = rgMapInt[iMap].ilOffset; + rguiNativeOffsetTemp[iMap] = rgMapInt[iMap].nativeStartOffset; + } + + // Since cMap is the min of cMapMax (and something else) and cMapMax is a USHORT, + // then cMap must fit in a USHORT as well + _ASSERTE(FitsIn(cMap)); + *pcMap = (USHORT) cMap; + *prguiILOffset = rguiILOffsetTemp.Extract(); + *prguiNativeOffset = rguiNativeOffsetTemp.Extract(); + + return S_OK; +} + + + + +#endif // #ifndef DACCESS_COMPILE + +/****************************************************************************** + * + ******************************************************************************/ +CodeRegionInfo CodeRegionInfo::GetCodeRegionInfo(DebuggerJitInfo *dji, MethodDesc *md, PTR_CORDB_ADDRESS_TYPE addr) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + MODE_ANY; + } + CONTRACTL_END; + + if (dji && dji->m_addrOfCode) + { + LOG((LF_CORDB, LL_EVERYTHING, "CRI::GCRI: simple case\n")); + return dji->m_codeRegionInfo; + } + else + { + LOG((LF_CORDB, LL_EVERYTHING, "CRI::GCRI: more complex case\n")); + CodeRegionInfo codeRegionInfo; + + // Use method desc from dji if present + if (dji && dji->m_fd) + { + _ASSERTE(!md || md == dji->m_fd); + md = dji->m_fd; + } + + if (!addr) + { + _ASSERTE(md); + addr = dac_cast(g_pEEInterface->GetFunctionAddress(md)); + } + else + { + _ASSERTE(!md || + (addr == dac_cast(g_pEEInterface->GetFunctionAddress(md)))); + } + + if (addr) + { + PCODE pCode = (PCODE)dac_cast(addr); +#ifdef _TARGET_ARM_ + pCode |= THUMB_CODE; +#endif + codeRegionInfo.InitializeFromStartAddress(pCode); + } + + return codeRegionInfo; + } +} + + +#ifndef DACCESS_COMPILE +/****************************************************************************** +// Helper function for getBoundaries to get around AMD64 compiler and +// contract holders with PAL_TRY in the same function. + ******************************************************************************/ +void Debugger::getBoundariesHelper(MethodDesc * md, + unsigned int *cILOffsets, + DWORD **pILOffsets) +{ + // + // CANNOT ADD A CONTRACT HERE. Contract is in getBoundaries + // + + // + // Grab the JIT info struct for this method. Create if needed, as this + // may be called before JITComplete. + // + DebuggerMethodInfo *dmi = NULL; + dmi = GetOrCreateMethodInfo(md->GetModule(), md->GetMemberDef()); + + if (dmi != NULL) + { + LOG((LF_CORDB,LL_INFO10000,"De::NGB: Got dmi 0x%x\n",dmi)); + +#if defined(FEATURE_ISYM_READER) + // Note: we need to make sure to enable preemptive GC here just in case we block in the symbol reader. + GCX_PREEMP_EEINTERFACE(); + + Module *pModule = md->GetModule(); + (void)pModule; //prevent "unused variable" error from GCC + _ASSERTE(pModule != NULL); + + SafeComHolder pReader(pModule->GetISymUnmanagedReader()); + + // If we got a reader, use it. + if (pReader != NULL) + { + // Grab the sym reader's method. + ISymUnmanagedMethod *pISymMethod; + + HRESULT hr = pReader->GetMethod(md->GetMemberDef(), + &pISymMethod); + + ULONG32 n = 0; + + if (SUCCEEDED(hr)) + { + // Get the count of sequence points. + hr = pISymMethod->GetSequencePointCount(&n); + _ASSERTE(SUCCEEDED(hr)); + + + LOG((LF_CORDB, LL_INFO100000, + "D::NGB: Reader seq pt count is %d\n", n)); + + ULONG32 *p; + + if (n > 0) + { + ULONG32 dummy; + + p = new ULONG32[n]; + _ASSERTE(p != NULL); // throws on oom errror + + hr = pISymMethod->GetSequencePoints(n, &dummy, + p, NULL, NULL, NULL, + NULL, NULL); + _ASSERTE(SUCCEEDED(hr)); + _ASSERTE(dummy == n); + + *pILOffsets = (DWORD*)p; + + // Translate the IL offets based on an + // instrumented IL map if one exists. + if (dmi->HasInstrumentedILMap()) + { + InstrumentedILOffsetMapping mapping = + dmi->GetRuntimeModule()->GetInstrumentedILOffsetMapping(dmi->m_token); + + for (SIZE_T i = 0; i < n; i++) + { + int origOffset = *p; + + *p = dmi->TranslateToInstIL( + &mapping, + origOffset, + bOriginalToInstrumented); + + LOG((LF_CORDB, LL_INFO100000, + "D::NGB: 0x%04x (Real IL:0x%x)\n", + origOffset, *p)); + + p++; + } + } +#ifdef LOGGING + else + { + for (SIZE_T i = 0; i < n; i++) + { + LOG((LF_CORDB, LL_INFO100000, + "D::NGB: 0x%04x \n", *p)); + p++; + } + } +#endif + } + else + *pILOffsets = NULL; + + pISymMethod->Release(); + } + else + { + + *pILOffsets = NULL; + + LOG((LF_CORDB, LL_INFO10000, + "De::NGB: failed to find method 0x%x in sym reader.\n", + md->GetMemberDef())); + } + + *cILOffsets = n; + } + else + { + LOG((LF_CORDB, LL_INFO100000, "D::NGB: no reader.\n")); + } + +#else // FEATURE_ISYM_READER + // We don't have ISymUnmanagedReader. Pretend there are no sequence points. + *cILOffsets = 0; +#endif // FEATURE_ISYM_READER + } + + LOG((LF_CORDB, LL_INFO100000, "D::NGB: cILOffsets=%d\n", *cILOffsets)); + return; +} +#endif + +/****************************************************************************** +// Use an ISymUnmanagedReader to get method sequence points. + ******************************************************************************/ +void Debugger::getBoundaries(MethodDesc * md, + unsigned int *cILOffsets, + DWORD **pILOffsets, + ICorDebugInfo::BoundaryTypes *implicitBoundaries) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + // May be here even when a debugger is not attached. + + // @@@ + // Implements DebugInterface API + + *cILOffsets = 0; + *pILOffsets = NULL; + *implicitBoundaries = ICorDebugInfo::DEFAULT_BOUNDARIES; + // If there has been an unrecoverable Left Side error, then we + // just pretend that there are no boundaries. + if (CORDBUnrecoverableError(this)) + { + return; + } + + // LCG methods have their own resolution scope that is seperate from a module + // so they shouldn't have their symbols looked up in the module PDB. Right now + // LCG methods have no symbols so we can just early out, but if they ever + // had some symbols attached we would need a different way of getting to them. + // See Dev10 issue 728519 + if(md->IsLCGMethod()) + { + return; + } + + // If JIT optimizations are allowed for the module this function + // lives in, then don't grab specific boundaries from the symbol + // store since any boundaries we give the JIT will be pretty much + // ignored anyway. + if (!CORDisableJITOptimizations(md->GetModule()->GetDebuggerInfoBits())) + { + *implicitBoundaries = ICorDebugInfo::BoundaryTypes(ICorDebugInfo::STACK_EMPTY_BOUNDARIES | + ICorDebugInfo::CALL_SITE_BOUNDARIES); + + return; + } + + Module* pModule = md->GetModule(); + DWORD dwBits = pModule->GetDebuggerInfoBits(); + if ((dwBits & DACF_IGNORE_PDBS) != 0) + { + // + // If told to explicitly ignore PDBs for this function, then bail now. + // + return; + } + + if( !pModule->IsSymbolReadingEnabled() ) + { + // Symbol reading is disabled for this module, so bail out early (for efficiency only) + return; + } + + if (pModule == SystemDomain::SystemModule()) + { + // We don't look up PDBs for mscorlib. This is not quite right, but avoids + // a bootstrapping problem. When an EXE loads, it has the option of setting + // the COM appartment model to STA if we need to. It is important that no + // other Coinitialize happens before this. Since loading the PDB reader uses + // com we can not come first. However managed code IS run before the COM + // appartment model is set, and thus we have a problem since this code is + // called for when JITTing managed code. We avoid the problem by just + // bailing for mscorlib. + return; + } + + // At this point, we're pulling in the debugger. + if (!HasLazyData()) + { + DebuggerLockHolder lockHolder(this); + LazyInit(); // throws + } + + getBoundariesHelper(md, cILOffsets, pILOffsets); + +#else + DacNotImpl(); +#endif // #ifndef DACCESS_COMPILE +} + + +/****************************************************************************** + * + ******************************************************************************/ +void Debugger::getVars(MethodDesc * md, ULONG32 *cVars, ICorDebugInfo::ILVarInfo **vars, + bool *extendOthers) +{ +#ifndef DACCESS_COMPILE + CONTRACTL + { + SO_INTOLERANT; + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + PRECONDITION(!ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + + + // At worst return no information + *cVars = 0; + *vars = NULL; + + // Just tell the JIT to extend everything. + // Note that if optimizations are enabled, the native compilers are + // free to ingore *extendOthers + *extendOthers = true; + + DWORD bits = md->GetModule()->GetDebuggerInfoBits(); + + if (CORDBUnrecoverableError(this)) + goto Exit; + + if (CORDisableJITOptimizations(bits)) +// if (!CORDebuggerAllowJITOpts(bits)) + { + // + // @TODO: Do we really need this code since *extendOthers==true? + // + + // Is this a vararg function? + BOOL fVarArg = false; + GetArgCount(md, &fVarArg); + + if (fVarArg) + { + COR_ILMETHOD *ilMethod = g_pEEInterface->MethodDescGetILHeader(md); + + if (ilMethod) + { + // It is, so we need to tell the JIT to give us the + // varags handle. + ICorDebugInfo::ILVarInfo *p = new ICorDebugInfo::ILVarInfo[1]; + _ASSERTE(p != NULL); // throws on oom error + + COR_ILMETHOD_DECODER header(ilMethod); + unsigned int ilCodeSize = header.GetCodeSize(); + + p->startOffset = 0; + p->endOffset = ilCodeSize; + p->varNumber = (DWORD) ICorDebugInfo::VARARGS_HND_ILNUM; + + *cVars = 1; + *vars = p; + } + } + } + + LOG((LF_CORDB, LL_INFO100000, "D::gV: cVars=%d, extendOthers=%d\n", + *cVars, *extendOthers)); + +Exit: + ; +#else + DacNotImpl(); +#endif // #ifndef DACCESS_COMPILE +} + + +#ifndef DACCESS_COMPILE + +// If we have a varargs function, we can't set the IP (we don't know how to pack/unpack the arguments), so if we +// call SetIP with fCanSetIPOnly = true, we need to check for that. +// Arguments: +// input: nEntries - number of entries in varNativeInfo +// varNativeInfo - array of entries describing the args and locals for the function +// output: true iff the function has varargs +BOOL Debugger::IsVarArgsFunction(unsigned int nEntries, PTR_NativeVarInfo varNativeInfo) +{ + for (unsigned int i = 0; i < nEntries; ++i) + { + if (varNativeInfo[i].loc.vlType == ICorDebugInfo::VLT_FIXED_VA) + { + return TRUE; + } + } + return FALSE; +} + +// We want to keep the 'worst' HRESULT - if one has failed (..._E_...) & the +// other hasn't, take the failing one. If they've both/neither failed, then +// it doesn't matter which we take. +// Note that this macro favors retaining the first argument +#define WORST_HR(hr1,hr2) (FAILED(hr1)?hr1:hr2) +/****************************************************************************** + * + ******************************************************************************/ +HRESULT Debugger::SetIP( bool fCanSetIPOnly, Thread *thread,Module *module, + mdMethodDef mdMeth, DebuggerJitInfo* dji, + SIZE_T offsetILTo, BOOL fIsIL) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(thread)); + PRECONDITION(CheckPointer(module)); + PRECONDITION(mdMeth != mdMethodDefNil); + } + CONTRACTL_END; + +#ifdef _DEBUG + static ConfigDWORD breakOnSetIP; + if (breakOnSetIP.val(CLRConfig::INTERNAL_DbgBreakOnSetIP)) _ASSERTE(!"DbgBreakOnSetIP"); +#endif + + HRESULT hr = S_OK; + HRESULT hrAdvise = S_OK; + + DWORD offsetILFrom; + CorDebugMappingResult map; + DWORD whichIgnore; + + ControllerStackInfo csi; + + BOOL exact; + SIZE_T offsetNatTo; + + PCODE pbDest = NULL; + BYTE *pbBase = NULL; + CONTEXT *pCtx = NULL; + DWORD dwSize = 0; + SIZE_T *rgVal1 = NULL; + SIZE_T *rgVal2 = NULL; + BYTE **pVCs = NULL; + + LOG((LF_CORDB, LL_INFO1000, "D::SIP: In SetIP ==> fCanSetIPOnly:0x%x <==!\n", fCanSetIPOnly)); + + if (ReJitManager::IsReJITEnabled()) + { + return CORDBG_E_SET_IP_IMPOSSIBLE; + } + + pCtx = GetManagedStoppedCtx(thread); + + // If we can't get a context, then we can't possibly be a in a good place + // to do a setip. + if (pCtx == NULL) + { + return CORDBG_S_BAD_START_SEQUENCE_POINT; + } + + // Implicit Caveat: We need to be the active frame. + // We can safely take a stack trace because the thread is synchronized. + StackTraceTicket ticket(thread); + csi.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL); + + ULONG offsetNatFrom = csi.m_activeFrame.relOffset; +#if defined(WIN64EXCEPTIONS) + if (csi.m_activeFrame.IsFuncletFrame()) + { + offsetNatFrom = (ULONG)((SIZE_T)GetControlPC(&(csi.m_activeFrame.registers)) - + (SIZE_T)(dji->m_addrOfCode)); + } +#endif // WIN64EXCEPTIONS + + _ASSERTE(dji != NULL); + + // On WIN64 platforms, it's important to use the total size of the + // parent method and the funclets below (i.e. m_sizeOfCode). Don't use + // the size of the individual funclets or the parent method. + pbBase = (BYTE*)CORDB_ADDRESS_TO_PTR(dji->m_addrOfCode); + dwSize = (DWORD)dji->m_sizeOfCode; +#if defined(WIN64EXCEPTIONS) + // Currently, method offsets are not bigger than 4 bytes even on WIN64. + // Assert that it is so here. + _ASSERTE((SIZE_T)dwSize == dji->m_sizeOfCode); +#endif // WIN64EXCEPTIONS + + + // Create our structure for analyzing this. + // @PERF: optimize - hold on to this so we don't rebuild it for both + // CanSetIP & SetIP. + int cFunclet = 0; + const DWORD * rgFunclet = NULL; +#if defined(WIN64EXCEPTIONS) + cFunclet = dji->GetFuncletCount(); + rgFunclet = dji->m_rgFunclet; +#endif // WIN64EXCEPTIONS + + EHRangeTree* pEHRT = new (nothrow) EHRangeTree(csi.m_activeFrame.pIJM, + csi.m_activeFrame.MethodToken, + dwSize, + cFunclet, + rgFunclet); + + // To maintain the current semantics, we will check the following right before SetIPFromSrcToDst() is called + // (instead of checking them now): + // 1) pEHRT == NULL + // 2) FAILED(pEHRT->m_hrInit) + + + { + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Got version info fine\n")); + + // Caveat: we need to start from a sequence point + offsetILFrom = dji->MapNativeOffsetToIL(offsetNatFrom, + &map, &whichIgnore); + if ( !(map & MAPPING_EXACT) ) + { + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Starting native offset is bad!\n")); + hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_START_SEQUENCE_POINT); + } + else + { // exact IL mapping + + if (!(dji->GetSrcTypeFromILOffset(offsetILFrom) & ICorDebugInfo::STACK_EMPTY)) + { + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Starting offset isn't stack empty!\n")); + hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_START_SEQUENCE_POINT); + } + } + + // Caveat: we need to go to a sequence point + if (fIsIL ) + { +#if defined(WIN64EXCEPTIONS) + int funcletIndexFrom = dji->GetFuncletIndex((CORDB_ADDRESS)offsetNatFrom, DebuggerJitInfo::GFIM_BYOFFSET); + offsetNatTo = dji->MapILOffsetToNativeForSetIP(offsetILTo, funcletIndexFrom, pEHRT, &exact); +#else // WIN64EXCEPTIONS + DebuggerJitInfo::ILToNativeOffsetIterator it; + dji->InitILToNativeOffsetIterator(it, offsetILTo); + offsetNatTo = it.CurrentAssertOnlyOne(&exact); +#endif // WIN64EXCEPTIONS + + if (!exact) + { + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest (via IL offset) is bad!\n")); + hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT); + } + } + else + { + offsetNatTo = offsetILTo; + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest of 0x%p (via native " + "offset) is fine!\n", offsetNatTo)); + } + + CorDebugMappingResult mapping; + DWORD which; + offsetILTo = dji->MapNativeOffsetToIL(offsetNatTo, &mapping, &which); + + // We only want to perhaps return CORDBG_S_BAD_END_SEQUENCE_POINT if + // we're not already returning CORDBG_S_BAD_START_SEQUENCE_POINT. + if (hr != CORDBG_S_BAD_START_SEQUENCE_POINT) + { + if ( !(mapping & MAPPING_EXACT) ) + { + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Ending native offset is bad!\n")); + hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT); + } + else + { + // + // All duplicate sequence points (ones with the same IL offset) should have the same SourceTypes. + // + if (!(dji->GetSrcTypeFromILOffset(offsetILTo) & ICorDebugInfo::STACK_EMPTY)) + { + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Ending offset isn't a sequence" + " point, or not stack empty!\n")); + hrAdvise = WORST_HR(hrAdvise, CORDBG_S_BAD_END_SEQUENCE_POINT); + } + } + } + + // Once we finally have a native offset, it had better be in range. + if (offsetNatTo >= dwSize) + { + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Code out of range! offsetNatTo = 0x%x, dwSize=0x%x\n", offsetNatTo, dwSize)); + hrAdvise = E_INVALIDARG; + goto LExit; + } + + pbDest = CodeRegionInfo::GetCodeRegionInfo(dji).OffsetToAddress(offsetNatTo); + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Dest is 0x%p\n", pbDest)); + + // Don't allow SetIP if the source or target is cold (SetIPFromSrcToDst does not + // correctly handle this case). + if (!CodeRegionInfo::GetCodeRegionInfo(dji).IsOffsetHot(offsetNatTo) || + !CodeRegionInfo::GetCodeRegionInfo(dji).IsOffsetHot(offsetNatFrom)) + { + hrAdvise = WORST_HR(hrAdvise, CORDBG_E_SET_IP_IMPOSSIBLE); + goto LExit; + } + } + + if (!fCanSetIPOnly) + { + hr = ShuffleVariablesGet(dji, + offsetNatFrom, + pCtx, + &rgVal1, + &rgVal2, + &pVCs); + LOG((LF_CORDB|LF_ENC, + LL_INFO10000, + "D::SIP: rgVal1 0x%X, rgVal2 0x%X\n", + rgVal1, + rgVal2)); + + if (FAILED(hr)) + { + // This will only fail fatally, so exit. + hrAdvise = WORST_HR(hrAdvise, hr); + goto LExit; + } + } + else // fCanSetIPOnly + { + if (IsVarArgsFunction(dji->GetVarNativeInfoCount(), dji->GetVarNativeInfo())) + { + hrAdvise = E_INVALIDARG; + goto LExit; + } + } + + + if (pEHRT == NULL) + { + hr = E_OUTOFMEMORY; + } + else if (FAILED(pEHRT->m_hrInit)) + { + hr = pEHRT->m_hrInit; + } + else + { + // + // This is a known, ok, violation. END_EXCEPTION_GLUE has a call to GetThrowable in it, but + // we will never hit it because we are passing in NULL below. This is to satisfy the static + // contract analyzer. + // + CONTRACT_VIOLATION(GCViolation); + + EX_TRY + { + hr =g_pEEInterface->SetIPFromSrcToDst(thread, + pbBase, + offsetNatFrom, + (DWORD)offsetNatTo, + fCanSetIPOnly, + &(csi.m_activeFrame.registers), + pCtx, + (void *)dji, + pEHRT); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + } + + // Get the return code, if any + if (hr != S_OK) + { + hrAdvise = WORST_HR(hrAdvise, hr); + goto LExit; + } + + // If we really want to do this, we'll have to put the + // variables into their new locations. + if (!fCanSetIPOnly && !FAILED(hrAdvise)) + { + // TODO: We should zero out any registers which have now become live GC roots, + // but which aren't tracked variables (i.e. they are JIT temporaries). Such registers may + // have garbage left over in them, and we don't want the GC to try and dereference them + // as object references. However, we can't easily tell here which of the callee-saved regs + // are used in this method and therefore safe to clear. + // + + hr = ShuffleVariablesSet(dji, + offsetNatTo, + pCtx, + &rgVal1, + &rgVal2, + pVCs); + + + if (hr != S_OK) + { + hrAdvise = WORST_HR(hrAdvise, hr); + goto LExit; + } + + _ASSERTE(pbDest != NULL); + + ::SetIP(pCtx, pbDest); + + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Set IP to be 0x%p\n", GetIP(pCtx))); + } + + +LExit: + if (rgVal1 != NULL) + { + DeleteInteropSafe(rgVal1); + } + + if (rgVal2 != NULL) + { + DeleteInteropSafe(rgVal2); + } + + if (pEHRT != NULL) + { + delete pEHRT; + } + + LOG((LF_CORDB, LL_INFO1000, "D::SIP:Returning 0x%x\n", hr)); + return hrAdvise; +} + +#include "nativevaraccessors.h" + +/****************************************************************************** + * + ******************************************************************************/ + +HRESULT Debugger::ShuffleVariablesGet(DebuggerJitInfo *dji, + SIZE_T offsetFrom, + CONTEXT *pCtx, + SIZE_T **prgVal1, + SIZE_T **prgVal2, + BYTE ***prgpVCs) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(dji)); + PRECONDITION(CheckPointer(pCtx)); + PRECONDITION(CheckPointer(prgVal1)); + PRECONDITION(CheckPointer(prgVal2)); + PRECONDITION(dji->m_sizeOfCode >= offsetFrom); + } + CONTRACTL_END; + + LONG cVariables = 0; + DWORD i; + + // + // Find the largest variable number + // + for (i = 0; i < dji->GetVarNativeInfoCount(); i++) + { + if ((LONG)(dji->GetVarNativeInfo()[i].varNumber) > cVariables) + { + cVariables = (LONG)(dji->GetVarNativeInfo()[i].varNumber); + } + } + + HRESULT hr = S_OK; + + // + // cVariables is a zero-based count of the number of variables. Increment it. + // + cVariables++; + + SIZE_T *rgVal1 = new (interopsafe, nothrow) SIZE_T[cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)]; + + SIZE_T *rgVal2 = NULL; + + if (rgVal1 == NULL) + { + hr = E_OUTOFMEMORY; + goto LExit; + } + + rgVal2 = new (interopsafe, nothrow) SIZE_T[cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM)]; + + if (rgVal2 == NULL) + { + hr = E_OUTOFMEMORY; + goto LExit; + } + + memset(rgVal1, 0, sizeof(SIZE_T) * (cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM))); + memset(rgVal2, 0, sizeof(SIZE_T) * (cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM))); + + LOG((LF_CORDB|LF_ENC, + LL_INFO10000, + "D::SVG cVariables %d, hiddens %d, rgVal1 0x%X, rgVal2 0x%X\n", + cVariables, + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM), + rgVal1, + rgVal2)); + + GetVariablesFromOffset(dji->m_fd, + dji->GetVarNativeInfoCount(), + dji->GetVarNativeInfo(), + offsetFrom, + pCtx, + rgVal1, + rgVal2, + cVariables + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM), + prgpVCs); + + +LExit: + if (!FAILED(hr)) + { + (*prgVal1) = rgVal1; + (*prgVal2) = rgVal2; + } + else + { + LOG((LF_CORDB, LL_INFO100, "D::SVG: something went wrong hr=0x%x!", hr)); + + (*prgVal1) = NULL; + (*prgVal2) = NULL; + + if (rgVal1 != NULL) + delete[] rgVal1; + + if (rgVal2 != NULL) + delete[] rgVal2; + } + + return hr; +} + +/****************************************************************************** + * + ******************************************************************************/ +HRESULT Debugger::ShuffleVariablesSet(DebuggerJitInfo *dji, + SIZE_T offsetTo, + CONTEXT *pCtx, + SIZE_T **prgVal1, + SIZE_T **prgVal2, + BYTE **rgpVCs) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(dji)); + PRECONDITION(CheckPointer(pCtx)); + PRECONDITION(CheckPointer(prgVal1)); + PRECONDITION(CheckPointer(prgVal2)); + PRECONDITION(dji->m_sizeOfCode >= offsetTo); + } + CONTRACTL_END; + + LOG((LF_CORDB|LF_ENC, + LL_INFO10000, + "D::SVS: rgVal1 0x%X, rgVal2 0x%X\n", + (*prgVal1), + (*prgVal2))); + + HRESULT hr = SetVariablesAtOffset(dji->m_fd, + dji->GetVarNativeInfoCount(), + dji->GetVarNativeInfo(), + offsetTo, + pCtx, + *prgVal1, + *prgVal2, + rgpVCs); + + LOG((LF_CORDB|LF_ENC, + LL_INFO100000, + "D::SVS deleting rgVal1 0x%X, rgVal2 0x%X\n", + (*prgVal1), + (*prgVal2))); + + DeleteInteropSafe(*prgVal1); + (*prgVal1) = NULL; + DeleteInteropSafe(*prgVal2); + (*prgVal2) = NULL; + return hr; +} + +// +// This class is used by Get and SetVariablesFromOffsets to manage a frameHelper +// list for the arguments and locals corresponding to each varNativeInfo. The first +// four are hidden args, but the remainder will all have a corresponding entry +// in the argument or local signature list. +// +// The structure of the array varNativeInfo contains home information for each variable +// at various points in the function. Thus, you have to search for the proper native offset +// (IP) in the varNativeInfo, and then find the correct varNumber in that native offset to +// find the correct home information. +// +// Important to note is that the JIT has hidden args that have varNumbers that are negative. +// Thus we cannot use varNumber as a strict index into our holder arrays, and instead shift +// indexes before indexing into our holder arrays. +// +// The hidden args are a fixed-sized array given by the value of 0-UNKNOWN_ILNUM. These are used +// to pass cookies about the arguments (var args, generics, retarg buffer etc.) to the function. +// The real arguments and locals are as one would expect. +// + +class GetSetFrameHelper +{ +public: + GetSetFrameHelper(); + ~GetSetFrameHelper(); + + HRESULT Init(MethodDesc* pMD); + + bool GetValueClassSizeOfVar(int varNum, ICorDebugInfo::VarLocType varType, SIZE_T* pSize); + int ShiftIndexForHiddens(int varNum); + +private: + MethodDesc* m_pMD; + SIZE_T* m_rgSize; + CorElementType* m_rgElemType; + ULONG m_numArgs; + ULONG m_numTotalVars; + + SIZE_T GetValueClassSize(MetaSig* pSig); + + static SIZE_T GetSizeOfElement(CorElementType cet); +}; + +// +// GetSetFrameHelper::GetSetFrameHelper() +// +// This is the constructor. It just initailizes all member variables. +// +// parameters: none +// +// return value: none +// +GetSetFrameHelper::GetSetFrameHelper() : m_pMD(NULL), m_rgSize(NULL), m_rgElemType(NULL), + m_numArgs(0), m_numTotalVars(0) +{ + LIMITED_METHOD_CONTRACT; +} + +// +// GetSetFrameHelper::Init() +// +// This method extracts the element type and the size of the arguments and locals of the method we are doing +// the SetIP on and stores this information in instance variables. +// +// parameters: pMD - MethodDesc of the method we are doing the SetIP on +// +// return value: S_OK or E_OUTOFMEMORY +// +HRESULT +GetSetFrameHelper::Init(MethodDesc *pMD) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(pMD)); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + COR_ILMETHOD* pILHeader = NULL; + m_pMD = pMD; + MetaSig *pLocSig = NULL; + MetaSig *pArgSig = NULL; + + m_rgSize = NULL; + m_rgElemType = NULL; + + // Initialize decoderOldIL before checking the method argument signature. + EX_TRY + { + pILHeader = pMD->GetILHeader(); + } + EX_CATCH_HRESULT(hr); + if (FAILED(hr)) + return hr; + + COR_ILMETHOD_DECODER decoderOldIL(pILHeader); + mdSignature mdLocalSig = (decoderOldIL.GetLocalVarSigTok()) ? (decoderOldIL.GetLocalVarSigTok()): + (mdSignatureNil); + + PCCOR_SIGNATURE pCallSig; + DWORD cbCallSigSize; + + pMD->GetSig(&pCallSig, &cbCallSigSize); + + if (pCallSig != NULL) + { + // Yes, we do need to pass in the text because this might be generic function! + SigTypeContext tmpContext(pMD); + + pArgSig = new (interopsafe, nothrow) MetaSig(pCallSig, + cbCallSigSize, + pMD->GetModule(), + &tmpContext, + MetaSig::sigMember); + + if (pArgSig == NULL) + { + IfFailGo(E_OUTOFMEMORY); + } + + m_numArgs = pArgSig->NumFixedArgs(); + + if (pArgSig->HasThis()) + { + m_numArgs++; + } + + // + // What should we do in this case? + // + /* + if (argSig.IsVarArg()) + m_numArgs++; + */ + } + + // allocation of pArgSig succeeded + ULONG cbSig; + PCCOR_SIGNATURE pLocalSig; + pLocalSig = NULL; + if (mdLocalSig != mdSignatureNil) + { + IfFailGo(pMD->GetModule()->GetMDImport()->GetSigFromToken(mdLocalSig, &cbSig, &pLocalSig)); + } + if (pLocalSig != NULL) + { + SigTypeContext tmpContext(pMD); + pLocSig = new (interopsafe, nothrow) MetaSig(pLocalSig, + cbSig, + pMD->GetModule(), + &tmpContext, + MetaSig::sigLocalVars); + + if (pLocSig == NULL) + { + IfFailGo(E_OUTOFMEMORY); + } + } + + // allocation of pLocalSig succeeded + m_numTotalVars = m_numArgs + (pLocSig != NULL ? pLocSig->NumFixedArgs() : 0); + + if (m_numTotalVars > 0) + { + m_rgSize = new (interopsafe, nothrow) SIZE_T[m_numTotalVars]; + m_rgElemType = new (interopsafe, nothrow) CorElementType[m_numTotalVars]; + + if ((m_rgSize == NULL) || (m_rgElemType == NULL)) + { + IfFailGo(E_OUTOFMEMORY); + } + else + { + // allocation of m_rgSize and m_rgElemType succeeded + for (ULONG i = 0; i < m_numTotalVars; i++) + { + // Choose the correct signature to walk. + MetaSig *pCur = NULL; + if (i < m_numArgs) + { + pCur = pArgSig; + } + else + { + pCur = pLocSig; + } + + // The "this" argument isn't stored in the signature, so we have to + // check for it manually. + if (i == 0 && pCur->HasThis()) + { + _ASSERTE(pCur == pArgSig); + + m_rgElemType[i] = ELEMENT_TYPE_CLASS; + m_rgSize[i] = sizeof(SIZE_T); + } + else + { + m_rgElemType[i] = pCur->NextArg(); + + if (m_rgElemType[i] == ELEMENT_TYPE_VALUETYPE) + { + m_rgSize[i] = GetValueClassSize(pCur); + } + else + { + m_rgSize[i] = GetSetFrameHelper::GetSizeOfElement(m_rgElemType[i]); + } + + LOG((LF_CORDB, LL_INFO10000, "GSFH::I: var 0x%x is of type %x, size:0x%x\n", + i, m_rgElemType[i], m_rgSize[i])); + } + } + } // allocation of m_rgSize and m_rgElemType succeeded + } // if there are variables to take care of + +ErrExit: + // clean up + if (pArgSig != NULL) + { + DeleteInteropSafe(pArgSig); + } + + if (pLocSig != NULL) + { + DeleteInteropSafe(pLocSig); + } + + if (FAILED(hr)) + { + if (m_rgSize != NULL) + { + DeleteInteropSafe(m_rgSize); + } + + if (m_rgElemType != NULL) + { + DeleteInteropSafe((int*)m_rgElemType); + } + } + + return hr; +} // GetSetFrameHelper::Init + +// +// GetSetFrameHelper::~GetSetFrameHelper() +// +// This is the destructor. It checks the two arrays we have allocated and frees the memory accordingly. +// +// parameters: none +// +// return value: none +// +GetSetFrameHelper::~GetSetFrameHelper() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if (m_rgSize) + { + DeleteInteropSafe(m_rgSize); + } + + if (m_rgElemType) + { + DeleteInteropSafe((int*)m_rgElemType); + } +} + +// +// GetSetFrameHelper::GetSizeOfElement() +// +// Given a CorElementType, this function returns the size of this type. +// Note that this function doesn't handle ELEMENT_TYPE_VALUETYPE. Use GetValueClassSize() instead. +// +// parameters: cet - the CorElementType of the argument/local we are dealing with +// +// return value: the size of the argument/local +// +// static +SIZE_T GetSetFrameHelper::GetSizeOfElement(CorElementType cet) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(cet != ELEMENT_TYPE_VALUETYPE); + } + CONTRACTL_END; + + if (!CorIsPrimitiveType(cet)) + { + return sizeof(SIZE_T); + } + else + { + switch (cet) + { + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: +#if defined(_WIN64) + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: +#endif // _WIN64 + case ELEMENT_TYPE_R8: + return 8; + + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: +#if !defined(_WIN64) + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: +#endif // !_WIN64 + case ELEMENT_TYPE_R4: + return 4; + + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + case ELEMENT_TYPE_CHAR: + return 2; + + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + case ELEMENT_TYPE_BOOLEAN: + return 1; + + case ELEMENT_TYPE_VOID: + case ELEMENT_TYPE_END: + _ASSERTE(!"debugger.cpp - Check this code path\n"); + return 0; + + case ELEMENT_TYPE_STRING: + return sizeof(SIZE_T); + + default: + _ASSERTE(!"debugger.cpp - Check this code path\n"); + return sizeof(SIZE_T); + } + } +} + +// +// GetSetFrameHelper::GetValueClassSize() +// +// Given a MetaSig pointer to the signature of a value type, this function returns its size. +// +// parameters: pSig - MetaSig pointer to the signature of a value type +// +// return value: the size of this value type +// +SIZE_T GetSetFrameHelper::GetValueClassSize(MetaSig* pSig) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(pSig)); + } + CONTRACTL_END; + + // We need to determine the number of bytes for this value-type. + SigPointer sp = pSig->GetArgProps(); + + TypeHandle vcType = TypeHandle(); + { + // Lookup operations run the class loader in non-load mode. + ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); + + // This will return Null if type is not restored + // @todo : is this what we want? + SigTypeContext typeContext(m_pMD); + vcType = sp.GetTypeHandleThrowing(m_pMD->GetModule(), + &typeContext, + // == FailIfNotLoaded + ClassLoader::DontLoadTypes); + } + // We need to know the size of the class in bytes. This means: + // - we need a specific instantiation (since that affects size) + // - but we don't care if it's shared (since it will be the same size either way) + _ASSERTE(!vcType.IsNull() && vcType.IsValueType()); + + return (vcType.GetMethodTable()->GetAlignedNumInstanceFieldBytes()); +} + +// +// GetSetFrameHelper::GetValueClassSizeOfVar() +// +// This method retrieves the size of the variable saved in the array m_rgSize. Also, it returns true +// if the variable is a value type. +// +// parameters: varNum - the variable number (arguments come before locals) +// varType - the type of variable home +// pSize - [out] the size +// +// return value: whether this variable is a value type +// +bool GetSetFrameHelper::GetValueClassSizeOfVar(int varNum, ICorDebugInfo::VarLocType varType, SIZE_T* pSize) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(varType != ICorDebugInfo::VLT_FIXED_VA); + PRECONDITION(pSize != NULL); + } + CONTRACTL_END; + + // preliminary checking + if (varNum < 0) + { + // Make sure this is one of the secret parameters (e.g. VASigCookie, generics context, etc.). + _ASSERTE(varNum > (int)ICorDebugInfo::MAX_ILNUM); + + *pSize = sizeof(LPVOID); + return false; + } + + // This check is only safe after we make sure that varNum is not negative. + if ((UINT)varNum >= m_numTotalVars) + { + _ASSERTE(!"invalid variable index encountered during setip"); + *pSize = 0; + return false; + } + + CorElementType cet = m_rgElemType[varNum]; + *pSize = m_rgSize[varNum]; + + if ((cet != ELEMENT_TYPE_VALUETYPE) || + (varType == ICorDebugInfo::VLT_REG) || + (varType == ICorDebugInfo::VLT_REG_REG) || + (varType == ICorDebugInfo::VLT_REG_STK) || + (varType == ICorDebugInfo::VLT_STK_REG)) + { + return false; + } + else + { + return true; + } +} + +int GetSetFrameHelper::ShiftIndexForHiddens(int varNum) +{ + LIMITED_METHOD_CONTRACT; + + // + // Need to shift them up so are appropriate index for rgVal arrays + // + return varNum - ICorDebugInfo::UNKNOWN_ILNUM; +} + +// Helper method pair to grab all, then set all, variables at a given +// point in a routine. +// NOTE: GetVariablesFromOffset and SetVariablesAtOffset are +// very similar - modifying one will probably need to be reflected in the other... +// rgVal1 and rgVal2 are preallocated by callers with estimated size. +// We pass in the size of the allocation in rRgValeSize. The safe index will be rgVal1[0..uRgValSize - 1] +// +HRESULT Debugger::GetVariablesFromOffset(MethodDesc *pMD, + UINT varNativeInfoCount, + ICorDebugInfo::NativeVarInfo *varNativeInfo, + SIZE_T offsetFrom, + CONTEXT *pCtx, + SIZE_T *rgVal1, + SIZE_T *rgVal2, + UINT uRgValSize, // number of elements of the preallocated rgVal1 and rgVal2 + BYTE ***rgpVCs) +{ + // @todo - convert this to throwing w/ holders. It will be cleaner. + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(rgpVCs)); + PRECONDITION(CheckPointer(pCtx)); + PRECONDITION(varNativeInfoCount == 0 || CheckPointer(varNativeInfo)); + PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal1)); + PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal2)); + // This may or may not be called on the helper thread. + } + CONTRACTL_END; + + *rgpVCs = NULL; + // if there are no locals, well, we are done! + + if (varNativeInfoCount == 0) + { + return S_OK; + } + + memset( rgVal1, 0, sizeof(SIZE_T)*uRgValSize); + memset( rgVal2, 0, sizeof(SIZE_T)*uRgValSize); + + LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::GVFO: %s::%s, infoCount:0x%x, from:0x%p\n", + pMD->m_pszDebugClassName, + pMD->m_pszDebugMethodName, + varNativeInfoCount, + offsetFrom)); + + GetSetFrameHelper frameHelper; + HRESULT hr = frameHelper.Init(pMD); + if (FAILED(hr)) + { + return hr; + } + // preallocate enough to hold all possible valueclass args & locals + // sure this is more than we need, but not a big deal and better + // than having to crawl through the frameHelper and count + ULONG cValueClasses = 0; + BYTE **rgpValueClasses = new (interopsafe, nothrow) BYTE *[varNativeInfoCount]; + if (rgpValueClasses == NULL) + { + return E_OUTOFMEMORY; + } + memset(rgpValueClasses, 0, sizeof(BYTE *)*varNativeInfoCount); + + hr = S_OK; + + LOG((LF_CORDB|LF_ENC, + LL_INFO10000, + "D::GVFO rgVal1 0x%X, rgVal2 0x%X\n", + rgVal1, + rgVal2)); + + // Now go through the full array and save off each arg and local + for (UINT i = 0; i< varNativeInfoCount;i++) + { + // Ignore variables not live at offsetFrom + // + // #VarLife + // + // The condition below is a little strange. If a var is alive when this is true: + // + // startOffset <= offsetFrom < endOffset + // + // Then you'd expect the negated expression below to be: + // + // startOffset > offsetFrom || endOffset <= offsetFrom + // + // instead of what we're doing ("<" instead of "<="): + // + // startOffset > offsetFrom || endOffset < offsetFrom + // + // I'm not sure if the condition below is a mistake, or if it's intentionally + // mirroring a workaround from FindNativeInfoInILVariableArray() (Debug\DI\module.cpp) + // to deal with optimized code. So I'm leaving it alone for now. See + // code:FindNativeInfoInILVariableArray for more info on this workaround. + if ((varNativeInfo[i].startOffset > offsetFrom) || + (varNativeInfo[i].endOffset < offsetFrom) || + (varNativeInfo[i].loc.vlType == ICorDebugInfo::VLT_INVALID)) + { + LOG((LF_CORDB|LF_ENC,LL_INFO10000, "D::GVFO [%2d] invalid\n", i)); + continue; + } + + SIZE_T cbClass; + bool isVC = frameHelper.GetValueClassSizeOfVar(varNativeInfo[i].varNumber, + varNativeInfo[i].loc.vlType, + &cbClass); + + if (!isVC) + { + int rgValIndex = frameHelper.ShiftIndexForHiddens(varNativeInfo[i].varNumber); + + _ASSERTE(rgValIndex >= 0 && rgValIndex < (int)uRgValSize); + + BOOL res = GetNativeVarVal(varNativeInfo[i].loc, + pCtx, + rgVal1 + rgValIndex, + rgVal2 + rgValIndex + WIN64_ARG(cbClass)); + + LOG((LF_CORDB|LF_ENC,LL_INFO10000, + "D::GVFO [%2d] varnum %d, nonVC type %x, addr %8.8x: %8.8x;%8.8x\n", + i, + varNativeInfo[i].varNumber, + varNativeInfo[i].loc.vlType, + NativeVarStackAddr(varNativeInfo[i].loc, pCtx), + rgVal1[rgValIndex], + rgVal2[rgValIndex])); + + if (res == TRUE) + { + continue; + } + + _ASSERTE(res == TRUE); + hr = E_FAIL; + break; + } + + // it's definately a value class + // Make space for it - note that it uses the VC index, NOT the variable index + _ASSERTE(cbClass != 0); + rgpValueClasses[cValueClasses] = new (interopsafe, nothrow) BYTE[cbClass]; + if (rgpValueClasses[cValueClasses] == NULL) + { + hr = E_OUTOFMEMORY; + break; + } + memcpy(rgpValueClasses[cValueClasses], + NativeVarStackAddr(varNativeInfo[i].loc, pCtx), + cbClass); + + // Move index up. + cValueClasses++; +#ifdef _DEBUG + LOG((LF_CORDB|LF_ENC,LL_INFO10000, + "D::GVFO [%2d] varnum %d, VC len %d, addr %8.8x, sample: %8.8x%8.8x\n", + i, + varNativeInfo[i].varNumber, + cbClass, + NativeVarStackAddr(varNativeInfo[i].loc, pCtx), + (rgpValueClasses[cValueClasses-1])[0], (rgpValueClasses[cValueClasses-1])[1])); +#endif + } + + LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::GVFO: returning %8.8x\n", hr)); + if (SUCCEEDED(hr)) + { + (*rgpVCs) = rgpValueClasses; + return hr; + } + + // We failed for some reason + if (rgpValueClasses != NULL) + { // free any memory we allocated for VCs here + while(cValueClasses > 0) + { + --cValueClasses; + DeleteInteropSafe(rgpValueClasses[cValueClasses]); // OK to delete NULL + } + DeleteInteropSafe(rgpValueClasses); + rgpValueClasses = NULL; + } + return hr; +} + +// NOTE: GetVariablesFromOffset and SetVariablesAtOffset are +// very similar - modifying one will probably need to be reflected in the other... +HRESULT Debugger::SetVariablesAtOffset(MethodDesc *pMD, + UINT varNativeInfoCount, + ICorDebugInfo::NativeVarInfo *varNativeInfo, + SIZE_T offsetTo, + CONTEXT *pCtx, + SIZE_T *rgVal1, + SIZE_T *rgVal2, + BYTE **rgpVCs) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(pCtx)); + PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgpVCs)); + PRECONDITION(varNativeInfoCount == 0 || CheckPointer(varNativeInfo)); + PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal1)); + PRECONDITION(varNativeInfoCount == 0 || CheckPointer(rgVal2)); + // This may or may not be called on the helper thread. + } + CONTRACTL_END; + + LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::SVAO: %s::%s, infoCount:0x%x, to:0x%p\n", + pMD->m_pszDebugClassName, + pMD->m_pszDebugMethodName, + varNativeInfoCount, + offsetTo)); + + if (varNativeInfoCount == 0) + { + return S_OK; + } + + GetSetFrameHelper frameHelper; + HRESULT hr = frameHelper.Init(pMD); + if (FAILED(hr)) + { + return hr; + } + + ULONG iVC = 0; + hr = S_OK; + + // Note that since we obtain all the variables in the first loop, we + // can now splatter those variables into their new locations + // willy-nilly, without the fear that variable locations that have + // been swapped might accidentally overwrite a variable value. + for (UINT i = 0;i< varNativeInfoCount;i++) + { + // Ignore variables not live at offsetTo + // + // If this IF condition looks wrong to you, see + // code:Debugger::GetVariablesFromOffset#VarLife for more info + if ((varNativeInfo[i].startOffset > offsetTo) || + (varNativeInfo[i].endOffset < offsetTo) || + (varNativeInfo[i].loc.vlType == ICorDebugInfo::VLT_INVALID)) + { + LOG((LF_CORDB|LF_ENC,LL_INFO10000, "D::SVAO [%2d] invalid\n", i)); + continue; + } + + SIZE_T cbClass; + bool isVC = frameHelper.GetValueClassSizeOfVar(varNativeInfo[i].varNumber, + varNativeInfo[i].loc.vlType, + &cbClass); + + if (!isVC) + { + int rgValIndex = frameHelper.ShiftIndexForHiddens(varNativeInfo[i].varNumber); + + _ASSERTE(rgValIndex >= 0); + + BOOL res = SetNativeVarVal(varNativeInfo[i].loc, + pCtx, + rgVal1[rgValIndex], + rgVal2[rgValIndex] + WIN64_ARG(cbClass)); + + LOG((LF_CORDB|LF_ENC,LL_INFO10000, + "D::SVAO [%2d] varnum %d, nonVC type %x, addr %8.8x: %8.8x;%8.8x\n", + i, + varNativeInfo[i].varNumber, + varNativeInfo[i].loc.vlType, + NativeVarStackAddr(varNativeInfo[i].loc, pCtx), + rgVal1[rgValIndex], + rgVal2[rgValIndex])); + + if (res == TRUE) + { + continue; + } + _ASSERTE(res == TRUE); + hr = E_FAIL; + break; + } + + // It's definately a value class. + _ASSERTE(cbClass != 0); + if (rgpVCs[iVC] == NULL) + { + // it's new in scope, so just clear it + memset(NativeVarStackAddr(varNativeInfo[i].loc, pCtx), 0, cbClass); + LOG((LF_CORDB|LF_ENC,LL_INFO10000, "D::SVAO [%2d] varnum %d, new VC len %d, addr %8.8x\n", + i, + varNativeInfo[i].varNumber, + cbClass, + NativeVarStackAddr(varNativeInfo[i].loc, pCtx))); + continue; + } + // it's a pre-existing VC, so copy it + memmove(NativeVarStackAddr(varNativeInfo[i].loc, pCtx), rgpVCs[iVC], cbClass); +#ifdef _DEBUG + LOG((LF_CORDB|LF_ENC,LL_INFO10000, + "D::SVAO [%2d] varnum %d, VC len %d, addr: %8.8x sample: %8.8x%8.8x\n", + i, + varNativeInfo[i].varNumber, + cbClass, + NativeVarStackAddr(varNativeInfo[i].loc, pCtx), + rgpVCs[iVC][0], + rgpVCs[iVC][1])); +#endif + // Now get rid of the memory + DeleteInteropSafe(rgpVCs[iVC]); + rgpVCs[iVC] = NULL; + iVC++; + } + + LOG((LF_CORDB|LF_ENC, LL_INFO10000, "D::SVAO: returning %8.8x\n", hr)); + + if (rgpVCs != NULL) + { + DeleteInteropSafe(rgpVCs); + } + + return hr; +} + +BOOL IsDuplicatePatch(SIZE_T *rgEntries, + ULONG cEntries, + SIZE_T Entry ) +{ + LIMITED_METHOD_CONTRACT; + + for( ULONG i = 0; i < cEntries;i++) + { + if (rgEntries[i] == Entry) + return TRUE; + } + return FALSE; +} + + +/****************************************************************************** +// HRESULT Debugger::MapAndBindFunctionBreakpoints(): For each breakpoint +// that we've set in any version of the existing function, +// set a correponding breakpoint in the new function if we haven't moved +// the patch to the new version already. +// +// This must be done _AFTER_ the MethodDesc has been udpated +// with the new address (ie, when GetFunctionAddress pFD returns +// the address of the new EnC code) +// +// Parameters: +// djiNew - this is the DJI created in D::JitComplete. +// If djiNew == NULL iff we aren't tracking debug-info. +// fd - the method desc that we're binding too. +// addrOfCode - address of the native blob of code we just jitted +// +// @todo Replace array with hashtable for improved efficiency +// @todo Need to factor code,so that we can selectively map forward DFK(ilOFfset) BPs + ******************************************************************************/ +HRESULT Debugger::MapAndBindFunctionPatches(DebuggerJitInfo *djiNew, + MethodDesc * fd, + CORDB_ADDRESS_TYPE *addrOfCode) +{ + // @@@ + // Internal helper API. Can be called from Debugger or Controller. + // + + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT; + PRECONDITION(!djiNew || djiNew->m_fd == fd); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + HASHFIND hf; + SIZE_T *pidTableEntry = NULL; + SIZE_T pidInCaseTableMoves; + Module *pModule = g_pEEInterface->MethodDescGetModule(fd); + mdMethodDef md = fd->GetMemberDef(); + + LOG((LF_CORDB,LL_INFO10000,"D::MABFP: All BPs will be mapped to " + "Ver:0x%04x (DJI:0x%08x)\n", djiNew?djiNew->m_methodInfo->GetCurrentEnCVersion():0, djiNew)); + + // We need to traverse the patch list while under the controller lock (small lock). + // But we can only send BreakpointSetErros while under the debugger lock (big lock). + // So to avoid a lock violation, we queue any errors we find under the small lock, + // and then send the whole list when under the big lock. + PATCH_UNORDERED_ARRAY listUnbindablePatches; + + + // First lock the patch table so it doesn't move while we're + // examining it. + LOG((LF_CORDB,LL_INFO10000, "D::MABFP: About to lock patch table\n")); + { + DebuggerController::ControllerLockHolder ch; + + // Manipulate tables AFTER lock's been acquired. + DebuggerPatchTable *pPatchTable = DebuggerController::GetPatchTable(); + GetBPMappingDuplicates()->Clear(); //dups are tracked per-version + + for (DebuggerControllerPatch *dcp = pPatchTable->GetFirstPatch(&hf); + dcp != NULL; + dcp = pPatchTable->GetNextPatch( &hf )) + { + + LOG((LF_CORDB, LL_INFO10000, "D::MABFP: got patch 0x%p\n", dcp)); + + // Only copy over breakpoints that are in this method + // Ideally we'd have a per-method index since there can be a lot of patches + // when the EnCBreakpoint patches are included. + if (dcp->key.module != pModule || dcp->key.md != md) + { + LOG((LF_CORDB, LL_INFO10000, "Patch not in this method\n")); + continue; + } + + // Do not copy over slave breakpoint patches. Instead place a new slave + // based off the master. + if (dcp->IsILSlavePatch()) + { + LOG((LF_CORDB, LL_INFO10000, "Not copying over slave breakpoint patch\n")); + continue; + } + + // If the patch is already bound, then we don't want to try to rebind it. + // Eg. It may be bound to a different generic method instantiation. + if (dcp->IsBound()) + { + LOG((LF_CORDB, LL_INFO10000, "Skipping already bound patch\n")); + continue; + } + + // Only apply breakpoint patches that are for this version. + // If the patch doesn't have a particular EnCVersion available from its data then + // we're (probably) not tracking JIT info. + if (dcp->IsBreakpointPatch() && dcp->HasEnCVersion() && djiNew && dcp->GetEnCVersion() != djiNew->m_encVersion) + { + LOG((LF_CORDB, LL_INFO10000, "Not applying breakpoint patch to new version\n")); + continue; + } + + // Only apply breakpoint and stepper patches + // + // The DJI gets deleted as part of the Unbind/Rebind process in MovedCode. + // This is to signal that we should not skip here. + // under exactly what scenarios (EnC, code pitching etc.) will this apply?... + // can't we be a little clearer about why we don't want to bind the patch in this arcance situation? + if (dcp->HasDJI() && !dcp->IsBreakpointPatch() && !dcp->IsStepperPatch()) + { + LOG((LF_CORDB, LL_INFO10000, "Neither stepper nor BP but we have valid a DJI (i.e. the DJI hasn't been deleted as part of the Unbind/MovedCode/Rebind mess)! - getting next patch!\n")); + continue; + } + + // Now check if we're tracking JIT info or not + if (djiNew == NULL) + { + // This means we put a patch in a method w/ no debug info. + _ASSERTE(dcp->IsBreakpointPatch() || + dcp->IsStepperPatch() || + dcp->controller->GetDCType() == DEBUGGER_CONTROLLER_THREAD_STARTER); + + // W/o Debug-info, We can only patch native offsets, and only at the start of the method (native offset 0). + // Why can't we patch other native offsets?? + // Maybe b/c we don't know if we're patching + // in the middle of an instruction. Though that's not a + // strict requirement. + // We can't even do a IL-offset 0 because that's after the prolog and w/o the debug-info, + // we don't know where the prolog ends. + // Failing this assert is arguably an API misusage - the debugger should have enabled + // jit-tracking if they wanted to put bps at offsets other than native:0. + if (dcp->IsNativePatch() && (dcp->offset == 0)) + { + DebuggerController::g_patches->BindPatch(dcp, addrOfCode); + DebuggerController::ActivatePatch(dcp); + } + else + { + // IF a debugger calls EnableJitDebugging(true, ...) in the module-load callback, + // we should never get here. + *(listUnbindablePatches.AppendThrowing()) = dcp; + } + + } + else + { + pidInCaseTableMoves = dcp->pid; + + // If we've already mapped this one to the current version, + // don't map it again. + LOG((LF_CORDB,LL_INFO10000,"D::MABFP: Checking if 0x%x is a dup...", + pidInCaseTableMoves)); + + if ( IsDuplicatePatch(GetBPMappingDuplicates()->Table(), + GetBPMappingDuplicates()->Count(), + pidInCaseTableMoves) ) + { + LOG((LF_CORDB,LL_INFO10000,"it is!\n")); + continue; + } + LOG((LF_CORDB,LL_INFO10000,"nope!\n")); + + // Attempt mapping from patch to new version of code, and + // we don't care if it turns out that there isn't a mapping. + // @todo-postponed: EnC: Make sure that this doesn't cause + // the patch-table to shift. + hr = MapPatchToDJI( dcp, djiNew ); + if (CORDBG_E_CODE_NOT_AVAILABLE == hr ) + { + *(listUnbindablePatches.AppendThrowing()) = dcp; + hr = S_OK; + } + + if (FAILED(hr)) + break; + + //Remember the patch id to prevent duplication later + pidTableEntry = GetBPMappingDuplicates()->Append(); + if (NULL == pidTableEntry) + { + hr = E_OUTOFMEMORY; + break; + } + + *pidTableEntry = pidInCaseTableMoves; + LOG((LF_CORDB,LL_INFO10000,"D::MABFP Adding 0x%x to list of " + "already mapped patches\n", pidInCaseTableMoves)); + } + } + + // unlock controller lock before sending events. + } + LOG((LF_CORDB,LL_INFO10000, "D::MABFP: Unlocked patch table\n")); + + + // Now send any Breakpoint bind error events. + if (listUnbindablePatches.Count() > 0) + { + LockAndSendBreakpointSetError(&listUnbindablePatches); + } + + return hr; +} + +/****************************************************************************** +// HRESULT Debugger::MapPatchToDJI(): Maps the given +// patch to the corresponding location at the new address. +// We assume that the new code has been JITTed. +// Returns: CORDBG_E_CODE_NOT_AVAILABLE - Indicates that a mapping wasn't +// available, and thus no patch was placed. The caller may or may +// not care. + ******************************************************************************/ +HRESULT Debugger::MapPatchToDJI( DebuggerControllerPatch *dcp,DebuggerJitInfo *djiTo) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT; + PRECONDITION(djiTo != NULL); + PRECONDITION(djiTo->m_jitComplete == true); + } + CONTRACTL_END; + + _ASSERTE(DebuggerController::HasLock()); +#ifdef _DEBUG + static BOOL shouldBreak = -1; + if (shouldBreak == -1) + shouldBreak = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgBreakOnMapPatchToDJI); + + if (shouldBreak > 0) { + _ASSERTE(!"DbgBreakOnMatchPatchToDJI"); + } +#endif + + LOG((LF_CORDB, LL_EVERYTHING, "Calling MapPatchToDJI\n")); + + // We shouldn't have been asked to map an already bound patch + _ASSERTE( !dcp->IsBound() ); + if ( dcp->IsBound() ) + { + return S_OK; + } + + // If the patch has no DJI then we're doing a UnbindFunctionPatches/RebindFunctionPatches. Either + // way, we simply want the most recent version. In the absence of EnC we should have djiCur == djiTo. + DebuggerJitInfo *djiCur = dcp->HasDJI() ? dcp->GetDJI() : djiTo; + PREFIX_ASSUME(djiCur != NULL); + + // If the source and destination are the same version, then this method + // decays into BindFunctionPatch's BindPatch function + if (djiCur->m_encVersion == djiTo->m_encVersion) + { + // If the patch is a "master" then make a new "slave" patch instead of + // binding the old one. This is to stop us mucking with the master breakpoint patch + // which we may need to bind several times for generic code. + if (dcp->IsILMasterPatch()) + { + LOG((LF_CORDB, LL_EVERYTHING, "Add, Bind, Activate new patch from master patch\n")); + if (dcp->controller->AddBindAndActivateILSlavePatch(dcp, djiTo)) + { + LOG((LF_CORDB, LL_INFO1000, "Add, Bind Activate went fine!\n" )); + return S_OK; + } + else + { + LOG((LF_CORDB, LL_INFO1000, "Didn't work for some reason!\n")); + + // Caller can track this HR and send error. + return CORDBG_E_CODE_NOT_AVAILABLE; + } + } + else + { + // + // We could actually have a native managed patch here. This patch is probably added + // as a result of tracing a patch. See if we can eliminate the need for this code path + // + _ASSERTE( dcp->GetKind() == PATCH_KIND_NATIVE_MANAGED ); + + // We have an unbound native patch (eg. for PatchTrace), lets try to bind and activate it + dcp->SetDJI(djiTo); + LOG((LF_CORDB, LL_EVERYTHING, "trying to bind patch... could be problem\n")); + if (DebuggerController::BindPatch(dcp, djiTo->m_fd, NULL)) + { + DebuggerController::ActivatePatch(dcp); + LOG((LF_CORDB, LL_INFO1000, "Application went fine!\n" )); + return S_OK; + } + else + { + LOG((LF_CORDB, LL_INFO1000, "Didn't apply for some reason!\n")); + + // Caller can track this HR and send error. + return CORDBG_E_CODE_NOT_AVAILABLE; + } + } + } + + // Breakpoint patches never get mapped over + _ASSERTE(!dcp->IsBreakpointPatch()); + + return S_OK; +} + +// +// Wrapper function for debugger to WaitForSingleObject. If CLR is hosted, +// notify host before we leave runtime. +// +DWORD Debugger::WaitForSingleObjectHelper(HANDLE handle, DWORD dwMilliseconds) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DWORD dw = 0; + EX_TRY + { + + // make sure that we let host know that we are leaving runtime. + LeaveRuntimeHolder holder((size_t)(::WaitForSingleObject)); + dw = ::WaitForSingleObject(handle,dwMilliseconds); + } + EX_CATCH + { + // Only possibility to enter here is when Thread::LeaveRuntime + // throws exception. + dw = WAIT_ABANDONED; + } + EX_END_CATCH(SwallowAllExceptions); + return dw; + +} + + +/* ------------------------------------------------------------------------ * + * EE Interface routines + * ------------------------------------------------------------------------ */ + +// +// SendSyncCompleteIPCEvent sends a Sync Complete event to the Right Side. +// +void Debugger::SendSyncCompleteIPCEvent() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(ThreadHoldsLock()); + + // Anyone sending the synccomplete must hold the TSL. + PRECONDITION(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + + // The sync complete is now only sent on a helper thread. + PRECONDITION(ThisIsHelperThreadWorker()); + MODE_COOPERATIVE; + + // We had better be trapping Runtime threads and not stopped yet. + PRECONDITION(m_stopped && m_trappingRuntimeThreads); + } + CONTRACTL_END; + + // @@@ + // Internal helper API. + // This is to send Sync Complete event to RightSide. + // We should have hold the debugger lock + // + + STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SSCIPCE: sync complete.\n"); + + // Synchronizing while in in rude shutdown should be extremely rare b/c we don't + // TART in rude shutdown. Shutdown must have started after we started to sync. + // We know we're not on the shutdown thread here. + // And we also know we can't block the shutdown thread (b/c it has the TSL and will + // get a free pass through the GC toggles that normally block threads for debugging). + if (g_fProcessDetach) + { + STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SSCIPCE: Skipping for shutdown.\n"); + return; + } + + // If we're not marked as attached yet, then do that now. + // This can be safely called multiple times. + // This can happen in the normal attach case. The Right-side sends an async-break, + // but we don't want to be considered attach until we've actually gotten our first synchronization. + // Else threads may slip forward during attach and send debug events while we're tyring to attach. + MarkDebuggerAttachedInternal(); + + DebuggerIPCControlBlock * pDCB; + pDCB = m_pRCThread->GetDCB(); + (void)pDCB; //prevent "unused variable" error from GCC + + PREFIX_ASSUME(pDCB != NULL); // must have DCB by the time we're sending IPC events. +#ifdef FEATURE_INTEROP_DEBUGGING + // The synccomplete can't be the first IPC event over. That's b/c the LS needs to know + // if we're interop-debugging and the RS needs to know special addresses for interop-debugging + // (like flares). All of this info is in the DCB. + if (pDCB->m_rightSideIsWin32Debugger) + { + + // If the Right Side is the win32 debugger of this process, then we need to throw a special breakpoint exception + // here instead of sending the sync complete event. The Right Side treats this the same as a sync complete + // event, but its also able to suspend unmanaged threads quickly. + // This also prevents races between sending the sync-complete and getting a native debug event + // (since the sync-complete becomes a native debug event, and all native debug events are serialized). + // + // Note: we reset the syncThreadIsLockFree event before sending the sync complete flare. This thread will set + // this event once its released the debugger lock. This will prevent the Right Side from suspending this thread + // until it has released the debugger lock. + Debugger::NotifyRightSideOfSyncComplete(); + } + else +#endif // FEATURE_INTEROP_DEBUGGING + { + STRESS_LOG0(LF_CORDB, LL_EVERYTHING, "GetIPCEventSendBuffer called in SendSyncCompleteIPCEvent\n"); + // Send the Sync Complete event to the Right Side + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, DB_IPCE_SYNC_COMPLETE); + + m_pRCThread->SendIPCEvent(); + } +} + +// +// Lookup or create a DebuggerModule for the given pDomainFile. +// +// Arguments: +// pDomainFile - non-null domain file. +// +// Returns: +// DebuggerModule instance for the given domain file. May be lazily created. +// +// Notes: +// @dbgtodo JMC - this should go away when we get rid of DebuggerModule. +// + +DebuggerModule * Debugger::LookupOrCreateModule(DomainFile * pDomainFile) +{ + _ASSERTE(pDomainFile != NULL); + LOG((LF_CORDB, LL_INFO1000, "D::LOCM df=0x%x\n", pDomainFile)); + DebuggerModule * pDModule = LookupOrCreateModule(pDomainFile->GetModule(), pDomainFile->GetAppDomain()); + LOG((LF_CORDB, LL_INFO1000, "D::LOCM m=0x%x ad=0x%x -> dm=0x%x\n", pDomainFile->GetModule(), pDomainFile->GetAppDomain(), pDModule)); + _ASSERTE(pDModule != NULL); + _ASSERTE(pDModule->GetDomainFile() == pDomainFile); + + return pDModule; +} + +// Overloaded Wrapper around for VMPTR_DomainFile-->DomainFile* +// +// Arguments: +// vmDomainFile - VMPTR cookie for a domain file. This can be NullPtr(). +// +// Returns: +// Debugger Module instance for the given domain file. May be lazily created. +// +// Notes: +// VMPTR comes from IPC events +DebuggerModule * Debugger::LookupOrCreateModule(VMPTR_DomainFile vmDomainFile) +{ + DomainFile * pDomainFile = vmDomainFile.GetRawPtr(); + if (pDomainFile == NULL) + { + return NULL; + } + return LookupOrCreateModule(pDomainFile); +} + +// Lookup or create a DebuggerModule for the given (Module, AppDomain) pair. +// +// Arguments: +// pModule - required runtime module. May be domain netural. +// pAppDomain - required appdomain that the module is in. +// +// Returns: +// Debugger Module isntance for the given domain file. May be lazily created. +// +DebuggerModule* Debugger::LookupOrCreateModule(Module* pModule, AppDomain *pAppDomain) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000, "D::LOCM m=0x%x ad=0x%x\n", pModule, pAppDomain)); + + // DebuggerModules are relative to a specific AppDomain so we should always be looking up a module / + // AppDomain pair. + _ASSERTE( pModule != NULL ); + _ASSERTE( pAppDomain != NULL ); + + // This is called from all over. We just need to lock in order to lookup. We don't need + // the lock when actually using the DebuggerModule (since it won't be unloaded as long as there is a thread + // in that appdomain). Many of our callers already have this lock, many don't. + // We can take the lock anyways because it's reentrant. + DebuggerDataLockHolder ch(g_pDebugger); // need to traverse module list + + // if this is a module belonging to the system assembly, then scan + // the complete list of DebuggerModules looking for the one + // with a matching appdomain id + // it. + + _ASSERTE( SystemDomain::SystemAssembly()->IsDomainNeutral() ); + + DebuggerModule* dmod = NULL; + + if (m_pModules != NULL) + { + if (pModule->GetAssembly()->IsDomainNeutral()) + { + // We have to make sure to lookup the module with the app domain parameter if the module lives in a shared assembly + dmod = m_pModules->GetModule(pModule, pAppDomain); + } + else + { + dmod = m_pModules->GetModule(pModule); + } + } + + // If it doesn't exist, create it. + if (dmod == NULL) + { + HRESULT hr = S_OK; + EX_TRY + { + DomainFile * pDomainFile = pModule->FindDomainFile(pAppDomain); + SIMPLIFYING_ASSUMPTION(pDomainFile != NULL); + dmod = AddDebuggerModule(pDomainFile); // throws + } + EX_CATCH_HRESULT(hr); + SIMPLIFYING_ASSUMPTION(dmod != NULL); // may not be true in OOM cases; but LS doesn't handle OOM. + } + + // The module must be in the AppDomain that was requested + _ASSERTE( (dmod == NULL) || (dmod->GetAppDomain() == pAppDomain) ); + + LOG((LF_CORDB, LL_INFO1000, "D::LOCM m=0x%x ad=0x%x -> dm=0x%x\n", pModule, pAppDomain, dmod)); + return dmod; +} + +// Create a new DebuggerModule object +// +// Arguments: +// pDomainFile- runtime domain file to create debugger module object around +// +// Returns: +// New instnace of a DebuggerModule. Throws on failure. +// +DebuggerModule* Debugger::AddDebuggerModule(DomainFile * pDomainFile) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000, "D::ADM df=0x%x\n", pDomainFile)); + DebuggerDataLockHolder chInfo(this); + + Module * pRuntimeModule = pDomainFile->GetCurrentModule(); + AppDomain * pAppDomain = pDomainFile->GetAppDomain(); + + HRESULT hr = CheckInitModuleTable(); + IfFailThrow(hr); + + DebuggerModule* pModule = new (interopsafe) DebuggerModule(pRuntimeModule, pDomainFile, pAppDomain); + _ASSERTE(pModule != NULL); // throws on oom + + TRACE_ALLOC(pModule); + + m_pModules->AddModule(pModule); // throws + // @dbgtodo inspection/exceptions - this may leak module in OOM case. LS is not OOM resilient; and we + // expect to get rid of DebuggerModule anyways. + + LOG((LF_CORDB, LL_INFO1000, "D::ADM df=0x%x -> dm=0x%x\n", pDomainFile, pModule)); + return pModule; +} + +// +// TrapAllRuntimeThreads causes every Runtime thread that is executing +// in the EE to trap and send the at safe point event to the RC thread as +// soon as possible. It also sets the EE up so that Runtime threads that +// are outside of the EE will trap when they try to re-enter. +// +// @TODO:: +// Neither pDbgLockHolder nor pAppDomain are used. +void Debugger::TrapAllRuntimeThreads() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + + // We acquired the lock b/c we're in a scope between LFES & UFES. + PRECONDITION(ThreadHoldsLock()); + + // This should never be called on a Temporary Helper thread. + PRECONDITION(IsDbgHelperSpecialThread() || + (g_pEEInterface->GetThread() == NULL) || + !g_pEEInterface->IsPreemptiveGCDisabled()); + } + CONTRACTL_END; + +#if !defined(FEATURE_DBGIPC_TRANSPORT_VM) + // Only sync if RS requested it. + if (!m_RSRequestedSync) + { + return; + } + m_RSRequestedSync = FALSE; +#endif + + // If we're doing shutdown, then don't bother trying to communicate w/ the RS. + // If we're not the thread doing shutdown, then we may be asynchronously killed by the OS. + // If we are the thread in shutdown, don't TART b/c that may block and do complicated stuff. + if (g_fProcessDetach) + { + STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::TART: Skipping for shutdown.\n"); + return; + } + + + // Only try to start trapping if we're not already trapping. + if (m_trappingRuntimeThreads == FALSE) + { + bool fSuspended; + + STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::TART: Trapping all Runtime threads.\n"); + + // There's no way that we should be stopped and still trying to call this function. + _ASSERTE(!m_stopped); + + // Mark that we're trapping now. + m_trappingRuntimeThreads = TRUE; + + // Take the thread store lock. + STRESS_LOG0(LF_CORDB,LL_INFO1000, "About to lock thread Store\n"); + ThreadSuspend::LockThreadStore(ThreadSuspend::SUSPEND_FOR_DEBUGGER); + STRESS_LOG0(LF_CORDB,LL_INFO1000, "Locked thread store\n"); + + // We start the suspension here, and let the helper thread finish it. + // If there's no helper thread, then we need to do helper duty. + { + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + fSuspended = g_pEEInterface->StartSuspendForDebug(NULL, TRUE); + } + + // We tell the RC Thread to check for other threads now and then and help them get synchronized. (This + // is similar to what is done when suspending threads for GC with the HandledJITCase() function.) + + // This does not block. + // Pinging this will waken the helper thread (or temp H. thread) and tell it to sweep & send + // the sync complete. + m_pRCThread->WatchForStragglers(); + + // It's possible we may not have a real helper thread. + // - on startup in dllmain, helper is blocked on DllMain loader lock. + // - on shutdown, helper has been removed on us. + // In those cases, we need somebody to send the sync-complete, and handle + // managed events, and wait for the continue. So we pretend to be the helper thread. + STRESS_LOG0(LF_CORDB, LL_EVERYTHING, "D::SSCIPCE: Calling IsRCThreadReady()\n"); + + // We must check the helper thread status while under the lock. + _ASSERTE(ThreadHoldsLock()); + // If we failed to suspend, then that means we must have multiple managed threads. + // That means that our helper is not blocked on starting up, thus we can wait infinite on it. + // Thus we don't need to do helper duty if the suspend fails. + bool fShouldDoHelperDuty = !m_pRCThread->IsRCThreadReady() && fSuspended; + if (fShouldDoHelperDuty && !g_fProcessDetach) + { + // In V1.0, we had the assumption that if the helper thread isn't ready yet, then we're in + // a state that SuspendForDebug will succeed on the first try, and thus we'll + // never call Sweep when doing helper thread duty. + _ASSERTE(fSuspended); + + // This call will do a ton of work, it will toggle the lock, + // and it will block until we receive a continue! + DoHelperThreadDuty(); + + // We will have released the TSL after the call to continue. + } + else + { + // We have a live and active helper thread which will handle events + // from the RS now that we're stopped. + // We need to release the TSL which we acquired above. (The helper will + // likely take this lock while doing stuff). + STRESS_LOG0(LF_CORDB,LL_INFO1000, "About to unlock thread store!\n"); + ThreadSuspend::UnlockThreadStore(FALSE, ThreadSuspend::SUSPEND_FOR_DEBUGGER); + STRESS_LOG0(LF_CORDB,LL_INFO1000, "TART: Unlocked thread store!\n"); + } + _ASSERTE(ThreadHoldsLock()); // still hold the lock. (though it may have been toggled) + } +} + + +// +// ReleaseAllRuntimeThreads releases all Runtime threads that may be +// stopped after trapping and sending the at safe point event. +// +void Debugger::ReleaseAllRuntimeThreads(AppDomain *pAppDomain) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + + // We acquired the lock b/c we're in a scope between LFES & UFES. + PRECONDITION(ThreadHoldsLock()); + + // Currently, this is only done on a helper thread. + PRECONDITION(ThisIsHelperThreadWorker()); + + // Make sure that we were stopped... + PRECONDITION(m_trappingRuntimeThreads && m_stopped); + } + CONTRACTL_END; + + //@todo APPD if we want true isolation, remove this & finish the work + pAppDomain = NULL; + + STRESS_LOG1(LF_CORDB, LL_INFO10000, "D::RART: Releasing all Runtime threads" + "for AppD 0x%x.\n", pAppDomain); + + // Mark that we're on our way now... + m_trappingRuntimeThreads = FALSE; + m_stopped = FALSE; + + // Go ahead and resume the Runtime threads. + g_pEEInterface->ResumeFromDebug(pAppDomain); +} + +// Given a method, get's its EnC version number. 1 if the method is not EnCed. +// Note that MethodDescs are reused between versions so this will give us +// the most recent EnC number. +int Debugger::GetMethodEncNumber(MethodDesc * pMethod) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DebuggerJitInfo * dji = GetLatestJitInfoFromMethodDesc(pMethod); + if (dji == NULL) + { + // If there's no DJI, couldn't have been EnCed. + return 1; + } + return (int) dji->m_encVersion; +} + + +bool Debugger::IsJMCMethod(Module* pModule, mdMethodDef tkMethod) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CORDebuggerAttached()); + } + CONTRACTL_END; + +#ifdef _DEBUG + Crst crstDbg(CrstIsJMCMethod, CRST_UNSAFE_ANYMODE); + PRECONDITION(crstDbg.IsSafeToTake()); +#endif + + DebuggerMethodInfo *pInfo = GetOrCreateMethodInfo(pModule, tkMethod); + + if (pInfo == NULL) + return false; + + return pInfo->IsJMCFunction(); +} + +/****************************************************************************** + * Called by Runtime when on a 1st chance Native Exception. + * This is likely when we hit a breakpoint / single-step. + * This is called for all native exceptions (except COM+) on managed threads, + * regardless of whether the debugger is attached. + ******************************************************************************/ +bool Debugger::FirstChanceNativeException(EXCEPTION_RECORD *exception, + CONTEXT *context, + DWORD code, + Thread *thread) +{ + + // @@@ + // Implement DebugInterface + // Can be called from EE exception code. Or from our M2UHandoffHijackFilter + // must be on managed thread. + + CONTRACTL + { + SO_TOLERANT; + NOTHROW; + + // No clear GC_triggers semantics here. See DispatchNativeException. + WRAPPER(GC_TRIGGERS); + MODE_ANY; + + PRECONDITION(CheckPointer(exception)); + PRECONDITION(CheckPointer(context)); + PRECONDITION(CheckPointer(thread)); + } + CONTRACTL_END; + + + // Ignore any notification exceptions sent from code:Debugger.SendRawEvent. + // This is not a common case, but could happen in some cases described + // in SendRawEvent. Either way, Left-Side and VM should just ignore these. + if (IsEventDebuggerNotification(exception, PTR_TO_CORDB_ADDRESS(g_pMSCorEE))) + { + return true; + } + + bool retVal; + + // Don't stop for native debugging anywhere inside our inproc-Filters. + CantStopHolder hHolder; + + if (!CORDBUnrecoverableError(this)) + { + retVal = DebuggerController::DispatchNativeException(exception, context, + code, thread); + } + else + { + retVal = false; + } + + return retVal; +} + +/****************************************************************************** + * + ******************************************************************************/ +PRD_TYPE Debugger::GetPatchedOpcode(CORDB_ADDRESS_TYPE *ip) +{ + WRAPPER_NO_CONTRACT; + + if (!CORDBUnrecoverableError(this)) + { + return DebuggerController::GetPatchedOpcode(ip); + } + else + { + PRD_TYPE mt; + InitializePRD(&mt); + return mt; + } +} + +/****************************************************************************** + * + ******************************************************************************/ +BOOL Debugger::CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address, /*OUT*/ PRD_TYPE *pOpcode) +{ + WRAPPER_NO_CONTRACT; + CONSISTENCY_CHECK(CheckPointer(address)); + CONSISTENCY_CHECK(CheckPointer(pOpcode)); + + if (CORDebuggerAttached() && !CORDBUnrecoverableError(this)) + { + return DebuggerController::CheckGetPatchedOpcode(address, pOpcode); + } + else + { + InitializePRD(pOpcode); + return FALSE; + } +} + +/****************************************************************************** + * + ******************************************************************************/ +void Debugger::TraceCall(const BYTE *code) +{ + CONTRACTL + { + // We're being called right before we call managed code. Can't trigger + // because there may be unprotected args on the stack. + MODE_COOPERATIVE; + GC_NOTRIGGER; + + NOTHROW; + } + CONTRACTL_END; + + + Thread * pCurThread = g_pEEInterface->GetThread(); + // Ensure we never even think about running managed code on the helper thread. + _ASSERTE(!ThisIsHelperThreadWorker() || !"You're running managed code on the helper thread"); + + // One threat is that our helper thread may be forced to execute a managed DLL main. + // In that case, it's before the helper thread proc is even executed, so our conventional + // IsHelperThread() checks are inadequate. + _ASSERTE((GetCurrentThreadId() != g_pRCThread->m_DbgHelperThreadOSTid) || !"You're running managed code on the helper thread"); + + _ASSERTE((g_pEEInterface->GetThreadFilterContext(pCurThread) == NULL) || !"Shouldn't run managed code w/ Filter-Context set"); + + if (!CORDBUnrecoverableError(this)) + { + // There are situations where our callers can't tolerate us throwing. + EX_TRY + { + // Since we have a try catch and the debugger code can deal properly with + // faults occuring inside DebuggerController::DispatchTraceCall, we can safely + // establish a FAULT_NOT_FATAL region. This is required since some callers can't + // tolerate faults. + FAULT_NOT_FATAL(); + + DebuggerController::DispatchTraceCall(pCurThread, code); + } + EX_CATCH + { + // We're being called for our benefit, not our callers. So if we fail, + // they don't care. + // Failure for us means that some steppers may miss their notification + // for entering managed code. + LOG((LF_CORDB, LL_INFO10000, "Debugger::TraceCall - inside catch, %p\n", code)); + } + EX_END_CATCH(SwallowAllExceptions); + } +} + +/****************************************************************************** + * For Just-My-Code (aka Just-User-Code). + * Invoked from a probe in managed code when we enter a user method and + * the flag (set by GetJMCFlagAddr) for that method is != 0. + * pIP - the ip within the method, right after the prolog. + * sp - stack pointer (frame pointer on x86) for the managed method we're entering. + * bsp - backing store pointer for the managed method we're entering + ******************************************************************************/ +void Debugger::OnMethodEnter(void * pIP) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + SO_NOT_MAINLINE; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000000, "D::OnMethodEnter(ip=%p)\n", pIP)); + + if (!CORDebuggerAttached()) + { + LOG((LF_CORDB, LL_INFO1000000, "D::OnMethodEnter returning since debugger attached.\n")); + return; + } + FramePointer fp = LEAF_MOST_FRAME; + DebuggerController::DispatchMethodEnter(pIP, fp); +} +/****************************************************************************** + * GetJMCFlagAddr + * Provide an address of the flag that the JMC probes use to decide whether + * or not to call TriggerMethodEnter. + * Called for each method that we jit. + * md - method desc for the JMC probe + * returns an address of a flag that the probe can use. + ******************************************************************************/ +DWORD* Debugger::GetJMCFlagAddr(Module * pModule) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + PRECONDITION(CheckPointer(pModule)); + } + CONTRACTL_END; + + // This callback will be invoked whenever we jit debuggable code. + // A debugger may not be attached yet, but we still need someplace + // to store this dword. + // Use the EE's module, because it's always around, even if a debugger + // is attached or not. + return &(pModule->m_dwDebuggerJMCProbeCount); +} + +/****************************************************************************** + * Updates the JMC flag on all the EE modules. + * We can do this as often as we'd like - though it's a perf hit. + ******************************************************************************/ +void Debugger::UpdateAllModuleJMCFlag(bool fStatus) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000000, "D::UpdateModuleJMCFlag to %d\n", fStatus)); + + _ASSERTE(HasDebuggerDataLock()); + + // Loop through each module. + // The module table is lazily allocated. As soon as we set JMC status on any module, that will cause an + // allocation of the module table. So if the table isn't allocated no module has JMC set, + // and so there is nothing to update. + if (m_pModules != NULL) + { + HASHFIND f; + for (DebuggerModule * m = m_pModules->GetFirstModule(&f); + m != NULL; + m = m_pModules->GetNextModule(&f)) + { + // the primary module may get called multiple times, but that's ok. + UpdateModuleJMCFlag(m->GetRuntimeModule(), fStatus); + } // end for all modules. + } +} + +/****************************************************************************** + * Updates the JMC flag on the given Primary module + * We can do this as often as we'd like - though it's a perf hit. + * If we've only changed methods in a single module, then we can just call this. + * If we do a more global thing (Such as enable MethodEnter), then that could + * affect all modules, so we use the UpdateAllModuleJMCFlag helper. + ******************************************************************************/ +void Debugger::UpdateModuleJMCFlag(Module * pRuntimeModule, bool fStatus) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(HasDebuggerDataLock()); + + + DWORD * pFlag = &(pRuntimeModule->m_dwDebuggerJMCProbeCount); + _ASSERTE(pFlag != NULL); + + if (pRuntimeModule->HasAnyJMCFunctions()) + { + // If this is a user-code module, then update the JMC flag + // the probes look at so that we get MethodEnter callbacks. + *pFlag = fStatus; + + LOG((LF_CORDB, LL_EVERYTHING, "D::UpdateModuleJMCFlag, module %p is user code\n", pRuntimeModule)); + } else { + LOG((LF_CORDB, LL_EVERYTHING, "D::UpdateModuleJMCFlag, module %p is not-user code\n", pRuntimeModule)); + + // if non-user code, flag should be 0 so that we don't waste + // cycles in the callbacks. + _ASSERTE(*pFlag == 0); + } +} + +// This sets the JMC status for the entire module. +// fStatus - default status for whole module +void Debugger::SetModuleDefaultJMCStatus(Module * pRuntimeModule, bool fStatus) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO100000, "DM::SetJMCStatus, status=%d, this=%p\n", fStatus, this)); + + // Ensure that all active DMIs have our status. + // All new DMIs can lookup their status from us. + // This should also update the module count of active JMC DMI's. + DebuggerMethodInfoTable * pTable = g_pDebugger->GetMethodInfoTable(); + + if (pTable != NULL) + { + Debugger::DebuggerDataLockHolder debuggerDataLockHolder(g_pDebugger); + HASHFIND info; + + for (DebuggerMethodInfo *dmi = pTable->GetFirstMethodInfo(&info); + dmi != NULL; + dmi = pTable->GetNextMethodInfo(&info)) + { + if (dmi->GetRuntimeModule() == pRuntimeModule) + { + // This DMI is in this module, so update its status + dmi->SetJMCStatus(fStatus); + } + } + } + + pRuntimeModule->SetJMCStatus(fStatus); + +#ifdef _DEBUG + // If we're disabling JMC in this module, then we shouldn't + // have any active JMC functions. + if (!fStatus) + { + _ASSERTE(!pRuntimeModule->HasAnyJMCFunctions()); + } +#endif +} + +/****************************************************************************** + * Called by GC to determine if it's safe to do a GC. + ******************************************************************************/ +bool Debugger::ThreadsAtUnsafePlaces(void) +{ + LIMITED_METHOD_CONTRACT; + + // If we're in shutdown mode, then all other threads are parked. + // Even if they claim to be at unsafe regions, they're still safe to do a GC. They won't touch + // their stacks. + if (m_fShutdownMode) + { + if (m_threadsAtUnsafePlaces > 0) + { + STRESS_LOG1(LF_CORDB, LL_INFO10000, "D::TAUP: Claiming safety in shutdown mode.%d\n", m_threadsAtUnsafePlaces); + } + return false; + } + + + return (m_threadsAtUnsafePlaces != 0); +} + +// +// SendBreakpoint is called by Runtime threads to send that they've +// hit a breakpoint to the Right Side. +// +void Debugger::SendBreakpoint(Thread *thread, CONTEXT *context, + DebuggerBreakpoint *breakpoint) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + +#ifdef _DEBUG + static BOOL shouldBreak = -1; + if (shouldBreak == -1) + shouldBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgBreakOnSendBreakpoint); + + if (shouldBreak > 0) { + _ASSERTE(!"DbgBreakOnSendBreakpoint"); + } +#endif + + LOG((LF_CORDB, LL_INFO10000, "D::SB: breakpoint BP:0x%x\n", breakpoint)); + + _ASSERTE((g_pEEInterface->GetThread() && + !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) || + g_fInControlC); + + _ASSERTE(ThreadHoldsLock()); + + // Send a breakpoint event to the Right Side + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_BREAKPOINT, + thread, + thread->GetDomain()); + ipce->BreakpointData.breakpointToken.Set(breakpoint); + _ASSERTE( breakpoint->m_pAppDomain == ipce->vmAppDomain.GetRawPtr()); + + m_pRCThread->SendIPCEvent(); +} + + +//--------------------------------------------------------------------------------------- +// Send a user breakpoint event for this thread and sycnhronize the process. +// +// Arguments: +// pThread - non-null thread to send user breakpoint event for. +// +// Notes: +// Can't assume that a debugger is attached (since it may detach before we get the lock). +void Debugger::SendUserBreakpointAndSynchronize(Thread * pThread) +{ + AtSafePlaceHolder unsafePlaceHolder(pThread); + + SENDIPCEVENT_BEGIN(this, pThread); + + // Actually send the event + if (CORDebuggerAttached()) + { + SendRawUserBreakpoint(pThread); + TrapAllRuntimeThreads(); + } + + SENDIPCEVENT_END; +} + +//--------------------------------------------------------------------------------------- +// +// SendRawUserBreakpoint is called by Runtime threads to send that +// they've hit a user breakpoint to the Right Side. This is the event +// send only part, since it can be called from a few different places. +// +// Arguments: +// pThread - [in] managed thread where user break point takes place. +// mus be curernt thread. +// +void Debugger::SendRawUserBreakpoint(Thread * pThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + + PRECONDITION(pThread == GetThread()); + + PRECONDITION(ThreadHoldsLock()); + + // Debugger must have been attached to get us to this point. + // We hold the Debugger-lock, so debugger could not have detached from + // underneath us either. + PRECONDITION(CORDebuggerAttached()); + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO10000, "D::SRUB: user breakpoint\n")); + + + + // Send a breakpoint event to the Right Side + DebuggerIPCEvent* pEvent = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(pEvent, + DB_IPCE_USER_BREAKPOINT, + pThread, + pThread->GetDomain()); + + m_pRCThread->SendIPCEvent(); +} + +// +// SendInterceptExceptionComplete is called by Runtime threads to send that +// they've completed intercepting an exception to the Right Side. This is the event +// send only part, since it can be called from a few different places. +// +void Debugger::SendInterceptExceptionComplete(Thread *thread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO10000, "D::SIEC: breakpoint\n")); + + _ASSERTE(!g_pEEInterface->IsPreemptiveGCDisabled()); + _ASSERTE(ThreadHoldsLock()); + + // Send a breakpoint event to the Right Side + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE, + thread, + thread->GetDomain()); + + m_pRCThread->SendIPCEvent(); +} + + + +// +// SendStep is called by Runtime threads to send that they've +// completed a step to the Right Side. +// +void Debugger::SendStep(Thread *thread, CONTEXT *context, + DebuggerStepper *stepper, + CorDebugStepReason reason) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO10000, "D::SS: step:token:0x%p reason:0x%x\n", + stepper, reason)); + + _ASSERTE((g_pEEInterface->GetThread() && + !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) || + g_fInControlC); + + _ASSERTE(ThreadHoldsLock()); + + // Send a step event to the Right Side + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_STEP_COMPLETE, + thread, + thread->GetDomain()); + ipce->StepData.stepperToken.Set(stepper); + ipce->StepData.reason = reason; + m_pRCThread->SendIPCEvent(); +} + +//------------------------------------------------------------------------------------------------- +// Send an EnC remap opportunity and block until it is continued. +// +// dji - current method information +// currentIP - IL offset within that method +// resumeIP - address of a SIZE_T that the RS will write to cross-process if they take the +// remap opportunity. *resumeIP is untouched if the RS does not remap. +//------------------------------------------------------------------------------------------------- +void Debugger::LockAndSendEnCRemapEvent(DebuggerJitInfo * dji, SIZE_T currentIP, SIZE_T *resumeIP) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; // From SendIPCEvent + PRECONDITION(dji != NULL); + } + CONTRACTL_END; + + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE:\n")); + + if (CORDBUnrecoverableError(this)) + return; + + MethodDesc * pFD = dji->m_fd; + + // Note that the debugger lock is reentrant, so we may or may not hold it already. + Thread *thread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, thread); + + // Send an EnC remap event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_ENC_REMAP, + thread, + thread->GetDomain()); + + ipce->EnCRemap.currentVersionNumber = dji->m_encVersion; + ipce->EnCRemap.resumeVersionNumber = dji->m_methodInfo->GetCurrentEnCVersion();; + ipce->EnCRemap.currentILOffset = currentIP; + ipce->EnCRemap.resumeILOffset = resumeIP; + ipce->EnCRemap.funcMetadataToken = pFD->GetMemberDef(); + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE: token 0x%x, from version %d to %d\n", + ipce->EnCRemap.funcMetadataToken, ipce->EnCRemap.currentVersionNumber, ipce->EnCRemap.resumeVersionNumber)); + + Module *pRuntimeModule = pFD->GetModule(); + + DebuggerModule * pDModule = LookupOrCreateModule(pRuntimeModule, thread->GetDomain()); + ipce->EnCRemap.vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL)); + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE: %s::%s " + "dmod:0x%x, methodDef:0x%x \n", + pFD->m_pszDebugClassName, pFD->m_pszDebugMethodName, + pDModule, + ipce->EnCRemap.funcMetadataToken)); + + // IPC event is now initialized, so we can send it over. + SendSimpleIPCEventAndBlock(); + + // This will block on the continue + SENDIPCEVENT_END; + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE: done\n")); + +} + +// Send the RemapComplete event and block until the debugger Continues +// pFD - specifies the method in which we've remapped into +void Debugger::LockAndSendEnCRemapCompleteEvent(MethodDesc *pFD) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRE:\n")); + + if (CORDBUnrecoverableError(this)) + return; + + Thread *thread = g_pEEInterface->GetThread(); + // Note that the debugger lock is reentrant, so we may or may not hold it already. + SENDIPCEVENT_BEGIN(this, thread); + + EX_TRY + { + // Ensure the DJI for the latest version of this method has been pre-created. + // It's not clear whether this is necessary or not, but it shouldn't hurt since + // we're going to need to create it anyway since we'll be debugging inside it. + DebuggerJitInfo *dji = g_pDebugger->GetLatestJitInfoFromMethodDesc(pFD); + (void)dji; //prevent "unused variable" error from GCC + _ASSERTE( dji != NULL ); + } + EX_CATCH + { + // GetLatestJitInfo could throw on OOM, but the debugger isn't resiliant to OOM. + // I'm not aware of any other legitimate reason why it may throw, so we'll ASSERT + // if it fails. + _ASSERTE(!"Unexpected exception from Debugger::GetLatestJitInfoFromMethodDesc on EnC remap complete"); + } + EX_END_CATCH(RethrowTerminalExceptions); + + // Send an EnC remap complete event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_ENC_REMAP_COMPLETE, + thread, + thread->GetDomain()); + + + ipce->EnCRemapComplete.funcMetadataToken = pFD->GetMemberDef(); + + Module *pRuntimeModule = pFD->GetModule(); + + DebuggerModule * pDModule = LookupOrCreateModule(pRuntimeModule, thread->GetDomain()); + ipce->EnCRemapComplete.vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL)); + + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRC: %s::%s " + "dmod:0x%x, methodDef:0x%x \n", + pFD->m_pszDebugClassName, pFD->m_pszDebugMethodName, + pDModule, + ipce->EnCRemap.funcMetadataToken)); + + // IPC event is now initialized, so we can send it over. + SendSimpleIPCEventAndBlock(); + + // This will block on the continue + SENDIPCEVENT_END; + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCRC: done\n")); + +} +// +// This function sends a notification to the RS about a specific update that has occurred as part of +// applying an Edit and Continue. We send notification only for function add/update and field add. +// At this point, the EE is already stopped for handling an EnC ApplyChanges operation, so no need +// to take locks etc. +// +void Debugger::SendEnCUpdateEvent(DebuggerIPCEventType eventType, + Module * pModule, + mdToken memberToken, + mdTypeDef classToken, + SIZE_T enCVersion) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCUFE:\n")); + + _ASSERTE(eventType == DB_IPCE_ENC_UPDATE_FUNCTION || + eventType == DB_IPCE_ENC_ADD_FUNCTION || + eventType== DB_IPCE_ENC_ADD_FIELD); + + if (CORDBUnrecoverableError(this)) + return; + + // Send an EnC UpdateFunction event to the Right Side. + DebuggerIPCEvent* event = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(event, + eventType, + NULL, + NULL); + + event->EnCUpdate.newVersionNumber = enCVersion; + event->EnCUpdate.memberMetadataToken = memberToken; + // we have to pass the class token across to the RS because we cannot look it up over + // there based on the added field/method because the metadata on the RS will not yet + // have the changes applied, so the token will not exist in its metadata and we have + // no way to find it. + event->EnCUpdate.classMetadataToken = classToken; + + _ASSERTE(pModule); + // we don't support shared assemblies, so must have an appdomain + _ASSERTE(pModule->GetDomain()->IsAppDomain()); + + DebuggerModule * pDModule = LookupOrCreateModule(pModule, pModule->GetDomain()->AsAppDomain()); + event->EnCUpdate.vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL)); + + m_pRCThread->SendIPCEvent(); + + LOG((LF_CORDB, LL_INFO10000, "D::LASEnCUE: done\n")); + +} + + +// +// Send a BreakpointSetError event to the Right Side if the given patch is for a breakpoint. Note: we don't care if this +// fails, there is nothing we can do about it anyway, and the breakpoint just wont hit. +// +void Debugger::LockAndSendBreakpointSetError(PATCH_UNORDERED_ARRAY * listUnbindablePatches) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + GC_TRIGGERS; + } + CONTRACTL_END; + + _ASSERTE(listUnbindablePatches != NULL); + + if (CORDBUnrecoverableError(this)) + return; + + + ULONG count = listUnbindablePatches->Count(); + _ASSERTE(count > 0); // must send at least 1 event. + + + Thread *thread = g_pEEInterface->GetThread(); + // Note that the debugger lock is reentrant, so we may or may not hold it already. + SENDIPCEVENT_BEGIN(this, thread); + + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + + for(ULONG i = 0; i < count; i++) + { + DebuggerControllerPatch *patch = listUnbindablePatches->Table()[i]; + _ASSERTE(patch != NULL); + + // Only do this for breakpoint controllers + DebuggerController *controller = patch->controller; + + if (controller->GetDCType() != DEBUGGER_CONTROLLER_BREAKPOINT) + { + continue; + } + + LOG((LF_CORDB, LL_INFO10000, "D::LASBSE:\n")); + + // Send a breakpoint set error event to the Right Side. + InitIPCEvent(ipce, DB_IPCE_BREAKPOINT_SET_ERROR, thread, thread->GetDomain()); + + ipce->BreakpointSetErrorData.breakpointToken.Set(static_cast (controller)); + + // IPC event is now initialized, so we can send it over. + m_pRCThread->SendIPCEvent(); + } + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + + // This will block on the continue + SENDIPCEVENT_END; + +} + +// +// Called from the controller to lock the debugger for event +// sending. This is called before controller events are sent, like +// breakpoint, step complete, and thread started. +// +// Note that it's possible that the debugger detached (and destroyed our IPC +// events) while we're waiting for our turn. +// So Callers should check for that case. +void Debugger::LockForEventSending(DebuggerLockHolder *dbgLockHolder) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + // @todo - Force our parents to bump up the stop-count. That way they can + // guarantee it's balanced. + IncCantStopCount(); + _ASSERTE(IsInCantStopRegion()); + + // What we need is for caller to get the debugger lock + if (dbgLockHolder != NULL) + { + dbgLockHolder->Acquire(); + } + +#ifdef _DEBUG + // Track our TID. We're not re-entrant. + //_ASSERTE(m_tidLockedForEventSending == 0); + m_tidLockedForEventSending = GetCurrentThreadId(); +#endif + +} + +// +// Called from the controller to unlock the debugger from event +// sending. This is called after controller events are sent, like +// breakpoint, step complete, and thread started. +// +void Debugger::UnlockFromEventSending(DebuggerLockHolder *dbgLockHolder) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + +#ifdef _DEBUG + //_ASSERTE(m_tidLockedForEventSending == GetCurrentThreadId()); + m_tidLockedForEventSending = 0; +#endif + if (dbgLockHolder != NULL) + { + dbgLockHolder->Release(); + } + // @todo - Force our parents to bump up the stop-count. That way they can + // guarantee it's balanced. + _ASSERTE(IsInCantStopRegion()); + DecCantStopCount(); +} + + +// +// Called from the controller after all events have been sent for a +// thread to sync the process. +// +void Debugger::SyncAllThreads(DebuggerLockHolder *dbgLockHolder) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SAT: sync all threads.\n"); + + Thread *pThread = g_pEEInterface->GetThread(); + (void)pThread; //prevent "unused variable" error from GCC + _ASSERTE((pThread && + !pThread->m_fPreemptiveGCDisabled) || + g_fInControlC); + + _ASSERTE(ThreadHoldsLock()); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); +} + +//--------------------------------------------------------------------------------------- +// Launch a debugger and then trigger a breakpoint (either managed or native) +// +// Arguments: +// useManagedBPForManagedAttach - TRUE if we should stop with a managed breakpoint +// when managed attached, FALSE if we should always +// stop with a native breakpoint +// pThread - the managed thread that attempts to launch the registered debugger +// pExceptionInfo - the unhandled exception info +// explicitUserRequest - TRUE if this attach is caused by a call to the Debugger.Launch() API. +// +// Returns: +// S_OK on success. Else failure. +// +// Notes: +// This function doesn't try to stop the launched native debugger by calling DebugBreak(). +// It sends a breakpoint event only for managed debuggers. +// +HRESULT Debugger::LaunchDebuggerForUser(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, + BOOL useManagedBPForManagedAttach, BOOL explicitUserRequest) +{ + WRAPPER_NO_CONTRACT; + + LOG((LF_CORDB, LL_INFO10000, "D::LDFU: Attaching Debugger.\n")); + + // + // Initiate a jit attach + // + JitAttach(pThread, pExceptionInfo, useManagedBPForManagedAttach, explicitUserRequest); + + if (useManagedBPForManagedAttach) + { + if(CORDebuggerAttached() && (g_pEEInterface->GetThread() != NULL)) + { + // + // Send a managed-breakpoint. + // + SendUserBreakpointAndSynchronize(g_pEEInterface->GetThread()); + } + else if (!CORDebuggerAttached() && IsDebuggerPresent()) + { + // + // If the registered debugger is not a managed debugger, send a native breakpoint + // + DebugBreak(); + } + } + else if(!useManagedBPForManagedAttach) + { + // + // Send a native breakpoint + // + DebugBreak(); + } + + if (!IsDebuggerPresent()) + { + LOG((LF_CORDB, LL_ERROR, "D::LDFU: Failed to launch the debugger.\n")); + } + + return S_OK; +} + + +// The following JDI structures will be passed to a debugger on Vista. Because we do not know when the debugger +// will be done looking at them, and there is at most one debugger attaching to the process, we always set them +// once and leave them set without the risk of clobbering something we care about. +JIT_DEBUG_INFO Debugger::s_DebuggerLaunchJitInfo = {0}; +EXCEPTION_RECORD Debugger::s_DebuggerLaunchJitInfoExceptionRecord = {0}; +CONTEXT Debugger::s_DebuggerLaunchJitInfoContext = {0}; + +//---------------------------------------------------------------------------- +// +// InitDebuggerLaunchJitInfo - initialize JDI structure on Vista +// +// Arguments: +// pThread - the managed thread with the unhandled excpetion +// pExceptionInfo - unhandled exception info +// +// Return Value: +// None +// +//---------------------------------------------------------------------------- +void Debugger::InitDebuggerLaunchJitInfo(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE((pExceptionInfo != NULL) && + (pExceptionInfo->ContextRecord != NULL) && + (pExceptionInfo->ExceptionRecord != NULL)); + + if ((pExceptionInfo == NULL) || (pExceptionInfo->ContextRecord == NULL) || (pExceptionInfo->ExceptionRecord == NULL)) + { + return; + } + + s_DebuggerLaunchJitInfoExceptionRecord = *pExceptionInfo->ExceptionRecord; + s_DebuggerLaunchJitInfoContext = *pExceptionInfo->ContextRecord; + + s_DebuggerLaunchJitInfo.dwSize = sizeof(s_DebuggerLaunchJitInfo); + s_DebuggerLaunchJitInfo.dwThreadID = pThread == NULL ? GetCurrentThreadId() : pThread->GetOSThreadId(); + s_DebuggerLaunchJitInfo.lpExceptionRecord = reinterpret_cast(&s_DebuggerLaunchJitInfoExceptionRecord); + s_DebuggerLaunchJitInfo.lpContextRecord = reinterpret_cast(&s_DebuggerLaunchJitInfoContext); + s_DebuggerLaunchJitInfo.lpExceptionAddress = s_DebuggerLaunchJitInfoExceptionRecord.ExceptionAddress != NULL ? + reinterpret_cast(s_DebuggerLaunchJitInfoExceptionRecord.ExceptionAddress) : + reinterpret_cast(reinterpret_cast(GetIP(pExceptionInfo->ContextRecord))); + +#if defined(_TARGET_X86_) + s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL; +#elif defined(_TARGET_AMD64_) + s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64; +#elif defined(_TARGET_ARM_) + s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_ARM; +#elif defined(_TARGET_ARM64_) + s_DebuggerLaunchJitInfo.dwProcessorArchitecture = PROCESSOR_ARCHITECTURE_ARM64; +#else +#error Unknown processor. +#endif +} + + +//---------------------------------------------------------------------------- +// +// GetDebuggerLaunchJitInfo - retrieve the initialized JDI structure on Vista +// +// Arguments: +// None +// +// Return Value: +// JIT_DEBUG_INFO * - pointer to JDI structure +// +//---------------------------------------------------------------------------- +JIT_DEBUG_INFO * Debugger::GetDebuggerLaunchJitInfo(void) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE((s_DebuggerLaunchJitInfo.lpExceptionAddress != NULL) && + (s_DebuggerLaunchJitInfo.lpExceptionRecord != NULL) && + (s_DebuggerLaunchJitInfo.lpContextRecord != NULL) && + (((EXCEPTION_RECORD *)(s_DebuggerLaunchJitInfo.lpExceptionRecord))->ExceptionAddress != NULL)); + + return &s_DebuggerLaunchJitInfo; +} +#endif // !DACCESS_COMPILE + + +// This function checks the registry for the debug launch setting upon encountering an exception or breakpoint. +DebuggerLaunchSetting Debugger::GetDbgJITDebugLaunchSetting() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#if FEATURE_PAL + DebuggerLaunchSetting setting = DLS_ATTACH_DEBUGGER; +#else + BOOL bAuto = FALSE; + + DebuggerLaunchSetting setting = DLS_ASK_USER; + + DWORD cchDbgFormat = MAX_LONGPATH; + INDEBUG(DWORD cchOldDbgFormat = cchDbgFormat); + +#if defined(DACCESS_COMPILE) + WCHAR * wszDbgFormat = new (nothrow) WCHAR[cchDbgFormat]; +#else + WCHAR * wszDbgFormat = new (interopsafe, nothrow) WCHAR[cchDbgFormat]; +#endif // DACCESS_COMPILE + + if (wszDbgFormat == NULL) + { + return setting; + } + + HRESULT hr = GetDebuggerSettingInfoWorker(wszDbgFormat, &cchDbgFormat, &bAuto); + while (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + _ASSERTE(cchDbgFormat > cchOldDbgFormat); + INDEBUG(cchOldDbgFormat = cchDbgFormat); + +#if defined(DACCESS_COMPILE) + delete [] wszDbgFormat; + wszDbgFormat = new (nothrow) WCHAR[cchDbgFormat]; +#else + DeleteInteropSafe(wszDbgFormat); + wszDbgFormat = new (interopsafe, nothrow) WCHAR[cchDbgFormat]; +#endif // DACCESS_COMPILE + + if (wszDbgFormat == NULL) + { + return setting; + } + + hr = GetDebuggerSettingInfoWorker(wszDbgFormat, &cchDbgFormat, &bAuto); + } + +#if defined(DACCESS_COMPILE) + delete [] wszDbgFormat; +#else + DeleteInteropSafe(wszDbgFormat); +#endif // DACCESS_COMPILE + + if (SUCCEEDED(hr) && bAuto) + { + setting = DLS_ATTACH_DEBUGGER; + } +#endif // FEATURE_PAL + + return setting; +} + +// Returns a bitfield reflecting the managed debugging state at the time of +// the jit attach. +CLR_DEBUGGING_PROCESS_FLAGS Debugger::GetAttachStateFlags() +{ + LIMITED_METHOD_DAC_CONTRACT; + return (CLR_DEBUGGING_PROCESS_FLAGS) + ((m_attachingForManagedEvent ? CLR_DEBUGGING_MANAGED_EVENT_PENDING : 0) + | (m_userRequestedDebuggerLaunch ? CLR_DEBUGGING_MANAGED_EVENT_DEBUGGER_LAUNCH : 0)); +} + +#ifndef DACCESS_COMPILE +//----------------------------------------------------------------------------- +// Get the full launch string for a jit debugger. +// +// If a jit-debugger is registed, then writes string into pStrArgsBuf and +// return true. +// +// If no jit-debugger is registered, then return false. +// +// Throws on error (like OOM). +//----------------------------------------------------------------------------- +bool Debugger::GetCompleteDebuggerLaunchString(SString * pStrArgsBuf) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifndef FEATURE_PAL + DWORD pid = GetCurrentProcessId(); + + SString ssDebuggerString; + GetDebuggerSettingInfo(ssDebuggerString, NULL); + + if (ssDebuggerString.IsEmpty()) + { + // No jit-debugger available. Don't make one up. + return false; + } + + // There is no security concern to expect that the debug string we retrieve from HKLM follows a certain + // format because changing HKLM keys requires admin priviledge. Padding with zeros is not a security mitigation, + // but rather a forward looking compability measure. If future verions of Windows introduces more parameters for + // JIT debugger launch, it is preferrable to pass zeros than other random values for those unsupported parameters. + pStrArgsBuf->Printf(ssDebuggerString, pid, GetUnmanagedAttachEvent(), GetDebuggerLaunchJitInfo(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + return true; +#else // !FEATURE_PAL + return false; +#endif // !FEATURE_PAL +} + +// Proxy code for EDA +struct EnsureDebuggerAttachedParams +{ + Debugger * m_pThis; + HRESULT m_retval; + PROCESS_INFORMATION * m_pProcessInfo; + EnsureDebuggerAttachedParams() : + m_pThis(NULL), m_retval(E_FAIL), m_pProcessInfo(NULL) {LIMITED_METHOD_CONTRACT; } +}; + +// This is called by the helper thread +void EDAHelperStub(EnsureDebuggerAttachedParams * p) +{ + WRAPPER_NO_CONTRACT; + + p->m_retval = p->m_pThis->EDAHelper(p->m_pProcessInfo); +} + +// This gets called just like the normal version, but it sends the call over to the helper thread +HRESULT Debugger::EDAHelperProxy(PROCESS_INFORMATION * pProcessInfo) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + _ASSERTE(!ThisIsHelperThreadWorker()); + _ASSERTE(ThreadHoldsLock()); + + HRESULT hr = LazyInitWrapper(); + if (FAILED(hr)) + { + // We already stress logged this case. + return hr; + } + + + if (!IsGuardPageGone()) + { + return EDAHelper(pProcessInfo); + } + + EnsureDebuggerAttachedParams p; + p.m_pThis = this; + p.m_pProcessInfo = pProcessInfo; + + LOG((LF_CORDB, LL_INFO1000000, "D::EDAHelperProxy\n")); + m_pRCThread->DoFavor((FAVORCALLBACK) EDAHelperStub, &p); + LOG((LF_CORDB, LL_INFO1000000, "D::EDAHelperProxy return\n")); + + return p.m_retval; +} + +// E_ABORT - if the attach was declined +// S_OK - Jit-attach successfully started +HRESULT Debugger::EDAHelper(PROCESS_INFORMATION *pProcessInfo) +{ + CONTRACTL + { + NOTHROW; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + + PRECONDITION(ThisMaybeHelperThread()); // on helper if stackoverflow. + } + CONTRACTL_END; + +#ifndef FEATURE_PAL + LOG((LF_CORDB, LL_INFO10000, "D::EDA: thread 0x%x is launching the debugger.\n", GetCurrentThreadId())); + + _ASSERTE(HasLazyData()); + + // Another potential hang. This may get run on the helper if we have a stack overflow. + // Hopefully the odds of 1 thread hitting a stack overflow while another is stuck holding the heap + // lock is very small. + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + BOOL fCreateSucceeded = FALSE; + + StackSString strDbgCommand; + const WCHAR * wszDbgCommand = NULL; + SString strCurrentDir; + const WCHAR * wszCurrentDir = NULL; + + EX_TRY + { + + // Get the debugger to launch. The returned string is via the strDbgCommand out param. Throws on error. + bool fHasDebugger = GetCompleteDebuggerLaunchString(&strDbgCommand); + if (fHasDebugger) + { + wszDbgCommand = strDbgCommand.GetUnicode(); + _ASSERTE(wszDbgCommand != NULL); // would have thrown on oom. + + LOG((LF_CORDB, LL_INFO10000, "D::EDA: launching with command [%S]\n", wszDbgCommand)); + + ClrGetCurrentDirectory(strCurrentDir); + wszCurrentDir = strCurrentDir.GetUnicode(); + } + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + STARTUPINFOW startupInfo = {0}; + startupInfo.cb = sizeof(STARTUPINFOW); + + DWORD errCreate = 0; + + if (wszDbgCommand != NULL) + { + // Create the debugger process + // When we are launching an debugger, we need to let the child process inherit our handles. + // This is necessary for the debugger to signal us that the attach is complete. + fCreateSucceeded = WszCreateProcess(NULL, const_cast (wszDbgCommand), + NULL, NULL, + TRUE, + CREATE_NEW_CONSOLE, + NULL, wszCurrentDir, + &startupInfo, + pProcessInfo); + errCreate = GetLastError(); + } + + if (!fCreateSucceeded) + { + LOG((LF_CORDB, LL_INFO10000, "D::EDA: debugger did not launch successfully.\n")); + return E_ABORT; + } + + LOG((LF_CORDB, LL_INFO10000, "D::EDA: debugger launched successfully.\n")); + return S_OK; +#else // !FEATURE_PAL + return E_ABORT; +#endif // !FEATURE_PAL +} + +// --------------------------------------------------------------------------------------------------------------------- +// This function decides who wins the race for any jit attach and marks the appropriate state that a jit +// attach is in progress. +// +// Arguments +// willSendManagedEvent - indicates whether or not we plan to send a managed debug event after the jit attach +// explicitUserRequest - TRUE if this attach is caused by a call to the Debugger.Launch() API. +// +// Returns +// TRUE - if some other thread already has jit attach in progress -> this thread should block until that is complete +// FALSE - this is the first thread to jit attach -> this thread should launch the debugger +// +// +BOOL Debugger::PreJitAttach(BOOL willSendManagedEvent, BOOL willLaunchDebugger, BOOL explicitUserRequest) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(!ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + LOG( (LF_CORDB, LL_INFO10000, "D::PreJA: Entering\n") ); + + // Multiple threads may be calling this, so need to take the lock. + if(!m_jitAttachInProgress) + { + // TODO: This is a known deadlock! Debugger::PreJitAttach is called during WatsonLastChance. + // If the event (exception/crash) happens while this thread is holding the ThreadStore + // lock, we may deadlock if another thread holds the DebuggerMutex and is waiting on + // the ThreadStore lock. The DebuggerMutex has to be broken into two smaller locks + // so that you can take that lock here when holding the ThreadStore lock. + DebuggerLockHolder dbgLockHolder(this); + + if (!m_jitAttachInProgress) + { + m_jitAttachInProgress = TRUE; + m_attachingForManagedEvent = willSendManagedEvent; + m_launchingDebugger = willLaunchDebugger; + m_userRequestedDebuggerLaunch = explicitUserRequest; + ResetEvent(GetUnmanagedAttachEvent()); + ResetEvent(GetAttachEvent()); + LOG( (LF_CORDB, LL_INFO10000, "D::PreJA: Leaving - first thread\n") ); + return TRUE; + } + } + + LOG( (LF_CORDB, LL_INFO10000, "D::PreJA: Leaving - following thread\n") ); + return FALSE; +} + +//--------------------------------------------------------------------------------------------------------------------- +// This function gets the jit debugger launched and waits for the native attach to complete +// Make sure you called PreJitAttach and it returned TRUE before you call this +// +// Arguments: +// pThread - the managed thread with the unhandled excpetion +// pExceptionInfo - the unhandled exception info +// +// Returns: +// S_OK if the debugger was launched successfully and a failing HRESULT otherwise +// +HRESULT Debugger::LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(!ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + // You need to have called PreJitAttach first to determine which thread gets to launch the debugger + _ASSERTE(m_jitAttachInProgress); + + LOG( (LF_CORDB, LL_INFO10000, "D::LJDANA: Entering\n") ); + PROCESS_INFORMATION processInfo = {0}; + DebuggerLockHolder dbgLockHolder(this); + + // + // If the JIT debugger failed to launch or if there is no JIT debugger, EDAHelperProxy will + // switch to preemptive GC mode to display a dialog to the user indicating the JIT debugger + // was unavailable. There are some rare cases where this could cause a deadlock with the + // debugger lock; however these are rare enough that fixing this doesn't meet the bar for + // Whidbey at this point. We might want to revisit this later however. + // + CONTRACT_VIOLATION(GCViolation); + + { + LOG((LF_CORDB, LL_INFO1000, "D::EDA: Initialize JDI.\n")); + + EXCEPTION_POINTERS exceptionPointer; + EXCEPTION_RECORD exceptionRecord; + CONTEXT context; + + if (pExceptionInfo == NULL) + { + ZeroMemory(&exceptionPointer, sizeof(exceptionPointer)); + ZeroMemory(&exceptionRecord, sizeof(exceptionRecord)); + ZeroMemory(&context, sizeof(context)); + + context.ContextFlags = CONTEXT_CONTROL; + ClrCaptureContext(&context); + + exceptionRecord.ExceptionAddress = reinterpret_cast(GetIP(&context)); + exceptionPointer.ContextRecord = &context; + exceptionPointer.ExceptionRecord = &exceptionRecord; + + pExceptionInfo = &exceptionPointer; + } + + InitDebuggerLaunchJitInfo(pThread, pExceptionInfo); + } + + // This will make the CreateProcess call to create the debugger process. + // We then expect that the debugger process will turn around and attach to us. + HRESULT hr = EDAHelperProxy(&processInfo); + if(FAILED(hr)) + { + return hr; + } + + LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: waiting on m_exUnmanagedAttachEvent and debugger's process handle\n")); + DWORD dwHandles = 2; + HANDLE arrHandles[2]; + arrHandles[0] = GetUnmanagedAttachEvent(); + arrHandles[1] = processInfo.hProcess; + + // Let the helper thread do the attach logic for us and wait for the + // attach event. Must release the lock before blocking on a wait. + dbgLockHolder.Release(); + + // Wait for one or the other to be set. Multiple threads could be waiting here. + // The events are manual events, so when they go high, all threads will be released. + DWORD res = WaitForMultipleObjectsEx(dwHandles, arrHandles, FALSE, INFINITE, FALSE); + + // We no long need to keep handles to the debugger process. + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + // Indicate to the caller that the attach was aborted + if (res == WAIT_OBJECT_0 + 1) + { + LOG((LF_CORDB, LL_INFO10000, "D::LJDANA: Debugger process is unexpectedly terminated!\n")); + return E_FAIL; + } + + // Otherwise, attach was successful (Note, only native attach is done so far) + _ASSERTE((res == WAIT_OBJECT_0) && "WaitForMultipleObjectsEx failed!"); + LOG( (LF_CORDB, LL_INFO10000, "D::LJDANA: Leaving\n") ); + return S_OK; + +} + +// Blocks until the debugger completes jit attach +void Debugger::WaitForDebuggerAttach() +{ + LIMITED_METHOD_CONTRACT; + + LOG( (LF_CORDB, LL_INFO10000, "D::WFDA:Entering\n") ); + + // if this thread previously called LaunchDebuggerAndNativeAttach then this wait is spurious, + // the event is still set and it continues immediately. If this is an auxilliary thread however + // then the wait is necessary + // If we are not launching the debugger (e.g. unhandled exception on Win7), then we should not + // wait on the unmanaged attach event. If the debugger is launched by the OS, then the unmanaged + // attach event passed to the debugger is created by the OS, not by us, so our event will never + // be signaled. + if (m_launchingDebugger) + { + WaitForSingleObject(GetUnmanagedAttachEvent(), INFINITE); + } + + // Wait until the pending managed debugger attach is completed + if (CORDebuggerPendingAttach() && !CORDebuggerAttached()) + { + LOG( (LF_CORDB, LL_INFO10000, "D::WFDA: Waiting for managed attach too\n") ); + WaitForSingleObject(GetAttachEvent(), INFINITE); + } + + // We can't reset the event here because some threads may + // be just about to wait on it. If we reset it before the + // other threads hit the wait, they'll block. + + // We have an innate race here that can't easily fix. The best + // we can do is have a super small window (by moving the reset as + // far out this making it very unlikely that a thread will + // hit the window. + + LOG( (LF_CORDB, LL_INFO10000, "D::WFDA: Leaving\n") ); +} + +// Cleans up after jit attach is complete +void Debugger::PostJitAttach() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + PRECONDITION(!ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + LOG( (LF_CORDB, LL_INFO10000, "D::PostJA: Entering\n") ); + // Multiple threads may be calling this, so need to take the lock. + DebuggerLockHolder dbgLockHolder(this); + + // clear the attaching flags which allows other threads to initiate jit attach if needed + m_jitAttachInProgress = FALSE; + m_attachingForManagedEvent = FALSE; + m_launchingDebugger = FALSE; + m_userRequestedDebuggerLaunch = FALSE; + // set the attaching events to unblock other threads waiting on this attach + // regardless of whether or not it completed + SetEvent(GetUnmanagedAttachEvent()); + SetEvent(GetAttachEvent()); + LOG( (LF_CORDB, LL_INFO10000, "D::PostJA: Leaving\n") ); +} + +//--------------------------------------------------------------------------------------- +// Launches a debugger and blocks waiting for it to either attach or abort the attach. +// +// Arguments: +// pThread - the managed thread with the unhandled excpetion +// pExceptionInfo - the unhandled exception info +// willSendManagedEvent - TRUE if after getting attached we will send a managed debug event +// explicitUserRequest - TRUE if this attach is caused by a call to the Debugger.Launch() API. +// +// Returns: +// None. Callers can requery if a debugger is attached. +// +// Assumptions: +// This may be called by multiple threads, each firing their own debug events. This function will handle locking. +// Thus this could block for an arbitrary length of time: +// - may need to prompt the user to decide if an attach occurs. +// - may block waiting for a debugger to attach. +// +// Notes: +// The launch string is retrieved from code:GetDebuggerSettingInfo. +// This will not do a sync-complete. Instead, the caller can send a debug event (the jit-attach +// event, such as a User-breakpoint or unhandled exception) and that can send a sync-complete, +// just as if the debugger was always attached. This ensures that the jit-attach event is in the +// same callback queue as any faked-up events that the Right-side Shim creates. +// +void Debugger::JitAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + + PRECONDITION(!ThisIsHelperThreadWorker()); // Must be a managed thread + } + CONTRACTL_END; + + if (IsDebuggerPresent()) + return; + + GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD(); + + EnsureDebuggerAttached(pThread, pExceptionInfo, willSendManagedEvent, explicitUserRequest); +} + +//----------------------------------------------------------------------------- +// Ensure that a debugger is attached. Will jit-attach if needed. +// +// Arguments +// pThread - the managed thread with the unhandled excpetion +// pExceptionInfo - the unhandled exception info +// willSendManagedEvent - true if after getting (or staying) attached we will send +// a managed debug event +// explicitUserRequest - true if this attach is caused by a call to the +// Debugger.Launch() API. +// +// Returns: +// None. Either a debugger is attached or it is not. +// +// Notes: +// There are several intermediate possible outcomes: +// - Debugger already attached before this was called. +// - JIT-atttach debugger spawned, and attached successfully. +// - JIT-attach debugger spawned, but declined to attach. +// - Failed to spawn jit-attach debugger. +// +// Ultimately, the only thing that matters at the end is whether a debugger +// is now attached, which is retreived via CORDebuggerAttached(). +//----------------------------------------------------------------------------- +void Debugger::EnsureDebuggerAttached(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_PREEMPTIVE; + PRECONDITION(!ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + LOG( (LF_CORDB,LL_INFO10000,"D::EDA\n") ); + + HRESULT hr = S_OK; + + // We could be in three states: + // 1) no debugger attached + // 2) native attached but not managed (yet?) + // 3) native attached and managed + + + // There is a race condition here that can be hit if multiple threads + // were to trigger jit attach at the right time + // Thread 1 starts jit attach + // Thread 2 also starts jit attach and gets to waiting for the attach complete + // Thread 1 rapidly completes the jit attach then starts it again + // Thread 2 may still be waiting from the first jit attach at this point + // + // Note that this isn't all that bad because if the debugger hasn't actually detached + // in the middle then the second jit attach will complete almost instantly and thread 2 + // is unblocked. If the debugger did detach in the middle then it seems reasonable for + // thread 2 to continue to wait until until the debugger is attached once again for the + // second attach. Basically if one jit attach completes and restarts fast enough it might + // just go unnoticed by some threads and it will be as if it never happened. Doesn't seem + // that bad as long as we know another jit attach is again in progress. + + BOOL startedJitAttach = FALSE; + + // First check to see if we need to launch the debugger ourselves + if(PreJitAttach(willSendManagedEvent, TRUE, explicitUserRequest)) + { + // if the debugger is already attached then we can't launch one + // and whatever attach state we are in is just what we get + if(IsDebuggerPresent()) + { + // unblock other threads waiting on our attach and clean up + PostJitAttach(); + return; + } + else + { + hr = LaunchJitDebuggerAndNativeAttach(pThread, pExceptionInfo); + if(FAILED(hr)) + { + // unblock other threads waiting on our attach and clean up + PostJitAttach(); + return; + } + } + startedJitAttach = TRUE; + } + + // at this point someone should have launched the native debugger and + // it is somewhere between not attached and attach complete + // (it might have even been completely attached before this function even started) + // step 2 - wait for the attach to complete + WaitForDebuggerAttach(); + + // step 3 - if we initiated then we also cleanup + if(startedJitAttach) + PostJitAttach(); + LOG( (LF_CORDB, LL_INFO10000, "D::EDA:Leaving\n") ); +} + + +// Proxy code for AttachDebuggerForBreakpoint +// Structure used in the proxy function callback +struct SendExceptionOnHelperThreadParams +{ + Debugger *m_pThis; + HRESULT m_retval; + Thread *m_pThread; + OBJECTHANDLE m_exceptionHandle; + bool m_continuable; + FramePointer m_framePointer; + SIZE_T m_nOffset; + CorDebugExceptionCallbackType m_eventType; + DWORD m_dwFlags; + + + SendExceptionOnHelperThreadParams() : + m_pThis(NULL), + m_retval(S_OK), + m_pThread(NULL) + {LIMITED_METHOD_CONTRACT; } +}; + +//************************************************************************** +// This function sends Exception and ExceptionCallback2 event. +// +// Arguments: +// pThread : managed thread which exception takes place +// exceptionHandle : handle to the managed exception object (usually +// something derived from System.Exception) +// fContinuable : true iff continuable +// framePointer : frame pointer associated with callback. +// nOffset : il offset associated with callback. +// eventType : type of callback +// dwFlags : additional flags (see CorDebugExceptionFlags). +// +// Returns: +// S_OK on sucess. Else some error. May also throw. +// +// Notes: +// This is a helper for code:Debugger.SendExceptionEventsWorker. +// See code:Debugger.SendException for more details about parameters. +// This is always called on a managed thread (never the helper thread) +// This will synchronize and block. +//************************************************************************** +HRESULT Debugger::SendExceptionHelperAndBlock( + Thread *pThread, + OBJECTHANDLE exceptionHandle, + bool fContinuable, + FramePointer framePointer, + SIZE_T nOffset, + CorDebugExceptionCallbackType eventType, + DWORD dwFlags) + +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + + PRECONDITION(CheckPointer(pThread)); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + // This is a normal event to send from LS to RS + SENDIPCEVENT_BEGIN(this, pThread); + + // This function can be called on helper thread or managed thread. + // However, we should be holding locks upon entry + + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + + // + // Send pre-Whidbey EXCEPTION IPC event. + // + InitIPCEvent(ipce, DB_IPCE_EXCEPTION, pThread, pThread->GetDomain()); + + ipce->Exception.vmExceptionHandle.SetRawPtr(exceptionHandle); + ipce->Exception.firstChance = (eventType == DEBUG_EXCEPTION_FIRST_CHANCE); + ipce->Exception.continuable = fContinuable; + hr = m_pRCThread->SendIPCEvent(); + + _ASSERTE(SUCCEEDED(hr) && "D::SE: Send ExceptionCallback event failed."); + + // + // Send Whidbey EXCEPTION IPC event. + // + InitIPCEvent(ipce, DB_IPCE_EXCEPTION_CALLBACK2, pThread, pThread->GetDomain()); + + ipce->ExceptionCallback2.framePointer = framePointer; + ipce->ExceptionCallback2.eventType = eventType; + ipce->ExceptionCallback2.nOffset = nOffset; + ipce->ExceptionCallback2.dwFlags = dwFlags; + ipce->ExceptionCallback2.vmExceptionHandle.SetRawPtr(exceptionHandle); + + LOG((LF_CORDB, LL_INFO10000, "D::SE: sending ExceptionCallback2 event")); + hr = m_pRCThread->SendIPCEvent(); + + if (eventType == DEBUG_EXCEPTION_FIRST_CHANCE) + { + pThread->GetExceptionState()->GetFlags()->SetSentDebugFirstChance(); + } + else + { + _ASSERTE(eventType == DEBUG_EXCEPTION_UNHANDLED); + } + + _ASSERTE(SUCCEEDED(hr) && "D::SE: Send ExceptionCallback2 event failed."); + + if (SUCCEEDED(hr)) + { + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + + // Let other Runtime threads handle their events. + SENDIPCEVENT_END; + + return hr; + +} + +// Send various first-chance / unhandled exception events. +// +// Assumptions: +// Caller has already determined that we want to send exception events. +// +// Notes: +// This is a helper function for code:Debugger.SendException +void Debugger::SendExceptionEventsWorker( + Thread * pThread, + bool fFirstChance, + bool fIsInterceptable, + bool fContinuable, + SIZE_T currentIP, + FramePointer framePointer, + bool atSafePlace) +{ + HRESULT hr = S_OK; + + ThreadExceptionState* pExState = pThread->GetExceptionState(); + // + // Figure out parameters to the IPC events. + // + const BYTE *ip; + + SIZE_T nOffset = (SIZE_T)ICorDebugInfo::NO_MAPPING; + DebuggerMethodInfo *pDebugMethodInfo = NULL; + + // If we're passed a zero IP or SP, then go to the ThreadExceptionState on the thread to get the data. Note: + // we can only do this if there is a context in the pExState. There are cases (most notably the + // EEPolicy::HandleFatalError case) where we don't have that. So we just leave the IP/SP 0. + if ((currentIP == 0) && (pExState->GetContextRecord() != NULL)) + { + ip = (BYTE *)GetIP(pExState->GetContextRecord()); + } + else + { + ip = (BYTE *)currentIP; + } + + if (g_pEEInterface->IsManagedNativeCode(ip)) + { + + MethodDesc *pMethodDesc = g_pEEInterface->GetNativeCodeMethodDesc(PCODE(ip)); + _ASSERTE(pMethodDesc != NULL); + + if (pMethodDesc != NULL) + { + DebuggerJitInfo *pDebugJitInfo = GetJitInfo(pMethodDesc, ip, &pDebugMethodInfo); + + if (pDebugJitInfo != NULL) + { + SIZE_T nativeOffset = CodeRegionInfo::GetCodeRegionInfo(pDebugJitInfo, pMethodDesc).AddressToOffset(ip); + CorDebugMappingResult mapResult; + DWORD which; + + nOffset = pDebugJitInfo->MapNativeOffsetToIL(nativeOffset, &mapResult, &which); + } + } + } + + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + + if (fFirstChance) + { + // We can call into this method when there is no exception in progress to alert + // the debugger to a stack overflow, however that case should never specify first + // chance. An exception must be in progress to check the flags on the exception state + _ASSERTE(pThread->IsExceptionInProgress()); + + // + // Send the first chance exception if we have not already and if it is not suppressed + // + if (m_sendExceptionsOutsideOfJMC && !pExState->GetFlags()->SentDebugFirstChance()) + { + // Blocking here is especially important so that the debugger can mark any code as JMC. + hr = SendExceptionHelperAndBlock( + pThread, + g_pEEInterface->GetThreadException(pThread), + fContinuable, + framePointer, + nOffset, + DEBUG_EXCEPTION_FIRST_CHANCE, + fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0); + + { + // Toggle GC into COOP to block this thread. + GCX_COOP_EEINTERFACE(); + + // + // If we weren't at a safe place when we enabled PGC, then go ahead and unmark that fact now that we've successfully + // disabled. + // + if (!atSafePlace) + { + g_pDebugger->DecThreadsAtUnsafePlaces(); + } + + ProcessAnyPendingEvals(pThread); + + // + // If we weren't at a safe place, increment the unsafe count before we enable preemptive mode. + // + if (!atSafePlace) + { + g_pDebugger->IncThreadsAtUnsafePlaces(); + } + } // end of GCX_CCOP_EEINTERFACE(); + } //end if (m_sendExceptionsOutsideOfJMC && !SentDebugFirstChance()) + + // + // If this is a JMC function, then we send a USER's first chance as well. + // + if ((pDebugMethodInfo != NULL) && + pDebugMethodInfo->IsJMCFunction() && + !pExState->GetFlags()->SentDebugUserFirstChance()) + { + SENDIPCEVENT_BEGIN(this, pThread); + + InitIPCEvent(ipce, DB_IPCE_EXCEPTION_CALLBACK2, pThread, pThread->GetDomain()); + + ipce->ExceptionCallback2.framePointer = framePointer; + ipce->ExceptionCallback2.eventType = DEBUG_EXCEPTION_USER_FIRST_CHANCE; + ipce->ExceptionCallback2.nOffset = nOffset; + ipce->ExceptionCallback2.dwFlags = fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0; + ipce->ExceptionCallback2.vmExceptionHandle.SetRawPtr(g_pEEInterface->GetThreadException(pThread)); + + LOG((LF_CORDB, LL_INFO10000, "D::SE: sending ExceptionCallback2 (USER FIRST CHANCE)")); + hr = m_pRCThread->SendIPCEvent(); + + _ASSERTE(SUCCEEDED(hr) && "D::SE: Send ExceptionCallback2 (User) event failed."); + + if (SUCCEEDED(hr)) + { + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + + pExState->GetFlags()->SetSentDebugUserFirstChance(); + + // Let other Runtime threads handle their events. + SENDIPCEVENT_END; + + } // end if (!SentDebugUserFirstChance) + + } // end if (firstChance) + else + { + // unhandled exception case + // if there is no exception in progress then we are sending a fake exception object + // as an indication of a fatal error (stack overflow). In this case it is illegal + // to read GetFlags() from the exception state. + // else if there is an exception in progress we only want to send the notification if + // we did not already send a CHF, previous unhandled, or unwind begin notification + BOOL sendNotification = TRUE; + if(pThread->IsExceptionInProgress()) + { + sendNotification = !pExState->GetFlags()->DebugCatchHandlerFound() && + !pExState->GetFlags()->SentDebugUnhandled() && + !pExState->GetFlags()->SentDebugUnwindBegin(); + } + + if(sendNotification) + { + hr = SendExceptionHelperAndBlock( + pThread, + g_pEEInterface->GetThreadException(pThread), + fContinuable, + LEAF_MOST_FRAME, + (SIZE_T)ICorDebugInfo::NO_MAPPING, + DEBUG_EXCEPTION_UNHANDLED, + fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0); + + if(pThread->IsExceptionInProgress()) + { + pExState->GetFlags()->SetSentDebugUnhandled(); + } + } + + } // end if (!firstChance) +} + +// +// SendException is called by Runtime threads to send that they've hit an Managed exception to the Right Side. +// This may block this thread and suspend the debuggee, and let the debugger inspect us. +// +// The thread's throwable should be set so that the debugger can inspect the current exception. +// It does not report native exceptions in native code (which is consistent because those don't have a +// managed exception object). +// +// This may kick off a jit-attach (in which case fAttaching==true), and so may be called even when no debugger +// is yet involved. +// +// Parameters: +// pThread - the thread throwing the exception. +// fFirstChance - true if this is a first chance exception. False if this is an unhandled exception. +// currentIP - absolute native address of the exception if it is from managed code. If this is 0, we try to find it +// based off the thread's current exception state. +// currentSP - stack pointer of the exception. This will get converted into a FramePointer and then used by the debugger +// to identify which stack frame threw the exception. +// currentBSP - additional information for IA64 only to identify the stack frame. +// fContinuable - not used. +// fAttaching - true iff this exception may initiate a jit-attach. In the common case, if this is true, then +// CorDebuggerAttached() is false. However, since a debugger can attach at any time, it's possible +// for another debugger to race against the jit-attach and win. Thus this may err on the side of being true. +// fForceNonInterceptable - This is used to determine if the exception is continuable (ie "Interceptible", +// we can handle a DB_IPCE_INTERCEPT_EXCEPTION event for it). If true, then the exception can not be continued. +// If false, we get continuation status from the exception properties of the current thread. +// +// Returns: +// S_OK on success (common case by far). +// propogates other errors. +// +HRESULT Debugger::SendException(Thread *pThread, + bool fFirstChance, + SIZE_T currentIP, + SIZE_T currentSP, + bool fContinuable, // not used by RS. + bool fAttaching, + bool fForceNonInterceptable, + EXCEPTION_POINTERS * pExceptionInfo) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + + MODE_ANY; + + PRECONDITION(HasLazyData()); + PRECONDITION(CheckPointer(pThread)); + PRECONDITION((pThread->GetFilterContext() == NULL) || !fFirstChance); + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::SendException\n")); + + if (CORDBUnrecoverableError(this)) + { + return (E_FAIL); + } + + // Mark if we're at an unsafe place. + AtSafePlaceHolder unsafePlaceHolder(pThread); + + // Grab the exception name from the current exception object to pass to the JIT attach. + bool fIsInterceptable; + + if (fForceNonInterceptable) + { + fIsInterceptable = false; + m_forceNonInterceptable = true; + } + else + { + fIsInterceptable = IsInterceptableException(pThread); + m_forceNonInterceptable = false; + } + + ThreadExceptionState* pExState = pThread->GetExceptionState(); + BOOL managedEventNeeded = ((!fFirstChance) || + (fFirstChance && (!pExState->GetFlags()->SentDebugFirstChance() || !pExState->GetFlags()->SentDebugUserFirstChance()))); + + // There must be a managed exception object to send a managed exception event + if (g_pEEInterface->IsThreadExceptionNull(pThread) && (pThread->LastThrownObjectHandle() == NULL)) + { + managedEventNeeded = FALSE; + } + + if (fAttaching) + { + JitAttach(pThread, pExceptionInfo, managedEventNeeded, FALSE); + // If the jit-attach occurred, CORDebuggerAttached() may now be true and we can + // just act as if a debugger was always attached. + } + + if(managedEventNeeded) + { + { + // We have to send enabled, so enable now. + GCX_PREEMP_EEINTERFACE(); + + // Send the exception events. Even in jit-attach case, we should now be fully attached. + if (CORDebuggerAttached()) + { + // Initialize frame-pointer associated with exception notification. + LPVOID stackPointer; + if ((currentSP == 0) && (pExState->GetContextRecord() != NULL)) + { + stackPointer = dac_cast(GetSP(pExState->GetContextRecord())); + } + else + { + stackPointer = (LPVOID)currentSP; + } + FramePointer framePointer = FramePointer::MakeFramePointer(stackPointer); + + + // Do the real work of sending the events + SendExceptionEventsWorker( + pThread, + fFirstChance, + fIsInterceptable, + fContinuable, + currentIP, + framePointer, + !unsafePlaceHolder.IsAtUnsafePlace()); + } + else + { + LOG((LF_CORDB,LL_INFO100, "D:SE: Skipping SendIPCEvent because not supposed to send anything, or RS detached.\n")); + } + } + + // If we weren't at a safe place when we switched to PREEMPTIVE, then go ahead and unmark that fact now + // that we're successfully back in COOPERATIVE mode. + unsafePlaceHolder.Clear(); + + { + GCX_COOP_EEINTERFACE(); + ProcessAnyPendingEvals(pThread); + } + } + + if (CORDebuggerAttached()) + { + return S_FALSE; + } + else + { + return S_OK; + } +} + + +/* + * ProcessAnyPendingEvals + * + * This function checks for, and then processes, any pending func-evals. + * + * Parameters: + * pThread - The thread to process. + * + * Returns: + * None. + * + */ +void Debugger::ProcessAnyPendingEvals(Thread *pThread) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + +#ifndef DACCESS_COMPILE + + // If no debugger is attached, then no evals to process. + // We may get here in oom situations during jit-attach, so we'll check now and be safe. + if (!CORDebuggerAttached()) + { + return; + } + + // + // Note: if there is a filter context installed, we may need remove it, do the eval, then put it back. I'm not 100% + // sure which yet... it kinda depends on whether or not we really need the filter context updated due to a + // collection during the func eval... + // + // If we need to do a func eval on this thread, then there will be a pending eval registered for this thread. We'll + // loop so long as there are pending evals registered. We block in FuncEvalHijackWorker after sending up the + // FuncEvalComplete event, so if the user asks for another func eval then there will be a new pending eval when we + // loop and check again. + // + DebuggerPendingFuncEval *pfe; + + while (GetPendingEvals() != NULL && (pfe = GetPendingEvals()->GetPendingEval(pThread)) != NULL) + { + DebuggerEval *pDE = pfe->pDE; + + _ASSERTE(pDE->m_evalDuringException); + _ASSERTE(pDE->m_thread == GetThread()); + + // Remove the pending eval from the hash. This ensures that if we take a first chance exception during the eval + // that we can do another nested eval properly. + GetPendingEvals()->RemovePendingEval(pThread); + + // Go ahead and do the pending func eval. pDE is invalid after this. + void *ret; + ret = ::FuncEvalHijackWorker(pDE); + + + // The return value should be NULL when FuncEvalHijackWorker is called as part of an exception. + _ASSERTE(ret == NULL); + } + + // If we need to re-throw a ThreadAbortException, go ahead and do it now. + if (GetThread()->m_StateNC & Thread::TSNC_DebuggerReAbort) + { + // Now clear the bit else we'll see it again when we process the Exception notification + // from this upcoming UserAbort exception. + pThread->ResetThreadStateNC(Thread::TSNC_DebuggerReAbort); + pThread->UserAbort(Thread::TAR_Thread, EEPolicy::TA_Safe, INFINITE, Thread::UAC_Normal); + } + +#endif + +} + + +/* + * FirstChanceManagedException is called by Runtime threads when crawling the managed stack frame + * for a handler for the exception. It is called for each managed call on the stack. + * + * Parameters: + * pThread - The thread the exception is occurring on. + * currentIP - the IP in the current stack frame. + * currentSP - the SP in the current stack frame. + * + * Returns: + * Always FALSE. + * + */ +bool Debugger::FirstChanceManagedException(Thread *pThread, SIZE_T currentIP, SIZE_T currentSP) +{ + + // @@@ + // Implement DebugInterface + // Can only be called from EE/exception + // must be on managed thread. + + CONTRACTL + { + THROWS; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + + PRECONDITION(CORDebuggerAttached()); + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::FCE: First chance exception, TID:0x%x, \n", GetThreadIdHelper(pThread))); + + _ASSERTE(GetThread() != NULL); + +#ifdef _DEBUG + static ConfigDWORD d_fce; + if (d_fce.val(CLRConfig::INTERNAL_D__FCE)) + _ASSERTE(!"Stop in Debugger::FirstChanceManagedException?"); +#endif + + SendException(pThread, TRUE, currentIP, currentSP, FALSE, FALSE, FALSE, NULL); + + return false; +} + + +/* + * FirstChanceManagedExceptionCatcherFound is called by Runtime threads when crawling the + * managed stack frame and a handler for the exception is found. + * + * Parameters: + * pThread - The thread the exception is occurring on. + * pTct - Contains the function information that has the catch clause. + * pEHClause - Contains the native offset information of the catch clause. + * + * Returns: + * None. + * + */ +void Debugger::FirstChanceManagedExceptionCatcherFound(Thread *pThread, + MethodDesc *pMD, TADDR pMethodAddr, + BYTE *currentSP, + EE_ILEXCEPTION_CLAUSE *pEHClause) +{ + + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + MODE_ANY; + } + CONTRACTL_END; + + // @@@ + // Implements DebugInterface + // Call by EE/exception. Must be on managed thread + _ASSERTE(GetThread() != NULL); + + // Quick check. + if (!CORDebuggerAttached()) + { + return; + } + + // Compute the offset + + DWORD nOffset = (DWORD)(SIZE_T)ICorDebugInfo::NO_MAPPING; + DebuggerMethodInfo *pDebugMethodInfo = NULL; + DebuggerJitInfo *pDebugJitInfo = NULL; + bool isInJMCFunction = false; + + if (pMD != NULL) + { + _ASSERTE(!pMD->IsILStub()); + + pDebugJitInfo = GetJitInfo(pMD, (const BYTE *) pMethodAddr, &pDebugMethodInfo); + if (pDebugMethodInfo != NULL) + { + isInJMCFunction = pDebugMethodInfo->IsJMCFunction(); + } + } + + // Here we check if debugger opted-out of receiving exception related events from outside of JMC methods + // or this exception ever crossed JMC frame (in this case we have already sent user first chance event) + if (m_sendExceptionsOutsideOfJMC || + isInJMCFunction || + pThread->GetExceptionState()->GetFlags()->SentDebugUserFirstChance()) + { + if (pDebugJitInfo != NULL) + { + CorDebugMappingResult mapResult; + DWORD which; + + // Map the native instruction to the IL instruction. + // Be sure to skip past the prolog on amd64/arm to get the right IL + // instruction (on x86 there will not be a prolog as x86 does not use + // funclets). + nOffset = pDebugJitInfo->MapNativeOffsetToIL( + pEHClause->HandlerStartPC, + &mapResult, + &which, + TRUE + ); + } + + bool fIsInterceptable = IsInterceptableException(pThread); + m_forceNonInterceptable = false; + DWORD dwFlags = fIsInterceptable ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0; + + FramePointer fp = FramePointer::MakeFramePointer(currentSP); + SendCatchHandlerFound(pThread, fp, nOffset, dwFlags); + } + + // flag that we catch handler found so that we won't send other mutually exclusive events + // such as unwind begin or unhandled + pThread->GetExceptionState()->GetFlags()->SetDebugCatchHandlerFound(); +} + +// Filter to trigger CHF callback +// Notify of a catch-handler found callback. +LONG Debugger::NotifyOfCHFFilter(EXCEPTION_POINTERS* pExceptionPointers, PVOID pData) +{ + CONTRACTL + { + if ((GetThread() == NULL) || g_pEEInterface->IsThreadExceptionNull(GetThread())) + { + NOTHROW; + GC_NOTRIGGER; + } + else + { + THROWS; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + MODE_ANY; + } + CONTRACTL_END; + + SCAN_IGNORE_TRIGGER; // Scan can't handle conditional contracts. + + // @@@ + // Implements DebugInterface + // Can only be called from EE + + // If no debugger is attached, then don't bother sending the events. + // This can't kick off a jit-attach. + if (!CORDebuggerAttached()) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // + // If this exception has never bubbled thru to managed code, then there is no + // useful information for the debugger and, in fact, it may be a completely + // internally handled runtime exception, so we should do nothing. + // + if ((GetThread() == NULL) || g_pEEInterface->IsThreadExceptionNull(GetThread())) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // Caller must pass in the stack address. This should match up w/ a Frame. + BYTE * pCatcherStackAddr = (BYTE*) pData; + + // If we don't have any catcher frame, then use ebp from the context. + if (pData == NULL) + { + pCatcherStackAddr = (BYTE*) GetFP(pExceptionPointers->ContextRecord); + } + else + { +#ifdef _DEBUG + _ASSERTE(pData != NULL); + { + // We want the CHF stack addr to match w/ the Internal Frame Cordbg sees + // in the stacktrace. + // The Internal Frame comes from an EE Frame. This means that the CHF stack + // addr must match that EE Frame exactly. Let's check that now. + + Frame * pFrame = reinterpret_cast(pData); + // Calling a virtual method will enforce that we have a valid Frame. ;) + // If we got passed in a random catch address, then when we cast to a Frame + // the vtable pointer will be bogus and this call will AV. + Frame::ETransitionType e; + e = pFrame->GetTransitionType(); + } +#endif + } + + // @todo - when Stubs-In-Stacktraces is always enabled, remove this. + if (!g_EnableSIS) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + // Stubs don't have an IL offset. + const SIZE_T offset = (SIZE_T)ICorDebugInfo::NO_MAPPING; + Thread *pThread = GetThread(); + DWORD dwFlags = IsInterceptableException(pThread) ? DEBUG_EXCEPTION_CAN_BE_INTERCEPTED : 0; + m_forceNonInterceptable = false; + + FramePointer fp = FramePointer::MakeFramePointer(pCatcherStackAddr); + + // + // If we have not sent a first-chance notification, do so now. + // + ThreadExceptionState* pExState = pThread->GetExceptionState(); + + if (!pExState->GetFlags()->SentDebugFirstChance()) + { + SendException(pThread, + TRUE, // first-chance + (SIZE_T)(GetIP(pExceptionPointers->ContextRecord)), // IP + (SIZE_T)pCatcherStackAddr, // SP + FALSE, // fContinuable + FALSE, // attaching + TRUE, // ForceNonInterceptable since we are transition stub, the first and last place + // that will see this exception. + pExceptionPointers); + } + + // Here we check if debugger opted-out of receiving exception related events from outside of JMC methods + // or this exception ever crossed JMC frame (in this case we have already sent user first chance event) + if (m_sendExceptionsOutsideOfJMC || pExState->GetFlags()->SentDebugUserFirstChance()) + { + SendCatchHandlerFound(pThread, fp, offset, dwFlags); + } + + // flag that we catch handler found so that we won't send other mutually exclusive events + // such as unwind begin or unhandled + pExState->GetFlags()->SetDebugCatchHandlerFound(); + +#ifdef DEBUGGING_SUPPORTED + + + if ( (pThread != NULL) && + (pThread->IsExceptionInProgress()) && + (pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo()) ) + { + // + // The debugger wants to intercept this exception. It may return in a failure case, + // in which case we want to continue thru this path. + // + ClrDebuggerDoUnwindAndIntercept(X86_FIRST_ARG(EXCEPTION_CHAIN_END) pExceptionPointers->ExceptionRecord); + } +#endif // DEBUGGING_SUPPORTED + + return EXCEPTION_CONTINUE_SEARCH; +} + + +// Actually send the catch handler found event. +// This can be used to send CHF for both regular managed catchers as well +// as stubs that catch (Func-eval, COM-Interop, AppDomains) +void Debugger::SendCatchHandlerFound( + Thread * pThread, + FramePointer fp, + SIZE_T nOffset, + DWORD dwFlags +) +{ + + CONTRACTL + { + THROWS; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + MODE_ANY; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::FirstChanceManagedExceptionCatcherFound\n")); + + if (pThread == NULL) + { + _ASSERTE(!"Bad parameter"); + LOG((LF_CORDB, LL_INFO10000, "D::FirstChanceManagedExceptionCatcherFound - Bad parameter.\n")); + return; + } + + if (CORDBUnrecoverableError(this)) + { + return; + } + + // + // Mark if we're at an unsafe place. + // + AtSafePlaceHolder unsafePlaceHolder(pThread); + + { + GCX_COOP_EEINTERFACE(); + + { + SENDIPCEVENT_BEGIN(this, pThread); + + if (CORDebuggerAttached() && + !pThread->GetExceptionState()->GetFlags()->DebugCatchHandlerFound() && + !pThread->GetExceptionState()->GetFlags()->SentDebugUnhandled() && + !pThread->GetExceptionState()->GetFlags()->SentDebugUnwindBegin()) + { + HRESULT hr; + + // + // Figure out parameters to the IPC events. + // + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + + // + // Send Whidbey EXCEPTION IPC event. + // + InitIPCEvent(ipce, DB_IPCE_EXCEPTION_CALLBACK2, pThread, pThread->GetDomain()); + + ipce->ExceptionCallback2.framePointer = fp; + ipce->ExceptionCallback2.eventType = DEBUG_EXCEPTION_CATCH_HANDLER_FOUND; + ipce->ExceptionCallback2.nOffset = nOffset; + ipce->ExceptionCallback2.dwFlags = dwFlags; + ipce->ExceptionCallback2.vmExceptionHandle.SetRawPtr(g_pEEInterface->GetThreadException(pThread)); + + LOG((LF_CORDB, LL_INFO10000, "D::FCMECF: sending ExceptionCallback2")); + hr = m_pRCThread->SendIPCEvent(); + + _ASSERTE(SUCCEEDED(hr) && "D::FCMECF: Send ExceptionCallback2 event failed."); + + // + // Stop all Runtime threads + // + TrapAllRuntimeThreads(); + + } // end if (!Attached) + else + { + LOG((LF_CORDB,LL_INFO1000, "D:FCMECF: Skipping SendIPCEvent because RS detached.\n")); + } + + // + // Let other Runtime threads handle their events. + // + SENDIPCEVENT_END; + } + + // + // If we weren't at a safe place when we enabled PGC, then go ahead and unmark that fact now that we've successfully + // disabled. + // + unsafePlaceHolder.Clear(); + + ProcessAnyPendingEvals(pThread); + } // end of GCX_COOP_EEINTERFACE(); + + return; +} + +/* + * ManagedExceptionUnwindBegin is called by Runtime threads when crawling the + * managed stack frame and unwinding them. + * + * Parameters: + * pThread - The thread the unwind is occurring on. + * + * Returns: + * None. + * + */ +void Debugger::ManagedExceptionUnwindBegin(Thread *pThread) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + // @@@ + // Implements DebugInterface + // Can only be called on managed threads + // + + LOG((LF_CORDB, LL_INFO10000, "D::ManagedExceptionUnwindBegin\n")); + + if (pThread == NULL) + { + _ASSERTE(!"Bad parameter"); + LOG((LF_CORDB, LL_INFO10000, "D::ManagedExceptionUnwindBegin - Bad parameter.\n")); + return; + } + + if (CORDBUnrecoverableError(this)) + { + return; + } + + // + // Mark if we're at an unsafe place. + // + AtSafePlaceHolder unsafePlaceHolder(pThread); + { + GCX_COOP_EEINTERFACE(); + + { + SENDIPCEVENT_BEGIN(this, pThread); + + if (CORDebuggerAttached() && + !pThread->GetExceptionState()->GetFlags()->SentDebugUnwindBegin() && + !pThread->GetExceptionState()->GetFlags()->DebugCatchHandlerFound() && + !pThread->GetExceptionState()->GetFlags()->SentDebugUnhandled()) + { + HRESULT hr; + + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + + // + // Send Whidbey EXCEPTION IPC event. + // + InitIPCEvent(ipce, DB_IPCE_EXCEPTION_UNWIND, pThread, pThread->GetDomain()); + + ipce->ExceptionUnwind.eventType = DEBUG_EXCEPTION_UNWIND_BEGIN; + ipce->ExceptionUnwind.dwFlags = 0; + + LOG((LF_CORDB, LL_INFO10000, "D::MEUB: sending ExceptionUnwind event")); + hr = m_pRCThread->SendIPCEvent(); + + _ASSERTE(SUCCEEDED(hr) && "D::MEUB: Send ExceptionUnwind event failed."); + + pThread->GetExceptionState()->GetFlags()->SetSentDebugUnwindBegin(); + + // + // Stop all Runtime threads + // + TrapAllRuntimeThreads(); + + } // end if (!Attached) + + // + // Let other Runtime threads handle their events. + // + SENDIPCEVENT_END; + } + + // + // If we weren't at a safe place when we enabled PGC, then go ahead and unmark that fact now that we've successfully + // disabled. + // + unsafePlaceHolder.Clear(); + } + + return; +} + +/* + * DeleteInterceptContext + * + * This function is called by the VM to release any debugger specific information for an + * exception object. It is called when the VM releases its internal exception stuff, i.e. + * ExInfo on X86 and ExceptionTracker on WIN64. + * + * + * Parameters: + * pContext - Debugger specific context. + * + * Returns: + * None. + * + * Notes: + * pContext is just a pointer to a DebuggerContinuableExceptionBreakpoint. + * + */ +void Debugger::DeleteInterceptContext(void *pContext) +{ + LIMITED_METHOD_CONTRACT; + + DebuggerContinuableExceptionBreakpoint *pBp = (DebuggerContinuableExceptionBreakpoint *)pContext; + + if (pBp != NULL) + { + DeleteInteropSafe(pBp); + } +} + + +// Get the frame point for an exception handler +FramePointer GetHandlerFramePointer(BYTE *pStack) +{ + FramePointer handlerFP; + +#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_) + // Refer to the comment in DispatchUnwind() to see why we have to add + // sizeof(LPVOID) to the handler ebp. + handlerFP = FramePointer::MakeFramePointer(LPVOID(pStack + sizeof(void*))); +#else + // ARM is similar to IA64 in that it uses the establisher frame as the + // handler. in this case we don't need to add sizeof(void*) to the FP. + handlerFP = FramePointer::MakeFramePointer((LPVOID)pStack); +#endif // _TARGET_ARM_ + + return handlerFP; +} + +// +// ExceptionFilter is called by the Runtime threads when an exception +// is being processed. +// - fd - MethodDesc of filter function +// - pMethodAddr - any address inside of the method. This lets us resolve exactly which version +// of the method is being executed (for EnC) +// - offset - native offset to handler. +// - pStack, pBStore - stack pointers. +// +void Debugger::ExceptionFilter(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack) +{ + CONTRACTL + { + MODE_COOPERATIVE; + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(!IsDbgHelperSpecialThread()); + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO10000, "D::EF: pStack:0x%x MD: %s::%s, offset:0x%x\n", + pStack, fd->m_pszDebugClassName, fd->m_pszDebugMethodName, offset)); + + // + // !!! Need to think through logic for when to step through filter code - + // perhaps only during a "step in". + // + + // + // !!! Eventually there may be some weird mechanics introduced for + // returning from the filter that we have to understand. For now we should + // be able to proceed normally. + // + + FramePointer handlerFP; + handlerFP = GetHandlerFramePointer(pStack); + + DebuggerJitInfo * pDJI = NULL; + EX_TRY + { + pDJI = GetJitInfo(fd, (const BYTE *) pMethodAddr); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + if (!fd->IsDynamicMethod() && (pDJI == NULL)) + { + // The only way we shouldn't have a DJI is from a dynamic method or from oom (which the LS doesn't handle). + _ASSERTE(!"Debugger doesn't support OOM scenarios."); + return; + } + + DebuggerController::DispatchUnwind(g_pEEInterface->GetThread(), + fd, pDJI, offset, handlerFP, STEP_EXCEPTION_FILTER); +} + + +// +// ExceptionHandle is called by Runtime threads when an exception is +// being handled. +// - fd - MethodDesc of filter function +// - pMethodAddr - any address inside of the method. This lets us resolve exactly which version +// of the method is being executed (for EnC) +// - offset - native offset to handler. +// - pStack, pBStore - stack pointers. +// +void Debugger::ExceptionHandle(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack) +{ + CONTRACTL + { + MODE_COOPERATIVE; + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(!IsDbgHelperSpecialThread()); + } + CONTRACTL_END; + + + FramePointer handlerFP; + handlerFP = GetHandlerFramePointer(pStack); + + DebuggerJitInfo * pDJI = NULL; + EX_TRY + { + pDJI = GetJitInfo(fd, (const BYTE *) pMethodAddr); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + + if (!fd->IsDynamicMethod() && (pDJI == NULL)) + { + // The only way we shouldn't have a DJI is from a dynamic method or from oom (which the LS doesn't handle). + _ASSERTE(!"Debugger doesn't support OOM scenarios."); + return; + } + + + DebuggerController::DispatchUnwind(g_pEEInterface->GetThread(), + fd, pDJI, offset, handlerFP, STEP_EXCEPTION_HANDLER); +} + +BOOL Debugger::ShouldAutoAttach() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(!CORDebuggerAttached()); + + // We're relying on the caller to determine the + + LOG((LF_CORDB, LL_INFO1000000, "D::SAD\n")); + + // Check if the user has specified a seting in the registry about what he + // wants done when an unhandled exception occurs. + DebuggerLaunchSetting dls = GetDbgJITDebugLaunchSetting(); + + return (dls == DLS_ATTACH_DEBUGGER); + + // @TODO cache the debugger launch setting. + +} + +BOOL Debugger::FallbackJITAttachPrompt() +{ + _ASSERTE(!CORDebuggerAttached()); + return (ATTACH_YES == this->ShouldAttachDebuggerProxy(false)); +} + +void Debugger::MarkDebuggerAttachedInternal() +{ + LIMITED_METHOD_CONTRACT; + + // Attach is complete now. + LOG((LF_CORDB, LL_INFO10000, "D::FEDA: Attach Complete!\n")); + g_pEEInterface->MarkDebuggerAttached(); + + _ASSERTE(HasLazyData()); +} +void Debugger::MarkDebuggerUnattachedInternal() +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(HasLazyData()); + + g_pEEInterface->MarkDebuggerUnattached(); +} + +//----------------------------------------------------------------------------- +// Favor to do lazy initialization on helper thread. +// This is needed to allow lazy intialization in Stack Overflow scenarios. +// We may or may not already be initialized. +//----------------------------------------------------------------------------- +void LazyInitFavor(void *) +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; + } + CONTRACTL_END; + Debugger::DebuggerLockHolder dbgLockHolder(g_pDebugger); + HRESULT hr; + hr = g_pDebugger->LazyInitWrapper(); + (void)hr; //prevent "unused variable" error from GCC + + // On checked builds, warn that we're hitting a scenario that debugging doesn't support. + _ASSERTE(SUCCEEDED(hr) || !"Couldn't initialize lazy data for LastChanceManagedException"); +} + +/****************************************************************************** + * + ******************************************************************************/ +LONG Debugger::LastChanceManagedException(EXCEPTION_POINTERS * pExceptionInfo, + Thread *pThread, + BOOL jitAttachRequested) +{ + CONTRACTL + { + NOTHROW; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + MODE_ANY; + } + CONTRACTL_END; + + // @@@ + // Implements DebugInterface. + // Can be run only on managed thread. + + LOG((LF_CORDB, LL_INFO10000, "D::LastChanceManagedException\n")); + + // Don't stop for native debugging anywhere inside our inproc-Filters. + CantStopHolder hHolder; + + EXCEPTION_RECORD * pExceptionRecord = pExceptionInfo->ExceptionRecord; + CONTEXT * pContext = pExceptionInfo->ContextRecord; + + // You're allowed to call this function with a NULL exception record and context. If you do, then its assumed + // that we want to head right down to asking the user if they want to attach a debugger. No need to try to + // dispatch the exception to the debugger controllers. You have to pass NULL for both the exception record and + // the context, though. They're a pair. Both have to be NULL, or both have to be valid. + _ASSERTE(((pExceptionRecord != NULL) && (pContext != NULL)) || + ((pExceptionRecord == NULL) && (pContext == NULL))); + + if (CORDBUnrecoverableError(this)) + { + return ExceptionContinueSearch; + } + + // We don't do anything on the second pass + if ((pExceptionRecord != NULL) && ((pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING) != 0)) + { + return ExceptionContinueSearch; + } + + // Let the controllers have a chance at it - this may be the only handler which can catch the exception if this + // is a native patch. + + if ((pThread != NULL) && + (pContext != NULL) && + CORDebuggerAttached() && + DebuggerController::DispatchNativeException(pExceptionRecord, + pContext, + pExceptionRecord->ExceptionCode, + pThread)) + { + return ExceptionContinueExecution; + } + + // Otherwise, run our last chance exception logic + ATTACH_ACTION action; + action = ATTACH_NO; + + if (CORDebuggerAttached() || jitAttachRequested) + { + LOG((LF_CORDB, LL_INFO10000, "D::BEH ... debugger attached.\n")); + + Thread *thread = g_pEEInterface->GetThread(); + _ASSERTE((thread != NULL) && (thread == pThread)); + + // ExceptionFlags is 0 for continuable, EXCEPTION_NONCONTINUABLE otherwise. Note that if we don't have an + // exception record, then we assume this is a non-continuable exception. + bool continuable = (pExceptionRecord != NULL) && (pExceptionRecord->ExceptionFlags == 0); + + LOG((LF_CORDB, LL_INFO10000, "D::BEH ... sending exception.\n")); + + HRESULT hr = E_FAIL; + + // In the jit-attach case, lazy-init. We may be in a stack-overflow, so do it via a favor to avoid + // using this thread's stack space. + if (jitAttachRequested) + { + m_pRCThread->DoFavor((FAVORCALLBACK) LazyInitFavor, NULL); + } + + // The only way we don't have lazy data at this point is in an OOM scenario, which + // the debugger doesn't support. + if (!HasLazyData()) + { + return ExceptionContinueSearch; + } + + + // In Whidbey, we used to set the filter CONTEXT when we hit an unhandled exception while doing + // mixed-mode debugging. This helps the debugger walk the stack since it can skip the leaf + // portion of the stack (including stack frames in the runtime) and start the stackwalk at the + // faulting stack frame. The code to set the filter CONTEXT is in a hijack function which is only + // used during mixed-mode debugging. + if (m_pRCThread->GetDCB()->m_rightSideIsWin32Debugger) + { + GCX_COOP(); + + _ASSERTE(thread->GetFilterContext() == NULL); + thread->SetFilterContext(pExceptionInfo->ContextRecord); + } + EX_TRY + { + // We pass the attaching status to SendException so that it knows + // whether to attach a debugger or not. We should really do the + // attach stuff out here and not bother with the flag. + hr = SendException(thread, + FALSE, + ((pContext != NULL) ? (SIZE_T)GetIP(pContext) : NULL), + ((pContext != NULL) ? (SIZE_T)GetSP(pContext) : NULL), + continuable, + !!jitAttachRequested, // If we are JIT attaching on an unhandled exceptioin, we force + !!jitAttachRequested, // the exception to be uninterceptable. + pExceptionInfo); + } + EX_CATCH + { + } + EX_END_CATCH(SwallowAllExceptions); + if (m_pRCThread->GetDCB()->m_rightSideIsWin32Debugger) + { + GCX_COOP(); + + thread->SetFilterContext(NULL); + } + } + else + { + // Note: we don't do anything on NO or TERMINATE. We just return to the exception logic, which will abort the + // app or not depending on what the CLR impl decides is appropiate. + _ASSERTE(action == ATTACH_TERMINATE || action == ATTACH_NO); + } + + return ExceptionContinueSearch; +} + +// +// NotifyUserOfFault notifies the user of a fault (unhandled exception +// or user breakpoint) in the process, giving them the option to +// attach a debugger or terminate the application. +// +int Debugger::NotifyUserOfFault(bool userBreakpoint, DebuggerLaunchSetting dls) +{ + LOG((LF_CORDB, LL_INFO1000000, "D::NotifyUserOfFault\n")); + + CONTRACTL + { + NOTHROW; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT;; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + int result = IDCANCEL; + + if (!CORDebuggerAttached()) + { + DWORD pid; + DWORD tid; + + pid = GetCurrentProcessId(); + tid = GetCurrentThreadId(); + + DWORD flags = 0; + UINT resIDMessage = 0; + + if (userBreakpoint) + { + resIDMessage = IDS_DEBUG_USER_BREAKPOINT_MSG; + flags |= MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION; + } + else + { + resIDMessage = IDS_DEBUG_UNHANDLED_EXCEPTION_MSG; + flags |= MB_OKCANCEL | MB_ICONEXCLAMATION; + } + + { + // Another potential hang. This may get run on the helper if we have a stack overflow. + // Hopefully the odds of 1 thread hitting a stack overflow while another is stuck holding the heap + // lock is very small. + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + result = MessageBox(resIDMessage, IDS_DEBUG_SERVICE_CAPTION, + flags, TRUE, TRUE, pid, pid, tid, tid); + } + } + + LOG((LF_CORDB, LL_INFO1000000, "D::NotifyUserOfFault left\n")); + return result; +} + + +// Proxy for ShouldAttachDebugger +struct ShouldAttachDebuggerParams { + Debugger* m_pThis; + bool m_fIsUserBreakpoint; + Debugger::ATTACH_ACTION m_retval; +}; + +// This is called by the helper thread +void ShouldAttachDebuggerStub(ShouldAttachDebuggerParams * p) +{ + WRAPPER_NO_CONTRACT; + + p->m_retval = p->m_pThis->ShouldAttachDebugger(p->m_fIsUserBreakpoint); +} + +// This gets called just like the normal version, but it sends the call over to the helper thread +Debugger::ATTACH_ACTION Debugger::ShouldAttachDebuggerProxy(bool fIsUserBreakpoint) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + } + CONTRACTL_END; + + if (!HasLazyData()) + { + DebuggerLockHolder lockHolder(this); + HRESULT hr = LazyInitWrapper(); + if (FAILED(hr)) + { + // We already stress logged this case. + return ATTACH_NO; + } + } + + + if (!IsGuardPageGone()) + return ShouldAttachDebugger(fIsUserBreakpoint); + + ShouldAttachDebuggerParams p; + p.m_pThis = this; + p.m_fIsUserBreakpoint = fIsUserBreakpoint; + + LOG((LF_CORDB, LL_INFO1000000, "D::SADProxy\n")); + m_pRCThread->DoFavor((FAVORCALLBACK) ShouldAttachDebuggerStub, &p); + LOG((LF_CORDB, LL_INFO1000000, "D::SADProxy return %d\n", p.m_retval)); + + return p.m_retval; +} + +//--------------------------------------------------------------------------------------- +// Do policy to determine if we should attach a debugger. +// +// Arguments: +// fIsUserBreakpoint - true iff this is in response to a user-breakpoint, else false. +// +// Returns: +// Action to perform based off policy. +// ATTACH_NO if a debugger is already attached. +Debugger::ATTACH_ACTION Debugger::ShouldAttachDebugger(bool fIsUserBreakpoint) +{ + CONTRACTL + { + NOTHROW; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + MODE_ANY; + } + CONTRACTL_END; + + + LOG((LF_CORDB, LL_INFO1000000, "D::SAD\n")); + + // If the debugger is already attached, not necessary to re-attach + if (CORDebuggerAttached()) + { + return ATTACH_NO; + } + + // Check if the user has specified a seting in the registry about what he wants done when an unhandled exception + // occurs. + DebuggerLaunchSetting dls = GetDbgJITDebugLaunchSetting(); + + + if (dls == DLS_ATTACH_DEBUGGER) + { + return ATTACH_YES; + } + else + { + // Only ask the user once if they wish to attach a debugger. This is because LastChanceManagedException can be called + // twice, which causes ShouldAttachDebugger to be called twice, which causes the user to have to answer twice. + static BOOL s_fHasAlreadyAsked = FALSE; + static ATTACH_ACTION s_action; + + + // This lock is also part of the above workaround. + // Must go to preemptive to take this lock since we'll trigger down the road. + GCX_PREEMP(); + DebuggerLockHolder lockHolder(this); + + // We always want to ask about user breakpoints! + if (!s_fHasAlreadyAsked || fIsUserBreakpoint) + { + if (!fIsUserBreakpoint) + s_fHasAlreadyAsked = TRUE; + + // While we could theoretically run into a deadlock if another thread + // which acquires the debugger lock in cooperative GC mode is blocked + // on this thread while it is running arbitrary user code out of the + // MessageBox message pump, given that this codepath will only be used + // on Win9x and that the chances of this happenning are quite slim, + // for Whidbey a GCViolation is acceptable. + CONTRACT_VIOLATION(GCViolation); + + // Ask the user if they want to attach + int iRes = NotifyUserOfFault(fIsUserBreakpoint, dls); + + // If it's a user-defined breakpoint, they must hit Retry to launch + // the debugger. If it's an unhandled exception, user must press + // Cancel to attach the debugger. + if ((iRes == IDCANCEL) || (iRes == IDRETRY)) + s_action = ATTACH_YES; + + else if ((iRes == IDABORT) || (iRes == IDOK)) + s_action = ATTACH_TERMINATE; + + else + s_action = ATTACH_NO; + } + + // dbgLockHolder goes out of scope - implicit Release + return s_action; + } +} + + +//--------------------------------------------------------------------------------------- +// SendUserBreakpoint is called by Runtime threads to send that they've hit +// a user breakpoint to the Right Side. +// +// Parameters: +// thread - managed thread that the breakpoint is on +// +// Notes: +// A user breakpoint is generally triggered by a call to System.Diagnostics.Debugger.Break. +// This can be very common. VB's 'stop' statement compiles to a Debugger.Break call. +// Some other CLR facilities (MDAs) may call this directly too. +// +// This may trigger a Jit attach. +// If the debugger is already attached, this will issue a step-out so that the UserBreakpoint +// appears to come from the callsite. +void Debugger::SendUserBreakpoint(Thread * thread) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + + PRECONDITION(thread != NULL); + PRECONDITION(thread == ::GetThread()); + } + CONTRACTL_END; + + +#ifdef _DEBUG + // For testing Watson, we want a consistent way to be able to generate a + // Fatal Execution Error + // So we have a debug-only knob in this particular managed call that can be used + // to artificially inject the error. + // This is only for testing. + static int fDbgInjectFEE = -1; + + if (fDbgInjectFEE == -1) + fDbgInjectFEE = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgInjectFEE); + + if (fDbgInjectFEE) + { + STRESS_LOG0(LF_CORDB, LL_INFO10000, "Debugger posting bogus FEE b/c knob DbgInjectFEE is set.\n"); + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + // These never return. + } +#endif + + if (CORDBUnrecoverableError(this)) + { + return; + } + + // UserBreakpoint behaves differently if we're under a debugger vs. a jit-attach. + // If we're under the debugger, it does an additional step-out to get us back to the call site. + + // If already attached, then do a step-out and send the userbreak event. + if (CORDebuggerAttached()) + { + // A debugger is already attached, so setup a DebuggerUserBreakpoint controller to get us out of the helper + // that got us here. The DebuggerUserBreakpoint will call AttachDebuggerForBreakpoint for us when we're out + // of the helper. The controller will delete itself when its done its work. + DebuggerUserBreakpoint::HandleDebugBreak(thread); + return; + } + + ATTACH_ACTION dbgAction = ShouldAttachDebugger(true); + + // No debugger is attached. Consider a JIT attach. + // This will do ShouldAttachDebugger() and wait for the results. + // - It may terminate if the user requested that. + // - It may do a full jit-attach. + if (dbgAction == ATTACH_YES) + { + JitAttach(thread, NULL, TRUE, FALSE); + } + else if (dbgAction == ATTACH_TERMINATE) + { + // ATTACH_TERMINATE indicates the the user wants to terminate the app. + LOG((LF_CORDB, LL_INFO10000, "D::SUB: terminating this process due to user request\n")); + + // Should this go through the host? + TerminateProcess(GetCurrentProcess(), 0); + _ASSERTE(!"Should never reach this point."); + } + else + { + _ASSERTE(dbgAction == ATTACH_NO); + } + + if (CORDebuggerAttached()) + { + // On jit-attach, we just send the UserBreak event. Don't do an extra step-out. + SendUserBreakpointAndSynchronize(thread); + } + else if (IsDebuggerPresent()) + { + DebugBreak(); + } +} + + +// void Debugger::ThreadCreated(): ThreadCreated is called when +// a new Runtime thread has been created, but before its ever seen +// managed code. This is a callback invoked by the EE into the Debugger. +// This will create a DebuggerThreadStarter patch, which will set +// a patch at the first instruction in the managed code. When we hit +// that patch, the DebuggerThreadStarter will invoke ThreadStarted, below. +// +// Thread* pRuntimeThread: The EE Thread object representing the +// runtime thread that has just been created. +void Debugger::ThreadCreated(Thread* pRuntimeThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // @@@ + // This function implements the DebugInterface. But it is also called from Attach + // logic internally. + // + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO100, "D::TC: thread created for 0x%x. ******\n", + GetThreadIdHelper(pRuntimeThread))); + + // Sanity check the thread. + _ASSERTE(pRuntimeThread != NULL); + _ASSERTE(pRuntimeThread->GetThreadId() != 0); + + + // Create a thread starter and enable its WillEnterManaged code + // callback. This will cause the starter to trigger once the + // thread has hit managed code, which will cause + // Debugger::ThreadStarted() to be called. NOTE: the starter will + // be deleted automatically when its done its work. + DebuggerThreadStarter *starter = new (interopsafe, nothrow) DebuggerThreadStarter(pRuntimeThread); + + if (starter == NULL) + { + CORDBDebuggerSetUnrecoverableWin32Error(this, 0, false); + return; + } + + starter->EnableTraceCall(LEAF_MOST_FRAME); +} + + +// void Debugger::ThreadStarted(): ThreadStarted is called when +// a new Runtime thread has reached its first managed code. This is +// called by the DebuggerThreadStarter patch's SendEvent method. +// +// Thread* pRuntimeThread: The EE Thread object representing the +// runtime thread that has just hit managed code. +void Debugger::ThreadStarted(Thread* pRuntimeThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // @@@ + // This method implemented DebugInterface but it is also called from Controller + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO100, "D::TS: thread attach : ID=%#x AD:%#x\n", + GetThreadIdHelper(pRuntimeThread), pRuntimeThread->GetDomain())); + + // We just need to send a VMPTR_Thread. The RS will get everything else it needs from DAC. + // + + _ASSERTE((g_pEEInterface->GetThread() && + !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) || + g_fInControlC); + _ASSERTE(ThreadHoldsLock()); + + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_THREAD_ATTACH, + pRuntimeThread, + pRuntimeThread->GetDomain()); + + + m_pRCThread->SendIPCEvent(); + + // + // Well, if this thread got created _after_ we started sync'ing + // then its Runtime thread flags don't have the fact that there + // is a debug suspend pending. We need to call over to the + // Runtime and set the flag in the thread now... + // + if (m_trappingRuntimeThreads) + { + g_pEEInterface->MarkThreadForDebugSuspend(pRuntimeThread); + } +} + + +//--------------------------------------------------------------------------------------- +// +// DetachThread is called by Runtime threads when they are completing +// their execution and about to be destroyed. +// +// Arguments: +// pRuntimeThread - Pointer to the runtime's thread object to detach. +// +// Return Value: +// None +// +//--------------------------------------------------------------------------------------- +void Debugger::DetachThread(Thread *pRuntimeThread) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + { + return; + } + + if (m_ignoreThreadDetach) + { + return; + } + + _ASSERTE (pRuntimeThread != NULL); + + + LOG((LF_CORDB, LL_INFO100, "D::DT: thread detach : ID=%#x AD:%#x.\n", + GetThreadIdHelper(pRuntimeThread), pRuntimeThread->GetDomain())); + + + // We may be killing a thread before the Thread-starter fired. + // So check (and cancel) any outstanding thread-starters. + // If we don't, this old thread starter may conflict w/ a new thread-starter + // if AppDomains or EE Thread's get recycled. + DebuggerController::CancelOutstandingThreadStarter(pRuntimeThread); + + // Controller lock is bigger than debugger lock. + // Don't take debugger lock before the CancelOutStandingThreadStarter function. + SENDIPCEVENT_BEGIN(this, pRuntimeThread); + + if (CORDebuggerAttached()) + { + // Send a detach thread event to the Right Side. + DebuggerIPCEvent * pEvent = m_pRCThread->GetIPCEventSendBuffer(); + + InitIPCEvent(pEvent, + DB_IPCE_THREAD_DETACH, + pRuntimeThread, + pRuntimeThread->GetDomain()); + + m_pRCThread->SendIPCEvent(); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + + // This prevents a race condition where we blocked on the Lock() + // above while another thread was sending an event and while we + // were blocked the debugger suspended us and so we wouldn't be + // resumed after the suspension about to happen below. + pRuntimeThread->ResetThreadStateNC(Thread::TSNC_DebuggerUserSuspend); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::DT: Skipping SendIPCEvent because RS detached.")); + } + + SENDIPCEVENT_END; +} + + +// +// SuspendComplete is called when the last Runtime thread reaches a safe point in response to having its trap flags set. +// This may be called on either the real helper thread or someone doing helper thread duty. +// +BOOL Debugger::SuspendComplete() +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + + // This will is conceptually mode-cooperative. + // But we haven't marked the runtime as stopped yet (m_stopped), so the contract + // subsystem doesn't realize it yet. + DISABLED(MODE_COOPERATIVE); + } + CONTRACTL_END; + + // @@@ + // Call from RCThread::MainLoop and TemporaryHelperThreadMainLoop. + // when all threads suspended. Can happen on managed thread or helper thread. + // If happen on managed thread, it must be doing the helper thread duty. + // + + _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); + + // We should be holding debugger lock m_mutex. + _ASSERTE(ThreadHoldsLock()); + + // We can't throw here (we're in the middle of the runtime suspension logic). + // But things below us throw. So we catch the exception, but then what state are we in? + + _ASSERTE((!g_pEEInterface->GetThread() || !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) || g_fInControlC); + _ASSERTE(ThisIsHelperThreadWorker()); + + STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::SC: suspension complete\n"); + + // We have suspended runtime. + + // We're stopped now. Marking m_stopped allows us to use MODE_COOPERATIVE contracts. + _ASSERTE(!m_stopped && m_trappingRuntimeThreads); + m_stopped = true; + + + // Send the sync complete event to the Right Side. + { + // If we fail to send the SyncComplete, what do we do? + CONTRACT_VIOLATION(ThrowsViolation); + + SendSyncCompleteIPCEvent(); // sets m_stopped = true... + } + + // Everything in the next scope is meant to mimic what we do UnlockForEventSending minus EnableEventHandling. + // We do the EEH part when we get the Continue event. + { +#ifdef _DEBUG + //_ASSERTE(m_tidLockedForEventSending == GetCurrentThreadId()); + m_tidLockedForEventSending = 0; +#endif + + // + // Event handling is re-enabled by the RCThread in response to a + // continue message from the Right Side. + + } + + // @todo - what should we do if this function failed? + return TRUE; +} + + + + +//--------------------------------------------------------------------------------------- +// +// Debugger::SendCreateAppDomainEvent - notify the RS of an AppDomain +// +// Arguments: +// pRuntimeAppdomain - pointer to the AppDomain +// +// Return Value: +// None +// +// Notes: +// This is used to notify the debugger of either a newly created +// AppDomain (when fAttaching is FALSE) or of existing AppDomains +// at attach time (fAttaching is TRUE). In both cases, this should +// be called before any LoadModule/LoadAssembly events are sent for +// this domain. Otherwise the RS will get an event for an AppDomain +// it doesn't recognize and ASSERT. +// +// For the non-attach case this means there is no need to enumerate +// the assemblies/modules in an AppDomain after sending this event +// because we know there won't be any. +// + +void Debugger::SendCreateAppDomainEvent(AppDomain * pRuntimeAppDomain) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + + MODE_COOPERATIVE; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + { + return; + } + + STRESS_LOG2(LF_CORDB, LL_INFO10000, "D::SCADE: AppDomain creation:%#08x, %#08x\n", + pRuntimeAppDomain, pRuntimeAppDomain->GetId().m_dwId); + + + + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread); + + + + // We may have detached while waiting in LockForEventSending, + // in which case we can't send the event. + if (CORDebuggerAttached()) + { + // Send a create appdomain event to the Right Side. + DebuggerIPCEvent * pEvent = m_pRCThread->GetIPCEventSendBuffer(); + + InitIPCEvent(pEvent, + DB_IPCE_CREATE_APP_DOMAIN, + pThread, + pRuntimeAppDomain); + + // Only send a pointer to the AppDomain, the RS will get everything else via DAC. + pEvent->AppDomainData.vmAppDomain.SetRawPtr(pRuntimeAppDomain); + m_pRCThread->SendIPCEvent(); + + TrapAllRuntimeThreads(); + } + + // Let other Runtime threads handle their events. + SENDIPCEVENT_END; + +} + + + + +// +// SendExitAppDomainEvent is called when an app domain is destroyed. +// +void Debugger::SendExitAppDomainEvent(AppDomain* pRuntimeAppDomain) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO100, "D::EAD: Exit AppDomain 0x%08x.\n", + pRuntimeAppDomain)); + + STRESS_LOG3(LF_CORDB, LL_INFO10000, "D::EAD: AppDomain exit:%#08x, %#08x, %#08x\n", + pRuntimeAppDomain, pRuntimeAppDomain->GetId().m_dwId, CORDebuggerAttached()); + + Thread *thread = g_pEEInterface->GetThread(); + // Prevent other Runtime threads from handling events. + SENDIPCEVENT_BEGIN(this, thread); + + if (CORDebuggerAttached()) + { + if (pRuntimeAppDomain->IsDefaultDomain() ) + { + // The Debugger expects to never get an unload event for the default Domain. + // Currently we should never get here because g_fProcessDetach will be true by + // the time this method is called. However, we'd like to know if this ever changes + _ASSERTE(!"Trying to deliver notification of unload for default domain" ); + return; + } + + // Send the exit appdomain event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_EXIT_APP_DOMAIN, + thread, + pRuntimeAppDomain); + m_pRCThread->SendIPCEvent(); + + // Delete any left over modules for this appdomain. + // Note that we're doing this under the lock. + if (m_pModules != NULL) + { + DebuggerDataLockHolder ch(this); + m_pModules->RemoveModules(pRuntimeAppDomain); + } + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::EAD: Skipping SendIPCEvent because RS detached.")); + } + + SENDIPCEVENT_END; +} + + + +// +// LoadAssembly is called when a new Assembly gets loaded. +// +void Debugger::LoadAssembly(DomainAssembly * pDomainAssembly) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO100, "D::LA: Load Assembly Asy:0x%p AD:0x%p which:%ls\n", + pDomainAssembly, pDomainAssembly->GetAppDomain(), pDomainAssembly->GetAssembly()->GetDebugName() )); + + if (!CORDebuggerAttached()) + { + return; + } + + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread) + + + if (CORDebuggerAttached()) + { + // Send a load assembly event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_LOAD_ASSEMBLY, + pThread, + pDomainAssembly->GetAppDomain()); + + ipce->AssemblyData.vmDomainAssembly.SetRawPtr(pDomainAssembly); + + m_pRCThread->SendIPCEvent(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::LA: Skipping SendIPCEvent because RS detached.")); + } + + // Stop all Runtime threads + if (CORDebuggerAttached()) + { + TrapAllRuntimeThreads(); + } + + SENDIPCEVENT_END; +} + + + +// +// UnloadAssembly is called when a Runtime thread unloads an assembly. +// +void Debugger::UnloadAssembly(DomainAssembly * pDomainAssembly) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO100, "D::UA: Unload Assembly Asy:0x%p AD:0x%p which:%ls\n", + pDomainAssembly, pDomainAssembly->GetAppDomain(), pDomainAssembly->GetAssembly()->GetDebugName() )); + + Thread *thread = g_pEEInterface->GetThread(); + // Note that the debugger lock is reentrant, so we may or may not hold it already. + SENDIPCEVENT_BEGIN(this, thread); + + // Send the unload assembly event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + + InitIPCEvent(ipce, + DB_IPCE_UNLOAD_ASSEMBLY, + thread, + pDomainAssembly->GetAppDomain()); + ipce->AssemblyData.vmDomainAssembly.SetRawPtr(pDomainAssembly); + + SendSimpleIPCEventAndBlock(); + + // This will block on the continue + SENDIPCEVENT_END; + +} + + + + +// +// LoadModule is called when a Runtime thread loads a new module and a debugger +// is attached. This also includes when a domain-neutral module is "loaded" into +// a new domain. +// +// TODO: remove pszModuleName and perhaps other args. +void Debugger::LoadModule(Module* pRuntimeModule, + LPCWSTR pszModuleName, // module file name. + DWORD dwModuleName, // length of pszModuleName in chars, not including null. + Assembly *pAssembly, + AppDomain *pAppDomain, + DomainFile * pDomainFile, + BOOL fAttaching) +{ + + CONTRACTL + { + NOTHROW; // not protected for Throws. + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + // @@@@ + // Implement DebugInterface but can be called internally as well. + // This can be called by EE loading module or when we are attaching called by IteratingAppDomainForAttaching + // + _ASSERTE(!fAttaching); + + if (CORDBUnrecoverableError(this)) + return; + + // If this is a dynamic module, then it's part of a multi-module assembly. The manifest + // module within the assembly contains metadata for all the module names in the assembly. + // When a new dynamic module is created, the manifest module's metadata is updated to + // include the new module (see code:Assembly.CreateDynamicModule). + // So we need to update the RS's copy of the metadata. One place the manifest module's + // metadata gets used is in code:DacDbiInterfaceImpl.GetModuleSimpleName + // + // See code:ReflectionModule.CaptureModuleMetaDataToMemory for why we send the metadata-refresh here. + if (pRuntimeModule->IsReflection() && !pRuntimeModule->IsManifest() && !fAttaching) + { + HRESULT hr = S_OK; + EX_TRY + { + // The loader lookups may throw or togggle GC mode, so do them inside a TRY/Catch and + // outside any debugger locks. + Module * pManifestModule = pRuntimeModule->GetAssembly()->GetManifestModule(); + + _ASSERTE(pManifestModule != pRuntimeModule); + _ASSERTE(pManifestModule->IsManifest()); + _ASSERTE(pManifestModule->GetAssembly() == pRuntimeModule->GetAssembly()); + + DomainFile * pManifestDomainFile = pManifestModule->GetDomainFile(pAppDomain); + + DebuggerLockHolder dbgLockHolder(this); + + // Raise the debug event. + // This still tells the debugger that the manifest module metadata is invalid and needs to + // be refreshed. + DebuggerIPCEvent eventMetadataUpdate; + InitIPCEvent(&eventMetadataUpdate, DB_IPCE_METADATA_UPDATE, NULL, pAppDomain); + + eventMetadataUpdate.MetadataUpdateData.vmDomainFile.SetRawPtr(pManifestDomainFile); + + SendRawEvent(&eventMetadataUpdate); + } + EX_CATCH_HRESULT(hr); + SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr); + } + + + DebuggerModule * module = NULL; + + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread); + + +#ifdef FEATURE_FUSION + // Fix for issue Whidbey - 106398 + // Populate the pdb to fusion cache. + + // + if (pRuntimeModule->IsIStream() == FALSE) + { + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + HRESULT hrCopy = S_OK; + EX_TRY + { + pRuntimeModule->FusionCopyPDBs(pRuntimeModule->GetPath()); + } + EX_CATCH_HRESULT(hrCopy); // ignore failures + } +#endif // FEATURE_FUSION + + DebuggerIPCEvent* ipce = NULL; + + // Don't create new record if already loaded. We do still want to send the ModuleLoad event, however. + // The RS has logic to ignore duplicate ModuleLoad events. We have to send what could possibly be a dup, though, + // due to some really nasty issues with getting proper assembly and module load events from the loader when dealing + // with shared assemblies. + module = LookupOrCreateModule(pDomainFile); + _ASSERTE(module != NULL); + + + // During a real LoadModule event, debugger can change jit flags. + // Can't do this during a fake event sent on attach. + // This is cleared after we send the LoadModule event. + module->SetCanChangeJitFlags(true); + + + // @dbgtodo inspection - Check whether the DomainFile we get is consistent with the Module and AppDomain we get. + // We should simply things when we actually get rid of DebuggerModule, possibly by just passing the + // DomainFile around. + _ASSERTE(module->GetDomainFile() == pDomainFile); + _ASSERTE(module->GetAppDomain() == pDomainFile->GetAppDomain()); + _ASSERTE(module->GetRuntimeModule() == pDomainFile->GetModule()); + + // Send a load module event to the Right Side. + ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce,DB_IPCE_LOAD_MODULE, pThread, pAppDomain); + + ipce->LoadModuleData.vmDomainFile.SetRawPtr(pDomainFile); + + m_pRCThread->SendIPCEvent(); + + { + // Stop all Runtime threads + HRESULT hr = S_OK; + EX_TRY + { + TrapAllRuntimeThreads(); + } + EX_CATCH_HRESULT(hr); // @dbgtodo synchronization - catch exception and go on to restore state. + // Synchronization feature crew needs to figure out what happens to TrapAllRuntimeThreads(). + } + + SENDIPCEVENT_END; + + // need to update pdb stream for SQL passed in pdb stream + // regardless attach or not. + // + if (pRuntimeModule->IsIStream()) + { + // Just ignore failures. Caller was just sending a debug event and we don't + // want that to interop non-debugging functionality. + HRESULT hr = S_OK; + EX_TRY + { + SendUpdateModuleSymsEventAndBlock(pRuntimeModule, pAppDomain); + } + EX_CATCH_HRESULT(hr); + } + + // Now that we're done with the load module event, can no longer change Jit flags. + module->SetCanChangeJitFlags(false); +} + + +//--------------------------------------------------------------------------------------- +// +// Special LS-only notification that a module has reached the FILE_LOADED level. For now +// this is only useful to bind breakpoints in generic instantiations from NGENd modules +// that we couldn't bind earlier (at LoadModule notification time) because the method +// iterator refuses to consider modules earlier than the FILE_LOADED level. Normally +// generic instantiations would have their breakpoints bound when they get JITted, but in +// the case of NGEN that may never happen, so we need to bind them here. +// +// Arguments: +// * pRuntimeModule - Module that just loaded +// * pAppDomain - AD into which the Module was loaded +// +// Assumptions: +// This is called during the loading process, and blocks that process from +// completing. The module has reached the FILE_LOADED stage, but typically not yet +// the IsReadyForTypeLoad stage. +// + +void Debugger::LoadModuleFinished(Module * pRuntimeModule, AppDomain * pAppDomain) +{ + CONTRACTL + { + SUPPORTS_DAC; + STANDARD_VM_CHECK; + } + CONTRACTL_END; + + _ASSERTE(pRuntimeModule != NULL); + _ASSERTE(pAppDomain != NULL); + + if (CORDBUnrecoverableError(this)) + return; + + // Just as an optimization, skip binding breakpoints if there's no debugger attached. + // If a debugger attaches at some point after here, it will be able to bind patches + // by making the request at that time. If a debugger detaches at some point after + // here, there's no harm in having extra patches bound. + if (!CORDebuggerAttached()) + return; + + // For now, this notification only does interesting work if the module that loaded is + // an NGENd module, because all we care about in this notification is ensuring NGENd + // methods get breakpoints bound on them + if (!pRuntimeModule->HasNativeImage()) + return; + + // This notification is called just before MODULE_READY_FOR_TYPELOAD gets set. But + // for shared modules (loaded into multiple domains), MODULE_READY_FOR_TYPELOAD has + // already been set if this module was already loaded into an earlier domain. For + // such cases, there's no need to bind breakpoints now because the module has already + // been fully loaded into at least one domain, and breakpoint binding has already + // been done for us + if (pRuntimeModule->IsReadyForTypeLoad()) + return; + +#ifdef _DEBUG + { + // This notification is called once the module is loaded + DomainFile * pDomainFile = pRuntimeModule->FindDomainFile(pAppDomain); + _ASSERTE((pDomainFile != NULL) && (pDomainFile->GetLoadLevel() >= FILE_LOADED)); + } +#endif // _DEBUG + + // Find all IL Master patches for this module, and bind & activate their + // corresponding slave patches. + { + DebuggerController::ControllerLockHolder ch; + + HASHFIND info; + DebuggerPatchTable * pTable = DebuggerController::GetPatchTable(); + + for (DebuggerControllerPatch * pMasterPatchCur = pTable->GetFirstPatch(&info); + pMasterPatchCur != NULL; + pMasterPatchCur = pTable->GetNextPatch(&info)) + { + if (!pMasterPatchCur->IsILMasterPatch()) + continue; + + DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pMasterPatchCur->key.module, pMasterPatchCur->key.md); + + // Found a relevant IL master patch. Now bind all corresponding slave patches + // that belong to this Module + DebuggerMethodInfo::DJIIterator it; + dmi->IterateAllDJIs(pAppDomain, pRuntimeModule, &it); + for (; !it.IsAtEnd(); it.Next()) + { + DebuggerJitInfo *dji = it.Current(); + _ASSERTE(dji->m_jitComplete); + + if (dji->m_encVersion != pMasterPatchCur->GetEnCVersion()) + continue; + + // Do we already have a slave for this DJI & Controller? If so, no need + // to add another one + BOOL fSlaveExists = FALSE; + HASHFIND f; + for (DebuggerControllerPatch * pSlavePatchCur = pTable->GetFirstPatch(&f); + pSlavePatchCur != NULL; + pSlavePatchCur = pTable->GetNextPatch(&f)) + { + if (pSlavePatchCur->IsILSlavePatch() && + (pSlavePatchCur->GetDJI() == dji) && + (pSlavePatchCur->controller == pMasterPatchCur->controller)) + { + fSlaveExists = TRUE; + break; + } + } + + if (fSlaveExists) + continue; + + pMasterPatchCur->controller->AddBindAndActivateILSlavePatch(pMasterPatchCur, dji); + } + } + } +} + + +// Send the raw event for Updating symbols. Debugger must query for contents from out-of-process +// +// Arguments: +// pRuntimeModule - required, module to send symbols for. May be domain neutral. +// pAppDomain - required, appdomain that module is in. +// +// Notes: +// This is just a ping event. Debugger must query for actual symbol contents. +// This keeps the launch + attach cases identical. +// This just sends the raw event and does not synchronize the runtime. +// Use code:Debugger.SendUpdateModuleSymsEventAndBlock for that. +void Debugger::SendRawUpdateModuleSymsEvent(Module *pRuntimeModule, AppDomain *pAppDomain) +{ +// @telest - do we need an #ifdef FEATURE_FUSION here? + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_PREEMPTIVE; + + PRECONDITION(ThreadHoldsLock()); + + // Debugger must have been attached to get us to this point. + // We hold the Debugger-lock, so debugger could not have detached from + // underneath us either. + PRECONDITION(CORDebuggerAttached()); + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this)) + return; + + // This event is used to trigger the ICorDebugManagedCallback::UpdateModuleSymbols + // callback. That callback is defined to pass a PDB stream, and so we still use this + // only for legacy compatibility reasons when we've actually got PDB symbols. + // New clients know they must request a new symbol reader after ClassLoad events. + if (pRuntimeModule->GetInMemorySymbolStreamFormat() != eSymbolFormatPDB) + return; // Non-PDB symbols + + DebuggerModule* module = LookupOrCreateModule(pRuntimeModule, pAppDomain); + PREFIX_ASSUME(module != NULL); + + DebuggerIPCEvent* ipce = NULL; + ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, DB_IPCE_UPDATE_MODULE_SYMS, + g_pEEInterface->GetThread(), + pAppDomain); + + ipce->UpdateModuleSymsData.vmDomainFile.SetRawPtr((module ? module->GetDomainFile() : NULL)); + + m_pRCThread->SendIPCEvent(); +} + +// +// UpdateModuleSyms is called when the symbols for a module need to be +// sent to the Right Side because they've changed. +// +// Arguments: +// pRuntimeModule - required, module to send symbols for. May be domain neutral. +// pAppDomain - required, appdomain that module is in. +// +// +// Notes: +// This will send the event (via code:Debugger.SendRawUpdateModuleSymsEvent) and then synchronize +// the runtime waiting for a continue. +// +// This should only be called in cases where we reasonably expect to send symbols. +// However, this may not send symbols if the symbols aren't available. +void Debugger::SendUpdateModuleSymsEventAndBlock(Module* pRuntimeModule, AppDomain *pAppDomain) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + if (CORDBUnrecoverableError(this) || !CORDebuggerAttached()) + { + return; + } + + CGrowableStream * pStream = pRuntimeModule->GetInMemorySymbolStream(); + LOG((LF_CORDB, LL_INFO10000, "D::UMS: update module syms RuntimeModule:0x%08x CGrowableStream:0x%08x\n", pRuntimeModule, pStream)); + if (pStream == NULL) + { + // No in-memory Pdb available. + STRESS_LOG1(LF_CORDB, LL_INFO10000, "No syms available %p", pRuntimeModule); + return; + } + + SENDIPCEVENT_BEGIN(this, g_pEEInterface->GetThread()); // toggles to preemptive + + // Actually send the event + if (CORDebuggerAttached()) + { + SendRawUpdateModuleSymsEvent(pRuntimeModule, pAppDomain); + TrapAllRuntimeThreads(); + } + + SENDIPCEVENT_END; +} + + +// +// UnloadModule is called by the Runtime for each module (including shared ones) +// in an AppDomain that is being unloaded, when a debugger is attached. +// In the EE, a module may be domain-neutral and therefore shared across all AppDomains. +// We abstract this detail away in the Debugger and consider each such EE module to correspond +// to multiple "Debugger Module" instances (one per AppDomain). +// Therefore, this doesn't necessarily mean the runtime is unloading the module, just +// that the Debugger should consider it's (per-AppDomain) DebuggerModule to be unloaded. +// +void Debugger::UnloadModule(Module* pRuntimeModule, + AppDomain *pAppDomain) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + // @@@@ + // implements DebugInterface. + // can only called by EE on Module::NotifyDebuggerUnload + // + + if (CORDBUnrecoverableError(this)) + return; + + + + LOG((LF_CORDB, LL_INFO100, "D::UM: unload module Mod:%#08x AD:%#08x runtimeMod:%#08x modName:%ls\n", + LookupOrCreateModule(pRuntimeModule, pAppDomain), pAppDomain, pRuntimeModule, pRuntimeModule->GetDebugName())); + + + Thread *thread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, thread); + + if (CORDebuggerAttached()) + { + + DebuggerModule* module = LookupOrCreateModule(pRuntimeModule, pAppDomain); + if (module == NULL) + { + LOG((LF_CORDB, LL_INFO100, "D::UM: module already unloaded AD:%#08x runtimeMod:%#08x modName:%ls\n", + pAppDomain, pRuntimeModule, pRuntimeModule->GetDebugName())); + goto LExit; + } + _ASSERTE(module != NULL); + + STRESS_LOG3(LF_CORDB, LL_INFO10000, "D::UM: Unloading Mod:%#08x, %#08x, %#08x\n", + pRuntimeModule, pAppDomain, pRuntimeModule->IsIStream()); + + // Note: the appdomain the module was loaded in must match the appdomain we're unloading it from. If it doesn't, + // then we've either found the wrong DebuggerModule in LookupModule or we were passed bad data. + _ASSERTE(module->GetAppDomain() == pAppDomain); + + // Send the unload module event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, DB_IPCE_UNLOAD_MODULE, thread, pAppDomain); + ipce->UnloadModuleData.vmDomainFile.SetRawPtr((module ? module->GetDomainFile() : NULL)); + ipce->UnloadModuleData.debuggerAssemblyToken.Set(pRuntimeModule->GetClassLoader()->GetAssembly()); + m_pRCThread->SendIPCEvent(); + + // + // Cleanup the module (only for resources consumed when a debugger is attached) + // + + // Remove all patches that apply to this module/AppDomain combination + AppDomain* domainToRemovePatchesIn = NULL; // all domains by default + if( pRuntimeModule->GetAssembly()->IsDomainNeutral() ) + { + // Deactivate all the patches specific to the AppDomain being unloaded + domainToRemovePatchesIn = pAppDomain; + } + // Note that we'll explicitly NOT delete DebuggerControllers, so that + // the Right Side can delete them later. + DebuggerController::RemovePatchesFromModule(pRuntimeModule, domainToRemovePatchesIn); + + // Deactive all JMC functions in this module. We don't do this for shared assemblies + // because JMC status is not maintained on a per-AppDomain basis and we don't + // want to change the JMC behavior of the module in other domains. + if( !pRuntimeModule->GetAssembly()->IsDomainNeutral() ) + { + LOG((LF_CORDB, LL_EVERYTHING, "Setting all JMC methods to false:\n")); + DebuggerDataLockHolder debuggerDataLockHolder(this); + DebuggerMethodInfoTable * pTable = GetMethodInfoTable(); + if (pTable != NULL) + { + HASHFIND info; + + for (DebuggerMethodInfo *dmi = pTable->GetFirstMethodInfo(&info); + dmi != NULL; + dmi = pTable->GetNextMethodInfo(&info)) + { + if (dmi->m_module == pRuntimeModule) + { + dmi->SetJMCStatus(false); + } + } + } + LOG((LF_CORDB, LL_EVERYTHING, "Done clearing JMC methods!\n")); + } + + // Delete the Left Side representation of the module. + if (m_pModules != NULL) + { + DebuggerDataLockHolder chInfo(this); + m_pModules->RemoveModule(pRuntimeModule, pAppDomain); + } + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::UM: Skipping SendIPCEvent because RS detached.")); + } + +LExit: + SENDIPCEVENT_END; +} + +// Called when this module is completely gone from ALL AppDomains, regardless of +// whether a debugger is attached. +// Note that this doesn't get called until after the ADUnload is complete, which happens +// asyncronously in Whidbey (and won't happen at all if the process shuts down first). +// This is normally not called only domain-neutral assemblies because they can't be unloaded. +// However, it may be called if the loader fails to completely load a domain-neutral assembly. +void Debugger::DestructModule(Module *pModule) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO100, "D::DM: destruct module runtimeMod:%#08x modName:%ls\n", + pModule, pModule->GetDebugName())); + + // @@@ + // Implements DebugInterface. + // It is called for Module::Destruct. We do not need to send any IPC event. + + DebuggerLockHolder dbgLockHolder(this); + + // We should have removed all patches at AD unload time (or detach time if the + // debugger detached). + _ASSERTE( !DebuggerController::ModuleHasPatches(pModule) ); + + // Do module clean-up that applies even when no debugger is attached. + // Ideally, we might like to do this cleanup more eagerly and detministically, + // but we don't currently get any early AD unload callback from the loader + // when no debugger is attached. Perhaps we should make the loader + // call this callback earlier. + RemoveModuleReferences(pModule); +} + + +// Internal helper to remove all the DJIs / DMIs and other references for a given Module. +// If we don't remove the DJIs / DMIs, then we're subject to recycling bugs because the underlying +// MethodDescs will get removed. Thus we'll look up a new MD and it will pull up an old DMI that matched +// the old MD. Now the DMI and MD are out of sync and it's downhill from there. +// Note that DMIs may be used (and need cleanup) even when no debugger is attached. +void Debugger::RemoveModuleReferences( Module* pModule ) +{ + _ASSERTE( ThreadHoldsLock() ); + + // We want to remove all references to the module from the various + // tables. It's not just possible, but probable, that the module + // will be re-loaded at the exact same address, and in that case, + // we'll have piles of entries in our DJI table that mistakenly + // match this new module. + // Note that this doesn't apply to domain neutral assemblies, that only + // get unloaded when the process dies. We won't be reclaiming their + // DJIs/patches b/c the process is going to die, so we'll reclaim + // the memory when the various hashtables are unloaded. + + if (m_pMethodInfos != NULL) + { + HRESULT hr = S_OK; + if (!HasLazyData()) + { + hr = LazyInitWrapper(); + } + + if (SUCCEEDED(hr)) + { + DebuggerDataLockHolder debuggerDataLockHolder(this); + + m_pMethodInfos->ClearMethodsOfModule(pModule); + + // DebuggerDataLockHolder out of scope - release implied + } + } +} + +//--------------------------------------------------------------------------------------- +// +// SendClassLoadUnloadEvent - notify the RS of a class either loading or unloading. +// +// Arguments: +// +// fAttaching - true if a debugger is in the process of attaching +// +// Return Value: +// None +// +//--------------------------------------------------------------------------------------- +void Debugger::SendClassLoadUnloadEvent (mdTypeDef classMetadataToken, + DebuggerModule * pClassDebuggerModule, + Assembly *pAssembly, + AppDomain *pAppDomain, + BOOL fIsLoadEvent) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + + LOG((LF_CORDB,LL_INFO10000, "D::SCLUE: Tok:0x%x isLoad:0x%x Mod:%#08x AD:%#08x\n", + classMetadataToken, fIsLoadEvent, pClassDebuggerModule, pAppDomain)); + + DebuggerIPCEvent * pEvent = m_pRCThread->GetIPCEventSendBuffer(); + + BOOL fIsReflection = pClassDebuggerModule->GetRuntimeModule()->IsReflection(); + + if (fIsLoadEvent == TRUE) + { + // We need to update Metadata before Symbols (since symbols depend on metadata) + // It's debatable which needs to come first: Class Load or Sym update. + // V1.1 sent Sym Update first so that binding at the class load has the latest symbols. + // However, The Class Load may need to be in sync with updating new metadata, + // and that has to come before the Sym update. + InitIPCEvent(pEvent, DB_IPCE_LOAD_CLASS, g_pEEInterface->GetThread(), pAppDomain); + + pEvent->LoadClass.classMetadataToken = classMetadataToken; + pEvent->LoadClass.vmDomainFile.SetRawPtr((pClassDebuggerModule ? pClassDebuggerModule->GetDomainFile() : NULL)); + pEvent->LoadClass.classDebuggerAssemblyToken.Set(pAssembly); + + + // For class loads in dynamic modules, RS knows that the metadata has now grown and is invalid. + // RS will re-fetch new metadata from out-of-process. + } + else + { + InitIPCEvent(pEvent, DB_IPCE_UNLOAD_CLASS, g_pEEInterface->GetThread(), pAppDomain); + + pEvent->UnloadClass.classMetadataToken = classMetadataToken; + pEvent->UnloadClass.vmDomainFile.SetRawPtr((pClassDebuggerModule ? pClassDebuggerModule->GetDomainFile() : NULL)); + pEvent->UnloadClass.classDebuggerAssemblyToken.Set(pAssembly); + } + + m_pRCThread->SendIPCEvent(); + + if (fIsLoadEvent && fIsReflection) + { + // Send the raw event, but don't actually sync and block the runtime. + SendRawUpdateModuleSymsEvent(pClassDebuggerModule->GetRuntimeModule(), pAppDomain); + } + +} + + + +/****************************************************************************** + * + ******************************************************************************/ +BOOL Debugger::SendSystemClassLoadUnloadEvent(mdTypeDef classMetadataToken, + Module *classModule, + BOOL fIsLoadEvent) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + if (!m_dClassLoadCallbackCount) + { + return FALSE; + } + + BOOL fRetVal = FALSE; + + Assembly *pAssembly = classModule->GetAssembly(); + + if (!m_pAppDomainCB->Lock()) + return (FALSE); + + AppDomainInfo *pADInfo = m_pAppDomainCB->FindFirst(); + + while (pADInfo != NULL) + { + AppDomain *pAppDomain = pADInfo->m_pAppDomain; + _ASSERTE(pAppDomain != NULL); + + // Only notify for app domains where the module has been fully loaded already + // We used to make a different check here domain->ContainsAssembly() but that + // triggers too early in the loading process. FindDomainFile will not become + // non-NULL until the module is fully loaded into the domain which is what we + // want. + if ((classModule->FindDomainFile(pAppDomain) != NULL ) && + !(fIsLoadEvent && pAppDomain->IsUnloading()) ) + { + // Find the Left Side module that this class belongs in. + DebuggerModule* pModule = LookupOrCreateModule(classModule, pAppDomain); + _ASSERTE(pModule != NULL); + + // Only send a class load event if they're enabled for this module. + if (pModule && pModule->ClassLoadCallbacksEnabled()) + { + SendClassLoadUnloadEvent(classMetadataToken, + pModule, + pAssembly, + pAppDomain, + fIsLoadEvent); + fRetVal = TRUE; + } + } + + pADInfo = m_pAppDomainCB->FindNext(pADInfo); + } + + m_pAppDomainCB->Unlock(); + + return fRetVal; +} + + +// +// LoadClass is called when a Runtime thread loads a new Class. +// Returns TRUE if an event is sent, FALSE otherwise +BOOL Debugger::LoadClass(TypeHandle th, + mdTypeDef classMetadataToken, + Module *classModule, + AppDomain *pAppDomain) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + // @@@ + // Implements DebugInterface + // This can be called by EE/Loader when class is loaded. + // + + BOOL fRetVal = FALSE; + + if (CORDBUnrecoverableError(this)) + return FALSE; + + // Note that pAppDomain may be null. The AppDomain isn't used here, and doesn't make a lot of sense since + // we may be delivering the notification for a class in an assembly which is loaded into multiple AppDomains. We + // handle this in SendSystemClassLoadUnloadEvent below by looping through all AppDomains and dispatching + // events for each that contain this assembly. + + LOG((LF_CORDB, LL_INFO10000, "D::LC: load class Tok:%#08x Mod:%#08x AD:%#08x classMod:%#08x modName:%ls\n", + classMetadataToken, (pAppDomain == NULL) ? NULL : LookupOrCreateModule(classModule, pAppDomain), + pAppDomain, classModule, classModule->GetDebugName())); + + // + // If we're attaching, then we only need to send the event. We + // don't need to disable event handling or lock the debugger + // object. + // + SENDIPCEVENT_BEGIN(this, g_pEEInterface->GetThread()); + + if (CORDebuggerAttached()) + { + fRetVal = SendSystemClassLoadUnloadEvent(classMetadataToken, classModule, TRUE); + + if (fRetVal == TRUE) + { + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::LC: Skipping SendIPCEvent because RS detached.")); + } + + SENDIPCEVENT_END; + + return fRetVal; +} + + +// +// UnloadClass is called when a Runtime thread unloads a Class. +// +void Debugger::UnloadClass(mdTypeDef classMetadataToken, + Module *classModule, + AppDomain *pAppDomain) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + // @@@ + // Implements DebugInterface + // Can only be called from EE + + if (CORDBUnrecoverableError(this)) + { + return; + } + + LOG((LF_CORDB, LL_INFO10000, "D::UC: unload class Tok:0x%08x Mod:%#08x AD:%#08x runtimeMod:%#08x modName:%ls\n", + classMetadataToken, LookupOrCreateModule(classModule, pAppDomain), pAppDomain, classModule, classModule->GetDebugName())); + + Assembly *pAssembly = classModule->GetClassLoader()->GetAssembly(); + DebuggerModule *pModule = LookupOrCreateModule(classModule, pAppDomain); + + if ((pModule == NULL) || !pModule->ClassLoadCallbacksEnabled()) + { + return; + } + + SENDIPCEVENT_BEGIN(this, g_pEEInterface->GetThread()); + + if (CORDebuggerAttached()) + { + _ASSERTE((pAppDomain != NULL) && (pAssembly != NULL) && (pModule != NULL)); + + SendClassLoadUnloadEvent(classMetadataToken, pModule, pAssembly, pAppDomain, FALSE); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::UC: Skipping SendIPCEvent because RS detached.")); + } + + // Let other Runtime threads handle their events. + SENDIPCEVENT_END; + +} + +/****************************************************************************** + * + ******************************************************************************/ +void Debugger::FuncEvalComplete(Thread* pThread, DebuggerEval *pDE) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifndef DACCESS_COMPILE + + if (CORDBUnrecoverableError(this)) + return; + + LOG((LF_CORDB, LL_INFO1000, "D::FEC: func eval complete pDE:%p evalType:%d %s %s\n", + pDE, pDE->m_evalType, pDE->m_successful ? "Success" : "Fail", pDE->m_aborted ? "Abort" : "Completed")); + + + _ASSERTE(pDE->m_completed); + _ASSERTE((g_pEEInterface->GetThread() && !g_pEEInterface->GetThread()->m_fPreemptiveGCDisabled) || g_fInControlC); + _ASSERTE(ThreadHoldsLock()); + + // If we need to rethrow a ThreadAbortException then set the thread's state so we remember that. + if (pDE->m_rethrowAbortException) + { + pThread->SetThreadStateNC(Thread::TSNC_DebuggerReAbort); + } + + + // + // Get the domain that the result is valid in. The RS will cache this in the ICorDebugValue + // Note: it's possible that the AppDomain has (or is about to be) unloaded, which could lead to a + // crash when we use the DebuggerModule. Ideally we'd only be using AppDomain IDs here. + // We can't easily convert our ADID to an AppDomain* (SystemDomain::GetAppDomainFromId) + // because we can't proove that that the AppDomain* would be valid (not unloaded). + // + AppDomain *pDomain = pThread->GetDomain(); + AppDomain *pResultDomain = ((pDE->m_debuggerModule == NULL) ? pDomain : pDE->m_debuggerModule->GetAppDomain()); + _ASSERTE( pResultDomain->GetId() == pDE->m_appDomainId ); + + // Send a func eval complete event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, DB_IPCE_FUNC_EVAL_COMPLETE, pThread, pDomain); + + ipce->FuncEvalComplete.funcEvalKey = pDE->m_funcEvalKey; + ipce->FuncEvalComplete.successful = pDE->m_successful; + ipce->FuncEvalComplete.aborted = pDE->m_aborted; + ipce->FuncEvalComplete.resultAddr = pDE->m_result; + ipce->FuncEvalComplete.vmAppDomain.SetRawPtr(pResultDomain); + ipce->FuncEvalComplete.vmObjectHandle = pDE->m_vmObjectHandle; + + LOG((LF_CORDB, LL_INFO1000, "D::FEC: TypeHandle is %p\n", pDE->m_resultType.AsPtr())); + + Debugger::TypeHandleToExpandedTypeInfo(pDE->m_retValueBoxing, // whether return values get boxed or not depends on the particular FuncEval we're doing... + pResultDomain, + pDE->m_resultType, + &ipce->FuncEvalComplete.resultType); + + _ASSERTE(ipce->FuncEvalComplete.resultType.elementType != ELEMENT_TYPE_VALUETYPE); + + // We must adjust the result address to point to the right place + ipce->FuncEvalComplete.resultAddr = ArgSlotEndianessFixup((ARG_SLOT*)ipce->FuncEvalComplete.resultAddr, + GetSizeForCorElementType(ipce->FuncEvalComplete.resultType.elementType)); + + LOG((LF_CORDB, LL_INFO1000, "D::FEC: returned el %04x resultAddr %p\n", + ipce->FuncEvalComplete.resultType.elementType, ipce->FuncEvalComplete.resultAddr)); + + m_pRCThread->SendIPCEvent(); + +#endif +} + +/****************************************************************************** + * + ******************************************************************************/ +bool Debugger::ResumeThreads(AppDomain* pAppDomain) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(ThisIsHelperThreadWorker()); + } + CONTRACTL_END; + + // Okay, mark that we're not stopped anymore and let the + // Runtime threads go... + ReleaseAllRuntimeThreads(pAppDomain); + + // Return that we've continued the process. + return true; +} + + +class CodeBuffer +{ +public: + + BYTE *getCodeBuffer(DebuggerJitInfo *dji) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + CodeRegionInfo codeRegionInfo = CodeRegionInfo::GetCodeRegionInfo(dji); + + if (codeRegionInfo.getAddrOfColdCode()) + { + _ASSERTE(codeRegionInfo.getSizeOfHotCode() != 0); + _ASSERTE(codeRegionInfo.getSizeOfColdCode() != 0); + S_SIZE_T totalSize = S_SIZE_T( codeRegionInfo.getSizeOfHotCode() ) + + S_SIZE_T( codeRegionInfo.getSizeOfColdCode() ); + if ( totalSize.IsOverflow() ) + { + _ASSERTE(0 && "Buffer overflow error in getCodeBuffer"); + return NULL; + } + + BYTE *code = (BYTE *) buffer.AllocNoThrow( totalSize.Value() ); + if (code) + { + memcpy(code, + (void *) codeRegionInfo.getAddrOfHotCode(), + codeRegionInfo.getSizeOfHotCode()); + + memcpy(code + codeRegionInfo.getSizeOfHotCode(), + (void *) codeRegionInfo.getAddrOfColdCode(), + codeRegionInfo.getSizeOfColdCode()); + + // Now patch the control transfer instructions + } + + return code; + } + else + { + return dac_cast(codeRegionInfo.getAddrOfHotCode()); + } + } +private: + + CQuickBytes buffer; +}; + + +//--------------------------------------------------------------------------------------- +// +// Called on the helper thread to serialize metadata so it can be read out-of-process. +// +// Arguments: +// pModule - module that needs metadata serialization +// countBytes - out value, holds the number of bytes which were allocated in the +// serialized buffer +// +// Return Value: +// A pointer to a serialized buffer of metadata. The caller should free this bufer using +// DeleteInteropSafe +// +// Assumptions: +// This is called on the helper-thread, or a thread pretending to be the helper-thread. +// For any synchronous message, the debuggee should be synchronized. The only async +// messages are Attach and Async-Break. +// +// +//--------------------------------------------------------------------------------------- +BYTE* Debugger::SerializeModuleMetaData(Module * pModule, DWORD * countBytes) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "Debugger::SMMD called\n")); + + // Do not release the emitter. This is a weak reference. + IMetaDataEmit *pEmitter = pModule->GetEmitter(); + _ASSERTE(pEmitter != NULL); + + HRESULT hr; + BYTE* metadataBuffer = NULL; + ReleaseHolder pInternalEmitter; + ULONG originalUpdateMode; + hr = pEmitter->QueryInterface(IID_IMDInternalEmit, (void **)&pInternalEmitter); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD pEmitter doesn't support IID_IMDInternalEmit hr=0x%x\n", hr)); + ThrowHR(hr); + } + _ASSERTE(pInternalEmitter != NULL); + + hr = pInternalEmitter->SetMDUpdateMode(MDUpdateExtension, &originalUpdateMode); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD SetMDUpdateMode failed hr=0x%x\n", hr)); + ThrowHR(hr); + } + _ASSERTE(originalUpdateMode == MDUpdateFull); + + hr = pEmitter->GetSaveSize(cssQuick, countBytes); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD GetSaveSize failed hr=0x%x\n", hr)); + pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL); + ThrowHR(hr); + } + + EX_TRY + { + metadataBuffer = new (interopsafe) BYTE[*countBytes]; + } + EX_CATCH + { + LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD Allocation failed\n")); + pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL); + EX_RETHROW; + } + EX_END_CATCH(SwallowAllExceptions); + _ASSERTE(metadataBuffer != NULL); // allocation would throw first + + // Caller ensures serialization that guarantees that the metadata doesn't grow underneath us. + hr = pEmitter->SaveToMemory(metadataBuffer, *countBytes); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO10, "Debugger::SMMD SaveToMemory failed hr=0x%x\n", hr)); + DeleteInteropSafe(metadataBuffer); + pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL); + ThrowHR(hr); + } + + pInternalEmitter->SetMDUpdateMode(originalUpdateMode, NULL); + LOG((LF_CORDB, LL_INFO10000, "Debugger::SMMD exiting\n")); + return metadataBuffer; +} + +//--------------------------------------------------------------------------------------- +// +// Handle an IPC event from the Debugger. +// +// Arguments: +// event - IPC event to handle. +// +// Return Value: +// True if the event was a continue. Else false. +// +// Assumptions: +// This is called on the helper-thread, or a thread pretending to be the helper-thread. +// For any synchronous message, the debuggee should be synchronized. The only async +// messages are Attach and Async-Break. +// +// Notes: +// HandleIPCEvent is called by the RC thread in response to an event +// from the Debugger Interface. No other IPC events, nor any Runtime +// events will come in until this method returns. Returns true if this +// was a Continue event. +// +// If this function is called on native debugger helper thread, we will +// handle everything. However if this is called on managed thread doing +// helper thread duty, we will fail on operation since we are mainly +// waiting for CONTINUE message from the RS. +// +// +//--------------------------------------------------------------------------------------- + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +bool Debugger::HandleIPCEvent(DebuggerIPCEvent * pEvent) +{ + CONTRACTL + { + THROWS; + if (g_pEEInterface->GetThread() != NULL) { GC_TRIGGERS; } else { GC_NOTRIGGER; } + + PRECONDITION(ThisIsHelperThreadWorker()); + + if (m_stopped) + { + MODE_COOPERATIVE; + } + else + { + MODE_ANY; + } + } + CONTRACTL_END; + + // If we're the temporary helper thread, then we may reject certain operations. + bool temporaryHelp = ThisIsTempHelperThread(); + + +#ifdef _DEBUG + // This reg key allows us to test our unhandled event filter installed in HandleIPCEventWrapper + // to make sure it works properly. + static int s_fDbgFaultInHandleIPCEvent = -1; + if (s_fDbgFaultInHandleIPCEvent == -1) + { + s_fDbgFaultInHandleIPCEvent = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgFaultInHandleIPCEvent); + } + + // If we need to fault, let's generate an access violation. + if (s_fDbgFaultInHandleIPCEvent) + { + *((volatile BYTE *)0) = 0; + } +#endif + + BOOL fSuccess; + bool fContinue = false; + HRESULT hr = S_OK; + + LOG((LF_CORDB, LL_INFO10000, "D::HIPCE: got %s\n", IPCENames::GetName(pEvent->type))); + DbgLog((DebuggerIPCEventType)(pEvent->type & DB_IPCE_TYPE_MASK)); + + // As for runtime is considered stopped, it means that managed threads will not + // execute anymore managed code. However, these threads may be still running for + // unmanaged code. So it is not true that we do not need to hold the lock while processing + // synchrnoized event. + // + // The worst of all, it is the special case where user break point and exception can + // be sent as part of attach if debugger was launched by managed app. + // + DebuggerLockHolder dbgLockHolder(this, FALSE); + + if ((pEvent->type & DB_IPCE_TYPE_MASK) == DB_IPCE_ASYNC_BREAK || + (pEvent->type & DB_IPCE_TYPE_MASK) == DB_IPCE_ATTACHING) + { + dbgLockHolder.Acquire(); + } + else + { + _ASSERTE(m_stopped); + _ASSERTE(ThreadHoldsLock()); + } + + + switch (pEvent->type & DB_IPCE_TYPE_MASK) + { + + case DB_IPCE_ATTACHING: + // In V3, Attach is atomic, meaning that there isn't a complex handshake back and forth between LS + RS. + // the RS sends a single-attaching event and attaches at the first response from the Left-side. + StartCanaryThread(); + + // In V3 after attaching event was handled we iterate throughout all ADs and made shadow copies of PDBs in the BIN directories. + // After all AppDomain, DomainAssembly and modules iteration was available in out-of-proccess model in V4 the code that enables + // PDBs to be copied was not called at attach time. + // Eliminating PDBs copying side effect is an issue: Dev10 #927143 + EX_TRY + { + IterateAppDomainsForPdbs(); + } + EX_CATCH_HRESULT(hr); // ignore failures + + if (m_jitAttachInProgress) + { + // For jit-attach, mark that we're attached now. + // This lets callers to code:Debugger.JitAttach check the flag and + // send the jit-attach event just like a normal event. + MarkDebuggerAttachedInternal(); + + // set the managed attach event so that waiting threads can continue + VERIFY(SetEvent(GetAttachEvent())); + break; + } + + VERIFY(SetEvent(GetAttachEvent())); + + // + // For regular (non-jit) attach, fall through to do an async break. + // + + case DB_IPCE_ASYNC_BREAK: + { + if (temporaryHelp) + { + // Don't support async break on temporary helper thread. + // Well, this function does not return HR. So this means that + // ASYNC_BREAK event will be catching silently while we are + // doing helper thread duty! + // + hr = CORDBG_E_NOTREADY; + } + else + { + // not synchornized. We get debugger lock upon the function entry + _ASSERTE(ThreadHoldsLock()); + + // Simply trap all Runtime threads if we're not already trying to. + if (!m_trappingRuntimeThreads) + { + // If the RS sent an Async-break, then that's an explicit request. + m_RSRequestedSync = TRUE; + TrapAllRuntimeThreads(); // Non-blocking... + } + } + break; + } + + case DB_IPCE_CONTINUE: + { + GetCanary()->ClearCache(); + + fContinue = ResumeThreads(pEvent->vmAppDomain.GetRawPtr()); + + // + // Go ahead and release the TSL now that we're continuing. This ensures that we've held + // the thread store lock the entire time the Runtime was just stopped. + // + ThreadSuspend::UnlockThreadStore(FALSE, ThreadSuspend::SUSPEND_FOR_DEBUGGER); + + break; + } + + case DB_IPCE_BREAKPOINT_ADD: + { + + // + // Currently, we can't create a breakpoint before a + // function desc is available. + // Also, we can't know if a breakpoint is ok + // prior to the method being JITted. + // + + _ASSERTE(hr == S_OK); + DebuggerBreakpoint * pDebuggerBP = NULL; + + DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->BreakpointData.vmDomainFile); + Module * pModule = pDebuggerModule->GetRuntimeModule(); + DebuggerMethodInfo * pDMI = GetOrCreateMethodInfo(pModule, pEvent->BreakpointData.funcMetadataToken); + MethodDesc * pMethodDesc = pEvent->BreakpointData.nativeCodeMethodDescToken.UnWrap(); + + DebuggerJitInfo * pDJI = NULL; + if ((pMethodDesc != NULL) && (pDMI != NULL)) + { + pDJI = pDMI->FindOrCreateInitAndAddJitInfo(pMethodDesc); + } + + { + // If we haven't been either JITted or EnC'd yet, then + // we'll put a patch in by offset, implicitly relative + // to the first version of the code. + + pDebuggerBP = new (interopsafe, nothrow) DebuggerBreakpoint(pModule, + pEvent->BreakpointData.funcMetadataToken, + pEvent->vmAppDomain.GetRawPtr(), + pEvent->BreakpointData.offset, + !pEvent->BreakpointData.isIL, + pEvent->BreakpointData.encVersion, + pMethodDesc, + pDJI, + &fSuccess); + + TRACE_ALLOC(pDebuggerBP); + + if ((pDebuggerBP != NULL) && !fSuccess) + { + DeleteInteropSafe(pDebuggerBP); + pDebuggerBP = NULL; + hr = CORDBG_E_UNABLE_TO_SET_BREAKPOINT; + } + } + + if ((pDebuggerBP == NULL) && !FAILED(hr)) + { + hr = E_OUTOFMEMORY; + } + + LOG((LF_CORDB,LL_INFO10000,"\tBP Add: BPTOK:" + "0x%x, tok=0x%08x, offset=0x%x, isIL=%d dm=0x%x m=0x%x\n", + pDebuggerBP, + pEvent->BreakpointData.funcMetadataToken, + pEvent->BreakpointData.offset, + pEvent->BreakpointData.isIL, + pDebuggerModule, + pModule)); + + // + // We're using a two-way event here, so we place the + // result event into the _receive_ buffer, not the send + // buffer. + // + + DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCEvent(pIPCResult, + DB_IPCE_BREAKPOINT_ADD_RESULT, + g_pEEInterface->GetThread(), + pEvent->vmAppDomain); + + pIPCResult->BreakpointData.breakpointToken.Set(pDebuggerBP); + pIPCResult->hr = hr; + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_STEP: + { + LOG((LF_CORDB,LL_INFO10000, "D::HIPCE: stepIn:0x%x frmTok:0x%x" + "StepIn:0x%x RangeIL:0x%x RangeCount:0x%x MapStop:0x%x " + "InterceptStop:0x%x AppD:0x%x\n", + pEvent->StepData.stepIn, + pEvent->StepData.frameToken.GetSPValue(), + pEvent->StepData.stepIn, + pEvent->StepData.rangeIL, + pEvent->StepData.rangeCount, + pEvent->StepData.rgfMappingStop, + pEvent->StepData.rgfInterceptStop, + pEvent->vmAppDomain.GetRawPtr())); + + // @todo memory allocation - bad if we're synced + Thread * pThread = pEvent->StepData.vmThreadToken.GetRawPtr(); + AppDomain * pAppDomain = pEvent->vmAppDomain.GetRawPtr(); + + DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCEvent(pIPCResult, + DB_IPCE_STEP_RESULT, + pThread, + pEvent->vmAppDomain); + + if (temporaryHelp) + { + // Can't step on the temporary helper thread. + pIPCResult->hr = CORDBG_E_NOTREADY; + } + else + { + DebuggerStepper * pStepper; + + if (pEvent->StepData.IsJMCStop) + { + pStepper = new (interopsafe, nothrow) DebuggerJMCStepper(pThread, + pEvent->StepData.rgfMappingStop, + pEvent->StepData.rgfInterceptStop, + pAppDomain); + } + else + { + pStepper = new (interopsafe, nothrow) DebuggerStepper(pThread, + pEvent->StepData.rgfMappingStop, + pEvent->StepData.rgfInterceptStop, + pAppDomain); + } + + if (pStepper == NULL) + { + pIPCResult->hr = E_OUTOFMEMORY; + + m_pRCThread->SendIPCReply(); + + break; + } + TRACE_ALLOC(pStepper); + + unsigned int cRanges = pEvent->StepData.totalRangeCount; + + _ASSERTE(cRanges == 0 || ((cRanges > 0) && (cRanges == pEvent->StepData.rangeCount))); + + if (!pStepper->Step(pEvent->StepData.frameToken, + pEvent->StepData.stepIn, + &(pEvent->StepData.range), + cRanges, + ((cRanges > 0) ? pEvent->StepData.rangeIL : false))) + { + pIPCResult->hr = E_OUTOFMEMORY; + + m_pRCThread->SendIPCReply(); + + DeleteInteropSafe(pStepper); + break; + } + + pIPCResult->StepData.stepperToken.Set(pStepper); + + + } // end normal step case. + + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_STEP_OUT: + { + // @todo memory allocation - bad if we're synced + Thread * pThread = pEvent->StepData.vmThreadToken.GetRawPtr(); + AppDomain * pAppDomain = pEvent->vmAppDomain.GetRawPtr(); + + DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCEvent(pIPCResult, + DB_IPCE_STEP_RESULT, + pThread, + pAppDomain); + + if (temporaryHelp) + { + // Can't step on the temporary helper thread. + pIPCResult->hr = CORDBG_E_NOTREADY; + } + else + { + DebuggerStepper * pStepper; + + if (pEvent->StepData.IsJMCStop) + { + pStepper = new (interopsafe, nothrow) DebuggerJMCStepper(pThread, + pEvent->StepData.rgfMappingStop, + pEvent->StepData.rgfInterceptStop, + pAppDomain); + } + else + { + pStepper = new (interopsafe, nothrow) DebuggerStepper(pThread, + pEvent->StepData.rgfMappingStop, + pEvent->StepData.rgfInterceptStop, + pAppDomain); + } + + + if (pStepper == NULL) + { + pIPCResult->hr = E_OUTOFMEMORY; + m_pRCThread->SendIPCReply(); + + break; + } + + TRACE_ALLOC(pStepper); + + // Safe to stack trace b/c we're stopped. + StackTraceTicket ticket(pThread); + + pStepper->StepOut(pEvent->StepData.frameToken, ticket); + + pIPCResult->StepData.stepperToken.Set(pStepper); + } + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_BREAKPOINT_REMOVE: + { + // @todo memory allocation - bad if we're synced + + DebuggerBreakpoint * pDebuggerBP = pEvent->BreakpointData.breakpointToken.UnWrap(); + + pDebuggerBP->Delete(); + } + break; + + case DB_IPCE_STEP_CANCEL: + { + // @todo memory allocation - bad if we're synced + LOG((LF_CORDB,LL_INFO10000, "D:HIPCE:Got STEP_CANCEL for stepper 0x%p\n", + pEvent->StepData.stepperToken.UnWrap())); + + DebuggerStepper * pStepper = pEvent->StepData.stepperToken.UnWrap(); + + pStepper->Delete(); + } + break; + + case DB_IPCE_SET_ALL_DEBUG_STATE: + { + Thread * pThread = pEvent->SetAllDebugState.vmThreadToken.GetRawPtr(); + CorDebugThreadState debugState = pEvent->SetAllDebugState.debugState; + + LOG((LF_CORDB,LL_INFO10000,"HandleIPCE: SetAllDebugState: except thread 0x%08x (ID:0x%x) to state 0x%x\n", + pThread, + (pThread != NULL) ? GetThreadIdHelper(pThread) : 0, + debugState)); + + if (!g_fProcessDetach) + { + g_pEEInterface->SetAllDebugState(pThread, debugState); + } + + STRESS_LOG1(LF_CORDB,LL_INFO10000,"HandleIPC: Got 0x%x back from SetAllDebugState\n", hr); + + // Just send back an HR. + DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer(); + + PREFIX_ASSUME(pIPCResult != NULL); + + InitIPCEvent(pIPCResult, DB_IPCE_SET_DEBUG_STATE_RESULT, NULL, NULL); + + pIPCResult->hr = S_OK; + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_GET_GCHANDLE_INFO: + // Given an unvalidated GC-handle, find out all the info about it to view the object + // at the other end + { + OBJECTHANDLE objectHandle = pEvent->GetGCHandleInfo.GCHandle.GetRawPtr(); + + DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer(); + + PREFIX_ASSUME(pIPCResult != NULL); + + InitIPCEvent(pIPCResult, DB_IPCE_GET_GCHANDLE_INFO_RESULT, NULL, NULL); + + bool fValid = SUCCEEDED(ValidateGCHandle(objectHandle)); + + AppDomain * pAppDomain = NULL; + + if(fValid) + { + // Get the appdomain + ADIndex appDomainIndex = HndGetHandleADIndex(objectHandle); + pAppDomain = SystemDomain::GetAppDomainAtIndex(appDomainIndex); + + _ASSERTE(pAppDomain != NULL); + } + + pIPCResult->hr = S_OK; + pIPCResult->GetGCHandleInfoResult.vmAppDomain.SetRawPtr(pAppDomain); + pIPCResult->GetGCHandleInfoResult.fValid = fValid; + + m_pRCThread->SendIPCReply(); + + } + break; + + case DB_IPCE_GET_BUFFER: + { + GetAndSendBuffer(m_pRCThread, pEvent->GetBuffer.bufSize); + } + break; + + case DB_IPCE_RELEASE_BUFFER: + { + SendReleaseBuffer(m_pRCThread, pEvent->ReleaseBuffer.pBuffer); + } + break; +#ifdef EnC_SUPPORTED + case DB_IPCE_APPLY_CHANGES: + { + LOG((LF_ENC, LL_INFO100, "D::HIPCE: DB_IPCE_APPLY_CHANGES 1\n")); + + DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->ApplyChanges.vmDomainFile); + // + // @todo handle error. + // + hr = ApplyChangesAndSendResult(pDebuggerModule, + pEvent->ApplyChanges.cbDeltaMetadata, + (BYTE*) CORDB_ADDRESS_TO_PTR(pEvent->ApplyChanges.pDeltaMetadata), + pEvent->ApplyChanges.cbDeltaIL, + (BYTE*) CORDB_ADDRESS_TO_PTR(pEvent->ApplyChanges.pDeltaIL)); + + LOG((LF_ENC, LL_INFO100, "D::HIPCE: DB_IPCE_APPLY_CHANGES 2\n")); + } + break; +#endif // EnC_SUPPORTED + + case DB_IPCE_SET_CLASS_LOAD_FLAG: + { + DebuggerModule *pDebuggerModule = LookupOrCreateModule(pEvent->SetClassLoad.vmDomainFile); + + _ASSERTE(pDebuggerModule != NULL); + + LOG((LF_CORDB, LL_INFO10000, + "D::HIPCE: class load flag is %d for module 0x%p\n", + pEvent->SetClassLoad.flag, + pDebuggerModule)); + + pDebuggerModule->EnableClassLoadCallbacks((BOOL)pEvent->SetClassLoad.flag); + } + break; + + case DB_IPCE_IS_TRANSITION_STUB: + GetAndSendTransitionStubInfo((CORDB_ADDRESS_TYPE*)pEvent->IsTransitionStub.address); + break; + + case DB_IPCE_MODIFY_LOGSWITCH: + g_pEEInterface->DebuggerModifyingLogSwitch (pEvent->LogSwitchSettingMessage.iLevel, + pEvent->LogSwitchSettingMessage.szSwitchName.GetString()); + + break; + + case DB_IPCE_ENABLE_LOG_MESSAGES: + { + bool fOnOff = pEvent->LogSwitchSettingMessage.iLevel ? true : false; + EnableLogMessages (fOnOff); + } + break; + + case DB_IPCE_SET_IP: + + { + // This is a synchronous event (reply required) + DebuggerIPCEvent * pIPCResult = m_pRCThread->GetIPCEventReceiveBuffer(); + + // Don't have an explicit reply msg + InitIPCReply(pIPCResult, DB_IPCE_SET_IP); + + if (temporaryHelp) + { + pIPCResult->hr = CORDBG_E_NOTREADY; + } + else if (!g_fProcessDetach) + { + // + // Since this pointer is coming from the RS, it may be NULL or something + // unexpected in an OOM situation. Quickly just sanity check them. + // + Thread * pThread = pEvent->SetIP.vmThreadToken.GetRawPtr(); + Module * pModule = pEvent->SetIP.vmDomainFile.GetRawPtr()->GetModule(); + + // Get the DJI for this function + DebuggerMethodInfo * pDMI = GetOrCreateMethodInfo(pModule, pEvent->SetIP.mdMethod); + DebuggerJitInfo * pDJI = NULL; + if (pDMI != NULL) + { + // In the EnC case, if we look for an older version, we need to find the DJI by starting + // address, rather than just by MethodDesc. In the case of generics, we may need to create a DJI, so we + pDJI = pDMI->FindJitInfo(pEvent->SetIP.vmMethodDesc.GetRawPtr(), + (TADDR)pEvent->SetIP.startAddress); + if (pDJI == NULL) + { + // In the case of other functions, we may need to lazily create a DJI, so we need + // FindOrCreate semantics for those. + pDJI = pDMI->FindOrCreateInitAndAddJitInfo(pEvent->SetIP.vmMethodDesc.GetRawPtr()); + } + } + + if ((pDJI != NULL) && (pThread != NULL) && (pModule != NULL)) + { + CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&(pIPCResult->hr), GetCanary()); + + if (SUCCEEDED(pIPCResult->hr)) + { + pIPCResult->hr = SetIP(pEvent->SetIP.fCanSetIPOnly, + pThread, + pModule, + pEvent->SetIP.mdMethod, + pDJI, + pEvent->SetIP.offset, + pEvent->SetIP.fIsIL + ); + } + } + else + { + pIPCResult->hr = E_INVALIDARG; + } + } + else + { + pIPCResult->hr = S_OK; + } + + // Send the result + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_DETACH_FROM_PROCESS: + LOG((LF_CORDB, LL_INFO10000, "Detaching from process!\n")); + + // Delete all controllers (remove patches etc.) + DebuggerController::DeleteAllControllers(); + // Note that we'd like to be able to do this assert here + // _ASSERTE(DebuggerController::GetNumberOfPatches() == 0); + // However controllers may get queued for deletion if there is outstanding + // work and so we can't gaurentee the deletion will complete now. + // @dbgtodo inspection: This shouldn't be an issue in the complete V3 architecture + + MarkDebuggerUnattachedInternal(); + + m_pRCThread->RightSideDetach(); + + + // Clear JMC status + { + LOG((LF_CORDB, LL_EVERYTHING, "Setting all JMC methods to false:\n")); + // On detach, set all DMI's JMC status to false. + // We have to do this b/c we clear the DebuggerModules and allocated + // new ones on re-attach; and the DMI & DM need to be in sync + // (in this case, agreeing that JMC-status = false). + // This also syncs the EE modules and disables all JMC probes. + DebuggerMethodInfoTable * pMethodInfoTable = g_pDebugger->GetMethodInfoTable(); + + if (pMethodInfoTable != NULL) + { + HASHFIND hashFind; + DebuggerDataLockHolder debuggerDataLockHolder(this); + + for (DebuggerMethodInfo * pMethodInfo = pMethodInfoTable->GetFirstMethodInfo(&hashFind); + pMethodInfo != NULL; + pMethodInfo = pMethodInfoTable->GetNextMethodInfo(&hashFind)) + { + pMethodInfo->SetJMCStatus(false); + } + } + LOG((LF_CORDB, LL_EVERYTHING, "Done clearing JMC methods!\n")); + } + + // Clean up the hash of DebuggerModules + // This method is overridden to also free all DebuggerModule objects + if (m_pModules != NULL) + { + + // Removes all DebuggerModules + DebuggerDataLockHolder ch(this); + m_pModules->Clear(); + + } + + // Reply to the detach message before we release any Runtime threads. This ensures that the debugger will get + // the detach reply before the process exits if the main thread is near exiting. + m_pRCThread->SendIPCReply(); + + // Let the process run free now... there is no debugger to bother it anymore. + fContinue = ResumeThreads(NULL); + + // + // Go ahead and release the TSL now that we're continuing. This ensures that we've held + // the thread store lock the entire time the Runtime was just stopped. + // + ThreadSuspend::UnlockThreadStore(FALSE, ThreadSuspend::SUSPEND_FOR_DEBUGGER); + break; + +#ifndef DACCESS_COMPILE + + case DB_IPCE_FUNC_EVAL: + { + // This is a synchronous event (reply required) + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + Thread * pThread = pEvent->FuncEval.vmThreadToken.GetRawPtr(); + + InitIPCEvent(pEvent, DB_IPCE_FUNC_EVAL_SETUP_RESULT, pThread, pThread->GetDomain()); + + BYTE * pbArgDataArea = NULL; + DebuggerEval * pDebuggerEvalKey = NULL; + + pEvent->hr = FuncEvalSetup(&(pEvent->FuncEval), &pbArgDataArea, &pDebuggerEvalKey); + + // Send the result of how the func eval setup went. + pEvent->FuncEvalSetupComplete.argDataArea = PTR_TO_CORDB_ADDRESS(pbArgDataArea); + pEvent->FuncEvalSetupComplete.debuggerEvalKey.Set(pDebuggerEvalKey); + + m_pRCThread->SendIPCReply(); + } + + break; + +#endif + + case DB_IPCE_SET_REFERENCE: + { + // This is a synchronous event (reply required) + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCReply(pEvent, DB_IPCE_SET_REFERENCE_RESULT); + + pEvent->hr = SetReference(pEvent->SetReference.objectRefAddress, + pEvent->SetReference.vmObjectHandle, + pEvent->SetReference.newReference); + + // Send the result of how the set reference went. + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_SET_VALUE_CLASS: + { + // This is a synchronous event (reply required) + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCReply(pEvent, DB_IPCE_SET_VALUE_CLASS_RESULT); + + pEvent->hr = SetValueClass(pEvent->SetValueClass.oldData, + pEvent->SetValueClass.newData, + &pEvent->SetValueClass.type); + + // Send the result of how the set reference went. + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_GET_THREAD_FOR_TASKID: + { + TASKID taskid = pEvent->GetThreadForTaskId.taskid; + Thread *pThread = ThreadStore::GetThreadList(NULL); + Thread *pThreadRet = NULL; + + while (pThread != NULL) + { + if (pThread->GetTaskId() == taskid) + { + pThreadRet = pThread; + break; + } + pThread = ThreadStore::GetThreadList(pThread); + } + + // This is a synchronous event (reply required) + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCReply(pEvent, DB_IPCE_GET_THREAD_FOR_TASKID_RESULT); + + pEvent->GetThreadForTaskIdResult.vmThreadToken.SetRawPtr(pThreadRet); + pEvent->hr = S_OK; + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_CREATE_HANDLE: + { + Object * pObject = (Object*)pEvent->CreateHandle.objectToken; + OBJECTREF objref = ObjectToOBJECTREF(pObject); + AppDomain * pAppDomain = pEvent->vmAppDomain.GetRawPtr(); + BOOL fStrong = pEvent->CreateHandle.fStrong; + OBJECTHANDLE objectHandle; + + // This is a synchronous event (reply required) + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCReply(pEvent, DB_IPCE_CREATE_HANDLE_RESULT); + + { + // Handle creation may need to allocate memory. + // The API specifically limits the number of handls Cordbg can create, + // so we could preallocate and fail allocating anything beyond that. + CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&(pEvent->hr), GetCanary()); + + if (SUCCEEDED(pEvent->hr)) + { + if (fStrong == TRUE) + { + // create strong handle + objectHandle = pAppDomain->CreateStrongHandle(objref); + } + else + { + // create the weak long handle + objectHandle = pAppDomain->CreateLongWeakHandle(objref); + } + pEvent->CreateHandleResult.vmObjectHandle.SetRawPtr(objectHandle); + } + } + + m_pRCThread->SendIPCReply(); + break; + } + + case DB_IPCE_DISPOSE_HANDLE: + { + // DISPOSE an object handle + OBJECTHANDLE objectHandle = pEvent->DisposeHandle.vmObjectHandle.GetRawPtr(); + + if (pEvent->DisposeHandle.fStrong == TRUE) + { + DestroyStrongHandle(objectHandle); + } + else + { + DestroyLongWeakHandle(objectHandle); + } + break; + } + +#ifndef DACCESS_COMPILE + + case DB_IPCE_FUNC_EVAL_ABORT: + { + LOG((LF_CORDB, LL_INFO1000, "D::HIPCE: Got FuncEvalAbort for pDE:%08x\n", + pEvent->FuncEvalAbort.debuggerEvalKey.UnWrap())); + + // This is a synchronous event (reply required) + + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + InitIPCReply(pEvent,DB_IPCE_FUNC_EVAL_ABORT_RESULT); + + pEvent->hr = FuncEvalAbort(pEvent->FuncEvalAbort.debuggerEvalKey.UnWrap()); + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_FUNC_EVAL_RUDE_ABORT: + { + LOG((LF_CORDB, LL_INFO1000, "D::HIPCE: Got FuncEvalRudeAbort for pDE:%08x\n", + pEvent->FuncEvalRudeAbort.debuggerEvalKey.UnWrap())); + + // This is a synchronous event (reply required) + + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCReply(pEvent, DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT); + + pEvent->hr = FuncEvalRudeAbort(pEvent->FuncEvalRudeAbort.debuggerEvalKey.UnWrap()); + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_FUNC_EVAL_CLEANUP: + + // This is a synchronous event (reply required) + + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCReply(pEvent,DB_IPCE_FUNC_EVAL_CLEANUP_RESULT); + + pEvent->hr = FuncEvalCleanup(pEvent->FuncEvalCleanup.debuggerEvalKey.UnWrap()); + + m_pRCThread->SendIPCReply(); + + break; + +#endif + + case DB_IPCE_CONTROL_C_EVENT_RESULT: + { + // store the result of whether the event has been handled by the debugger and + // wake up the thread waiting for the result + SetDebuggerHandlingCtrlC(pEvent->hr == S_OK); + VERIFY(SetEvent(GetCtrlCMutex())); + } + break; + + // Set the JMC status on invididual methods + case DB_IPCE_SET_METHOD_JMC_STATUS: + { + // Get the info out of the event + DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->SetJMCFunctionStatus.vmDomainFile); + Module * pModule = pDebuggerModule->GetRuntimeModule(); + + bool fStatus = (pEvent->SetJMCFunctionStatus.dwStatus != 0); + + mdMethodDef token = pEvent->SetJMCFunctionStatus.funcMetadataToken; + + // Prepare reply + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCEvent(pEvent, DB_IPCE_SET_METHOD_JMC_STATUS_RESULT, NULL, NULL); + + pEvent->hr = S_OK; + + if (pDebuggerModule->HasAnyOptimizedCode() && fStatus) + { + // If there's optimized code, then we can't be set JMC status to true. + // That's because JMC probes are not injected in optimized code, and we + // need a JMC probe to have a JMC function. + pEvent->hr = CORDBG_E_CANT_SET_TO_JMC; + } + else + { + DebuggerDataLockHolder debuggerDataLockHolder(this); + // This may be called on an unjitted method, so we may + // have to create the MethodInfo. + DebuggerMethodInfo * pMethodInfo = GetOrCreateMethodInfo(pModule, token); + + if (pMethodInfo == NULL) + { + pEvent->hr = E_OUTOFMEMORY; + } + else + { + // Update the storage on the LS + pMethodInfo->SetJMCStatus(fStatus); + } + } + + // Send reply + m_pRCThread->SendIPCReply(); + } + break; + + // Get the JMC status on a given function + case DB_IPCE_GET_METHOD_JMC_STATUS: + { + // Get the method + DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->SetJMCFunctionStatus.vmDomainFile); + + Module * pModule = pDebuggerModule->GetRuntimeModule(); + + mdMethodDef token = pEvent->SetJMCFunctionStatus.funcMetadataToken; + + // Init reply + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + InitIPCEvent(pEvent, DB_IPCE_GET_METHOD_JMC_STATUS_RESULT, NULL, NULL); + + // + // This may be called on an unjitted method, so we may + // have to create the MethodInfo. + // + DebuggerMethodInfo * pMethodInfo = GetOrCreateMethodInfo(pModule, token); + + if (pMethodInfo == NULL) + { + pEvent->hr = E_OUTOFMEMORY; + } + else + { + bool fStatus = pMethodInfo->IsJMCFunction(); + pEvent->SetJMCFunctionStatus.dwStatus = fStatus; + pEvent->hr = S_OK; + } + + m_pRCThread->SendIPCReply(); + } + break; + + case DB_IPCE_SET_MODULE_JMC_STATUS: + { + // Get data out of event + DebuggerModule * pDebuggerModule = LookupOrCreateModule(pEvent->SetJMCFunctionStatus.vmDomainFile); + + bool fStatus = (pEvent->SetJMCFunctionStatus.dwStatus != 0); + + // Prepare reply + pEvent = m_pRCThread->GetIPCEventReceiveBuffer(); + + InitIPCReply(pEvent, DB_IPCE_SET_MODULE_JMC_STATUS_RESULT); + + pEvent->hr = S_OK; + + if (pDebuggerModule->HasAnyOptimizedCode() && fStatus) + { + // If there's optimized code, then we can't be set JMC status to true. + // That's because JMC probes are not injected in optimized code, and we + // need a JMC probe to have a JMC function. + pEvent->hr = CORDBG_E_CANT_SET_TO_JMC; + } + else + { + g_pDebugger->SetModuleDefaultJMCStatus(pDebuggerModule->GetRuntimeModule(), fStatus); + } + + + + // Send reply + m_pRCThread->SendIPCReply(); + } + break; + + + case DB_IPCE_INTERCEPT_EXCEPTION: + GetAndSendInterceptCommand(pEvent); + break; + + case DB_IPCE_RESOLVE_UPDATE_METADATA_1: + { + + LOG((LF_CORDB, LL_INFO10000, "D::HIPCE Handling DB_IPCE_RESOLVE_UPDATE_METADATA_1\n")); + // This isn't ideal - Making SerializeModuleMetaData not call new is hard, + // but the odds of trying to load a module after a thread is stopped w/ + // the heap lock should be pretty low. + // All of the metadata calls can violate this and call new. + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + Module * pModule = pEvent->MetadataUpdateRequest.vmModule.GetRawPtr(); + LOG((LF_CORDB, LL_INFO100000, "D::HIPCE Got module 0x%x\n", pModule)); + + DWORD countBytes = 0; + + // This will allocate memory. Debugger will then copy from here and send a + // DB_IPCE_RESOLVE_UPDATE_METADATA_2 to free this memory. + BYTE* pData = NULL; + EX_TRY + { + LOG((LF_CORDB, LL_INFO100000, "D::HIPCE Calling SerializeModuleMetaData\n")); + pData = SerializeModuleMetaData(pModule, &countBytes); + + } + EX_CATCH_HRESULT(hr); + + LOG((LF_CORDB, LL_INFO100000, "D::HIPCE hr is 0x%x\n", hr)); + + DebuggerIPCEvent * pResult = m_pRCThread->GetIPCEventReceiveBuffer(); + InitIPCEvent(pResult, DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT, NULL, NULL); + + pResult->MetadataUpdateRequest.pMetadataStart = pData; + pResult->MetadataUpdateRequest.nMetadataSize = countBytes; + pResult->hr = hr; + LOG((LF_CORDB, LL_INFO1000000, "D::HIPCE metadataStart=0x%x, nMetadataSize=0x%x\n", pData, countBytes)); + + m_pRCThread->SendIPCReply(); + LOG((LF_CORDB, LL_INFO1000000, "D::HIPCE reply sent\n")); + } + break; + + case DB_IPCE_RESOLVE_UPDATE_METADATA_2: + { + // Delete memory allocated with DB_IPCE_RESOLVE_UPDATE_METADATA_1. + BYTE * pData = (BYTE *) pEvent->MetadataUpdateRequest.pMetadataStart; + DeleteInteropSafe(pData); + + DebuggerIPCEvent * pResult = m_pRCThread->GetIPCEventReceiveBuffer(); + InitIPCEvent(pResult, DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT, NULL, NULL); + pResult->hr = S_OK; + m_pRCThread->SendIPCReply(); + } + + break; + + default: + // We should never get an event that we don't know about. + CONSISTENCY_CHECK_MSGF(false, ("Unknown Debug-Event on LS:id=0x%08x.", pEvent->type)); + LOG((LF_CORDB, LL_INFO10000, "Unknown event type: 0x%08x\n", + pEvent->type)); + } + + STRESS_LOG0(LF_CORDB, LL_INFO10000, "D::HIPCE: finished handling event\n"); + + // dbgLockHolder goes out of scope - implicit Release + return fContinue; +} +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +/* + * GetAndSendInterceptCommand + * + * This function processes an INTERCEPT_EXCEPTION IPC event, sending the appropriate response. + * + * Parameters: + * event - the event to process. + * + * Returns: + * hr - HRESULT. + * + */ +HRESULT Debugger::GetAndSendInterceptCommand(DebuggerIPCEvent *event) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + _ASSERTE((event->type & DB_IPCE_TYPE_MASK) == DB_IPCE_INTERCEPT_EXCEPTION); + + // + // Simple state validation first. + // + Thread *pThread = event->InterceptException.vmThreadToken.GetRawPtr(); + + if ((pThread != NULL) && + !m_forceNonInterceptable && + IsInterceptableException(pThread)) + { + ThreadExceptionState* pExState = pThread->GetExceptionState(); + + // We can only have one interception going on at any given time. + if (!pExState->GetFlags()->DebuggerInterceptInfo()) + { + // + // Now start processing the parameters from the event. + // + FramePointer targetFramePointer = event->InterceptException.frameToken; + + ControllerStackInfo csi; + + // Safe because we're stopped. + StackTraceTicket ticket(pThread); + csi.GetStackInfo(ticket, pThread, targetFramePointer, NULL); + + if (csi.m_targetFrameFound) + { + // + // If the target frame is below the point where the current exception was + // thrown from, then we should reject this interception command. This + // can happen in a func-eval during an exception callback, or during a + // breakpoint in a filter function. Or it can just be a user error. + // + CONTEXT* pContext = pExState->GetContextRecord(); + + // This is an approximation on IA64, where we should use the caller SP instead of + // the current SP. However, if the targetFramePointer is valid, the comparison should + // still work. targetFramePointer should be valid because it ultimately comes from a + // full stackwalk. + FramePointer excepFramePointer = FramePointer::MakeFramePointer(GetSP(pContext)); + + if (IsCloserToRoot(excepFramePointer, targetFramePointer)) + { + hr = CORDBG_E_CURRENT_EXCEPTION_IS_OUTSIDE_CURRENT_EXECUTION_SCOPE; + goto LSendResponse; + } + + + // + // If the instruction that faulted is not in this managed code, at the leaf + // frame, then the IP is actually the return address from the managed or + // unmanaged function that really did fault. Thus, we actually want the + // IP of the call instruction. I fake this by simply subtracting 1 from + // the IP, which is close enough approximation for the search below. + // + if (pExState->GetContextRecord() != NULL) + { + // If the faulting instruction is not in managed code, then the interception frame + // must be non-leaf. + if (!g_pEEInterface->IsManagedNativeCode((BYTE *)(GetIP(pExState->GetContextRecord())))) + { + csi.m_activeFrame.relOffset--; + } + else + { + MethodDesc *pMethodDesc = g_pEEInterface->GetNativeCodeMethodDesc(dac_cast(GetIP(pExState->GetContextRecord()))); + + // check if the interception frame is the leaf frame + if ((pMethodDesc == NULL) || + (pMethodDesc != csi.m_activeFrame.md) || + (GetSP(pExState->GetContextRecord()) != GetRegdisplaySP(&(csi.m_activeFrame.registers)))) + { + csi.m_activeFrame.relOffset--; + } + } + } + + // + // Now adjust the IP to be the previous zero-stack depth sequence point. + // + SIZE_T foundOffset = 0; + DebuggerJitInfo *pJitInfo = csi.m_activeFrame.GetJitInfoFromFrame(); + + if (pJitInfo != NULL) + { + ICorDebugInfo::SourceTypes src; + + ULONG relOffset = csi.m_activeFrame.relOffset; + +#if defined(WIN64EXCEPTIONS) + int funcletIndex = PARENT_METHOD_INDEX; + + // For funclets, we need to make sure that the stack empty sequence point we use is + // in the same funclet as the current offset. + if (csi.m_activeFrame.IsFuncletFrame()) + { + funcletIndex = pJitInfo->GetFuncletIndex(relOffset, DebuggerJitInfo::GFIM_BYOFFSET); + } + + // Refer to the loop using pMap below. + DebuggerILToNativeMap* pMap = NULL; +#endif // WIN64EXCEPTIONS + + for (unsigned int i = 0; i < pJitInfo->GetSequenceMapCount(); i++) + { + SIZE_T startOffset = pJitInfo->GetSequenceMap()[i].nativeStartOffset; + + if (DbgIsSpecialILOffset(pJitInfo->GetSequenceMap()[i].ilOffset)) + { + LOG((LF_CORDB, LL_INFO10000, + "D::HIPCE: not placing breakpoint at special offset 0x%x\n", startOffset)); + continue; + } + + if ((i >= 1) && (startOffset == pJitInfo->GetSequenceMap()[i-1].nativeStartOffset)) + { + LOG((LF_CORDB, LL_INFO10000, + "D::HIPCE: not placing redundant breakpoint at duplicate offset 0x%x\n", startOffset)); + continue; + } + + if (startOffset > relOffset) + { + LOG((LF_CORDB, LL_INFO10000, + "D::HIPCE: Stopping scan for breakpoint at offset 0x%x\n", startOffset)); + continue; + } + + src = pJitInfo->GetSequenceMap()[i].source; + + if (!(src & ICorDebugInfo::STACK_EMPTY)) + { + LOG((LF_CORDB, LL_INFO10000, "D::HIPCE: not placing E&C breakpoint at offset " + "0x%x b/c not STACK_EMPTY:it's 0x%x\n", startOffset, src)); + continue; + } + + if ((foundOffset < startOffset) && (startOffset <= relOffset) +#if defined(WIN64EXCEPTIONS) + // Check if we are still in the same funclet. + && (funcletIndex == pJitInfo->GetFuncletIndex(startOffset, DebuggerJitInfo::GFIM_BYOFFSET)) +#endif // WIN64EXCEPTIONS + ) + { + LOG((LF_CORDB, LL_INFO10000, "D::HIPCE: updating breakpoint at native offset 0x%x\n", + startOffset)); + foundOffset = startOffset; +#if defined(WIN64EXCEPTIONS) + // Save the map entry for modification later. + pMap = &(pJitInfo->GetSequenceMap()[i]); +#endif // WIN64EXCEPTIONS + } + } + +#if defined(WIN64EXCEPTIONS) + // This is nasty. Starting recently we could have multiple sequence points with the same IL offset + // in the SAME funclet/parent method (previously different sequence points with the same IL offset + // imply that they are in different funclet/parent method). Fortunately, we only run into this + // if we have a loop which throws a range check failed exception. The code for throwing the + // exception executes out of line (this is JIT-specific, of course). The following loop makes sure + // that when we interecept the exception, we intercept it at the smallest native offset instead + // of intercepting it right before we throw the exception. + for (/* no initialization */; pMap > pJitInfo->GetSequenceMap() ; pMap--) + { + if (pMap->ilOffset == (pMap-1)->ilOffset) + { + foundOffset = (pMap-1)->nativeStartOffset; + } + else + { + break; + } + } + _ASSERTE(foundOffset < relOffset); +#endif // WIN64EXCEPTIONS + + // + // Set up a breakpoint on the intercept IP + // + DebuggerContinuableExceptionBreakpoint *pBreakpoint; + + pBreakpoint = new (interopsafe, nothrow) DebuggerContinuableExceptionBreakpoint(pThread, + foundOffset, + pJitInfo, + csi.m_activeFrame.currentAppDomain + ); + + if (pBreakpoint != NULL) + { + // + // Set up the VM side of intercepting. + // + if (pExState->GetDebuggerState()->SetDebuggerInterceptInfo(csi.m_activeFrame.pIJM, + pThread, + csi.m_activeFrame.MethodToken, + csi.m_activeFrame.md, + foundOffset, +#if defined (_TARGET_ARM_ )|| defined (_TARGET_ARM64_ ) + // ARM requires the caller stack pointer, not the current stack pointer + CallerStackFrame::FromRegDisplay(&(csi.m_activeFrame.registers)), +#else + StackFrame::FromRegDisplay(&(csi.m_activeFrame.registers)), +#endif + pExState->GetFlags() + )) + { + // + // Make sure no more exception callbacks come thru. + // + pExState->GetFlags()->SetSentDebugFirstChance(); + pExState->GetFlags()->SetSentDebugUserFirstChance(); + pExState->GetFlags()->SetSentDebugUnwindBegin(); + + // + // Save off this breakpoint, so that if the exception gets unwound before we hit + // the breakpoint - the exeception info can call back to remove it. + // + pExState->GetDebuggerState()->SetDebuggerInterceptContext((void *)pBreakpoint); + + hr = S_OK; + } + else // VM could not set up for intercept + { + DeleteInteropSafe(pBreakpoint); + hr = E_INVALIDARG; + } + + } + else // could not allocate for breakpoint + { + hr = E_OUTOFMEMORY; + } + + } + else // could not get JitInfo + { + hr = E_FAIL; + } + + } + else // target frame not found. + { + hr = E_INVALIDARG; + } + + } + else // already set up for an intercept. + { + hr = CORDBG_E_INTERCEPT_FRAME_ALREADY_SET; + } + + } + else if (pThread == NULL) + { + hr = E_INVALIDARG; // pThread is NULL. + } + else + { + hr = CORDBG_E_NONINTERCEPTABLE_EXCEPTION; + } + +LSendResponse: + + // + // Prepare reply + // + event = m_pRCThread->GetIPCEventReceiveBuffer(); + InitIPCReply(event, DB_IPCE_INTERCEPT_EXCEPTION_RESULT); + event->hr = hr; + + // + // Send reply + // + m_pRCThread->SendIPCReply(); + + return hr; +} + +// Poll & wait for the real helper thread to come up. +// It's possible that the helper thread is blocked by DllMain, and so we can't +// Wait infinite. If this poll does timeout, then it just means we're likely +// go do helper duty instead of have the real helper do it. +void Debugger::PollWaitingForHelper() +{ + + LOG((LF_CORDB, LL_INFO10000, "PollWaitingForHelper() start\n")); + + DebuggerIPCControlBlock * pDCB = g_pRCThread->GetDCB(); + + PREFIX_ASSUME(pDCB != NULL); + + int nTotalMSToWait = 8 * 1000; + + // Spin waiting for either the real helper thread or a temp. to be ready. + // This should never timeout unless the helper is blocked on the loader lock. + while (!pDCB->m_helperThreadId && !pDCB->m_temporaryHelperThreadId) + { + STRESS_LOG1(LF_CORDB,LL_INFO1000, "PollWaitForHelper. %d\n", nTotalMSToWait); + + // If we hold the lock, we'll block the helper thread and this poll is not useful + _ASSERTE(!ThreadHoldsLock()); + + const DWORD dwTime = 50; + ClrSleepEx(dwTime, FALSE); + nTotalMSToWait -= dwTime; + + if (nTotalMSToWait <= 0) + { + LOG((LF_CORDB, LL_INFO10000, "PollWaitingForHelper() timeout\n")); + return; + } + } + + LOG((LF_CORDB, LL_INFO10000, "PollWaitingForHelper() succeed\n")); + return; +} + + + + +void Debugger::TypeHandleToBasicTypeInfo(AppDomain *pAppDomain, TypeHandle th, DebuggerIPCE_BasicTypeData *res) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::THTBTI: converting left-side type handle to basic right-side type info, ELEMENT_TYPE: %d.\n", th.GetSignatureCorElementType())); + // GetSignatureCorElementType returns E_T_CLASS for E_T_STRING... :-( + if (th.IsNull()) + { + res->elementType = ELEMENT_TYPE_VOID; + } + else if (th.GetMethodTable() == g_pObjectClass) + { + res->elementType = ELEMENT_TYPE_OBJECT; + } + else if (th.GetMethodTable() == g_pStringClass) + { + res->elementType = ELEMENT_TYPE_STRING; + } + else + { + res->elementType = th.GetSignatureCorElementType(); + } + + switch (res->elementType) + { + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_FNPTR: + case ELEMENT_TYPE_BYREF: + res->vmTypeHandle = WrapTypeHandle(th); + res->metadataToken = mdTokenNil; + res->vmDomainFile.SetRawPtr(NULL); + break; + + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_VALUETYPE: + { + res->vmTypeHandle = th.HasInstantiation() ? WrapTypeHandle(th) : VMPTR_TypeHandle::NullPtr(); + // only set if instantiated + res->metadataToken = th.GetCl(); + DebuggerModule * pDModule = LookupOrCreateModule(th.GetModule(), pAppDomain); + res->vmDomainFile.SetRawPtr((pDModule ? pDModule->GetDomainFile() : NULL)); + break; + } + + default: + res->vmTypeHandle = VMPTR_TypeHandle::NullPtr(); + res->metadataToken = mdTokenNil; + res->vmDomainFile.SetRawPtr(NULL); + break; + } + return; +} + +void Debugger::TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed, + AppDomain *pAppDomain, + TypeHandle th, + DebuggerIPCE_ExpandedTypeData *res) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (th.IsNull()) + { + res->elementType = ELEMENT_TYPE_VOID; + } + else if (th.GetMethodTable() == g_pObjectClass) + { + res->elementType = ELEMENT_TYPE_OBJECT; + } + else if (th.GetMethodTable() == g_pStringClass) + { + res->elementType = ELEMENT_TYPE_STRING; + } + else + { + LOG((LF_CORDB, LL_INFO10000, "D::THTETI: converting left-side type handle to expanded right-side type info, ELEMENT_TYPE: %d.\n", th.GetSignatureCorElementType())); + // GetSignatureCorElementType returns E_T_CLASS for E_T_STRING... :-( + res->elementType = th.GetSignatureCorElementType(); + } + + switch (res->elementType) + { + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + _ASSERTE(th.IsArray()); + res->ArrayTypeData.arrayRank = th.AsArray()->GetRank(); + TypeHandleToBasicTypeInfo(pAppDomain, + th.AsArray()->GetArrayElementTypeHandle(), + &(res->ArrayTypeData.arrayTypeArg)); + break; + + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + if (boxed == AllBoxed) + { + res->elementType = ELEMENT_TYPE_CLASS; + goto treatAllValuesAsBoxed; + } + _ASSERTE(th.IsTypeDesc()); + TypeHandleToBasicTypeInfo(pAppDomain, + th.AsTypeDesc()->GetTypeParam(), + &(res->UnaryTypeData.unaryTypeArg)); + break; + + case ELEMENT_TYPE_VALUETYPE: + if (boxed == OnlyPrimitivesUnboxed || boxed == AllBoxed) + res->elementType = ELEMENT_TYPE_CLASS; + // drop through + + case ELEMENT_TYPE_CLASS: + { +treatAllValuesAsBoxed: + res->ClassTypeData.typeHandle = th.HasInstantiation() ? WrapTypeHandle(th) : VMPTR_TypeHandle::NullPtr(); // only set if instantiated + res->ClassTypeData.metadataToken = th.GetCl(); + DebuggerModule * pModule = LookupOrCreateModule(th.GetModule(), pAppDomain); + res->ClassTypeData.vmDomainFile.SetRawPtr((pModule ? pModule->GetDomainFile() : NULL)); + _ASSERTE(!res->ClassTypeData.vmDomainFile.IsNull()); + break; + } + + case ELEMENT_TYPE_FNPTR: + { + if (boxed == AllBoxed) + { + res->elementType = ELEMENT_TYPE_CLASS; + goto treatAllValuesAsBoxed; + } + res->NaryTypeData.typeHandle = WrapTypeHandle(th); + break; + } + default: + // The element type is sufficient, unless the type is effectively a "boxed" + // primitive value type... + if (boxed == AllBoxed) + { + res->elementType = ELEMENT_TYPE_CLASS; + goto treatAllValuesAsBoxed; + } + break; + } + LOG((LF_CORDB, LL_INFO10000, "D::THTETI: converted left-side type handle to expanded right-side type info, res->ClassTypeData.typeHandle = 0x%08x.\n", res->ClassTypeData.typeHandle.GetRawPtr())); + return; +} + + +HRESULT Debugger::BasicTypeInfoToTypeHandle(DebuggerIPCE_BasicTypeData *data, TypeHandle *pRes) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::BTITTH: expanding basic right-side type to left-side type, ELEMENT_TYPE: %d.\n", data->elementType)); + *pRes = TypeHandle(); + TypeHandle th; + switch (data->elementType) + { + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + _ASSERTE(!data->vmTypeHandle.IsNull()); + th = GetTypeHandle(data->vmTypeHandle); + break; + + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_VALUETYPE: + { + if (!data->vmTypeHandle.IsNull()) + { + th = GetTypeHandle(data->vmTypeHandle); + } + else + { + DebuggerModule *pDebuggerModule = g_pDebugger->LookupOrCreateModule(data->vmDomainFile); + + th = g_pEEInterface->FindLoadedClass(pDebuggerModule->GetRuntimeModule(), data->metadataToken); + if (th.IsNull()) + { + LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: class isn't loaded.\n")); + return CORDBG_E_CLASS_NOT_LOADED; + } + + _ASSERTE(th.GetNumGenericArgs() == 0); + } + break; + } + + case ELEMENT_TYPE_FNPTR: + { + _ASSERTE(!data->vmTypeHandle.IsNull()); + th = GetTypeHandle(data->vmTypeHandle); + break; + } + + default: + th = g_pEEInterface->FindLoadedElementType(data->elementType); + break; + } + if (th.IsNull()) + return CORDBG_E_CLASS_NOT_LOADED; + *pRes = th; + return S_OK; +} + +// Iterate through the type argument data, creating type handles as we go. +void Debugger::TypeDataWalk::ReadTypeHandles(unsigned int nTypeArgs, TypeHandle *ppResults) +{ + WRAPPER_NO_CONTRACT; + + for (unsigned int i = 0; i < nTypeArgs; i++) + ppResults[i] = ReadTypeHandle(); + } + +TypeHandle Debugger::TypeDataWalk::ReadInstantiation(Module *pModule, mdTypeDef tok, unsigned int nTypeArgs) +{ + WRAPPER_NO_CONTRACT; + + DWORD dwAllocSize; + if (!ClrSafeInt::multiply(nTypeArgs, sizeof(TypeHandle), dwAllocSize)) + { + ThrowHR(COR_E_OVERFLOW); + } + TypeHandle * inst = (TypeHandle *) _alloca(dwAllocSize); + ReadTypeHandles(nTypeArgs, inst) ; + TypeHandle th = g_pEEInterface->LoadInstantiation(pModule, tok, nTypeArgs, inst); + if (th.IsNull()) + COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg")); + return th; +} + +TypeHandle Debugger::TypeDataWalk::ReadTypeHandle() +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + } + CONTRACTL_END; + + DebuggerIPCE_TypeArgData * data = ReadOne(); + if (!data) + COMPlusThrow(kArgumentException, W("Argument_InvalidGenericArg")); + + LOG((LF_CORDB, LL_INFO10000, "D::ETITTH: expanding right-side type to left-side type, ELEMENT_TYPE: %d.\n", data->data.elementType)); + + TypeHandle th; + CorElementType et = data->data.elementType; + switch (et) + { + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + if(data->numTypeArgs == 1) + { + TypeHandle typar = ReadTypeHandle(); + switch (et) + { + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + th = g_pEEInterface->LoadArrayType(data->data.elementType, typar, data->data.ArrayTypeData.arrayRank); + break; + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + th = g_pEEInterface->LoadPointerOrByrefType(data->data.elementType, typar); + break; + default: + _ASSERTE(0); + } + } + break; + + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_VALUETYPE: + { + DebuggerModule *pDebuggerModule = g_pDebugger->LookupOrCreateModule(data->data.ClassTypeData.vmDomainFile); + th = ReadInstantiation(pDebuggerModule->GetRuntimeModule(), data->data.ClassTypeData.metadataToken, data->numTypeArgs); + break; + } + + case ELEMENT_TYPE_FNPTR: + { + SIZE_T cbAllocSize; + if ((!ClrSafeInt::multiply(data->numTypeArgs, sizeof(TypeHandle), cbAllocSize)) || + (cbAllocSize != (size_t)(cbAllocSize))) + { + _ASSERTE(COR_E_OVERFLOW); + cbAllocSize = UINT_MAX; + } + TypeHandle * inst = (TypeHandle *) _alloca(cbAllocSize); + ReadTypeHandles(data->numTypeArgs, inst) ; + th = g_pEEInterface->LoadFnptrType(inst, data->numTypeArgs); + break; + } + + default: + th = g_pEEInterface->LoadElementType(data->data.elementType); + break; + } + if (th.IsNull()) + COMPlusThrow(kArgumentNullException, W("ArgumentNull_Type")); + return th; + +} + +// +// GetAndSendTransitionStubInfo figures out if an address is a stub +// address and sends the result back to the right side. +// +void Debugger::GetAndSendTransitionStubInfo(CORDB_ADDRESS_TYPE *stubAddress) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::GASTSI: IsTransitionStub. Addr=0x%08x\n", stubAddress)); + + bool result = false; + + result = g_pEEInterface->IsStub((const BYTE *)stubAddress); + + + // If its not a stub, then maybe its an address in mscoree? + if (result == false) + { + result = (IsIPInModule(g_pMSCorEE, (PCODE)stubAddress) == TRUE); + } + + // This is a synchronous event (reply required) + DebuggerIPCEvent *event = m_pRCThread->GetIPCEventReceiveBuffer(); + InitIPCEvent(event, DB_IPCE_IS_TRANSITION_STUB_RESULT, NULL, NULL); + event->IsTransitionStubResult.isStub = result; + + // Send the result + m_pRCThread->SendIPCReply(); +} + +/* + * A generic request for a buffer in the left-side for use by the right-side + * + * This is a synchronous event (reply required). + */ +HRESULT Debugger::GetAndSendBuffer(DebuggerRCThread* rcThread, ULONG bufSize) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // This is a synchronous event (reply required) + DebuggerIPCEvent* event = rcThread->GetIPCEventReceiveBuffer(); + PREFIX_ASSUME(event != NULL); + InitIPCEvent(event, DB_IPCE_GET_BUFFER_RESULT, NULL, NULL); + + // Allocate the buffer + event->GetBufferResult.hr = AllocateRemoteBuffer( bufSize, &event->GetBufferResult.pBuffer ); + + // Send the result + return rcThread->SendIPCReply(); +} + +/* + * Allocate a buffer in the left-side for use by the right-side + */ +HRESULT Debugger::AllocateRemoteBuffer( ULONG bufSize, void **ppBuffer ) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // The call to Append below will call CUnorderedArray, which will call unsafe New. + HRESULT hr; + CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&hr, GetCanary()); + if( FAILED(hr) ) + { + return hr; + } + + // Actually allocate the buffer + BYTE* pBuffer = new (interopsafe, nothrow) BYTE[bufSize]; + + LOG((LF_CORDB, LL_EVERYTHING, "D::ARB: new'd 0x%x\n", *ppBuffer)); + + // Check for out of memory error + if (pBuffer == NULL) + { + return E_OUTOFMEMORY; + } + + // Track the allocation so we can free it later + void **ppNextBlob = GetMemBlobs()->Append(); + if( ppNextBlob == NULL ) + { + DeleteInteropSafe( pBuffer ); + return E_OUTOFMEMORY; + } + *ppNextBlob = pBuffer; + + // Return the allocated memory + *ppBuffer = pBuffer; + return S_OK; +} + +/* + * Used to release a previously-requested buffer + * + * This is a synchronous event (reply required). + */ +HRESULT Debugger::SendReleaseBuffer(DebuggerRCThread* rcThread, void *pBuffer) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO10000, "D::SRB for buffer 0x%x\n", pBuffer)); + + // This is a synchronous event (reply required) + DebuggerIPCEvent* event = rcThread->GetIPCEventReceiveBuffer(); + PREFIX_ASSUME(event != NULL); + InitIPCEvent(event, DB_IPCE_RELEASE_BUFFER_RESULT, NULL, NULL); + + _ASSERTE(pBuffer != NULL); + + // Free the memory + ReleaseRemoteBuffer(pBuffer, true); + + // Indicate success in reply + event->ReleaseBufferResult.hr = S_OK; + + // Send the result + return rcThread->SendIPCReply(); +} + + +// +// Used to delete the buffer previously-requested by the right side. +// We've factored the code since both the ~Debugger and SendReleaseBuffer +// methods do this. +// +HRESULT Debugger::ReleaseRemoteBuffer(void *pBuffer, bool removeFromBlobList) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_EVERYTHING, "D::RRB: Releasing RS-alloc'd buffer 0x%x\n", pBuffer)); + + // Remove the buffer from the blob list if necessary. + if (removeFromBlobList) + { + USHORT cBlobs = GetMemBlobs()->Count(); + void **rgpBlobs = GetMemBlobs()->Table(); + + USHORT i; + for (i = 0; i < cBlobs; i++) + { + if (rgpBlobs[i] == pBuffer) + { + GetMemBlobs()->DeleteByIndex(i); + break; + } + } + + // We should have found a match. All buffers passed to ReleaseRemoteBuffer + // should have been allocated with AllocateRemoteBuffer and not yet freed. + _ASSERTE( i < cBlobs ); + } + + // Delete the buffer. (Need cast for GCC template support) + DeleteInteropSafe( (BYTE*)pBuffer ); + + return S_OK; +} + +// +// UnrecoverableError causes the Left Side to enter a state where no more +// debugging can occur and we leave around enough information for the +// Right Side to tell what happened. +// +void Debugger::UnrecoverableError(HRESULT errorHR, + unsigned int errorCode, + const char *errorFile, + unsigned int errorLine, + bool exitThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10, + "Unrecoverable error: hr=0x%08x, code=%d, file=%s, line=%d\n", + errorHR, errorCode, errorFile, errorLine)); + + // + // Setting this will ensure that not much else happens... + // + m_unrecoverableError = TRUE; + + // + // Fill out the control block with the error. + // in-proc will find out when the function fails + // + DebuggerIPCControlBlock *pDCB = m_pRCThread->GetDCB(); + + PREFIX_ASSUME(pDCB != NULL); + + pDCB->m_errorHR = errorHR; + pDCB->m_errorCode = errorCode; + + // + // If we're told to, exit the thread. + // + if (exitThread) + { + LOG((LF_CORDB, LL_INFO10, + "Thread exiting due to unrecoverable error.\n")); + ExitThread(errorHR); + } +} + +// +// Callback for IsThreadAtSafePlace's stack walk. +// +StackWalkAction Debugger::AtSafePlaceStackWalkCallback(CrawlFrame *pCF, + VOID* data) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(CheckPointer(pCF)); + PRECONDITION(CheckPointer(data)); + } + CONTRACTL_END; + + bool *atSafePlace = (bool*)data; + LOG((LF_CORDB, LL_INFO100000, "D:AtSafePlaceStackWalkCallback\n")); + + if (pCF->IsFrameless() && pCF->IsActiveFunc()) + { + LOG((LF_CORDB, LL_INFO1000000, "D:AtSafePlaceStackWalkCallback, IsFrameLess() and IsActiveFunc()\n")); + if (g_pEEInterface->CrawlFrameIsGcSafe(pCF)) + { + LOG((LF_CORDB, LL_INFO1000000, "D:AtSafePlaceStackWalkCallback - TRUE: CrawlFrameIsGcSafe()\n")); + *atSafePlace = true; + } + } + return SWA_ABORT; +} + +// +// Determine, via a quick one frame stack walk, if a given thread is +// in a gc safe place. +// +bool Debugger::IsThreadAtSafePlaceWorker(Thread *thread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(CheckPointer(thread)); + } + CONTRACTL_END; + + bool atSafePlace = false; + + // Setup our register display. + REGDISPLAY rd; + CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread); + + _ASSERTE(!(g_pEEInterface->GetThreadFilterContext(thread) && ISREDIRECTEDTHREAD(thread))); + if (context != NULL) + { + g_pEEInterface->InitRegDisplay(thread, &rd, context, TRUE); + } + else + { + CONTEXT ctx; + ZeroMemory(&rd, sizeof(rd)); + ZeroMemory(&ctx, sizeof(ctx)); +#if defined(_TARGET_X86_) + rd.ControlPC = ctx.Eip; + rd.PCTAddr = (TADDR)&(ctx.Eip); +#else + FillRegDisplay(&rd, &ctx); +#endif + + if (ISREDIRECTEDTHREAD(thread)) + { + thread->GetFrame()->UpdateRegDisplay(&rd); + } + } + + // Do the walk. If it fails, we don't care, because we default + // atSafePlace to false. + g_pEEInterface->StackWalkFramesEx( + thread, + &rd, + Debugger::AtSafePlaceStackWalkCallback, + (VOID*)(&atSafePlace), + QUICKUNWIND | HANDLESKIPPEDFRAMES | + DISABLE_MISSING_FRAME_DETECTION); + +#ifdef LOGGING + if (!atSafePlace) + LOG((LF_CORDB | LF_GC, LL_INFO1000, + "Thread 0x%x is not at a safe place.\n", + GetThreadIdHelper(thread))); +#endif + + return atSafePlace; +} + +bool Debugger::IsThreadAtSafePlace(Thread *thread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(CheckPointer(thread)); + } + CONTRACTL_END; + + + if (m_fShutdownMode) + { + return true; + } + + // + // + // Make sure this fix is evaluated when doing real work for debugging SO handling. + // + // On the Stack Overflow code path calling IsThreadAtSafePlaceWorker as it is + // currently implemented is way too stack intensive. For now we cheat and just + // say that if a thread is in the middle of handling a SO it is NOT at a safe + // place. This is a reasonably safe assumption to make and hopefully shouldn't + // result in deadlocking the debugger. + if ( (thread->IsExceptionInProgress()) && + (g_pEEInterface->GetThreadException(thread) == CLRException::GetPreallocatedStackOverflowExceptionHandle()) ) + { + return false; + } + // + else + { + return IsThreadAtSafePlaceWorker(thread); + } +} + +//----------------------------------------------------------------------------- +// Get the complete user state flags. +// This will collect flags both from the EE and from the LS. +// This is the real implementation of the RS's ICorDebugThread::GetUserState(). +// +// Parameters: +// pThread - non-null thread to get state for. +// +// Returns: a CorDebugUserState flags enum describing state. +//----------------------------------------------------------------------------- +CorDebugUserState Debugger::GetFullUserState(Thread *pThread) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(pThread)); + } + CONTRACTL_END; + + CorDebugUserState state = g_pEEInterface->GetPartialUserState(pThread); + + bool fSafe = IsThreadAtSafePlace(pThread); + if (!fSafe) + { + state = (CorDebugUserState) (state | USER_UNSAFE_POINT); + } + + return state; +} + +/****************************************************************************** + * + * Helper for debugger to get an unique thread id + * If we are not in Fiber mode, we can safely use OSThreadId + * Otherwise, we will use our own unique ID. + * + * We will return our unique ID when our host is hosting Thread. + * + * + ******************************************************************************/ +DWORD Debugger::GetThreadIdHelper(Thread *pThread) +{ + WRAPPER_NO_CONTRACT; + + if (!CLRTaskHosted()) + { + // use the plain old OS Thread ID + return pThread->GetOSThreadId(); + } + else + { + // use our unique thread ID + return pThread->GetThreadId(); + } +} + +//----------------------------------------------------------------------------- +// Called by EnC during remapping to get information about the local vars. +// EnC will then use this to set values in the new version to their corresponding +// values from the old version. +// +// Returns a pointer to the debugger's copies of the maps. Caller +// does not own the memory provided via vars outparameter. +//----------------------------------------------------------------------------- +void Debugger::GetVarInfo(MethodDesc * fd, // [IN] method of interest + void *DebuggerVersionToken, // [IN] which edit version + SIZE_T * cVars, // [OUT] size of 'vars' + const ICorDebugInfo::NativeVarInfo **vars // [OUT] map telling where local vars are stored + ) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + } + CONTRACTL_END; + + DebuggerJitInfo * ji = (DebuggerJitInfo *)DebuggerVersionToken; + + // If we didn't supply a DJI, then we're asking for the most recent version. + if (ji == NULL) + { + ji = GetLatestJitInfoFromMethodDesc(fd); + } + _ASSERTE(fd == ji->m_fd); + + PREFIX_ASSUME(ji != NULL); + + *vars = ji->GetVarNativeInfo(); + *cVars = ji->GetVarNativeInfoCount(); +} + +#include "openum.h" + +#ifdef EnC_SUPPORTED + +//--------------------------------------------------------------------------------------- +// +// Apply an EnC edit to the CLR datastructures and send the result event to the +// debugger right-side. +// +// Arguments: +// pDebuggerModule - the module in which the edit should occur +// cbMetadata - the number of bytes in pMetadata +// pMetadata - pointer to the delta metadata +// cbIL - the number of bytes in pIL +// pIL - pointer to the delta IL +// +// Return Value: +// +// Assumptions: +// +// Notes: +// +// This is just the first half of processing an EnC request (hot swapping). This updates +// the metadata and other CLR data structures to reflect the edit, but does not directly +// affect code which is currently running. In order to achieve on-stack replacement +// (remap of running code), we mine all old methods with "EnC remap breakpoints" +// (instances of DebuggerEnCBreakpoint) at many sequence points. When one of those +// breakpoints is hit, we give the debugger a RemapOpportunity event and give it a +// chance to remap the execution to the new version of the method. +// + +HRESULT Debugger::ApplyChangesAndSendResult(DebuggerModule * pDebuggerModule, + DWORD cbMetadata, + BYTE *pMetadata, + DWORD cbIL, + BYTE *pIL) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // @todo - if EnC never works w/ interop, caller New on the helper thread may be ok. + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + HRESULT hr = S_OK; + + LOG((LF_ENC, LL_INFO100, "Debugger::ApplyChangesAndSendResult\n")); + + Module *pModule = pDebuggerModule->GetRuntimeModule(); + if (! pModule->IsEditAndContinueEnabled()) + { + hr = CORDBG_E_ENC_MODULE_NOT_ENC_ENABLED; + } + else + { + // Violation with the following call stack: + // CONTRACT in MethodTableBuilder::InitMethodDesc + // CONTRACT in EEClass::AddMethod + // CONTRACT in EditAndContinueModule::AddMethod + // CONTRACT in EditAndContinueModule::ApplyEditAndContinue + // CONTRACT in EEDbgInterfaceImpl::EnCApplyChanges + // VIOLATED--> CONTRACT in Debugger::ApplyChangesAndSendResult + CONTRACT_VIOLATION(GCViolation); + + // Tell the VM to apply the edit + hr = g_pEEInterface->EnCApplyChanges( + (EditAndContinueModule*)pModule, cbMetadata, pMetadata, cbIL, pIL); + } + + LOG((LF_ENC, LL_INFO100, "Debugger::ApplyChangesAndSendResult 2\n")); + + DebuggerIPCEvent* event = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(event, + DB_IPCE_APPLY_CHANGES_RESULT, + NULL, + NULL); + + event->ApplyChangesResult.hr = hr; + + // Send the result + return m_pRCThread->SendIPCEvent(); +} + +// +// This structure is used to hold a list of the sequence points in a function and +// determine which should have remap breakpoints applied to them for EnC +// +class EnCSequencePointHelper +{ +public: + // Calculates remap info given the supplied JitInfo + EnCSequencePointHelper(DebuggerJitInfo *pJitInfo); + ~EnCSequencePointHelper(); + + // Returns true if the specified sequence point (given by it's index in the + // sequence point table in the JitInfo) should get an EnC remap breakpoint. + BOOL ShouldSetRemapBreakpoint(unsigned int offsetIndex); + +private: + DebuggerJitInfo *m_pJitInfo; + + DebugOffsetToHandlerInfo *m_pOffsetToHandlerInfo; +}; + +// +// Goes through the list of sequence points for a function and determines whether or not each +// is a valid Remap Breakpoint location (not in a special offset, must be empty stack, and not in a handler. +// +EnCSequencePointHelper::EnCSequencePointHelper(DebuggerJitInfo *pJitInfo) + : m_pOffsetToHandlerInfo(NULL), + m_pJitInfo(pJitInfo) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (m_pJitInfo->GetSequenceMapCount() == 0) + { + return; + } + + // Construct a list of native offsets we may want to place EnC breakpoints at + m_pOffsetToHandlerInfo = new DebugOffsetToHandlerInfo[m_pJitInfo->GetSequenceMapCount()]; + for (unsigned int i = 0; i < m_pJitInfo->GetSequenceMapCount(); i++) + { + // By default this slot is unused. We want the indexes in m_pOffsetToHandlerInfo + // to correspond to the indexes of m_pJitInfo->GetSequenceMapCount, so we rely + // on a -1 offset to indicate that a DebuggerOffsetToHandlerInfo is unused. + // However, it would be cleaner and permit a simpler API to the EE if we just + // had an array mapping the offsets instead. + m_pOffsetToHandlerInfo[i].offset = (SIZE_T) -1; + m_pOffsetToHandlerInfo[i].isInFilterOrHandler = FALSE; + + SIZE_T offset = m_pJitInfo->GetSequenceMap()[i].nativeStartOffset; + + // Check if this is a "special" IL offset, such as representing the prolog or eppilog, + // or other region not directly mapped to native code. + if (DbgIsSpecialILOffset(pJitInfo->GetSequenceMap()[i].ilOffset)) + { + LOG((LF_ENC, LL_INFO10000, + "D::UF: not placing E&C breakpoint at special offset 0x%x (IL: 0x%x)\n", + offset, m_pJitInfo->GetSequenceMap()[i].ilOffset)); + continue; + } + + // Skip duplicate sequence points + if (i >=1 && offset == pJitInfo->GetSequenceMap()[i-1].nativeStartOffset) + { + LOG((LF_ENC, LL_INFO10000, + "D::UF: not placing redundant E&C " + "breakpoint at duplicate offset 0x%x (IL: 0x%x)\n", + offset, m_pJitInfo->GetSequenceMap()[i].ilOffset)); + continue; + } + + // Skip sequence points that aren't due to the evaluation stack being empty + // We can only remap at stack-empty points (since we don't have a mapping for + // contents of the evaluation stack). + if (!(pJitInfo->GetSequenceMap()[i].source & ICorDebugInfo::STACK_EMPTY)) + { + LOG((LF_ENC, LL_INFO10000, + "D::UF: not placing E&C breakpoint at offset " + "0x%x (IL: 0x%x) b/c not STACK_EMPTY:it's 0x%x\n", offset, + m_pJitInfo->GetSequenceMap()[i].ilOffset, pJitInfo->GetSequenceMap()[i].source)); + continue; + } + + // So far this sequence point looks good, so store it's native offset so we can get + // EH information about it from the EE. + LOG((LF_ENC, LL_INFO10000, + "D::UF: possibly placing E&C breakpoint at offset " + "0x%x (IL: 0x%x)\n", offset, m_pJitInfo->GetSequenceMap()[i].ilOffset)); + m_pOffsetToHandlerInfo[i].offset = m_pJitInfo->GetSequenceMap()[i].nativeStartOffset; + + } + + // Ask the EE to fill in the isInFilterOrHandler bit for the native offsets we're interested in + g_pEEInterface->DetermineIfOffsetsInFilterOrHandler( + (BYTE *)pJitInfo->m_addrOfCode, m_pOffsetToHandlerInfo, m_pJitInfo->GetSequenceMapCount()); +} + +EnCSequencePointHelper::~EnCSequencePointHelper() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (m_pOffsetToHandlerInfo) + { + delete m_pOffsetToHandlerInfo; + } +} + +// +// Returns if we should set a remap breakpoint at a given offset. We only set them at 0-depth stack +// and not when inside a handler, either finally, filter, or catch +// +BOOL EnCSequencePointHelper::ShouldSetRemapBreakpoint(unsigned int offsetIndex) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + CANNOT_TAKE_LOCK; + } + CONTRACTL_END; + + { + // GetSequenceMapCount calls LazyInitBounds() which can eventually + // call ExecutionManager::IncrementReader + CONTRACT_VIOLATION(TakesLockViolation); + _ASSERTE(offsetIndex <= m_pJitInfo->GetSequenceMapCount()); + } + + // If this slot is unused (offset -1), we excluded it early + if (m_pOffsetToHandlerInfo[offsetIndex].offset == (SIZE_T) -1) + { + return FALSE; + } + + // Otherwise, check the isInFilterOrHandler bit + if (m_pOffsetToHandlerInfo[offsetIndex].isInFilterOrHandler) + { + LOG((LF_ENC, LL_INFO10000, + "D::UF: not placing E&C breakpoint in filter/handler at offset 0x%x\n", + m_pOffsetToHandlerInfo[offsetIndex].offset)); + return FALSE; + } + + return TRUE; +} + + +//----------------------------------------------------------------------------- +// For each function that's EnC-ed, the EE will call either UpdateFunction +// (if the function already is loaded + jitted) or Addfunction +// +// This is called before the EE updates the MethodDesc, so pMD does not yet +// point to the version we'll be remapping to. +//----------------------------------------------------------------------------- +HRESULT Debugger::UpdateFunction(MethodDesc* pMD, SIZE_T encVersion) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + PRECONDITION(ThisIsHelperThread()); // guarantees we're serialized. + PRECONDITION(IsStopped()); + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::UF: updating " + "%s::%s to version %d\n", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, encVersion)); + + // tell the RS that this function has been updated so that it can create new CorDBFunction + Module *pModule = g_pEEInterface->MethodDescGetModule(pMD); + _ASSERTE(pModule != NULL); + mdToken methodDef = pMD->GetMemberDef(); + SendEnCUpdateEvent(DB_IPCE_ENC_UPDATE_FUNCTION, + pModule, + methodDef, + pMD->GetMethodTable()->GetCl(), + encVersion); + + DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pModule, methodDef); + if (dmi == NULL) + { + return E_OUTOFMEMORY; + } + + // The DMI always holds the most current EnC version number. We always JIT the most + // current version of the function, so when we do see a JitBegin we will create a new + // dji for it and stash the current version there. We don't want to change the current + // jit info because it has to maintain the version for the code it corresponds to. + dmi->SetCurrentEnCVersion(encVersion); + + // This is called before the MethodDesc is updated to point to the new function. + // So this call will get the most recent old function. + DebuggerJitInfo *pJitInfo = GetLatestJitInfoFromMethodDesc(pMD); + + if (pJitInfo == NULL ) + { + LOG((LF_CORDB,LL_INFO10000,"Unable to get DJI by recently " + "D::UF: JITted version number (it hasn't been jitted yet)," + "which is fine\n")); + return S_OK; + } + + // + // Mine the old version of the method with patches so that we can provide + // remap opportunities whenever the old version of the method is executed. + // + + if (pJitInfo->m_encBreakpointsApplied) + { + LOG((LF_CORDB,LL_INFO10000,"D::UF: Breakpoints already applied\n")); + return S_OK; + } + + LOG((LF_CORDB,LL_INFO10000,"D::UF: Applying breakpoints\n")); + + // We only place the patches if we have jit info for this + // function, i.e., its already been jitted. Otherwise, the EE will + // pickup the new method on the next JIT anyway. + + ICorDebugInfo::SourceTypes src; + + EnCSequencePointHelper sequencePointHelper(pJitInfo); + + // For each offset in the IL->Native map, set a new EnC breakpoint on the + // ones that we know could be remap points. + for (unsigned int i = 0; i < pJitInfo->GetSequenceMapCount(); i++) + { + // Skip if this isn't a valid remap point (eg. is in an exception handler) + if (! sequencePointHelper.ShouldSetRemapBreakpoint(i)) + { + continue; + } + + SIZE_T offset = pJitInfo->GetSequenceMap()[i].nativeStartOffset; + + LOG((LF_CORDB, LL_INFO10000, + "D::UF: placing E&C breakpoint at native offset 0x%x\n", + offset)); + + DebuggerEnCBreakpoint *bp; + + // Create and activate a new EnC remap breakpoint here in the old version of the method + bp = new (interopsafe) DebuggerEnCBreakpoint( offset, + pJitInfo, + DebuggerEnCBreakpoint::REMAP_PENDING, + (AppDomain *)pModule->GetDomain()); + + _ASSERTE(bp != NULL); + } + + pJitInfo->m_encBreakpointsApplied = true; + + return S_OK; +} + +// Called to update a function that hasn't yet been loaded (and so we don't have a MethodDesc). +// This may be updating an existing function on a type that hasn't been loaded +// or adding a new function to a type that hasn't been loaded. +// We need to notify the debugger so that it can properly track version info. +HRESULT Debugger::UpdateNotYetLoadedFunction(mdMethodDef token, Module * pModule, SIZE_T encVersion) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + + PRECONDITION(ThisIsHelperThread()); + PRECONDITION(ThreadHoldsLock()); // must have lock since we're on helper and stopped. + } + CONTRACTL_END; + + DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pModule, token); + if (! dmi) + { + return E_OUTOFMEMORY; + } + dmi->SetCurrentEnCVersion(encVersion); + + + // Must tell the RS that this function has been added so that it can create new CorDBFunction. + mdTypeDef classToken = 0; + + HRESULT hr = pModule->GetMDImport()->GetParentToken(token, &classToken); + if (FAILED(hr)) + { + // We never expect this to actually fail, but just in case it does for some other crazy reason, + // we'll return before we AV. + CONSISTENCY_CHECK_MSGF(false, ("Class lookup failed:mdToken:0x%08x, pModule=%p. hr=0x%08x\n", token, pModule, hr)); + return hr; + } + + SendEnCUpdateEvent(DB_IPCE_ENC_ADD_FUNCTION, pModule, token, classToken, encVersion); + + + return S_OK; +} + +// Called to add a new function when the type has been loaded already. +// This is effectively the same as above, except that we're given a +// MethodDesc instead of a module and token. +// This should probably be merged into a single method since the caller +// should always have a module and token available in both cases. +HRESULT Debugger::AddFunction(MethodDesc* pMD, SIZE_T encVersion) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + + PRECONDITION(ThisIsHelperThread()); + PRECONDITION(ThreadHoldsLock()); // must have lock since we're on helper and stopped. + } + CONTRACTL_END; + + DebuggerDataLockHolder debuggerDataLockHolder(this); + + LOG((LF_CORDB, LL_INFO10000, "D::AF: adding " + "%s::%s to version %d\n", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, encVersion)); + + _ASSERTE(pMD != NULL); + Module *pModule = g_pEEInterface->MethodDescGetModule(pMD); + _ASSERTE(pModule != NULL); + mdToken methodDef = pMD->GetMemberDef(); + + // tell the RS that this function has been added so that it can create new CorDBFunction + SendEnCUpdateEvent( DB_IPCE_ENC_ADD_FUNCTION, + pModule, + methodDef, + pMD->GetMethodTable()->GetCl(), + encVersion); + + DebuggerMethodInfo *dmi = CreateMethodInfo(pModule, methodDef); + if (! dmi) + { + return E_OUTOFMEMORY; + } + dmi->SetCurrentEnCVersion(encVersion); + + return S_OK; +} + +// Invoke when a field is added to a class using EnC +HRESULT Debugger::AddField(FieldDesc* pFD, SIZE_T encVersion) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::AFld: adding " + "%8.8d::%8.8d to version %d\n", pFD->GetApproxEnclosingMethodTable()->GetCl(), pFD->GetMemberDef(), encVersion)); + + // tell the RS that this field has been added so that it can update it's structures + SendEnCUpdateEvent( DB_IPCE_ENC_ADD_FIELD, + pFD->GetModule(), + pFD->GetMemberDef(), + pFD->GetApproxEnclosingMethodTable()->GetCl(), + encVersion); + + return S_OK; +} + +// +// RemapComplete is called when we are just about to resume into +// the function so that we can setup our breakpoint to trigger +// a call to the RemapComplete callback once the function is actually +// on the stack. We need to wait until the function is jitted before +// we can add the trigger, which doesn't happen until we call +// ResumeInUpdatedFunction in the VM +// +// addr is address within the given function, which we use to determine +// exact EnC version. +// +HRESULT Debugger::RemapComplete(MethodDesc* pMD, TADDR addr, SIZE_T nativeOffset) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + } + CONTRACTL_END; + + _ASSERTE(pMD != NULL); + _ASSERTE(addr != NULL); + + LOG((LF_CORDB, LL_INFO10000, "D::RC: installed remap complete patch for " + "%s::%s to version %d\n", pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName)); + + DebuggerMethodInfo *dmi = GetOrCreateMethodInfo(pMD->GetModule(), pMD->GetMemberDef()); + + if (dmi == NULL) + { + return E_OUTOFMEMORY; + } + + DebuggerJitInfo *pJitInfo = GetJitInfo(pMD, (const BYTE *) addr); + + if (pJitInfo == NULL) + { + _ASSERTE(!"Debugger doesn't handle OOM"); + return E_OUTOFMEMORY; + } + _ASSERTE(pJitInfo->m_addrOfCode + nativeOffset == addr); + + DebuggerEnCBreakpoint *bp; + + // Create and activate a new REMAP_COMPLETE EnC breakpoint to let us know when + // the EE has completed the remap process. + // This will be deleted when the patch is hit. + bp = new (interopsafe, nothrow) DebuggerEnCBreakpoint( nativeOffset, + pJitInfo, + DebuggerEnCBreakpoint::REMAP_COMPLETE, + (AppDomain *)pMD->GetModule()->GetDomain()); + if (bp == NULL) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +//----------------------------------------------------------------------------- +// Called by EnC stuff to map an IL offset to a native offset for the given +// method described by (pMD, nativeFnxStart). +// +// pMD - methoddesc for method being remapped +// ilOffset - incoming offset in old method to remap. +// nativeFnxStart - address of new function. This can be used to find the DJI +// for the new method. +// nativeOffset - outparameter for native linear offset relative to start address. +//----------------------------------------------------------------------------- + +HRESULT Debugger::MapILInfoToCurrentNative(MethodDesc *pMD, + SIZE_T ilOffset, + TADDR nativeFnxStart, + SIZE_T *nativeOffset) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + PRECONDITION(nativeOffset != NULL); + PRECONDITION(CheckPointer(pMD)); + PRECONDITION(nativeFnxStart != NULL); + } + CONTRACTL_END; + + _ASSERTE(HasLazyData()); // only used for EnC, should have already inited. + + + LOG((LF_CORDB, LL_INFO1000000, "D::MILITCN: %s::%s ilOff:0x%x, " + ", natFnx:0x%x dji:0x%x\n", pMD->m_pszDebugClassName, + pMD->m_pszDebugMethodName, ilOffset, nativeFnxStart)); + + *nativeOffset = 0; + DebuggerJitInfo *djiTo = GetJitInfo( pMD, (const BYTE *)nativeFnxStart); + if (djiTo == NULL) + { + _ASSERTE(!"No DJI in EnC case: should only happen on oom. Debugger doesn't support OOM."); + return E_FAIL; + } + + DebuggerJitInfo::ILToNativeOffsetIterator it; + djiTo->InitILToNativeOffsetIterator(it, ilOffset); + *nativeOffset = it.CurrentAssertOnlyOne(NULL); + return S_OK; +} + +#endif // EnC_SUPPORTED + +//--------------------------------------------------------------------------------------- +// Hijack worker stub called from asm stub. This can then delegate to other hijacks. +// +// Arguments: +// pContext - context from which we were hijacked. Always non-null. +// pRecord - exception record if hijacked from an exception event. +// Else null (if hijacked from a managed IP). +// reason - hijack reason. Use this to delegate to the proper hijack stub. +// pData - arbitrary data for the hijack to use. (eg, such as a DebuggerEval object) +// +// Returns: +// This does not return. Instead it restores this threads context to pContext. +// +// Assumptions: +// If hijacked at an exception event, the debugger must have cleared the exception. +// +// Notes: +// The debugger hijacked the thread to get us here via the DacDbi Hijack primitive. +// This is called from a hand coded asm stub. +// +void STDCALL ExceptionHijackWorker( + CONTEXT * pContext, + EXCEPTION_RECORD * pRecord, + EHijackReason::EHijackReason reason, + void * pData) +{ + STRESS_LOG0(LF_CORDB,LL_INFO100, "D::EHW: Enter ExceptionHijackWorker\n"); + + // We could have many different reasons for hijacking. Switch and invoke the proper hijacker. + switch(reason) + { + case EHijackReason::kUnhandledException: + STRESS_LOG0(LF_CORDB,LL_INFO10, "D::EHW: Calling g_pDebugger->UnhandledHijackWorker()\n"); + _ASSERTE(pData == NULL); + g_pDebugger->UnhandledHijackWorker(pContext, pRecord); + break; +#ifdef FEATURE_INTEROP_DEBUGGING + case EHijackReason::kM2UHandoff: + _ASSERTE(pData == NULL); + g_pDebugger->M2UHandoffHijackWorker(pContext, pRecord); + break; + case EHijackReason::kFirstChanceSuspend: + _ASSERTE(pData == NULL); + g_pDebugger->FirstChanceSuspendHijackWorker(pContext, pRecord); + break; + case EHijackReason::kGenericHijack: + _ASSERTE(pData == NULL); + g_pDebugger->GenericHijackFunc(); + break; +#endif + default: + CONSISTENCY_CHECK_MSGF(false, ("Unrecognized Hijack code: %d", reason)); + } + + // Currently, no Hijack actually returns yet. + UNREACHABLE(); + + // If we return to this point, then we'll restore ourselves. + // We've got the context that we were hijacked from, so we should be able to just + // call SetThreadContext on ourself to fix us. +} + +#if defined(WIN64EXCEPTIONS) && !defined(FEATURE_PAL) + +#if defined(_TARGET_AMD64_) +// ---------------------------------------------------------------------------- +// EmptyPersonalityRoutine +// +// Description: +// This personality routine is used to work around a limitation of the OS unwinder when we return +// ExceptionCollidedUnwind. +// See code:ExceptionHijackPersonalityRoutine for more information. +// +// Arguments: +// * pExceptionRecord - not used +// * MemoryStackFp - not used +// * BackingStoreFp - not used +// * pContextRecord - not used +// * pDispatcherContext - not used +// * GlobalPointer - not used +// +// Return Value: +// Always return ExceptionContinueSearch. +// + +EXCEPTION_DISPOSITION EmptyPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord, + IN ULONG64 MemoryStackFp, + IN OUT PCONTEXT pContextRecord, + IN OUT PDISPATCHER_CONTEXT pDispatcherContext) +{ + LIMITED_METHOD_CONTRACT; + return ExceptionContinueSearch; +} +#endif // _TARGET_AMD64_ + +//--------------------------------------------------------------------------------------- +// Personality routine for unwinder the assembly hijack stub on 64-bit. +// +// Arguments: +// standard Personality routine signature. +// +// Assumptions: +// This is caleld by the OS exception logic during exception handling. +// +// Notes: +// We just need 1 personality routine for the tiny assembly hijack stub. +// All the C++ code invoked by the stub is ok. +// +// This needs to fetch the original context that this thread was hijacked from +// (which the hijack pushed onto the stack) and pass that back to the OS. This lets +// ths OS unwind out of the hijack. +// +// This function should only be executed if an unhandled exception is intercepted by a managed debugger. +// Otherwise there should never be a 2nd pass exception dispatch crossing the hijack stub. +// +// The basic idea here is straightforward. The OS does an exception dispatch and hit our hijack stub. +// Since the hijack stub is not unwindable, we need a personality routine to restore the CONTEXT and +// tell the OS to continue the dispatch with that CONTEXT by returning ExceptionCollidedUnwind. +// +// However, empricially, the OS expects that when we return ExceptionCollidedUnwind, the function +// represented by the CONTEXT has a personality routine. The OS will actually AV if we return a NULL +// personality routine. +// +// On AMD64, we work around this by using an empty personality routine. + +EXTERN_C EXCEPTION_DISPOSITION +ExceptionHijackPersonalityRoutine(IN PEXCEPTION_RECORD pExceptionRecord + WIN64_ARG(IN ULONG64 MemoryStackFp) + NOT_WIN64_ARG(IN ULONG32 MemoryStackFp), + IN OUT PCONTEXT pContextRecord, + IN OUT PDISPATCHER_CONTEXT pDispatcherContext + ) +{ +#if defined(_TARGET_AMD64_) + CONTEXT * pHijackContext = NULL; + + // Get the 1st parameter (the Context) from hijack worker. + // EstablisherFrame points to the stack slot 8 bytes above the + // return address to the ExceptionHijack. This would contain the + // parameters passed to ExceptionHijackWorker, which is marked + // STDCALL, but the x64 calling convention lets the + // ExceptionHijackWorker use that stack space, resulting in the + // context being overwritten. Instead, we get the context from the + // previous stack frame, which contains the arguments to + // ExceptionHijack, placed there by the debugger in + // DacDbiInterfaceImpl::Hijack. This works because ExceptionHijack + // allocates exactly 4 stack slots. + pHijackContext = *reinterpret_cast(pDispatcherContext->EstablisherFrame + 0x20); + + // This copies pHijackContext into pDispatcherContext, which the OS can then + // use to walk the stack. + FixupDispatcherContext(pDispatcherContext, pHijackContext, pContextRecord, (PEXCEPTION_ROUTINE)EmptyPersonalityRoutine); +#else + _ASSERTE(!"NYI - ExceptionHijackPersonalityRoutine()"); +#endif + + // Returning ExceptionCollidedUnwind will cause the OS to take our new context record and + // dispatcher context and restart the exception dispatching on this call frame, which is + // exactly the behavior we want. + return ExceptionCollidedUnwind; +} +#endif // WIN64EXCEPTIONS && !FEATURE_PAL + + +// UEF Prototype from excep.cpp +LONG InternalUnhandledExceptionFilter_Worker(EXCEPTION_POINTERS *pExceptionInfo); + +//--------------------------------------------------------------------------------------- +// Hijack for a 2nd-chance exception. Will invoke the CLR's UEF. +// +// Arguments: +// pContext - context that this thread was hijacked from. +// pRecord - exception record of the exception that this was hijacked at. +// pData - random data. +// Notes: +// When under a native-debugger, the OS does not invoking the Unhandled Exception Filter (UEF). +// It dispatches a 2nd-chance Exception event instead. +// However, the CLR's UEF does lots of useful work (like dispatching the 2nd-chance managed exception, +// allowing func-eval on 2nd-chance, and allowing intercepting unhandled exceptions). +// So we'll emulate the OS behavior here by invoking the CLR's UEF directly. +// +void Debugger::UnhandledHijackWorker(CONTEXT * pContext, EXCEPTION_RECORD * pRecord) +{ + CONTRACTL + { + // The ultimate protection shield is that this hijack can be executed under the same circumstances + // as a top-level UEF that pinvokes into managed code + // - That means we're GC-triggers safe + // - that means that we can crawl the stack. (1st-pass EH logic ensures this). + // We need to be GC-triggers because this may invoke a func-eval. + GC_TRIGGERS; + + // Don't throw out of a hijack! There's nobody left to catch this. + NOTHROW; + + // We expect to always be in preemptive here by the time we get this unhandled notification. + // We know this is true because a native UEF is preemptive. + // More detail: + // 1) If we got here from a software exception (eg, Throw from C#), then the jit helper + // toggled us to preemptive before calling RaiseException(). + // 2) If we got here from a hardware exception in managed code, then the 1st-pass already did + // some magic to get us into preemptive. On x86, this is magic. On 64-bit, it did some magic + // to push a Faulting-Exception-Frame and rethrow the exception as a software exception. + MODE_PREEMPTIVE; + + + PRECONDITION(CheckPointer(pContext)); + PRECONDITION(CheckPointer(pRecord)); + } + CONTRACTL_END; + + EXCEPTION_POINTERS exceptionInfo; + exceptionInfo.ContextRecord = pContext; + exceptionInfo.ExceptionRecord = pRecord; + + // Snag the Runtime thread. Since we're hijacking a managed exception, we should always have one. + Thread * pThread = g_pEEInterface->GetThread(); + (void)pThread; //prevent "unused variable" error from GCC + _ASSERTE(pThread != NULL); + + BOOL fSOException = FALSE; + + if ((pRecord != NULL) && + (pRecord->ExceptionCode == STATUS_STACK_OVERFLOW)) + { + fSOException = TRUE; + } + + // because we hijack here during jit attach invoked by the OS we need to make sure that the debugger is completely + // attached before continuing. If we ever hijacked here when an attach was not in progress this function returns + // immediately so no problems there. + WaitForDebuggerAttach(); + PostJitAttach(); + + // On Win7 WatsonLastChance returns CONTINUE_SEARCH for unhandled exceptions execpt stack overflow, and + // lets OS launch debuggers for us. Before the unhandled exception reaches the OS, CLR UEF has already + // processed this unhandled exception. Thus, we should not call into CLR UEF again if it is the case. + if (RunningOnWin7() && + pThread && + (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException) || + pThread->HasThreadStateNC(Thread::TSNC_AppDomainContainUnhandled) || + fSOException)) + { + + FrameWithCookie fef; +#if defined(WIN64EXCEPTIONS) + *((&fef)->GetGSCookiePtr()) = GetProcessGSCookie(); +#endif // WIN64EXCEPTIONS + if ((pContext != NULL) && fSOException) + { + GCX_COOP(); // Must be cooperative to modify frame chain. + + // EEPolicy::HandleFatalStackOverflow pushes a FaultingExceptionFrame on the stack after SO + // exception. Our hijack code runs in the exception context, and overwrites the stack space + // after SO excpetion, so this frame was popped out before invoking RaiseFailFast. We need to + // put it back here for running func-eval code. + // This cumbersome code should be removed once SO synchronization is moved to be completely + // out-of-process. + fef.InitAndLink(pContext); + } + + STRESS_LOG0(LF_CORDB, LL_INFO10, "D::EHW: Calling NotifyDebuggerLastChance\n"); + NotifyDebuggerLastChance(pThread, &exceptionInfo, TRUE); + + // Continuing from a second chance managed exception causes the process to exit. + TerminateProcess(GetCurrentProcess(), 0); + } + + // Since this is a unhandled managed exception: + // - we always have a Thread* object. + // - we always have a throwable + // - we executed through the 1st-pass of the EH logic. This means the 1st-pass could do work + // to enforce certain invariants (like the ones listed here, or ensuring the thread can be crawled) + + // Need to call the CLR's UEF. This will do all the key work including: + // - send the managed 2nd-chance exception event. + // - deal with synchronization. + // - allow func-evals. + // - deal with interception. + + // If intercepted, then this never returns. It will manually invoke the unwinders and fix the context. + + // InternalUnhandledExceptionFilter_Worker has a throws contract, but should not be throwing in any + // conditions we care about. This hijack should never throw, so catch everything. + HRESULT hrIgnore; + EX_TRY + { + InternalUnhandledExceptionFilter_Worker(&exceptionInfo); + } + EX_CATCH_HRESULT(hrIgnore); + + // Continuing from a second chance managed exception causes the process to exit. + TerminateProcess(GetCurrentProcess(), 0); +} + +#ifdef FEATURE_INTEROP_DEBUGGING +// +// This is the handler function that is put in place of a thread's top-most SEH handler function when it is hijacked by +// the Right Side during an unmanaged first chance exception. +// +typedef EXCEPTION_DISPOSITION (__cdecl *SEHHandler)(EXCEPTION_RECORD *pExceptionRecord, + EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame, + CONTEXT *pContext, + void *DispatcherContext); +#define DOSPEW 0 + +#if DOSPEW +#define SPEW(s) s +#else +#define SPEW(s) +#endif + + + + +//----------------------------------------------------------------------------- +// Hijack when we have a M2U handoff. +// This happens when we do a step-out from Managed-->Unmanaged, and so we hit a managed patch in Native code. +// This also happens when a managed stepper does a step-in to unmanaged code. +// Since we're in native code, there's no CPFH, and so we have to hijack. +// @todo- could this be removed? Step-out to native is illegal in v2.0, and do existing +// CLR filters catch the step-in patch? +// @dbgtodo controller/stepping - this will be completely unneeded in V3 when all stepping is oop +//----------------------------------------------------------------------------- +VOID Debugger::M2UHandoffHijackWorker(CONTEXT *pContext, + EXCEPTION_RECORD *pExceptionRecord) +{ + // We must use a static contract here because the function does not return normally + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_TRIGGERS; // from sending managed event + STATIC_CONTRACT_MODE_PREEMPTIVE; // we're in umanaged code. + SO_NOT_MAINLINE_FUNCTION; + + + LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Context=0x%p exception record=0x%p\n", + pContext, pExceptionRecord)); + + // We should only be here for a BP + _ASSERTE(pExceptionRecord->ExceptionCode == STATUS_BREAKPOINT); + + // Get the current runtime thread. This is only an optimized TLS access. + // Since we're coming off a managed-step, we should always have a thread. + Thread *pEEThread = g_pEEInterface->GetThread(); + _ASSERTE(pEEThread != NULL); + + _ASSERTE(!pEEThread->GetInteropDebuggingHijacked()); + pEEThread->SetInteropDebuggingHijacked(TRUE); + + //win32 has a weird property where EIP points after the BP in the debug event + //so we are adjusting it to point at the BP + CORDbgAdjustPCForBreakInstruction((DT_CONTEXT*)pContext); + LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Context ip set to 0x%p\n", GetIP(pContext))); + + _ASSERTE(!ISREDIRECTEDTHREAD(pEEThread)); + + // Don't bother setting FilterContext here because we already pass it to FirstChanceNativeException. + // Shortcut right to our dispatch native exception logic, there may be no COMPlusFrameHandler in place! + EX_TRY + { + LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Calling FirstChanceNativeException\n")); + bool okay; + okay = g_pDebugger->FirstChanceNativeException(pExceptionRecord, + pContext, + pExceptionRecord->ExceptionCode, + pEEThread); + _ASSERTE(okay == true); + LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: FirstChanceNativeException returned\n")); + } + EX_CATCH + { + // It would be really bad if somebody threw here. We're actually outside of managed code, + // so there's not a lot we can do besides just swallow the exception and hope for the best. + LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: ERROR! FirstChanceNativeException threw an exception\n")); + } + EX_END_CATCH(SwallowAllExceptions); + + _ASSERTE(!ISREDIRECTEDTHREAD(pEEThread)); + _ASSERTE(pEEThread->GetInteropDebuggingHijacked()); + pEEThread->SetInteropDebuggingHijacked(FALSE); + + // This signal will be received by the RS and it will use SetThreadContext + // to clear away the entire hijack frame. This function does not return. + LOG((LF_CORDB, LL_INFO1000, "D::M2UHHW: Flaring hijack complete\n")); + SignalHijackComplete(); + + _ASSERTE(!"UNREACHABLE"); +} + +//----------------------------------------------------------------------------- +// This hijack is run after receiving an IB event that we don't know how the +// debugger will want to continue. Under the covers we clear the event and divert +// execution here where we block until the debugger decides whether or not to clear +// the event. At that point we exit this hijack and the LS diverts execution back +// to the offending instruction. +// We don't know: +// - whether we have an EE-thread? +// - how we're going to continue this (handled / not-handled). +// +// But we do know that: +// - this exception does not belong to the CLR. +// - this thread is not in cooperative mode. +//----------------------------------------------------------------------------- +LONG Debugger::FirstChanceSuspendHijackWorker(CONTEXT *pContext, + EXCEPTION_RECORD *pExceptionRecord) +{ + // if we aren't set up to do interop debugging this function should just bail out + if(m_pRCThread == NULL) + return EXCEPTION_CONTINUE_SEARCH; + + DebuggerIPCControlBlock *pDCB = m_pRCThread->GetDCB(); + if(pDCB == NULL) + return EXCEPTION_CONTINUE_SEARCH; + + if (!pDCB->m_rightSideIsWin32Debugger) + return EXCEPTION_CONTINUE_SEARCH; + + // at this point we know that there is an interop debugger attached. This makes it safe to send + // flares +#if DOSPEW + DWORD tid = GetCurrentThreadId(); +#endif + + SPEW(fprintf(stderr, "0x%x D::FCHF: in first chance hijack filter.\n", tid)); + SPEW(fprintf(stderr, "0x%x D::FCHF: pExceptionRecord=0x%p (%d), pContext=0x%p (%d)\n", tid, pExceptionRecord, sizeof(EXCEPTION_RECORD), + pContext, sizeof(CONTEXT))); +#if defined(_TARGET_AMD64_) + SPEW(fprintf(stderr, "0x%x D::FCHF: code=0x%08x, addr=0x%p, Rip=0x%p, Rsp=0x%p, EFlags=0x%08x\n", + tid, pExceptionRecord->ExceptionCode, pExceptionRecord->ExceptionAddress, pContext->Rip, pContext->Rsp, + pContext->EFlags)); +#elif defined(_TARGET_X86_) + SPEW(fprintf(stderr, "0x%x D::FCHF: code=0x%08x, addr=0x%08x, Eip=0x%08x, Esp=0x%08x, EFlags=0x%08x\n", + tid, pExceptionRecord->ExceptionCode, pExceptionRecord->ExceptionAddress, pContext->Eip, pContext->Esp, + pContext->EFlags)); + +#endif + + + // This memory is used as IPC during the hijack. We will place a pointer to this in + // either the EEThreadPtr or the EEDebuggerWord and then the RS can write info into + // the memory + DebuggerIPCFirstChanceData fcd; + // accessing through the volatile pointer to fend off some potential compiler optimizations. + // If the debugger changes that data from OOP we need to see those updates + volatile DebuggerIPCFirstChanceData* pFcd = &fcd; + + + { + // Hijack filters are always in the can't stop range. + // The RS knows this b/c it knows which threads it hijacked. + // Bump up the CS counter so that any further calls in the LS can see this too. + // (This makes places where we assert that we're in a CS region happy). + CantStopHolder hCantStop; + + // Get the current runtime thread. This is only an optimized TLS access. + Thread *pEEThread = g_pEEInterface->GetThread(); + + // Is that really a ptr to a Thread? If the low bit is set or it its NULL then we don't have an EE Thread. If we + // have a EE Thread, then we know the original handler now. If not, we have to wait for the Right Side to fixup our + // handler chain once we've notified it that the exception does not belong to the runtime. Note: if we don't have an + // EE thread, then the exception never belongs to the Runtime. + bool hasEEThread = false; + if ((pEEThread != NULL) && !(((UINT_PTR)pEEThread) & 0x01)) + { + SPEW(fprintf(stderr, "0x%x D::FCHF: Has EE thread.\n", tid)); + hasEEThread = true; + } + + // Hook up the memory so RS can get to it + fcd.pLeftSideContext.Set((DT_CONTEXT*)pContext); + fcd.action = HIJACK_ACTION_EXIT_UNHANDLED; + fcd.debugCounter = 0; + if(hasEEThread) + { + SPEW(fprintf(stderr, "0x%x D::FCHF: Set Debugger word to 0x%p.\n", tid, pFcd)); + g_pEEInterface->SetThreadDebuggerWord(pEEThread, (VOID*) pFcd); + } + else + { + // this shouldn't be re-entrant + _ASSERTE(pEEThread == NULL); + + SPEW(fprintf(stderr, "0x%x D::FCHF: EEThreadPtr word to 0x%p.\n", tid, (BYTE*)pFcd + 1)); + g_pEEInterface->SetEEThreadPtr((void*) ((BYTE*)pFcd + 1)); + } + + // Signal the RS to tell us what to do + SPEW(fprintf(stderr, "0x%x D::FCHF: Signaling hijack started.\n", tid)); + SignalHijackStarted(); + SPEW(fprintf(stderr, "0x%x D::FCHF: Signaling hijack started complete. DebugCounter=0x%x\n", tid, pFcd->debugCounter)); + + if(pFcd->action == HIJACK_ACTION_WAIT) + { + // This exception does NOT belong to the CLR. + // If we belong to the CLR, then we either: + // - were a M2U transition, in which case we should be in a different Hijack + // - were a CLR exception in CLR code, in which case we should have continued and let the inproc handlers get it. + SPEW(fprintf(stderr, "0x%x D::FCHF: exception does not belong to the Runtime, hasEEThread=%d, pContext=0x%p\n", + tid, hasEEThread, pContext)); + + if(hasEEThread) + { + _ASSERTE(!pEEThread->GetInteropDebuggingHijacked()); // hijack is not re-entrant. + pEEThread->SetInteropDebuggingHijacked(TRUE); + + // Setting the FilterContext must be done in cooperative mode (since it's like pushing a Frame onto the Frame chain). + // Thus we have a violation. We don't really need the filter context specifically here, we're just using + // it for legacy purposes as a way to stash the context of the original exception (that this thread was hijacked from). + // @todo - use another way to store the context indepedent of the Filter context. + CONTRACT_VIOLATION(ModeViolation); + _ASSERTE(g_pEEInterface->GetThreadFilterContext(pEEThread) == NULL); + g_pEEInterface->SetThreadFilterContext(pEEThread, pContext); + } + + // Wait for the continue. We may / may not have an EE Thread for this, (and we're definitely + // not doing fiber-mode debugging), so just use a raw win32 API, and not some fancy fiber-safe call. + SPEW(fprintf(stderr, "0x%x D::FCHF: waiting for continue.\n", tid)); + + DWORD ret = WaitForSingleObject(g_pDebugger->m_pRCThread->GetDCB()->m_leftSideUnmanagedWaitEvent, + INFINITE); + + SPEW(fprintf(stderr, "0x%x D::FCHF: waiting for continue complete.\n", tid)); + if (ret != WAIT_OBJECT_0) + { + SPEW(fprintf(stderr, "0x%x D::FCHF: wait failed!\n", tid)); + } + + if(hasEEThread) + { + _ASSERTE(pEEThread->GetInteropDebuggingHijacked()); + pEEThread->SetInteropDebuggingHijacked(FALSE); + _ASSERTE(!ISREDIRECTEDTHREAD(pEEThread)); + + // See violation above. + CONTRACT_VIOLATION(ModeViolation); + g_pEEInterface->SetThreadFilterContext(pEEThread, NULL); + _ASSERTE(g_pEEInterface->GetThreadFilterContext(pEEThread) == NULL); + } + } + + SPEW(fprintf(stderr, "0x%x D::FCHF: signaling HijackComplete.\n", tid)); + SignalHijackComplete(); + SPEW(fprintf(stderr, "0x%x D::FCHF: done signaling HijackComplete. DebugCounter=0x%x\n", tid, pFcd->debugCounter)); + + // we should know what we are about to do now + _ASSERTE(pFcd->action != HIJACK_ACTION_WAIT); + + // cleanup from above + if (hasEEThread) + { + SPEW(fprintf(stderr, "0x%x D::FCHF: set debugger word = NULL.\n", tid)); + g_pEEInterface->SetThreadDebuggerWord(pEEThread, (VOID*) NULL); + } + else + { + SPEW(fprintf(stderr, "0x%x D::FCHF: set EEThreadPtr = NULL.\n", tid)); + g_pEEInterface->SetEEThreadPtr(NULL); + } + + } // end can't stop region + + if(pFcd->action == HIJACK_ACTION_EXIT_HANDLED) + { + SPEW(fprintf(stderr, "0x%x D::FCHF: exiting with CONTINUE_EXECUTION\n", tid)); + return EXCEPTION_CONTINUE_EXECUTION; + } + else + { + SPEW(fprintf(stderr, "0x%x D::FCHF: exiting with CONTINUE_SEARCH\n", tid)); + _ASSERTE(pFcd->action == HIJACK_ACTION_EXIT_UNHANDLED); + return EXCEPTION_CONTINUE_SEARCH; + } +} + +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) +void GenericHijackFuncHelper() +{ +#if DOSPEW + DWORD tid = GetCurrentThreadId(); +#endif + // Hijack filters are always in the can't stop range. + // The RS knows this b/c it knows which threads it hijacked. + // Bump up the CS counter so that any further calls in the LS can see this too. + // (This makes places where we assert that we're in a CS region happy). + CantStopHolder hCantStop; + + SPEW(fprintf(stderr, "0x%x D::GHF: in generic hijack.\n", tid)); + + // There is no need to setup any context pointer or interact with the Right Side in anyway. We simply wait for + // the continue event to be set. + SPEW(fprintf(stderr, "0x%x D::GHF: waiting for continue.\n", tid)); + + // If this thread has an EE thread and that EE thread has preemptive gc disabled, then mark that there is a + // thread at an unsafe place and enable pgc. This will allow us to sync even with this thread hijacked. + bool disabled = false; + + Thread *pEEThread = g_pEEInterface->GetThread(); + + if ((pEEThread != NULL) && !(((UINT_PTR)pEEThread) & 0x01)) + { + disabled = g_pEEInterface->IsPreemptiveGCDisabled(); + _ASSERTE(!disabled); + + _ASSERTE(!pEEThread->GetInteropDebuggingHijacked()); + pEEThread->SetInteropDebuggingHijacked(TRUE); + } + + DWORD ret = WaitForSingleObject(g_pRCThread->GetDCB()->m_leftSideUnmanagedWaitEvent, + INFINITE); + + if (ret != WAIT_OBJECT_0) + { + SPEW(fprintf(stderr, "0x%x D::GHF: wait failed!\n", tid)); + } + + // Get the continue type. Non-zero means that the exception was not cleared by the Right Side and therefore has + // not been handled. Zero means that the exception has been cleared. (Presumably, the debugger altered the + // thread's context before clearing the exception, so continuing will give a different result.) + DWORD continueType = 0; + + pEEThread = g_pEEInterface->GetThread(); + + if (((UINT_PTR)pEEThread) & 0x01) + { + // There is no EE Thread for this thread, so we null out the TLS word so we don't confuse the Runtime. + continueType = 1; + g_pEEInterface->SetEEThreadPtr(NULL); + pEEThread = NULL; + } + else if (pEEThread) + { + // We've got a Thread ptr, so get the continue type out of the thread's debugger word. + continueType = (DWORD) g_pEEInterface->GetThreadDebuggerWord(pEEThread); + + _ASSERTE(pEEThread->GetInteropDebuggingHijacked()); + pEEThread->SetInteropDebuggingHijacked(FALSE); + } + + SPEW(fprintf(stderr, "0x%x D::GHF: continued with %d.\n", tid, continueType)); + + if (continueType) + { + SPEW(fprintf(stderr, "0x%x D::GHF: calling ExitProcess\n", tid)); + + // Continuing from a second chance exception without clearing the exception causes the process to + // exit. Note: the continue type will only be non-zero if this hijack was setup for a second chance + // exception. If the hijack was setup for another type of debug event, then we'll never get here. + // + // We explicitly terminate the process directly instead of going through any escalation policy because: + // 1) that's what a native-only debugger would do. Interop and Native-only should be the same. + // 2) there's no CLR escalation policy anyways for *native* unhandled exceptions. + // 3) The escalation policy may do lots of extra confusing work (like fire MDAs) that can only cause + // us grief. + TerminateProcess(GetCurrentProcess(), 0); + } + + SPEW(fprintf(stderr, "0x%x D::GHF: signaling continue...\n", tid)); +} +#endif + + +// +// This is the function that a thread is hijacked to by the Right Side during a variety of debug events. This function +// must be naked. +// +#if defined(_TARGET_X86_) +__declspec(naked) +#endif // defined (_x86_) +void Debugger::GenericHijackFunc(void) +{ +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) + +#if defined(_TARGET_X86_) + _asm + { + push ebp + mov ebp,esp + sub esp,__LOCAL_SIZE + } +#endif + // We can't have C++ classes w/ dtors in a declspec naked, so just have call into a helper. + GenericHijackFuncHelper(); + +#if defined(_TARGET_X86_) + _asm + { + mov esp,ebp + pop ebp + } +#endif + + // This signals the Right Side that this thread is ready to have its context restored. + ExceptionNotForRuntime(); + +#else + _ASSERTE(!"@todo - port GenericHijackFunc"); +#endif // defined (_x86_) + + _ASSERTE(!"Should never get here (Debugger::GenericHijackFunc)"); +} + + + + +//#ifdef _TARGET_X86_ +// +// This is the function that is called when we determine that a first chance exception hijack has +// begun and memory is prepared for the RS to tell the LS what to do +// +void Debugger::SignalHijackStarted(void) +{ + WRAPPER_NO_CONTRACT; + +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) + SignalHijackStartedFlare(); +#else + _ASSERTE(!"@todo - port the flares to the platform your running on."); +#endif +} + +// +// This is the function that is called when we determine that a first chance exception really belongs to the Runtime, +// and that that exception is due to a managed->unmanaged transition. This notifies the Right Side of this and the Right +// Side fixes up the thread's execution state from there, making sure to remember that it needs to continue to hide the +// hijack state of the thread. +// +void Debugger::ExceptionForRuntimeHandoffStart(void) +{ + WRAPPER_NO_CONTRACT; + +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) + ExceptionForRuntimeHandoffStartFlare(); +#else + _ASSERTE(!"@todo - port the flares to the platform your running on."); +#endif + +} + +// +// This is the function that is called when the original handler returns after we've determined that an exception was +// due to a managed->unmanaged transition. This notifies the Right Side of this and the Right Side fixes up the thread's +// execution state from there, making sure to turn off its flag indicating that the thread's hijack state should still +// be hidden. +// +void Debugger::ExceptionForRuntimeHandoffComplete(void) +{ + WRAPPER_NO_CONTRACT; + +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) + ExceptionForRuntimeHandoffCompleteFlare(); +#else + _ASSERTE(!"@todo - port the flares to the platform your running on."); +#endif + +} + +// +// This signals the RS that a hijack function is ready to return. This will cause the RS to restore +// the thread context +// +void Debugger::SignalHijackComplete(void) +{ + WRAPPER_NO_CONTRACT; + +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) + SignalHijackCompleteFlare(); +#else + _ASSERTE(!"@todo - port the flares to the platform your running on."); +#endif + +} + +// +// This is the function that is called when we determine that a first chance exception does not belong to the +// Runtime. This notifies the Right Side of this and the Right Side fixes up the thread's execution state from there. +// +void Debugger::ExceptionNotForRuntime(void) +{ + WRAPPER_NO_CONTRACT; + +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) + ExceptionNotForRuntimeFlare(); +#else + _ASSERTE(!"@todo - port the flares to the platform your running on."); +#endif +} + +// +// This is the function that is called when we want to send a sync complete event to the Right Side when it is the Win32 +// debugger of this process. This notifies the Right Side of this and the Right Side fixes up the thread's execution +// state from there. +// +void Debugger::NotifyRightSideOfSyncComplete(void) +{ + WRAPPER_NO_CONTRACT; + STRESS_LOG0(LF_CORDB, LL_INFO100000, "D::NRSOSC: Sending flare...\n"); +#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) + NotifyRightSideOfSyncCompleteFlare(); +#else + _ASSERTE(!"@todo - port the flares to the platform your running on."); +#endif + STRESS_LOG0(LF_CORDB, LL_INFO100000, "D::NRSOSC: Flare sent\n"); +} + +#endif // FEATURE_INTEROP_DEBUGGING + +/****************************************************************************** + * + ******************************************************************************/ +bool Debugger::GetILOffsetFromNative (MethodDesc *pFunc, const BYTE *pbAddr, + DWORD nativeOffset, DWORD *ilOffset) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + } + CONTRACTL_END; + + if (!HasLazyData()) + { + DebuggerLockHolder dbgLockHolder(this); + // This is an entry path into the debugger, so make sure we're inited. + LazyInit(); + } + + // Sometimes we'll get called w/ an instantiating stub MD. + if (pFunc->IsWrapperStub()) + { + pFunc = pFunc->GetWrappedMethodDesc(); + } + + DebuggerJitInfo *jitInfo = + GetJitInfo(pFunc, (const BYTE *)pbAddr); + + if (jitInfo != NULL) + { + CorDebugMappingResult map; + DWORD whichIDontCare; + + *ilOffset = jitInfo->MapNativeOffsetToIL( + nativeOffset, + &map, + &whichIDontCare); + + return true; + } + + return false; +} + +/****************************************************************************** + * + ******************************************************************************/ +DWORD Debugger::GetHelperThreadID(void ) +{ + LIMITED_METHOD_CONTRACT; + + return m_pRCThread->GetDCB() + ->m_temporaryHelperThreadId; +} + + +// HRESULT Debugger::InsertToMethodInfoList(): Make sure +// that there's only one head of the the list of DebuggerMethodInfos +// for the (implicitly) given MethodDef/Module pair. +HRESULT +Debugger::InsertToMethodInfoList( DebuggerMethodInfo *dmi ) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO10000,"D:IAHOL DMI: dmi:0x%08x\n", dmi)); + + HRESULT hr = S_OK; + + _ASSERTE(dmi != NULL); + + _ASSERTE(HasDebuggerDataLock()); + + // CHECK_DJI_TABLE_DEBUGGER; + + hr = CheckInitMethodInfoTable(); + + if (FAILED(hr)) { + return (hr); + } + + DebuggerMethodInfo *dmiPrev = m_pMethodInfos->GetMethodInfo(dmi->m_module, dmi->m_token); + + _ASSERTE((dmiPrev == NULL) || ((dmi->m_token == dmiPrev->m_token) && (dmi->m_module == dmiPrev->m_module))); + + LOG((LF_CORDB,LL_INFO10000,"D:IAHOL: current head of dmi list:0x%08x\n",dmiPrev)); + + if (dmiPrev != NULL) + { + dmi->m_prevMethodInfo = dmiPrev; + dmiPrev->m_nextMethodInfo = dmi; + + _ASSERTE(dmi->m_module != NULL); + hr = m_pMethodInfos->OverwriteMethodInfo(dmi->m_module, + dmi->m_token, + dmi, + FALSE); + + LOG((LF_CORDB,LL_INFO10000,"D:IAHOL: DMI version 0x%04x for token 0x%08x\n", + dmi->GetCurrentEnCVersion(),dmi->m_token)); + } + else + { + LOG((LF_CORDB, LL_EVERYTHING, "AddMethodInfo being called in D:IAHOL\n")); + hr = m_pMethodInfos->AddMethodInfo(dmi->m_module, + dmi->m_token, + dmi); + } +#ifdef _DEBUG + dmiPrev = m_pMethodInfos->GetMethodInfo(dmi->m_module, dmi->m_token); + LOG((LF_CORDB,LL_INFO10000,"D:IAHOL: new head of dmi list:0x%08x\n", + dmiPrev)); +#endif //_DEBUG + + // DebuggerDataLockHolder out of scope - release implied + return hr; +} + +//----------------------------------------------------------------------------- +// Helper to get an SString through the IPC buffer. +// We do this by putting the SString data into a LS_RS_buffer object, +// and then the RS reads it out as soon as it's queued. +// It's very very important that the SString's buffer is around while we send the event. +// So we pass the SString by reference in case there's an implicit conversion (because +// we don't want to do the conversion on a temporary object and then lose that object). +//----------------------------------------------------------------------------- +void SetLSBufferFromSString(Ls_Rs_StringBuffer * pBuffer, SString & str) +{ + // Copy string contents (+1 for null terminator) into a LS_RS_Buffer. + // Then the RS can pull it out as a null-terminated string. + pBuffer->SetLsData( + (BYTE*) str.GetUnicode(), + (str.GetCount() +1)* sizeof(WCHAR) + ); +} + +//************************************************************* +// structure that we to marshal MDA Notification event data. +//************************************************************* +struct SendMDANotificationParams +{ + Thread * m_pThread; // may be NULL. Lets us send on behalf of other threads. + + // Pass SStrings by ptr in case to guarantee that they're shared (in case we internally modify their storage). + SString * m_szName; + SString * m_szDescription; + SString * m_szXML; + CorDebugMDAFlags m_flags; + + SendMDANotificationParams( + Thread * pThread, // may be NULL. Lets us send on behalf of other threads. + SString * szName, + SString * szDescription, + SString * szXML, + CorDebugMDAFlags flags + ) : + m_pThread(pThread), + m_szName(szName), + m_szDescription(szDescription), + m_szXML(szXML), + m_flags(flags) + { + LIMITED_METHOD_CONTRACT; + } + +}; + +//----------------------------------------------------------------------------- +// Actually send the MDA event. (Could be on any thread) +// Parameters: +// params - data to initialize the IPC event. +//----------------------------------------------------------------------------- +void Debugger::SendRawMDANotification( + SendMDANotificationParams * params +) +{ + // Send the unload assembly event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + + Thread * pThread = params->m_pThread; + AppDomain *pAppDomain = (pThread != NULL) ? pThread->GetDomain() : NULL; + + InitIPCEvent(ipce, + DB_IPCE_MDA_NOTIFICATION, + pThread, + pAppDomain); + + SetLSBufferFromSString(&ipce->MDANotification.szName, *(params->m_szName)); + SetLSBufferFromSString(&ipce->MDANotification.szDescription, *(params->m_szDescription)); + SetLSBufferFromSString(&ipce->MDANotification.szXml, *(params->m_szXML)); + ipce->MDANotification.dwOSThreadId = GetCurrentThreadId(); + ipce->MDANotification.flags = params->m_flags; + + m_pRCThread->SendIPCEvent(); +} + +//----------------------------------------------------------------------------- +// Send an MDA notification. This ultimately translates to an ICorDebugMDA object on the Right-Side. +// Called by EE to send a MDA debug event. This will block on the debug event +// until the RS continues us. +// Debugger may or may not be attached. If bAttached, then this +// will trigger a jitattach as well. +// See MDA documentation for what szName, szDescription + szXML should look like. +// The debugger just passes them through. +// +// Parameters: +// pThread - thread for debug event. May be null. +// szName - short name of MDA. +// szDescription - full description of MDA. +// szXML - xml string for MDA. +// bAttach - do a JIT-attach +//----------------------------------------------------------------------------- +void Debugger::SendMDANotification( + Thread * pThread, // may be NULL. Lets us send on behalf of other threads. + SString * szName, + SString * szDescription, + SString * szXML, + CorDebugMDAFlags flags, + BOOL bAttach +) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + PREFIX_ASSUME(szName != NULL); + PREFIX_ASSUME(szDescription != NULL); + PREFIX_ASSUME(szXML != NULL); + + // Note: we normally don't send events like this when there is an unrecoverable error. However, + // if a host attempts to setup fiber mode on a thread, then we'll set an unrecoverable error + // and use an MDA to 1) tell the user and 2) get the Right Side to notice the unrecoverable error. + // Therefore, we'll go ahead and send a MDA event if the unrecoverable error is + // CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS. + DebuggerIPCControlBlock *pDCB = m_pRCThread->GetDCB(); + + + // If the MDA is ocuring very early in startup before the DCB is setup, then bail. + if (pDCB == NULL) + { + return; + } + + if (CORDBUnrecoverableError(this) && (pDCB->m_errorHR != CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS)) + { + return; + } + + // Validate flags. Make sure that folks don't start passing flags that we don't handle. + // If pThread != current thread, caller should either pass in MDA_FLAG_SLIP or guarantee + // that pThread is not slipping. + _ASSERTE((flags & ~(MDA_FLAG_SLIP)) == 0); + + // Helper thread should not be triggering MDAs. The helper thread is executing code in a very constrained + // and controlled region and shouldn't be able to do anything dangerous. + // If we revise this in the future, we should probably just post the event to the RS w/ use the MDA_FLAG_SLIP flag, + // and then not bother suspending the runtime. The RS will get it on its next event. + // The jit-attach logic below assumes we're not on the helper. (If we are on the helper, then a debugger should already + // be attached) + if (ThisIsHelperThreadWorker()) + { + CONSISTENCY_CHECK_MSGF(false, ("MDA '%s' fired on *helper* thread.\r\nDesc:%s", + szName->GetUnicode(), szDescription->GetUnicode() + )); + + // If for some reason we're wrong about the assert above, we'll just ignore the MDA (rather than potentially deadlock) + return; + } + + // Public entry point into the debugger. May cause a jit-attach, so we may need to be lazily-init. + if (!HasLazyData()) + { + DebuggerLockHolder dbgLockHolder(this); + // This is an entry path into the debugger, so make sure we're inited. + LazyInit(); + } + + + // Cases: + // 1) Debugger already attached, send event normally (ignore severity) + // 2) No debugger attached, Non-severe probe - ignore. + // 3) No debugger attached, Severe-probe - do a jit-attach. + bool fTryJitAttach = bAttach == TRUE; + + // Check case #2 - no debugger, and no jit-attach. Early opt out. + if (!CORDebuggerAttached() && !fTryJitAttach) + { + return; + } + + if (pThread == NULL) + { + // If there's no thread object, then we're not blocking after the event, + // and thus this probe may slip. + flags = (CorDebugMDAFlags) (flags | MDA_FLAG_SLIP); + } + + { + GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD(); + + // For "Severe" probes, we'll do a jit attach dialog + if (fTryJitAttach) + { + // May return: + // - S_OK if we do a jit-attach, + // - S_FALSE if a debugger is already attached. + // - Error in other cases.. + + JitAttach(pThread, NULL, TRUE, FALSE); + } + + // Debugger may be attached now... + if (CORDebuggerAttached()) + { + SendMDANotificationParams params(pThread, szName, szDescription, szXML, flags); + + // Non-attach case. Send like normal event. + // This includes if someone launch the debugger during the meantime. + // just send the event + SENDIPCEVENT_BEGIN(this, pThread); + + // Send Log message event to the Right Side + SendRawMDANotification(¶ms); + + // Stop all Runtime threads + // Even if we don't have a managed thead object, this will catch us at the next good spot. + TrapAllRuntimeThreads(); + + // Let other Runtime threads handle their events. + SENDIPCEVENT_END; + } + } // end of GCX_PREEMP_EEINTERFACE_TOGGLE() +} + +//************************************************************* +// This method sends a log message over to the right side for the debugger to log it. +// +// The CLR doesn't assign any semantics to the level or cateogory values. +// The BCL has a level convention (LoggingLevels enum), but this isn't exposed publicly, +// so we shouldn't base our behavior on it in any way. +//************************************************************* +void Debugger::SendLogMessage(int iLevel, + SString * pSwitchName, + SString * pMessage) +{ + CONTRACTL + { + GC_TRIGGERS; + THROWS; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::SLM: Sending log message.\n")); + + // Send the message only if the debugger is attached to this appdomain. + // Note the the debugger may detach at any time, so we'll have to check + // this again after we get the lock. + AppDomain *pAppDomain = g_pEEInterface->GetThread()->GetDomain(); + + if (!CORDebuggerAttached()) + { + return; + } + + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread); + + // Send Log message event to the Right Side + SendRawLogMessage( + pThread, + pAppDomain, + iLevel, + pSwitchName, + pMessage); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + + // Let other Runtime threads handle their events. + SENDIPCEVENT_END; +} + + +//************************************************************* +// +// Helper function to just send LogMessage event. Can be called on either +// helper thread or managed thread. +// +//************************************************************* +void Debugger::SendRawLogMessage( + Thread *pThread, + AppDomain *pAppDomain, + int iLevel, + SString * pCategory, + SString * pMessage +) +{ + DebuggerIPCEvent* ipce; + + + // We should have hold debugger lock + // This can happen on either native helper thread or managed thread + _ASSERTE(ThreadHoldsLock()); + + // It's possible that the debugger dettached while we were waiting + // for our lock. Check again and abort the event if it did. + if (!CORDebuggerAttached()) + { + return; + } + + ipce = m_pRCThread->GetIPCEventSendBuffer(); + + // Send a LogMessage event to the Right Side + InitIPCEvent(ipce, + DB_IPCE_FIRST_LOG_MESSAGE, + pThread, + pAppDomain); + + ipce->FirstLogMessage.iLevel = iLevel; + ipce->FirstLogMessage.szCategory.SetString(pCategory->GetUnicode()); + SetLSBufferFromSString(&ipce->FirstLogMessage.szContent, *pMessage); + + m_pRCThread->SendIPCEvent(); +} + + +// This function sends a message to the right side informing it about +// the creation/modification of a LogSwitch +void Debugger::SendLogSwitchSetting(int iLevel, + int iReason, + __in_z LPCWSTR pLogSwitchName, + __in_z LPCWSTR pParentSwitchName) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000, "D::SLSS: Sending log switch message switch=%S parent=%S.\n", + pLogSwitchName, pParentSwitchName)); + + // Send the message only if the debugger is attached to this appdomain. + if (!CORDebuggerAttached()) + { + return; + } + + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread); + + if (CORDebuggerAttached()) + { + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_LOGSWITCH_SET_MESSAGE, + pThread, + pThread->GetDomain()); + + ipce->LogSwitchSettingMessage.iLevel = iLevel; + ipce->LogSwitchSettingMessage.iReason = iReason; + + + ipce->LogSwitchSettingMessage.szSwitchName.SetString(pLogSwitchName); + + if (pParentSwitchName == NULL) + { + pParentSwitchName = W(""); + } + + ipce->LogSwitchSettingMessage.szParentSwitchName.SetString(pParentSwitchName); + + m_pRCThread->SendIPCEvent(); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::SLSS: Skipping SendIPCEvent because RS detached.")); + } + + SENDIPCEVENT_END; +} + +// send a custom debugger notification to the RS +// Arguments: +// input: pThread - thread on which the notification occurred +// pDomain - domain file for the domain in which the notification occurred +// classToken - metadata token for the type of the notification object +void Debugger::SendCustomDebuggerNotification(Thread * pThread, + DomainFile * pDomain, + mdTypeDef classToken) +{ + CONTRACTL + { + GC_TRIGGERS; + THROWS; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO10000, "D::SLM: Sending log message.\n")); + + // Send the message only if the debugger is attached to this appdomain. + // Note the the debugger may detach at any time, so we'll have to check + // this again after we get the lock. + if (!CORDebuggerAttached()) + { + return; + } + + Thread *curThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, curThread); + + if (CORDebuggerAttached()) + { + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_CUSTOM_NOTIFICATION, + curThread, + curThread->GetDomain()); + + VMPTR_DomainFile vmDomainFile = VMPTR_DomainFile::MakePtr(pDomain); + + ipce->CustomNotification.classToken = classToken; + ipce->CustomNotification.vmDomainFile = vmDomainFile; + + + m_pRCThread->SendIPCEvent(); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::SCDN: Skipping SendIPCEvent because RS detached.")); + } + + SENDIPCEVENT_END; +} + + +//----------------------------------------------------------------------------- +// +// Add the AppDomain to the list stored in the IPC block. It adds the id and +// the name. +// +// Arguments: +// pAppDomain - The runtime app domain object to add. +// +// Return Value: +// S_OK on success, else detailed error code. +// +HRESULT Debugger::AddAppDomainToIPC(AppDomain *pAppDomain) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + LPCWSTR szName = NULL; + + LOG((LF_CORDB, LL_INFO100, "D::AADTIPC: Executing AADTIPC for AppDomain 0x%08x (0x%x).\n", + pAppDomain, + pAppDomain->GetId().m_dwId)); + + STRESS_LOG2(LF_CORDB, LL_INFO10000, "D::AADTIPC: AddAppDomainToIPC:%#08x, %#08x\n", + pAppDomain, pAppDomain->GetId().m_dwId); + + + + _ASSERTE(m_pAppDomainCB->m_iTotalSlots > 0); + _ASSERTE(m_pAppDomainCB->m_rgListOfAppDomains != NULL); + + { + // + // We need to synchronize this routine with the attach logic. The "normal" + // attach case uses the HelperThread and TrapAllRuntimeThreads to synchronize + // the runtime before sending any of the events (including AppDomainCreates) + // to the right-side. Thus, we can synchronize with this case by forcing us + // to go co-operative. If we were already co-op, then the helper thread will + // wait to start the attach until all co-op threads are paused. If we were + // pre-emptive, then going co-op will suspend us until the HelperThread finishes. + // + // The second case is under the IPC event for ATTACHING, which is where there are + // zero app domains, so it is considered an 'early attach' case. To synchronize + // with this we have to grab and hold the AppDomainDB lock. + // + + GCX_COOP(); + + // Lock the list + if (!m_pAppDomainCB->Lock()) + { + return E_FAIL; + } + + // Get a free entry from the list + AppDomainInfo *pAppDomainInfo = m_pAppDomainCB->GetFreeEntry(); + + // Function returns NULL if the list is full and a realloc failed. + if (!pAppDomainInfo) + { + hr = E_OUTOFMEMORY; + goto LErrExit; + } + + // copy the ID + pAppDomainInfo->m_id = pAppDomain->GetId().m_dwId; + + // Now set the AppDomainName. + + /* + * TODO : + * + * Make sure that returning NULL here does not result in a catastrophic + * failure. + * + * GetFriendlyNameNoThrow may call SetFriendlyName, which may call + * UpdateAppDomainEntryInIPC. There is no recursive death, however, because + * the AppDomainInfo object does not contain a pointer to the app domain + * yet. + */ + szName = pAppDomain->GetFriendlyNameForDebugger(); + pAppDomainInfo->SetName(szName); + + // Save on to the appdomain pointer + pAppDomainInfo->m_pAppDomain = pAppDomain; + + // bump the used slot count + m_pAppDomainCB->m_iNumOfUsedSlots++; + +LErrExit: + // UnLock the list + m_pAppDomainCB->Unlock(); + + // Send event to debugger if one is attached. + if (CORDebuggerAttached()) + { + SendCreateAppDomainEvent(pAppDomain); + } + } + + return hr; +} + + +/****************************************************************************** + * Remove the AppDomain from the list stored in the IPC block and send an ExitAppDomain + * event to the debugger if attached. + ******************************************************************************/ +HRESULT Debugger::RemoveAppDomainFromIPC (AppDomain *pAppDomain) +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + SO_INTOLERANT; + } + CONTRACTL_END; + + HRESULT hr = E_FAIL; + + LOG((LF_CORDB, LL_INFO100, "D::RADFIPC: Executing RADFIPC for AppDomain 0x%08x (0x%x).\n", + pAppDomain, + pAppDomain->GetId().m_dwId)); + + // if none of the slots are occupied, then simply return. + if (m_pAppDomainCB->m_iNumOfUsedSlots == 0) + return hr; + + // Lock the list + if (!m_pAppDomainCB->Lock()) + return (E_FAIL); + + + // Look for the entry + AppDomainInfo *pADInfo = m_pAppDomainCB->FindEntry(pAppDomain); + + // Shouldn't be trying to remove an appdomain that was never added + if (!pADInfo) + { + // We'd like to assert this, but there is a small window where we may have + // called AppDomain::Init (and so it's fair game to call Stop, and hence come here), + // but not yet published the app domain. + // _ASSERTE(!"D::RADFIPC: trying to remove an AppDomain that was never added"); + hr = (E_FAIL); + goto ErrExit; + } + + // Release the entry + m_pAppDomainCB->FreeEntry(pADInfo); + +ErrExit: + // UnLock the list + m_pAppDomainCB->Unlock(); + + // send event to debugger if one is attached + if (CORDebuggerAttached()) + { + SendExitAppDomainEvent(pAppDomain); + } + + return hr; +} + +/****************************************************************************** + * Update the AppDomain in the list stored in the IPC block. + ******************************************************************************/ +HRESULT Debugger::UpdateAppDomainEntryInIPC(AppDomain *pAppDomain) +{ + CONTRACTL + { + NOTHROW; + if (GetThread()) { GC_TRIGGERS;} else {DISABLED(GC_NOTRIGGER);} + SO_INTOLERANT; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + LPCWSTR szName = NULL; + + LOG((LF_CORDB, LL_INFO100, + "D::UADEIIPC: Executing UpdateAppDomainEntryInIPC ad:0x%x.\n", + pAppDomain)); + + // if none of the slots are occupied, then simply return. + if (m_pAppDomainCB->m_iNumOfUsedSlots == 0) + return (E_FAIL); + + // Lock the list + if (!m_pAppDomainCB->Lock()) + return (E_FAIL); + + // Look up the info entry + AppDomainInfo *pADInfo = m_pAppDomainCB->FindEntry(pAppDomain); + + if (!pADInfo) + { + hr = E_FAIL; + goto ErrExit; + } + + // Update the name only if new name is non-null + szName = pADInfo->m_pAppDomain->GetFriendlyNameForDebugger(); + pADInfo->SetName(szName); + + LOG((LF_CORDB, LL_INFO100, + "D::UADEIIPC: New name:%ls (AD:0x%x)\n", pADInfo->m_szAppDomainName, + pAppDomain)); + +ErrExit: + // UnLock the list + m_pAppDomainCB->Unlock(); + + return hr; +} + +HRESULT Debugger::CopyModulePdb(Module* pRuntimeModule) +{ + CONTRACTL + { + THROWS; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + SO_NOT_MAINLINE; + + PRECONDITION(ThisIsHelperThread()); + MODE_ANY; + } + CONTRACTL_END; + + if (!pRuntimeModule->IsVisibleToDebugger()) + { + return S_OK; + } + + HRESULT hr = S_OK; +#ifdef FEATURE_FUSION + // + // Populate the pdb to fusion cache. + // + if (pRuntimeModule->IsIStream() == FALSE) + { + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + + EX_TRY + { + pRuntimeModule->FusionCopyPDBs(pRuntimeModule->GetPath()); + } + EX_CATCH_HRESULT(hr); // ignore failures + } +#endif // FEATURE_FUSION + + return hr; +} + +/****************************************************************************** + * When attaching to a process, this is called to enumerate all of the + * AppDomains currently in the process and allow modules pdbs to be copied over to the shadow dir maintaining out V2 in-proc behaviour. + ******************************************************************************/ +HRESULT Debugger::IterateAppDomainsForPdbs() +{ + CONTRACTL + { + THROWS; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + SO_NOT_MAINLINE; + + PRECONDITION(ThisIsHelperThread()); + MODE_ANY; + } + CONTRACTL_END; + + STRESS_LOG0(LF_CORDB, LL_INFO100, "Entered function IterateAppDomainsForPdbs()\n"); + HRESULT hr = S_OK; + + // Lock the list + if (!m_pAppDomainCB->Lock()) + return (E_FAIL); + + // Iterate through the app domains + AppDomainInfo *pADInfo = m_pAppDomainCB->FindFirst(); + + while (pADInfo) + { + STRESS_LOG3(LF_CORDB, LL_INFO100, "Iterating over domain %#08x AD:%#08x %ls\n", pADInfo->m_pAppDomain->GetId().m_dwId, pADInfo->m_pAppDomain, pADInfo->m_szAppDomainName); + + AppDomain::AssemblyIterator i; + i = pADInfo->m_pAppDomain->IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeLoading | kIncludeExecution)); + CollectibleAssemblyHolder pDomainAssembly; + while (i.Next(pDomainAssembly.This())) + { + if (!pDomainAssembly->IsVisibleToDebugger()) + continue; + + DomainAssembly::ModuleIterator j = pDomainAssembly->IterateModules(kModIterIncludeLoading); + while (j.Next()) + { + DomainFile * pDomainFile = j.GetDomainFile(); + if (!pDomainFile->ShouldNotifyDebugger()) + continue; + + Module* pRuntimeModule = pDomainFile->GetModule(); + CopyModulePdb(pRuntimeModule); + } + if (pDomainAssembly->ShouldNotifyDebugger()) + { + CopyModulePdb(pDomainAssembly->GetModule()); + } + } + + // Get the next appdomain in the list + pADInfo = m_pAppDomainCB->FindNext(pADInfo); + } + + // Unlock the list + m_pAppDomainCB->Unlock(); + + STRESS_LOG0(LF_CORDB, LL_INFO100, "Exiting function IterateAppDomainsForPdbs\n"); + + return hr; +} + + +/****************************************************************************** + * + ******************************************************************************/ +HRESULT Debugger::InitAppDomainIPC(void) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + SO_INTOLERANT; + + PRECONDITION(CheckPointer(m_pAppDomainCB)); + } + CONTRACTL_END; + + // Ensure that if we throw here, the Terminate will get called and cleanup all resources. + // This will make Init an atomic operation - it either fully inits or fully fails. + class EnsureCleanup + { + Debugger * m_pThis; + + public: + EnsureCleanup(Debugger * pThis) + { + m_pThis = pThis; + } + + void SupressCleanup() + { + m_pThis = NULL; + } + + ~EnsureCleanup() + { + if (m_pThis != NULL) + { + m_pThis->TerminateAppDomainIPC(); + } + } + } hEnsureCleanup(this); + + DWORD dwStrLen = 0; + SString szExeName; + int i; + + // all fields in the object can be zero initialized. + // If we throw, before fully initializing this, then cleanup won't try to free + // uninited values. + ZeroMemory(m_pAppDomainCB, sizeof(*m_pAppDomainCB)); + + // Fix for issue: whidbey 143061 + // We are creating the mutex as hold, when we unlock, the EndThreadAffinity in + // hosting case will be unbalanced. + // Ideally, I would like to fix this by creating mutex not-held and call Lock method. + // This way, when we clean up the OOM, (as you can tell, we never release the mutex in + // some error cases), we can change it to holder class. + // + Thread::BeginThreadAffinity(); + + // Create a mutex to allow the Left and Right Sides to properly + // synchronize. The Right Side will spin until m_hMutex is valid, + // then it will acquire it before accessing the data. + HandleHolder hMutex(WszCreateMutex(NULL, TRUE/*hold*/, NULL)); + if (hMutex == NULL) + { + ThrowLastError(); + } + if (!m_pAppDomainCB->m_hMutex.SetLocal(hMutex)) + { + ThrowLastError(); + } + hMutex.SuppressRelease(); + + m_pAppDomainCB->m_iSizeInBytes = INITIAL_APP_DOMAIN_INFO_LIST_SIZE * + sizeof (AppDomainInfo); + + // Number of slots in AppDomainListElement array + m_pAppDomainCB->m_rgListOfAppDomains = new AppDomainInfo[INITIAL_APP_DOMAIN_INFO_LIST_SIZE]; + _ASSERTE(m_pAppDomainCB->m_rgListOfAppDomains != NULL); // throws on oom + + + m_pAppDomainCB->m_iTotalSlots = INITIAL_APP_DOMAIN_INFO_LIST_SIZE; + + // Initialize each AppDomainListElement + for (i = 0; i < INITIAL_APP_DOMAIN_INFO_LIST_SIZE; i++) + { + m_pAppDomainCB->m_rgListOfAppDomains[i].FreeEntry(); + } + + // also initialize the process name + dwStrLen = WszGetModuleFileName(NULL, + szExeName); + + + // If we couldn't get the name, then use a nice default. + if (dwStrLen == 0) + { + szExeName.Set(W("")); + dwStrLen = szExeName.GetCount(); + } + + // If we got the name, copy it into a buffer. dwStrLen is the + // count of characters in the name, not including the null + // terminator. + m_pAppDomainCB->m_szProcessName = new WCHAR[dwStrLen + 1]; + _ASSERTE(m_pAppDomainCB->m_szProcessName != NULL); // throws on oom + + wcscpy_s(m_pAppDomainCB->m_szProcessName, dwStrLen + 1, szExeName); + + // Add 1 to the string length so the Right Side will copy out the + // null terminator, too. + m_pAppDomainCB->m_iProcessNameLengthInBytes = (dwStrLen + 1) * sizeof(WCHAR); + + if (m_pAppDomainCB->m_hMutex != NULL) + { + m_pAppDomainCB->Unlock(); + } + + hEnsureCleanup.SupressCleanup(); + return S_OK; +} + +/****************************************************************************** + * Unitialize the AppDomain IPC block + * Returns: + * S_OK -if fully unitialized + * E_FAIL - if we can't get ownership of the block, and thus no unitialization + * work is done. + ******************************************************************************/ +HRESULT Debugger::TerminateAppDomainIPC(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_INTOLERANT; + } + CONTRACTL_END; + + // If we have no AppDomain block, then we can consider it's already terminated. + if (m_pAppDomainCB == NULL) + return S_OK; + + HRESULT hr = S_OK; + + // Lock the list + // If there's no mutex, then we're in a partially created state. + // This means InitAppDomainIPC failed halfway through. But we're still thread safe + // since other threads can't access us if we don't have the mutex. + if ((m_pAppDomainCB->m_hMutex != NULL) && !m_pAppDomainCB->Lock()) + { + // The callers don't check our return value, we may want to know when we can't gracefully clean up + LOG((LF_CORDB, LL_INFO10, "Debugger::TerminateAppDomainIPC: Failed to get AppDomain IPC lock, not cleaning up.\n")); + + // If the lock is valid, but we can't get it, then we can't really + // uninitialize since someone else is using the block. + return (E_FAIL); + } + + // The shared IPC segment could still be around after the debugger + // object has been destroyed during process shutdown. So, reset + // the UsedSlots count to 0 so that any out of process clients + // enumeratingthe app domains in this process see 0 AppDomains. + m_pAppDomainCB->m_iNumOfUsedSlots = 0; + m_pAppDomainCB->m_iTotalSlots = 0; + + // Now delete the memory alloacted for AppDomainInfo array + delete [] m_pAppDomainCB->m_rgListOfAppDomains; + m_pAppDomainCB->m_rgListOfAppDomains = NULL; + + delete [] m_pAppDomainCB->m_szProcessName; + m_pAppDomainCB->m_szProcessName = NULL; + m_pAppDomainCB->m_iProcessNameLengthInBytes = 0; + + // Set the mutex handle to NULL. + // If the Right Side acquires the mutex, it will verify + // that the handle is still not NULL. If it is, then it knows it + // really lost. + RemoteHANDLE m = m_pAppDomainCB->m_hMutex; + m_pAppDomainCB->m_hMutex.m_hLocal = NULL; + + // And bring us back to a fully unintialized state. + ZeroMemory(m_pAppDomainCB, sizeof(*m_pAppDomainCB)); + + // We're done. release and close the mutex. Note that this must be done + // after we clear it out above to ensure there is no race condition. + if( m != NULL ) + { + VERIFY(ReleaseMutex(m)); + m.Close(); + } + + return hr; +} + + +#ifndef DACCESS_COMPILE + +// +// FuncEvalSetup sets up a function evaluation for the given method on the given thread. +// +HRESULT Debugger::FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo, + BYTE **argDataArea, + DebuggerEval **debuggerEvalKey) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_NOT_MAINLINE; + } + CONTRACTL_END; + + Thread *pThread = pEvalInfo->vmThreadToken.GetRawPtr(); + + + // + // If TS_AbortRequested (which may have been set by a pending FuncEvalAbort), + // we will not be able to do a new func-eval + // + // @TODO: Remember the current value of m_State, reset m_State as appropriate, + // do the new func-eval, and then set m_State to the original value + if (pThread->m_State & Thread::TS_AbortRequested) + return CORDBG_E_FUNC_EVAL_BAD_START_POINT; + + if (g_fProcessDetach) + return CORDBG_E_FUNC_EVAL_BAD_START_POINT; + + // If there is no guard page on this thread, then we've taken a stack overflow exception and can't run managed + // code on this thread. Therefore, we can't do a func eval on this thread. + if (!pThread->DetermineIfGuardPagePresent()) + { + return CORDBG_E_ILLEGAL_IN_STACK_OVERFLOW; + } + + bool fInException = pEvalInfo->evalDuringException; + + // The thread has to be at a GC safe place for now, just in case the func eval causes a collection. Processing an + // exception also counts as a "safe place." Eventually, we'd like to have to avoid this check and eval anyway, but + // that's a way's off... + if (!fInException && !g_pDebugger->IsThreadAtSafePlace(pThread)) + return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT; + + // For now, we assume that the target thread must be stopped in managed code due to a single step or a + // breakpoint. Being stopped while sending a first or second chance exception is also valid, and there may or may + // not be a filter context when we do a func eval from such places. This will loosen over time, eventually allowing + // threads that are stopped anywhere in managed code to perform func evals. + CONTEXT *filterContext = GetManagedStoppedCtx(pThread); + + if (filterContext == NULL && !fInException) + { + return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT; + } + + // Create a DebuggerEval to hold info about this eval while its in progress. Constructor copies the thread's + // CONTEXT. + DebuggerEval *pDE = new (interopsafe, nothrow) DebuggerEval(filterContext, pEvalInfo, fInException); + + if (pDE == NULL) + { + return E_OUTOFMEMORY; + } + else if (!pDE->Init()) + { + // We fail to change the m_breakpointInstruction field to PAGE_EXECUTE_READWRITE permission. + return E_FAIL; + } + + SIZE_T argDataAreaSize = 0; + + argDataAreaSize += pEvalInfo->genericArgsNodeCount * sizeof(DebuggerIPCE_TypeArgData); + + if ((pEvalInfo->funcEvalType == DB_IPCE_FET_NORMAL) || + (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_OBJECT) || + (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_OBJECT_NC)) + argDataAreaSize += pEvalInfo->argCount * sizeof(DebuggerIPCE_FuncEvalArgData); + else if (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_STRING) + argDataAreaSize += pEvalInfo->stringSize; + else if (pEvalInfo->funcEvalType == DB_IPCE_FET_NEW_ARRAY) + argDataAreaSize += pEvalInfo->arrayRank * sizeof(SIZE_T); + + if (argDataAreaSize > 0) + { + pDE->m_argData = new (interopsafe, nothrow) BYTE[argDataAreaSize]; + + if (pDE->m_argData == NULL) + { + DeleteInteropSafeExecutable(pDE); + return E_OUTOFMEMORY; + } + + // Pass back the address of the argument data area so the right side can write to it for us. + *argDataArea = pDE->m_argData; + } + + // Set the thread's IP (in the filter context) to our hijack function if we're stopped due to a breakpoint or single + // step. + if (!fInException) + { + _ASSERTE(filterContext != NULL); + + ::SetIP(filterContext, (UINT_PTR)GetEEFuncEntryPoint(::FuncEvalHijack)); + + // Don't be fooled into thinking you can push things onto the thread's stack now. If the thread is stopped at a + // breakpoint or from a single step, then its really suspended in the SEH filter. ESP in the thread's CONTEXT, + // therefore, points into the middle of the thread's current stack. So we pass things we need in the hijack in + // the thread's registers. + + // Set the first argument to point to the DebuggerEval. +#if defined(_TARGET_X86_) + filterContext->Eax = (DWORD)pDE; +#elif defined(_TARGET_AMD64_) +#ifdef UNIX_AMD64_ABI + filterContext->Rdi = (SIZE_T)pDE; +#else // UNIX_AMD64_ABI + filterContext->Rcx = (SIZE_T)pDE; +#endif // !UNIX_AMD64_ABI +#elif defined(_TARGET_ARM_) + filterContext->R0 = (DWORD)pDE; +#elif defined(_TARGET_ARM64_) + filterContext->X0 = (SIZE_T)pDE; +#else + PORTABILITY_ASSERT("Debugger::FuncEvalSetup is not implemented on this platform."); +#endif + + // + // To prevent GCs until the func-eval gets a chance to run, we increment the counter here. + // We only need to do this if we have changed the filter CONTEXT, since the stack will be unwalkable + // in this case. + // + g_pDebugger->IncThreadsAtUnsafePlaces(); + } + else + { + HRESULT hr = CheckInitPendingFuncEvalTable(); + + if (FAILED(hr)) + { + DeleteInteropSafeExecutable(pDE); // Note this runs the destructor for DebuggerEval, which releases its internal buffers + return (hr); + } + // If we're in an exception, then add a pending eval for this thread. This will cause us to perform the func + // eval when the user continues the process after the current exception event. + GetPendingEvals()->AddPendingEval(pDE->m_thread, pDE); + } + + + // Return that all went well. Tracing the stack at this point should not show that the func eval is setup, but it + // will show a wrong IP, so it shouldn't be done. + *debuggerEvalKey = pDE; + + LOG((LF_CORDB, LL_INFO100000, "D:FES for pDE:%08x evalType:%d on thread %#x, id=0x%x\n", + pDE, pDE->m_evalType, pThread, GetThreadIdHelper(pThread))); + + return S_OK; +} + +// +// FuncEvalSetupReAbort sets up a function evaluation specifically to rethrow a ThreadAbortException on the given +// thread. +// +HRESULT Debugger::FuncEvalSetupReAbort(Thread *pThread, Thread::ThreadAbortRequester requester) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_NOT_MAINLINE; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000, + "D::FESRA: performing reabort on thread %#x, id=0x%x\n", + pThread, GetThreadIdHelper(pThread))); + + // The thread has to be at a GC safe place. It should be, since this is only done in response to a previous eval + // completing with a ThreadAbortException. + if (!g_pDebugger->IsThreadAtSafePlace(pThread)) + return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT; + + // Grab the filter context. + CONTEXT *filterContext = GetManagedStoppedCtx(pThread); + + if (filterContext == NULL) + { + return CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT; + } + + // Create a DebuggerEval to hold info about this eval while its in progress. Constructor copies the thread's + // CONTEXT. + DebuggerEval *pDE = new (interopsafe, nothrow) DebuggerEval(filterContext, pThread, requester); + + if (pDE == NULL) + { + return E_OUTOFMEMORY; + } + else if (!pDE->Init()) + { + // We fail to change the m_breakpointInstruction field to PAGE_EXECUTE_READWRITE permission. + return E_FAIL; + } + + // Set the thread's IP (in the filter context) to our hijack function. + _ASSERTE(filterContext != NULL); + + ::SetIP(filterContext, (UINT_PTR)GetEEFuncEntryPoint(::FuncEvalHijack)); + +#ifdef _TARGET_X86_ // reliance on filterContext->Eip & Eax + // Set EAX to point to the DebuggerEval. + filterContext->Eax = (DWORD)pDE; +#elif defined(_TARGET_AMD64_) + // Set RCX to point to the DebuggerEval. + filterContext->Rcx = (SIZE_T)pDE; +#elif defined(_TARGET_ARM_) + filterContext->R0 = (DWORD)pDE; +#elif defined(_TARGET_ARM64_) + filterContext->X0 = (SIZE_T)pDE; +#else + PORTABILITY_ASSERT("FuncEvalSetupReAbort (Debugger.cpp) is not implemented on this platform."); +#endif + + // Now clear the bit requesting a re-abort + pThread->ResetThreadStateNC(Thread::TSNC_DebuggerReAbort); + + g_pDebugger->IncThreadsAtUnsafePlaces(); + + // Return that all went well. Tracing the stack at this point should not show that the func eval is setup, but it + // will show a wrong IP, so it shouldn't be done. + + return S_OK; +} + +// +// FuncEvalAbort: Does a gentle abort of a func-eval already in progress. +// Because this type of abort waits for the thread to get to a good state, +// it may never return, or may time out. +// + +// +// Wait at most 0.5 seconds. +// +#define FUNC_EVAL_DEFAULT_TIMEOUT_VALUE 500 + +HRESULT +Debugger::FuncEvalAbort( + DebuggerEval *debuggerEvalKey + ) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + } + CONTRACTL_END; + + DebuggerEval *pDE = (DebuggerEval*) debuggerEvalKey; + HRESULT hr = S_OK; + CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&hr, GetCanary()); + if (FAILED(hr)) + { + return hr; + } + + + if (pDE->m_aborting == DebuggerEval::FE_ABORT_NONE) + { + // Remember that we're aborting this func eval. + pDE->m_aborting = DebuggerEval::FE_ABORT_NORMAL; + + LOG((LF_CORDB, LL_INFO1000, + "D::FEA: performing UserAbort on thread %#x, id=0x%x\n", + pDE->m_thread, GetThreadIdHelper(pDE->m_thread))); + + if (!g_fProcessDetach && !pDE->m_completed) + { + // + // Perform a stop on the thread that the eval is running on. + // This will cause a ThreadAbortException to be thrown on the thread. + // + EX_TRY + { + hr = pDE->m_thread->UserAbort(Thread::TAR_FuncEval, EEPolicy::TA_Safe, (DWORD)FUNC_EVAL_DEFAULT_TIMEOUT_VALUE, Thread::UAC_Normal); + if (hr == HRESULT_FROM_WIN32(ERROR_TIMEOUT)) + { + hr = S_OK; + } + } + EX_CATCH + { + _ASSERTE(!"Unknown exception from UserAbort(), not expected"); + } + EX_END_CATCH(EX_RETHROW); + + } + + LOG((LF_CORDB, LL_INFO1000, "D::FEA: UserAbort complete.\n")); + } + + return hr; +} + +// +// FuncEvalRudeAbort: Does a rude abort of a func-eval in progress. This +// leaves the thread in an undetermined state. +// +HRESULT +Debugger::FuncEvalRudeAbort( + DebuggerEval *debuggerEvalKey + ) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + SO_NOT_MAINLINE; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(&hr, GetCanary()); + if (FAILED(hr)) + { + return hr; + } + + + DebuggerEval *pDE = debuggerEvalKey; + + + if (!(pDE->m_aborting & DebuggerEval::FE_ABORT_RUDE)) + { + // + // Remember that we're aborting this func eval. + // + pDE->m_aborting = (DebuggerEval::FUNC_EVAL_ABORT_TYPE)(pDE->m_aborting | DebuggerEval::FE_ABORT_RUDE); + + LOG((LF_CORDB, LL_INFO1000, + "D::FEA: performing RudeAbort on thread %#x, id=0x%x\n", + pDE->m_thread, Debugger::GetThreadIdHelper(pDE->m_thread))); + + if (!g_fProcessDetach && !pDE->m_completed) + { + // + // Perform a stop on the thread that the eval is running on. + // This will cause a ThreadAbortException to be thrown on the thread. + // + EX_TRY + { + hr = pDE->m_thread->UserAbort(Thread::TAR_FuncEval, EEPolicy::TA_Rude, (DWORD)FUNC_EVAL_DEFAULT_TIMEOUT_VALUE, Thread::UAC_Normal); + if (hr == HRESULT_FROM_WIN32(ERROR_TIMEOUT)) + { + hr = S_OK; + } + } + EX_CATCH + { + _ASSERTE(!"Unknown exception from UserAbort(), not expected"); + EX_RETHROW; + } + EX_END_CATCH(RethrowTerminalExceptions); + } + + LOG((LF_CORDB, LL_INFO1000, "D::FEA: RudeAbort complete.\n")); + } + + return hr; +} + +// +// FuncEvalCleanup cleans up after a function evaluation is released. +// +HRESULT Debugger::FuncEvalCleanup(DebuggerEval *debuggerEvalKey) +{ + LIMITED_METHOD_CONTRACT; + + DebuggerEval *pDE = debuggerEvalKey; + + _ASSERTE(pDE->m_completed); + + LOG((LF_CORDB, LL_INFO1000, "D::FEC: pDE:%08x 0x%08x, id=0x%x\n", + pDE, pDE->m_thread, GetThreadIdHelper(pDE->m_thread))); + + DeleteInteropSafeExecutable(pDE->m_bpInfoSegment); + DeleteInteropSafe(pDE); + + return S_OK; +} + +#endif // ifndef DACCESS_COMPILE + +// +// SetReference sets an object reference for the Right Side, +// respecting the write barrier for references that are in the heap. +// +HRESULT Debugger::SetReference(void *objectRefAddress, + VMPTR_OBJECTHANDLE vmObjectHandle, + void *newReference) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + hr = ValidateObject((Object *)newReference); + if (FAILED(hr)) + { + return hr; + } + + + // If the object ref isn't in a handle, then go ahead and use + // SetObjectReference. + if (vmObjectHandle.IsNull()) + { + OBJECTREF *dst = (OBJECTREF*)objectRefAddress; + OBJECTREF src = *((OBJECTREF*)&newReference); + + SetObjectReferenceUnchecked(dst, src); + } + else + { + + // If the object reference to set is inside of a handle, then + // fixup the handle. + OBJECTHANDLE h = vmObjectHandle.GetRawPtr(); + OBJECTREF src = *((OBJECTREF*)&newReference); + HndAssignHandle(h, src); + } + + return S_OK; +} + +// +// SetValueClass sets a value class for the Right Side, respecting the write barrier for references that are embedded +// within in the value class. +// +HRESULT Debugger::SetValueClass(void *oldData, void *newData, DebuggerIPCE_BasicTypeData * type) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + TypeHandle th; + hr = BasicTypeInfoToTypeHandle(type, &th); + + if (FAILED(hr)) + return CORDBG_E_CLASS_NOT_LOADED; + + // Update the value class. + CopyValueClassUnchecked(oldData, newData, th.GetMethodTable()); + + // Free the buffer that is holding the new data. This is a buffer that was created in response to a GET_BUFFER + // message, so we release it with ReleaseRemoteBuffer. + ReleaseRemoteBuffer((BYTE*)newData, true); + + return hr; +} + +/****************************************************************************** + * + ******************************************************************************/ +HRESULT Debugger::SetILInstrumentedCodeMap(MethodDesc *fd, + BOOL fStartJit, + ULONG32 cILMapEntries, + COR_IL_MAP rgILMapEntries[]) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS_FROM_GETJITINFO; + } + CONTRACTL_END; + + if (!HasLazyData()) + { + DebuggerLockHolder dbgLockHolder(this); + // This is an entry path into the debugger, so make sure we're inited. + LazyInit(); + } + + DebuggerMethodInfo * dmi = GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef()); + if (dmi == NULL) + { + return E_OUTOFMEMORY; + } + + dmi->SetInstrumentedILMap(rgILMapEntries, cILMapEntries); + + return S_OK; +} + +// +// 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 Debugger::EarlyHelperThreadDeath(void) +{ + WRAPPER_NO_CONTRACT; + + if (m_pRCThread) + m_pRCThread->EarlyHelperThreadDeath(); +} + +// +// This tells the debugger that shutdown of the in-proc debugging services has begun. We need to know this during +// managed/unmanaged debugging so we can stop doing certian things to the process (like hijacking threads.) +// +void Debugger::ShutdownBegun(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_INTOLERANT; + } + CONTRACTL_END; + + + // Shouldn't be Debugger-stopped if we're shutting down. + // However, shutdown can occur in preemptive mode. Thus if the RS does an AsyncBreak late + // enough, then the LS will appear to be stopped but may still shutdown. + // Since the debuggee can exit asynchronously at any time (eg, suppose somebody forcefully + // kills it with taskman), this doesn't introduce a new case. + // That aside, it would be great to be able to assert this: + //_ASSERTE(!IsStopped()); + + if (m_pRCThread != NULL) + { + DebuggerIPCControlBlock *dcb = m_pRCThread->GetDCB(); + + if ((dcb != NULL) && (dcb->m_rightSideIsWin32Debugger)) + dcb->m_shutdownBegun = true; + } +} + +/* + * LockDebuggerForShutdown + * + * This routine is used during shutdown to tell the in-process portion of the + * debugger to synchronize with any threads that are currently using the + * debugging facilities such that no more threads will run debugging services. + * + * This is accomplished by transitioning the debugger lock in to a state where + * it will block all threads, except for the finalizer, shutdown, and helper thread. + */ +void Debugger::LockDebuggerForShutdown(void) +{ +#ifndef DACCESS_COMPILE + + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_INTOLERANT; + MODE_ANY; + } + CONTRACTL_END; + + DebuggerLockHolder dbgLockHolder(this); + + // Shouldn't be Debugger-stopped if we're shutting down. + // However, shutdown can occur in preemptive mode. Thus if the RS does an AsyncBreak late + // enough, then the LS will appear to be stopped but may still shutdown. + // Since the debuggee can exit asynchronously at any time (eg, suppose somebody forcefully + // kills it with taskman), this doesn't introduce a new case. + // That aside, it would be great to be able to assert this: + //_ASSERTE(!IsStopped()); + + // After setting this flag, nonspecial threads will not be able to + // take the debugger lock. + m_fShutdownMode = true; + + m_ignoreThreadDetach = TRUE; +#else + DacNotImpl(); +#endif +} + + +/* + * DisableDebugger + * + * This routine is used by the EE to inform the debugger that it should block all + * threads from executing as soon as it can. Any thread entering the debugger can + * block infinitely, as well. + * + * This is accomplished by transitioning the debugger lock into a mode where it will + * block all threads infinitely rather than taking the lock. + * + */ +void Debugger::DisableDebugger(void) +{ +#ifndef DACCESS_COMPILE + + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_INTOLERANT; + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + m_fDisabled = true; + + CORDBDebuggerSetUnrecoverableError(this, CORDBG_E_DEBUGGING_DISABLED, false); + +#else + DacNotImpl(); +#endif +} + + +/**************************************************************************** + * This will perform the duties of the helper thread if none already exists. + * This is called in the case that the loader lock is held and so no new + * threads can be spun up to be the helper thread, so the existing thread + * must be the helper thread until a new one can spin up. + * This is also called in the shutdown case (g_fProcessDetach==true) and our + * helper may have already been blown away. + ***************************************************************************/ +void Debugger::DoHelperThreadDuty() +{ + CONTRACTL + { + SO_NOT_MAINLINE; + THROWS; + WRAPPER(GC_TRIGGERS); + } + CONTRACTL_END; + + // This should not be a real helper thread. + _ASSERTE(!IsDbgHelperSpecialThread()); + _ASSERTE(ThreadHoldsLock()); + + // We may be here in the shutdown case (only if the shutdown started after we got here). + // We'll get killed randomly anyways, so not much we can do. + + // These assumptions are based off us being called from TART. + _ASSERTE(ThreadStore::HoldingThreadStore() || g_fProcessDetach); // got this from TART + _ASSERTE(m_trappingRuntimeThreads); // We're only called from TART. + _ASSERTE(!m_stopped); // we haven't sent the sync-complete yet. + + // Can't have 2 threads doing helper duty. + _ASSERTE(m_pRCThread->GetDCB()->m_temporaryHelperThreadId == 0); + + LOG((LF_CORDB, LL_INFO1000, + "D::SSCIPCE: helper thread is not ready, doing helper " + "thread duty...\n")); + + // We're the temporary helper thread now. + DWORD dwMyTID = GetCurrentThreadId(); + m_pRCThread->GetDCB()->m_temporaryHelperThreadId = dwMyTID; + + // Make sure the helper thread has something to wait on while + // we're trying to be the helper thread. + VERIFY(ResetEvent(m_pRCThread->GetHelperThreadCanGoEvent())); + + // We have not sent the sync-complete flare yet. + + // Now that we've synchronized, we'll eventually send the sync-complete. But we're currently within the + // scope of sombody already sending an event. So unlock from that event so that we can send the sync-complete. + // Don't release the debugger lock + // + UnlockFromEventSending(NULL); + + // We are the temporary helper thread. We will not deal with everything! But just pump for + // continue. + // + m_pRCThread->TemporaryHelperThreadMainLoop(); + + // We do not need to relock it since we never release it. + LockForEventSending(NULL); + _ASSERTE(ThreadHoldsLock()); + + + STRESS_LOG1(LF_CORDB, LL_INFO1000, + "D::SSCIPCE: done doing helper thread duty. " + "Current helper thread id=0x%x\n", + m_pRCThread->GetDCB()->m_helperThreadId); + + // We're not the temporary helper thread anymore. + _ASSERTE(m_pRCThread->GetDCB()->m_temporaryHelperThreadId == dwMyTID); + m_pRCThread->GetDCB()->m_temporaryHelperThreadId = 0; + + // Let the helper thread go if its waiting on us. + VERIFY(SetEvent(m_pRCThread->GetHelperThreadCanGoEvent())); +} + + + +// This function is called from the EE to notify the right side +// whenever the name of a thread or AppDomain changes +// +// Notes: +// This just sends a ping event to notify that the name has been changed. +// It does not send the actual updated name. Instead, the debugger can query for the name. +// +// For an AppDomain name change: +// - pAppDoamin != NULL +// - name retrieved via ICorDebugAppDomain::GetName +// +// For a Thread name change: +// - pAppDomain == NULL, pThread != NULL +// - name retrieved via a func-eval of Thread::get_Name +HRESULT Debugger::NameChangeEvent(AppDomain *pAppDomain, Thread *pThread) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + // Don't try to send one of these if the thread really isn't setup + // yet. This can happen when initially setting up an app domain, + // before the appdomain create event has been sent. Since the app + // domain create event hasn't been sent yet in this case, its okay + // to do this... + if (g_pEEInterface->GetThread() == NULL) + return S_OK; + + // Skip if thread doesn't yet have native ID. + // This can easily happen if an app sets Thread.Name before it calls Thread.Start. + // Since this is just a ping-event, it's ignorable. The debugger can query the thread name at Thread.Start in this case. + // This emulates whidbey semantics. + if (pThread != NULL) + { + if (pThread->GetOSThreadId() == 0) + { + return S_OK; + } + } + + LOG((LF_CORDB, LL_INFO1000, "D::NCE: Sending NameChangeEvent 0x%x 0x%x\n", + pAppDomain, pThread)); + + Thread *curThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, curThread); + + if (CORDebuggerAttached()) + { + + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_NAME_CHANGE, + curThread, + curThread->GetDomain()); + + + if (pAppDomain) + { + ipce->NameChange.eventType = APP_DOMAIN_NAME_CHANGE; + ipce->NameChange.vmAppDomain.SetRawPtr(pAppDomain); + } + else + { + // Thread Name + ipce->NameChange.eventType = THREAD_NAME_CHANGE; + _ASSERTE (pThread); + ipce->NameChange.vmThread.SetRawPtr(pThread); + } + + m_pRCThread->SendIPCEvent(); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::NCE: Skipping SendIPCEvent because RS detached.")); + } + + SENDIPCEVENT_END; + + return S_OK; + +} + +//--------------------------------------------------------------------------------------- +// +// Send an event to the RS indicating that there's a Ctrl-C or Ctrl-Break. +// +// Arguments: +// dwCtrlType - represents the type of the event (Ctrl-C or Ctrl-Break) +// +// Return Value: +// Return TRUE if the event has been handled by the debugger. +// + +BOOL Debugger::SendCtrlCToDebugger(DWORD dwCtrlType) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + LOG((LF_CORDB, LL_INFO1000, "D::SCCTD: Sending CtrlC Event 0x%x\n", dwCtrlType)); + + // Prevent other Runtime threads from handling events. + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread); + + if (CORDebuggerAttached()) + { + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, + DB_IPCE_CONTROL_C_EVENT, + pThread, + NULL); + + // The RS doesn't do anything with dwCtrlType + m_pRCThread->SendIPCEvent(); + + // Stop all Runtime threads + TrapAllRuntimeThreads(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::SCCTD: Skipping SendIPCEvent because RS detached.")); + } + + SENDIPCEVENT_END; + + // now wait for notification from the right side about whether or not + // the out-of-proc debugger is handling ControlC events. + WaitForSingleObjectHelper(GetCtrlCMutex(), INFINITE); + + return GetDebuggerHandlingCtrlC(); +} + +// Allows the debugger to keep an up to date list of special threads +HRESULT Debugger::UpdateSpecialThreadList(DWORD cThreadArrayLength, + DWORD *rgdwThreadIDArray) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(g_pRCThread != NULL); + + DebuggerIPCControlBlock *pIPC = g_pRCThread->GetDCB(); + _ASSERTE(pIPC); + + if (!pIPC) + return (E_FAIL); + + // Save the thread list information, and mark the dirty bit so + // the right side knows. + pIPC->m_specialThreadList = rgdwThreadIDArray; + pIPC->m_specialThreadListLength = cThreadArrayLength; + pIPC->m_specialThreadListDirty = true; + + return (S_OK); +} + +// Updates the pointer for the debugger services +void Debugger::SetIDbgThreadControl(IDebuggerThreadControl *pIDbgThreadControl) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + if (m_pIDbgThreadControl) + m_pIDbgThreadControl->Release(); + + m_pIDbgThreadControl = pIDbgThreadControl; + + if (m_pIDbgThreadControl) + m_pIDbgThreadControl->AddRef(); +} + +// +// If a thread is Win32 suspended right after hitting a breakpoint instruction, but before the OS has transitioned the +// thread over to the user-level exception dispatching logic, then we may see the IP pointing after the breakpoint +// instruction. There are times when the Runtime will use the IP to try to determine what code as run in the prolog or +// epilog, most notably when unwinding a frame. If the thread is suspended in such a case, then the unwind will believe +// that the instruction that the breakpoint replaced has really been executed, which is not true. This confuses the +// unwinding logic. This function is called from Thread::HandledJITCase() to help us recgonize when this may have +// happened and allow us to skip the unwind and abort the HandledJITCase. +// +// The criteria is this: +// +// 1) If a debugger is attached. +// +// 2) If the instruction before the IP is a breakpoint instruction. +// +// 3) If the IP is in the prolog or epilog of a managed function. +// +BOOL Debugger::IsThreadContextInvalid(Thread *pThread) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + BOOL invalid = FALSE; + + // Get the thread context. + CONTEXT ctx; + ctx.ContextFlags = CONTEXT_CONTROL; + BOOL success = pThread->GetThreadContext(&ctx); + + if (success) + { + // Check single-step flag + if (IsSSFlagEnabled(reinterpret_cast(&ctx) ARM_ARG(pThread))) + { + // Can't hijack a thread whose SS-flag is set. This could lead to races + // with the thread taking the SS-exception. + // The debugger's controller filters will poll for GC to avoid starvation. + STRESS_LOG0(LF_CORDB, LL_EVERYTHING, "HJC - Hardware trace flag applied\n"); + return TRUE; + } + } + + if (success) + { +#ifdef _TARGET_X86_ + // Grab Eip - 1 + LPVOID address = (((BYTE*)GetIP(&ctx)) - 1); + + EX_TRY + { + // Use AVInRuntimeImplOkHolder. + AVInRuntimeImplOkayHolder AVOkay; + + // Is it a breakpoint? + if (AddressIsBreakpoint((CORDB_ADDRESS_TYPE*)address)) + { + size_t prologSize; // Unused... + if (g_pEEInterface->IsInPrologOrEpilog((BYTE*)GetIP(&ctx), &prologSize)) + { + LOG((LF_CORDB, LL_INFO1000, "D::ITCI: thread is after a BP and in prolog or epilog.\n")); + invalid = TRUE; + } + } + } + EX_CATCH + { + // If we fault trying to read the byte before EIP, then we know that its not a breakpoint. + // Do nothing. The default return value is FALSE. + } + EX_END_CATCH(SwallowAllExceptions); +#else // _TARGET_X86_ + // Non-x86 can detect whether the thread is suspended after an exception is hit but before + // the kernel has dispatched the exception to user mode by trap frame reporting. + // See Thread::IsContextSafeToRedirect(). +#endif // _TARGET_X86_ + } + else + { + // If we can't get the context, then its definetly invalid... ;) + LOG((LF_CORDB, LL_INFO1000, "D::ITCI: couldn't get thread's context!\n")); + invalid = TRUE; + } + + return invalid; +} + + +// notification when a SQL connection begins +void Debugger::CreateConnection(CONNID dwConnectionId, __in_z WCHAR *wzName) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO1000, "D::CreateConnection %d\n.", dwConnectionId)); + + if (CORDBUnrecoverableError(this)) + return; + + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread); + + if (CORDebuggerAttached()) + { + DebuggerIPCEvent* ipce; + + // Send a update module syns event to the Right Side. + ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, DB_IPCE_CREATE_CONNECTION, + pThread, + NULL); + ipce->CreateConnection.connectionId = dwConnectionId; + _ASSERTE(wzName != NULL); + ipce->CreateConnection.wzConnectionName.SetString(wzName); + + m_pRCThread->SendIPCEvent(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::CreateConnection: Skipping SendIPCEvent because RS detached.")); + } + + // Stop all Runtime threads if we actually sent an event + if (CORDebuggerAttached()) + { + TrapAllRuntimeThreads(); + } + + SENDIPCEVENT_END; +} + +// notification when a SQL connection ends +void Debugger::DestroyConnection(CONNID dwConnectionId) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO1000, "D::DestroyConnection %d\n.", dwConnectionId)); + + if (CORDBUnrecoverableError(this)) + return; + + Thread *thread = g_pEEInterface->GetThread(); + // Note that the debugger lock is reentrant, so we may or may not hold it already. + SENDIPCEVENT_BEGIN(this, thread); + + // Send a update module syns event to the Right Side. + DebuggerIPCEvent* ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, DB_IPCE_DESTROY_CONNECTION, + thread, + NULL); + ipce->ConnectionChange.connectionId = dwConnectionId; + + // IPC event is now initialized, so we can send it over. + SendSimpleIPCEventAndBlock(); + + // This will block on the continue + SENDIPCEVENT_END; + +} + +// notification for SQL connection changes +void Debugger::ChangeConnection(CONNID dwConnectionId) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT; + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + } + CONTRACTL_END; + + LOG((LF_CORDB,LL_INFO1000, "D::ChangeConnection %d\n.", dwConnectionId)); + + if (CORDBUnrecoverableError(this)) + return; + + Thread *pThread = g_pEEInterface->GetThread(); + SENDIPCEVENT_BEGIN(this, pThread); + + if (CORDebuggerAttached()) + { + DebuggerIPCEvent* ipce; + + // Send a update module syns event to the Right Side. + ipce = m_pRCThread->GetIPCEventSendBuffer(); + InitIPCEvent(ipce, DB_IPCE_CHANGE_CONNECTION, + pThread, + NULL); + ipce->ConnectionChange.connectionId = dwConnectionId; + m_pRCThread->SendIPCEvent(); + } + else + { + LOG((LF_CORDB,LL_INFO1000, "D::ChangeConnection: Skipping SendIPCEvent because RS detached.")); + } + + // Stop all Runtime threads if we actually sent an event + if (CORDebuggerAttached()) + { + TrapAllRuntimeThreads(); + } + + SENDIPCEVENT_END; +} + + +// +// Are we the helper thread? +// Some important things about running on the helper thread: +// - there's only 1, so guaranteed to be thread-safe. +// - we'll never run managed code. +// - therefore, Never GC. +// - It listens for events from the RS. +// - It's the only thread to send a sync complete. +// +bool ThisIsHelperThreadWorker(void) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SO_TOLERANT; + } + CONTRACTL_END; + + // This can + Thread * pThread; + pThread = GetThreadNULLOk(); + + // First check for a real helper thread. This will do a FLS access. + bool fIsHelperThread = !!IsDbgHelperSpecialThread(); + if (fIsHelperThread) + { + // If we're on the real helper thread, we never run managed code + // and so we'd better not have an EE thread object. + _ASSERTE((pThread == NULL) || !"The helper thread should not being running managed code.\n" + "Are you running managed code inside the dllmain? If so, your scenario is invalid and this" + "assert is only the tip of the iceberg.\n"); + return true; + } + + // Even if we're not on the real helper thread, we may still be on a thread + // pretending to be the helper. (Helper Duty, etc). + DWORD id = GetCurrentThreadId(); + + // Check for temporary helper thread. + if (ThisIsTempHelperThread(id)) + { + return true; + } + + return false; +} + +// +// Make call to the static method. +// This is exposed to the contracts susbsystem so that the helper thread can call +// things on MODE_COOPERATIVE. +// +bool Debugger::ThisIsHelperThread(void) +{ + WRAPPER_NO_CONTRACT; + + return ThisIsHelperThreadWorker(); +} + +// Check if we're the temporary helper thread. Have 2 forms of this, 1 that assumes the current +// thread (but has the overhead of an extra call to GetCurrentThreadId() if we laready know the tid. +bool ThisIsTempHelperThread() +{ + WRAPPER_NO_CONTRACT; + + DWORD id = GetCurrentThreadId(); + return ThisIsTempHelperThread(id); +} + +bool ThisIsTempHelperThread(DWORD tid) +{ + WRAPPER_NO_CONTRACT; + + // If helper thread class isn't created, then there's no helper thread. + // No one is doing helper thread duty either. + // It's also possible we're in a shutdown case and have already deleted the + // data for the helper thread. + if (g_pRCThread != NULL) + { + // May be the temporary helper thread... + DebuggerIPCControlBlock * pBlock = g_pRCThread->GetDCB(); + if (pBlock != NULL) + { + DWORD idTemp = pBlock->m_temporaryHelperThreadId; + + if (tid == idTemp) + { + return true; + } + } + } + return false; + +} + + +// This function is called when host call ICLRSecurityAttributeManager::setDacl. +// It will redacl our SSE, RSEA, RSER events. +HRESULT Debugger::ReDaclEvents(PSECURITY_DESCRIPTOR securityDescriptor) +{ + WRAPPER_NO_CONTRACT; + + return m_pRCThread->ReDaclEvents(securityDescriptor); +} + +/* static */ +void Debugger::AcquireDebuggerDataLock(Debugger *pDebugger) +{ + WRAPPER_NO_CONTRACT; + + if (!g_fProcessDetach) + { + pDebugger->GetDebuggerDataLock()->Enter(); + } +} + +/* static */ +void Debugger::ReleaseDebuggerDataLock(Debugger *pDebugger) +{ + WRAPPER_NO_CONTRACT; + + if (!g_fProcessDetach) + { + pDebugger->GetDebuggerDataLock()->Leave(); + } +} + + +#else // DACCESS_COMPILE + +// determine whether the LS holds the data lock. If it does, we will assume the locked data is in an +// inconsistent state and will throw an exception. The DAC will execute this if we are executing code +// that takes the lock. +// Arguments: input: pDebugger - the LS debugger data structure +/* static */ +void Debugger::AcquireDebuggerDataLock(Debugger *pDebugger) +{ + SUPPORTS_DAC; + + if (pDebugger->GetDebuggerDataLock()->GetEnterCount() != 0) + { + ThrowHR(CORDBG_E_PROCESS_NOT_SYNCHRONIZED); + } +} + +void Debugger::ReleaseDebuggerDataLock(Debugger *pDebugger) +{ +} +#endif // DACCESS_COMPILE + +/* ------------------------------------------------------------------------ * + * Functions for DebuggerHeap executable memory allocations + * ------------------------------------------------------------------------ */ + +DebuggerHeapExecutableMemoryAllocator::~DebuggerHeapExecutableMemoryAllocator() +{ + while (m_pages != NULL) + { + DebuggerHeapExecutableMemoryPage *temp = m_pages->GetNextPage(); + + // Free this page + INDEBUG(BOOL ret =) VirtualFree(m_pages, 0, MEM_RELEASE); + ASSERT(ret == TRUE); + + m_pages = temp; + } + + ASSERT(m_pages == NULL); +} + +void* DebuggerHeapExecutableMemoryAllocator::Allocate(DWORD numberOfBytes) +{ + if (numberOfBytes > DBG_MAX_EXECUTABLE_ALLOC_SIZE) + { + ASSERT(!"Allocating more than DBG_MAX_EXECUTABLE_ALLOC_SIZE at once is unsupported and breaks our assumptions."); + return NULL; + } + + if (numberOfBytes == 0) + { + // Should we allocate anything in this case? + ASSERT(!"Allocate called with 0 for numberOfBytes!"); + return NULL; + } + + CrstHolder execMemAllocCrstHolder(&m_execMemAllocMutex); + + int chunkToUse = -1; + DebuggerHeapExecutableMemoryPage *pageToAllocateOn = NULL; + for (DebuggerHeapExecutableMemoryPage *currPage = m_pages; currPage != NULL; currPage = currPage->GetNextPage()) + { + if (CheckPageForAvailability(currPage, &chunkToUse)) + { + pageToAllocateOn = currPage; + break; + } + } + + if (pageToAllocateOn == NULL) + { + // No existing page had availability, so create a new page and use that. + pageToAllocateOn = AddNewPage(); + if (pageToAllocateOn == NULL) + { + ASSERT(!"Call to AddNewPage failed!"); + return NULL; + } + + if (!CheckPageForAvailability(pageToAllocateOn, &chunkToUse)) + { + ASSERT(!"No availability on new page?"); + return NULL; + } + } + + return ChangePageUsage(pageToAllocateOn, chunkToUse, ChangePageUsageAction::ALLOCATE); +} + +int DebuggerHeapExecutableMemoryAllocator::Free(void* addr) +{ + ASSERT(addr != NULL); + + CrstHolder execMemAllocCrstHolder(&m_execMemAllocMutex); + + DebuggerHeapExecutableMemoryPage *pageToFreeIn = static_cast(addr)->data.startOfPage; + + if (pageToFreeIn == NULL) + { + ASSERT(!"Couldn't locate page in which to free!"); + return -1; + } + + int chunkNum = static_cast(addr)->data.chunkNumber; + + // Sanity check: assert that the address really represents the start of a chunk. + ASSERT(((uint64_t)addr - (uint64_t)pageToFreeIn) % 64 == 0); + + ChangePageUsage(pageToFreeIn, chunkNum, ChangePageUsageAction::FREE); + + return 0; +} + +DebuggerHeapExecutableMemoryPage* DebuggerHeapExecutableMemoryAllocator::AddNewPage() +{ + void* newPageAddr = VirtualAlloc(NULL, sizeof(DebuggerHeapExecutableMemoryPage), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + + DebuggerHeapExecutableMemoryPage *newPage = new (newPageAddr) DebuggerHeapExecutableMemoryPage; + newPage->SetNextPage(m_pages); + + // Add the new page to the linked list of pages + m_pages = newPage; + return newPage; +} + +bool DebuggerHeapExecutableMemoryAllocator::CheckPageForAvailability(DebuggerHeapExecutableMemoryPage* page, /* _Out_ */ int* chunkToUse) +{ + uint64_t occupancy = page->GetPageOccupancy(); + bool available = occupancy != UINT64_MAX; + + if (!available) + { + if (chunkToUse) + { + *chunkToUse = -1; + } + + return false; + } + + if (chunkToUse) + { + // Start i at 62 because first chunk is reserved + for (int i = 62; i >= 0; i--) + { + uint64_t mask = ((uint64_t)1 << i); + if ((mask & occupancy) == 0) + { + *chunkToUse = 64 - i - 1; + break; + } + } + } + + return true; +} + +void* DebuggerHeapExecutableMemoryAllocator::ChangePageUsage(DebuggerHeapExecutableMemoryPage* page, int chunkNumber, ChangePageUsageAction action) +{ + ASSERT(action == ChangePageUsageAction::ALLOCATE || action == ChangePageUsageAction::FREE); + + uint64_t mask = (uint64_t)0x1 << (64 - chunkNumber - 1); + + uint64_t prevOccupancy = page->GetPageOccupancy(); + uint64_t newOccupancy = (action == ChangePageUsageAction::ALLOCATE) ? (prevOccupancy | mask) : (prevOccupancy ^ mask); + page->SetPageOccupancy(newOccupancy); + + return page->GetPointerToChunk(chunkNumber); +} + +/* ------------------------------------------------------------------------ * + * DebuggerHeap impl + * ------------------------------------------------------------------------ */ + +DebuggerHeap::DebuggerHeap() +{ +#ifdef USE_INTEROPSAFE_HEAP + m_hHeap = NULL; +#endif + m_fExecutable = FALSE; +} + +DebuggerHeap::~DebuggerHeap() +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + Destroy(); +} + +void DebuggerHeap::Destroy() +{ +#ifdef USE_INTEROPSAFE_HEAP + if (IsInit()) + { + ::HeapDestroy(m_hHeap); + m_hHeap = NULL; + } +#endif +#ifdef FEATURE_PAL + if (m_execMemAllocator != NULL) + { + delete m_execMemAllocator; + } +#endif +} + +bool DebuggerHeap::IsInit() +{ + LIMITED_METHOD_CONTRACT; +#ifdef USE_INTEROPSAFE_HEAP + return m_hHeap != NULL; +#else + return true; +#endif +} + +HRESULT DebuggerHeap::Init(BOOL fExecutable) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Have knob catch if we don't want to lazy init the debugger. + _ASSERTE(!g_DbgShouldntUseDebugger); + m_fExecutable = fExecutable; + +#ifdef USE_INTEROPSAFE_HEAP + // If already inited, then we're done. + // We normally don't double-init. However, we may oom between when we allocate the heap and when we do other initialization. + // We don't worry about backout code to free the heap. Rather, we'll just leave it alive and nop if we try to allocate it again. + if (IsInit()) + { + return S_OK; + } + +#ifndef HEAP_CREATE_ENABLE_EXECUTE +#define HEAP_CREATE_ENABLE_EXECUTE 0x00040000 // winnt create heap with executable pages +#endif + + // Create a standard, grow-able, thread-safe heap. + DWORD dwFlags = ((fExecutable == TRUE)? HEAP_CREATE_ENABLE_EXECUTE : 0); + m_hHeap = ::HeapCreate(dwFlags, 0, 0); + if (m_hHeap == NULL) + { + return HRESULT_FROM_GetLastError(); + } +#endif + +#ifdef FEATURE_PAL + m_execMemAllocator = new (nothrow) DebuggerHeapExecutableMemoryAllocator(); + ASSERT(m_execMemAllocator != NULL); + if (m_execMemAllocator == NULL) + { + return E_OUTOFMEMORY; + } +#endif + + return S_OK; +} + +// Only use canaries on x86 b/c they throw of alignment on Ia64. +#if defined(_DEBUG) && defined(_TARGET_X86_) +#define USE_INTEROPSAFE_CANARY +#endif + +#ifdef USE_INTEROPSAFE_CANARY +// Small header to to prefix interop-heap blocks. +// This lets us enforce that we don't delete interopheap data from a non-interop heap. +struct InteropHeapCanary +{ + ULONGLONG m_canary; + + // Raw address - this is what the heap alloc + free routines use. + // User address - this is what the user sees after we adjust the raw address for the canary + + // Given a raw address to an allocated block, get the canary + mark it. + static InteropHeapCanary * GetFromRawAddr(void * pStart) + { + _ASSERTE(pStart != NULL); + InteropHeapCanary * p = (InteropHeapCanary*) pStart; + p->Mark(); + return p; + } + + // Get the raw address from this canary. + void * GetRawAddr() + { + return (void*) this; + } + + // Get a canary from a start address. + static InteropHeapCanary * GetFromUserAddr(void * pStart) + { + _ASSERTE(pStart != NULL); + InteropHeapCanary * p = ((InteropHeapCanary*) pStart)-1; + p->Check(); + return p; + } + void * GetUserAddr() + { + this->Check(); + return (void*) (this + 1); + } + +protected: + void Check() + { + CONSISTENCY_CHECK_MSGF((m_canary == kInteropHeapCookie), + ("Using InteropSafe delete on non-interopsafe allocated memory.\n")); + } + void Mark() + { + m_canary = kInteropHeapCookie; + } + static const ULONGLONG kInteropHeapCookie = 0x12345678; +}; +#endif // USE_INTEROPSAFE_CANARY + +void *DebuggerHeap::Alloc(DWORD size) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifdef USE_INTEROPSAFE_CANARY + // Make sure we allocate enough space for the canary at the start. + size += sizeof(InteropHeapCanary); +#endif + + void *ret; +#ifdef USE_INTEROPSAFE_HEAP + _ASSERTE(m_hHeap != NULL); + ret = ::HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, size); +#else // USE_INTEROPSAFE_HEAP + + bool allocateOnHeap = true; + HANDLE hExecutableHeap = NULL; + +#ifdef FEATURE_PAL + if (m_fExecutable) + { + allocateOnHeap = false; + ret = m_execMemAllocator->Allocate(size); + } + else + { + hExecutableHeap = ClrGetProcessHeap(); + } +#else // FEATURE_PAL + hExecutableHeap = ClrGetProcessExecutableHeap(); +#endif + + if (allocateOnHeap) + { + if (hExecutableHeap == NULL) + { + return NULL; + } + + ret = ClrHeapAlloc(hExecutableHeap, NULL, S_SIZE_T(size)); + } + +#endif // USE_INTEROPSAFE_HEAP + +#ifdef USE_INTEROPSAFE_CANARY + if (ret == NULL) + { + return NULL; + } + InteropHeapCanary * pCanary = InteropHeapCanary::GetFromRawAddr(ret); + ret = pCanary->GetUserAddr(); +#endif + + return ret; +} + +// Realloc memory. +// If this fails, the original memory is still valid. +void *DebuggerHeap::Realloc(void *pMem, DWORD newSize, DWORD oldSize) +{ + CONTRACTL + { + SO_NOT_MAINLINE; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(pMem != NULL); + _ASSERTE(newSize != 0); + _ASSERTE(oldSize != 0); + +#if defined(USE_INTEROPSAFE_HEAP) && !defined(USE_INTEROPSAFE_CANARY) && !defined(FEATURE_PAL) + // No canaries in this case. + // Call into realloc. + void *ret; + + _ASSERTE(m_hHeap != NULL); + ret = ::HeapReAlloc(m_hHeap, HEAP_ZERO_MEMORY, pMem, newSize); +#else + // impl Realloc on top of alloc & free. + void *ret; + + ret = this->Alloc(newSize); + if (ret == NULL) + { + // Not supposed to free original memory in failure condition. + return NULL; + } + + memcpy(ret, pMem, oldSize); + this->Free(pMem); +#endif + + return ret; +} + +void DebuggerHeap::Free(void *pMem) +{ + CONTRACTL + { + SO_INTOLERANT; + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifdef USE_INTEROPSAFE_CANARY + // Check for canary + + if (pMem != NULL) + { + InteropHeapCanary * pCanary = InteropHeapCanary::GetFromUserAddr(pMem); + pMem = pCanary->GetRawAddr(); + } +#endif + +#ifdef USE_INTEROPSAFE_HEAP + if (pMem != NULL) + { + _ASSERTE(m_hHeap != NULL); + ::HeapFree(m_hHeap, 0, pMem); + } +#else + if (pMem != NULL) + { +#ifndef FEATURE_PAL + HANDLE hProcessExecutableHeap = ClrGetProcessExecutableHeap(); + _ASSERTE(hProcessExecutableHeap != NULL); + ClrHeapFree(hProcessExecutableHeap, NULL, pMem); +#else // !FEATURE_PAL + if(!m_fExecutable) + { + HANDLE hProcessHeap = ClrGetProcessHeap(); + _ASSERTE(hProcessHeap != NULL); + ClrHeapFree(hProcessHeap, NULL, pMem); + } + else + { + INDEBUG(int ret =) m_execMemAllocator->Free(pMem); + _ASSERTE(ret == 0); + } +#endif // !FEATURE_PAL + } +#endif +} + +#ifndef DACCESS_COMPILE + + +// Undef this so we can call them from the EE versions. +#undef UtilMessageBoxVA + +// Message box API for the left side of the debugger. This API handles calls from the +// debugger helper thread as well as from normal EE threads. It is the only one that +// should be used from inside the debugger left side. +int Debugger::MessageBox( + UINT uText, // Resource Identifier for Text message + UINT uCaption, // Resource Identifier for Caption + UINT uType, // Style of MessageBox + BOOL displayForNonInteractive, // Display even if the process is running non interactive + BOOL showFileNameInTitle, // Flag to show FileName in Caption + ...) // Additional Arguments +{ + CONTRACTL + { + MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT; + MODE_PREEMPTIVE; + NOTHROW; + + PRECONDITION(ThisMaybeHelperThread()); + } + CONTRACTL_END; + + va_list marker; + va_start(marker, showFileNameInTitle); + + // Add the MB_TASKMODAL style to indicate that the dialog should be displayed on top of the windows + // owned by the current thread and should prevent interaction with them until dismissed. + uType |= MB_TASKMODAL; + + int result = UtilMessageBoxVA(NULL, uText, uCaption, uType, displayForNonInteractive, showFileNameInTitle, marker); + va_end( marker ); + + return result; +} + +// Redefine this to an error just in case code is added after this point in the file. +#define UtilMessageBoxVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") + +#else // DACCESS_COMPILE +void +Debugger::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + DAC_ENUM_VTHIS(); + SUPPORTS_DAC; + _ASSERTE(m_rgHijackFunction != NULL); + + if ( flags != CLRDATA_ENUM_MEM_TRIAGE) + { + if (m_pMethodInfos.IsValid()) + { + m_pMethodInfos->EnumMemoryRegions(flags); + } + + DacEnumMemoryRegion(dac_cast(m_pLazyData), + sizeof(DebuggerLazyInit)); + } + + // Needed for stack walking from an initial native context. If the debugger can find the + // on-disk image of clr.dll, then this is not necessary. + DacEnumMemoryRegion(dac_cast(m_rgHijackFunction), sizeof(MemoryRange)*kMaxHijackFunctions); +} + + +// This code doesn't hang out in Frame/TransitionFrame/FuncEvalFrame::EnumMemoryRegions() like it would +// for other normal VM objects because we don't want to have code in VM directly referencing LS types. +// Frames.h's FuncEvalFrame simply does a forward decl of DebuggerEval and gets away with it because it +// never does anything but a cast of a TADDR. +void +Debugger::EnumMemoryRegionsIfFuncEvalFrame(CLRDataEnumMemoryFlags flags, Frame * pFrame) +{ + SUPPORTS_DAC; + + if ((pFrame != NULL) && (pFrame->GetFrameType() == Frame::TYPE_FUNC_EVAL)) + { + FuncEvalFrame * pFEF = dac_cast(pFrame); + DebuggerEval * pDE = pFEF->GetDebuggerEval(); + + if (pDE != NULL) + { + DacEnumMemoryRegion(dac_cast(pDE), sizeof(DebuggerEval), true); + + if (pDE->m_debuggerModule != NULL) + DacEnumMemoryRegion(dac_cast(pDE->m_debuggerModule), sizeof(DebuggerModule), true); + } + } +} + +#endif // #ifdef DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +void Debugger::StartCanaryThread() +{ + // we need to already have the rcthread running and the pointer stored + _ASSERTE(m_pRCThread != NULL && g_pRCThread == m_pRCThread); + _ASSERTE(m_pRCThread->GetDCB() != NULL); + _ASSERTE(GetCanary() != NULL); + + GetCanary()->Init(); +} +#endif // DACCESS_COMPILE + +#endif //DEBUGGING_SUPPORTED -- cgit v1.2.3