summaryrefslogtreecommitdiff
path: root/src/debug/ee/debugger.cpp
diff options
context:
space:
mode:
authorJiyoung Yun <jy910.yun@samsung.com>2016-11-23 10:09:09 (GMT)
committerJiyoung Yun <jy910.yun@samsung.com>2016-11-23 10:09:09 (GMT)
commit4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch)
tree98110734c91668dfdbb126fcc0e15ddbd93738ca /src/debug/ee/debugger.cpp
parentfa45f57ed55137c75ac870356a1b8f76c84b229c (diff)
downloadcoreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/debug/ee/debugger.cpp')
-rw-r--r--src/debug/ee/debugger.cpp17073
1 files changed, 17073 insertions, 0 deletions
diff --git a/src/debug/ee/debugger.cpp b/src/debug/ee/debugger.cpp
new file mode 100644
index 0000000..a06811c
--- /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 <limits.h>
+#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<TypeHandle *> (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<PTR_VOID>(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
+ // <Event Name>=Count;<Event Name>=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++ <m_iEntries)
+ {
+ dcp = (DebuggerMethodInfoEntry*)&(((DebuggerMethodInfoEntry *)m_pcEntries)[i]);
+ if(dcp->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<DebuggerIPCEvent *>(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("<xml>See the description</xml>"));
+
+ 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>@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.</TODO>
+ //
+ 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>@Todo If we're passed 0 for the newAddress param, the jit has been
+// cancelled & should be undone.</TODO>
+ ******************************************************************************/
+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<PTR_CBYTE>(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<UINT> rguiILOffsetTemp = new (nothrow) UINT[cMap];
+ if (rguiILOffsetTemp == NULL)
+ return E_OUTOFMEMORY;
+
+ NewArrayHolder<UINT> 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<USHORT>(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<PTR_CORDB_ADDRESS_TYPE>(g_pEEInterface->GetFunctionAddress(md));
+ }
+ else
+ {
+ _ASSERTE(!md ||
+ (addr == dac_cast<PTR_CORDB_ADDRESS_TYPE>(g_pEEInterface->GetFunctionAddress(md))));
+ }
+
+ if (addr)
+ {
+ PCODE pCode = (PCODE)dac_cast<TADDR>(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<ISymUnmanagedReader> 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.
+ // <TODO>@PERF: optimize - hold on to this so we don't rebuild it for both
+ // CanSetIP & SetIP.</TODO>
+ 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
+ {
+ // <NOTE WIN64>
+ // All duplicate sequence points (ones with the same IL offset) should have the same SourceTypes.
+ // </NOTE WIN64>
+ 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++;
+ }
+
+ // <TODO>
+ // What should we do in this case?
+ // </TODO>
+ /*
+ 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>@todo Replace array with hashtable for improved efficiency</TODO>
+// <TODO>@todo Need to factor code,so that we can selectively map forward DFK(ilOFfset) BPs</TODO>
+ ******************************************************************************/
+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.
+ // <NICE> under exactly what scenarios (EnC, code pitching etc.) will this apply?... </NICE>
+ // <NICE> can't we be a little clearer about why we don't want to bind the patch in this arcance situation?</NICE>
+ 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).
+ // <TODO> 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.</TODO>
+ // 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>@todo-postponed: EnC: Make sure that this doesn't cause
+ // the patch-table to shift.</TODO>
+ 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
+ {
+ // <TODO>
+ // 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
+ // </TODO>
+ _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>@todo APPD if we want true isolation, remove this & finish the work</TODO>
+ 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<DebuggerBreakpoint*> (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<ULONG64>(&s_DebuggerLaunchJitInfoExceptionRecord);
+ s_DebuggerLaunchJitInfo.lpContextRecord = reinterpret_cast<ULONG64>(&s_DebuggerLaunchJitInfoContext);
+ s_DebuggerLaunchJitInfo.lpExceptionAddress = s_DebuggerLaunchJitInfoExceptionRecord.ExceptionAddress != NULL ?
+ reinterpret_cast<ULONG64>(s_DebuggerLaunchJitInfoExceptionRecord.ExceptionAddress) :
+ reinterpret_cast<ULONG64>(reinterpret_cast<PVOID>(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<WCHAR*> (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);
+
+ // <TODO>
+ // 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.
+ // </TODO>
+ 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<PVOID>(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<PTR_VOID>(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<Frame*>(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<PTR_BYTE>(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<IMDInternalEmit> 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>@todo memory allocation - bad if we're synced</TODO>
+ 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>@todo memory allocation - bad if we're synced</TODO>
+ 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>@todo memory allocation - bad if we're synced</TODO>
+
+ DebuggerBreakpoint * pDebuggerBP = pEvent->BreakpointData.breakpointToken.UnWrap();
+
+ pDebuggerBP->Delete();
+ }
+ break;
+
+ case DB_IPCE_STEP_CANCEL:
+ {
+ // <TODO>@todo memory allocation - bad if we're synced</TODO>
+ 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<PCODE>(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<DWORD>::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<SIZE_T>::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;
+ }
+
+ // <TODO>
+ //
+ // 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;
+ }
+ // </TODO>
+ 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<CONTEXT **>(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<FaultingExceptionFrame> 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(&params);
+
+ // 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<DomainAssembly *> 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("<NoProcessName>"));
+ 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>@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</TODO>
+ 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<DT_CONTEXT *>(&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<DebuggerHeapExecutableMemoryChunk*>(addr)->data.startOfPage;
+
+ if (pageToFreeIn == NULL)
+ {
+ ASSERT(!"Couldn't locate page in which to free!");
+ return -1;
+ }
+
+ int chunkNum = static_cast<DebuggerHeapExecutableMemoryChunk*>(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<TADDR>(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<TADDR>(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<PTR_FuncEvalFrame>(pFrame);
+ DebuggerEval * pDE = pFEF->GetDebuggerEval();
+
+ if (pDE != NULL)
+ {
+ DacEnumMemoryRegion(dac_cast<TADDR>(pDE), sizeof(DebuggerEval), true);
+
+ if (pDE->m_debuggerModule != NULL)
+ DacEnumMemoryRegion(dac_cast<TADDR>(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