diff options
Diffstat (limited to 'src/debug/ee/debugger.h')
-rw-r--r-- | src/debug/ee/debugger.h | 3981 |
1 files changed, 3981 insertions, 0 deletions
diff --git a/src/debug/ee/debugger.h b/src/debug/ee/debugger.h new file mode 100644 index 0000000000..6368647946 --- /dev/null +++ b/src/debug/ee/debugger.h @@ -0,0 +1,3981 @@ +// 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.h +// + +// +// Header file for Runtime Controller classes of the COM+ Debugging Services. +// +//***************************************************************************** + +#ifndef DEBUGGER_H_ +#define DEBUGGER_H_ + +#include <windows.h> + +#include <utilcode.h> + +#include <metahost.h> + +#if defined(_DEBUG) && !defined(DACCESS_COMPILE) +#define LOGGING +#endif + +#include <log.h> + +#include "cor.h" +#include "corpriv.h" +#include "daccess.h" + +#include "common.h" +#include "winwrap.h" +#include "threads.h" +#include "frames.h" + +#include "appdomain.hpp" +#include "eedbginterface.h" +#include "dbginterface.h" +#include "corhost.h" + + +#include "corjit.h" +#include <dbgmeta.h> // <TODO>need to rip this out of here...</TODO> + +#include "frameinfo.h" + +#include "dllimportcallback.h" + +#include "canary.h" + +#undef ASSERT +#define CRASH(x) _ASSERTE(!x) +#define ASSERT(x) _ASSERTE(x) + + +#ifndef TRACE_MEMORY +#define TRACE_MEMORY 0 +#endif + +#if TRACE_MEMORY +#define TRACE_ALLOC(p) LOG((LF_CORDB, LL_INFO10000, \ + "--- Allocated %x at %s:%d\n", p, __FILE__, __LINE__)); +#define TRACE_FREE(p) LOG((LF_CORDB, LL_INFO10000, \ + "--- Freed %x at %s:%d\n", p, __FILE__, __LINE__)); +#else +#define TRACE_ALLOC(p) +#define TRACE_FREE(p) +#endif + +typedef CUnorderedArray<void*,11> UnorderedPtrArray; + +/* ------------------------------------------------------------------------ * + * Forward class declarations + * ------------------------------------------------------------------------ */ + +class DebuggerFrame; +class DebuggerModule; +class DebuggerModuleTable; +class Debugger; +class DebuggerBreakpoint; +class DebuggerPendingFuncEvalTable; +class DebuggerRCThread; +class DebuggerStepper; +class DebuggerMethodInfo; +class DebuggerJitInfo; +class DebuggerMethodInfoTable; +struct DebuggerControllerPatch; +class DebuggerEval; +class DebuggerControllerQueue; +class DebuggerController; +class Crst; + +typedef CUnorderedArray<DebuggerControllerPatch *, 17> PATCH_UNORDERED_ARRAY; +template<class T> void DeleteInteropSafe(T *p); +template<class T> void DeleteInteropSafeExecutable(T *p); + +typedef VPTR(class Debugger) PTR_Debugger; +typedef DPTR(struct DebuggerILToNativeMap) PTR_DebuggerILToNativeMap; +typedef DPTR(class DebuggerMethodInfo) PTR_DebuggerMethodInfo; +typedef VPTR(class DebuggerMethodInfoTable) PTR_DebuggerMethodInfoTable; +typedef DPTR(class DebuggerJitInfo) PTR_DebuggerJitInfo; +typedef DPTR(class DebuggerEval) PTR_DebuggerEval; +typedef DPTR(struct DebuggerIPCControlBlock) PTR_DebuggerIPCControlBlock; + + +/* ------------------------------------------------------------------------ * + * Global variables + * ------------------------------------------------------------------------ */ + +GPTR_DECL(Debugger, g_pDebugger); +GPTR_DECL(EEDebugInterface, g_pEEInterface); +#ifndef FEATURE_PAL +GVAL_DECL(HANDLE, g_hContinueStartupEvent); +#endif +extern DebuggerRCThread *g_pRCThread; + +//--------------------------------------------------------------------------------------- +// Holder to ensure our calls to IncThreadsAtUnsafePlaces and DecThreadsAtUnsafePlaces +class AtSafePlaceHolder +{ +public: + AtSafePlaceHolder(Thread * pThread); + + // Clear the holder. + ~AtSafePlaceHolder(); + + // True if the holder is acquired. + bool IsAtUnsafePlace(); + + // Clear the holder (call DecThreadsAtUnsafePlaces if needed) + void Clear(); + +private: + // If this is non-null, then the holder incremented the unsafe counter and it needs + // to decrement it. + Thread * m_pThreadAtUnsafePlace; +}; + + +template<BOOL COOPERATIVE, BOOL TOGGLE, BOOL IFTHREAD> +class GCHolderEEInterface +{ +public: + DEBUG_NOINLINE GCHolderEEInterface(); + DEBUG_NOINLINE ~GCHolderEEInterface(); +}; + +#ifndef DACCESS_COMPILE +template<BOOL TOGGLE, BOOL IFTHREAD> +class GCHolderEEInterface<TRUE, TOGGLE, IFTHREAD> +{ +private: + bool startInCoop; + +public: + DEBUG_NOINLINE GCHolderEEInterface() + { + SCAN_SCOPE_BEGIN; + STATIC_CONTRACT_MODE_COOPERATIVE; + + if (IFTHREAD && g_pEEInterface->GetThread() == NULL) + { + return; + } + + startInCoop = false; + + if (g_pEEInterface->IsPreemptiveGCDisabled()) + { + // we're starting in COOP, no need to switch + startInCoop = true; + } + else + { + // we're starting in PREEMP, need to switch to COOP + startInCoop = false; + g_pEEInterface->DisablePreemptiveGC(); + } + }; + + DEBUG_NOINLINE ~GCHolderEEInterface() + { + SCAN_SCOPE_END; + + if (IFTHREAD && g_pEEInterface->GetThread() == NULL) + { + return; + } + + _ASSERT(g_pEEInterface->IsPreemptiveGCDisabled()); + + if (TOGGLE) + { + // We're in COOP, toggle to PREEMPTIVE and back to COOP + // for synch purposes. + g_pEEInterface->EnablePreemptiveGC(); + g_pEEInterface->DisablePreemptiveGC(); + + // If we started in PREEMPTIVE switch back + if (!startInCoop) + { + g_pEEInterface->EnablePreemptiveGC(); + } + } + else + { + // If we started in PREEMPTIVE switch back + if (!startInCoop) + { + g_pEEInterface->EnablePreemptiveGC(); + } + } + }; +}; + +template<BOOL TOGGLE, BOOL IFTHREAD> +class GCHolderEEInterface<FALSE, TOGGLE, IFTHREAD> +{ +private: + bool startInCoop; + bool conditional; + + void EnterInternal(bool bStartInCoop, bool bConditional) + { + startInCoop = bStartInCoop; + conditional = bConditional; + + if (!conditional || (IFTHREAD && g_pEEInterface->GetThread() == NULL)) + { + return; + } + + if (g_pEEInterface->IsPreemptiveGCDisabled()) + { + // we're starting in COOP, we need to switch to PREEMP + startInCoop = true; + g_pEEInterface->EnablePreemptiveGC(); + } + else + { + // We're starting in PREEMP, no need to switch + startInCoop = false; + } + } + + void LeaveInternal() + { + if (!conditional || (IFTHREAD && g_pEEInterface->GetThread() == NULL)) + { + return; + } + + _ASSERTE(!g_pEEInterface->IsPreemptiveGCDisabled()); + + if (TOGGLE) + { + // Explicitly toggle to COOP for eventin + g_pEEInterface->DisablePreemptiveGC(); + + // If we started in PREEMPTIVE switch back to PREEMPTIVE + if (!startInCoop) + { + g_pEEInterface->EnablePreemptiveGC(); + } + } + else + { + // If we started in COOP, flip back to COOP at the end of the + // scope, if we started in preemptive we should be fine. + if (startInCoop) + { + g_pEEInterface->DisablePreemptiveGC(); + } + } + } + +public: + DEBUG_NOINLINE GCHolderEEInterface() + { + SCAN_SCOPE_BEGIN; + STATIC_CONTRACT_MODE_PREEMPTIVE; + + this->EnterInternal(false, true); + } + + DEBUG_NOINLINE GCHolderEEInterface(bool bConditional) + { + SCAN_SCOPE_BEGIN; + if (bConditional) + { + STATIC_CONTRACT_MODE_PREEMPTIVE; + } + + this->EnterInternal(false, bConditional); + } + + DEBUG_NOINLINE ~GCHolderEEInterface() + { + SCAN_SCOPE_END; + + this->LeaveInternal(); + }; +}; +#endif //DACCESS_COMPILE + +#define GCX_COOP_EEINTERFACE() \ + GCHolderEEInterface<TRUE, FALSE, FALSE> __gcCoop_onlyOneAllowedPerScope + +#define GCX_PREEMP_EEINTERFACE() \ + GCHolderEEInterface<FALSE, FALSE, FALSE> __gcCoop_onlyOneAllowedPerScope + +#define GCX_COOP_EEINTERFACE_TOGGLE() \ + GCHolderEEInterface<TRUE, TRUE, FALSE> __gcCoop_onlyOneAllowedPerScope + +#define GCX_PREEMP_EEINTERFACE_TOGGLE() \ + GCHolderEEInterface<FALSE, TRUE, FALSE> __gcCoop_onlyOneAllowedPerScope + +#define GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD() \ + GCHolderEEInterface<FALSE, TRUE, TRUE> __gcCoop_onlyOneAllowedPerScope + +#define GCX_PREEMP_EEINTERFACE_TOGGLE_COND(cond) \ + GCHolderEEInterface<FALSE, TRUE, FALSE> __gcCoop_onlyOneAllowedPerScope((cond)) + +#define GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD_COND(cond) \ + GCHolderEEInterface<FALSE, TRUE, TRUE> __gcCoop_onlyOneAllowedPerScope((cond)) + + + +// There are still some APIs that call new that we call from the helper thread. +// These are unsafe operations, so we wrap them here. Each of these is a potential hang. +inline DWORD UnsafeGetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue) +{ + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + return REGUTIL::GetConfigDWORD_DontUse_(name, defValue); +} + +inline DWORD UnsafeGetConfigDWORD(const CLRConfig::ConfigDWORDInfo & info) +{ + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + return CLRConfig::GetConfigValue(info); +} + +#define FILE_DEBUG INDEBUG(__FILE__) NOT_DEBUG(NULL) +#define LINE_DEBUG INDEBUG(__LINE__) NOT_DEBUG(0) + +#define CORDBDebuggerSetUnrecoverableWin32Error(__d, __code, __w) \ + ((__d)->UnrecoverableError(HRESULT_FROM_WIN32(GetLastError()), \ + (__code), FILE_DEBUG, LINE_DEBUG, (__w)), \ + HRESULT_FROM_GetLastError()) + +#define CORDBDebuggerSetUnrecoverableError(__d, __hr, __w) \ + (__d)->UnrecoverableError((__hr), \ + (__hr), FILE_DEBUG, LINE_DEBUG, (__w)) + +#define CORDBUnrecoverableError(__d) ((__d)->m_unrecoverableError == TRUE) + +/* ------------------------------------------------------------------------ * + * Helpers used for contract preconditions. + * ------------------------------------------------------------------------ */ + + +bool ThisIsHelperThreadWorker(void); +bool ThisIsTempHelperThread(); +bool ThisIsTempHelperThread(DWORD tid); + +#ifdef _DEBUG + +// Functions can be split up into 3 categories: +// 1.) Functions that must run on the helper thread. +// Returns true if this is the helper thread (or the thread +// doing helper-threadduty). + +// 2.) Functions that can't run on the helper thread. +// This is just !ThisIsHelperThread(); + +// 3.) Functions that may or may not run on the helper thread. +// Note this is trivially true, but it's presences means that +// we're not case #1 or #2, so it's still valuable. +inline bool ThisMaybeHelperThread() { return true; } + +#endif + + +// These are methods for transferring information between a REGDISPLAY and +// a DebuggerREGDISPLAY. +extern void CopyREGDISPLAY(REGDISPLAY* pDst, REGDISPLAY* pSrc); +extern void SetDebuggerREGDISPLAYFromREGDISPLAY(DebuggerREGDISPLAY* pDRD, REGDISPLAY* pRD); + +// +// PUSHED_REG_ADDR gives us NULL if the register still lives in the thread's context, or it gives us the address +// of where the register was pushed for this frame. +// +// This macro is used in CopyREGDISPLAY() and SetDebuggerREGDISPLAYFromREGDISPLAY(). We really should make +// DebuggerREGDISPLAY to be a class with these two methods, but unfortunately, the RS has no notion of REGDISPLAY. +inline LPVOID PushedRegAddr(REGDISPLAY* pRD, LPVOID pAddr) +{ + LIMITED_METHOD_CONTRACT; + +#if defined(_TARGET_AMD64_) + if ( ((UINT_PTR)(pAddr) >= (UINT_PTR)pRD->pCurrentContextPointers) && + ((UINT_PTR)(pAddr) <= ((UINT_PTR)pRD->pCurrentContextPointers + sizeof(_KNONVOLATILE_CONTEXT_POINTERS))) ) +#else + if ( ((UINT_PTR)(pAddr) >= (UINT_PTR)pRD->pContext) && + ((UINT_PTR)(pAddr) <= ((UINT_PTR)pRD->pContext + sizeof(T_CONTEXT))) ) +#endif + return NULL; + + // (Microsoft 2/9/07 - putting this in an else clause confuses gcc for some reason, so I've moved + // it to here) + return pAddr; +} + +bool HandleIPCEventWrapper(Debugger* pDebugger, DebuggerIPCEvent *e); + +HRESULT ValidateObject(Object *objPtr); + +//----------------------------------------------------------------------------- +// Execution control needs several ways to get at the context of a thread +// stopped in mangaged code (stepping, setip, func-eval). +// We want to abstract away a few things: +// - active: this thread is stopped at a patch +// - inactive: this threads was managed suspended somewhere in jitted code +// because of some other active thread. +// +// In general, execution control operations administered from the helper thread +// can occur on any managed thread (active or inactive). +// Intermediate triggers (eg, TriggerPatch) only occur on an active thread. +// +// Viewing the context in terms of Active vs. Inactive lets us abstract away +// filter context, redirected context, and interop hijacks. +//----------------------------------------------------------------------------- + +// Get the context for a thread stopped (perhaps temporarily) in managed code. +// The process may be live or stopped. +// This thread could be 'active' (stopped at patch) or inactive. +// This context should always be in managed code and this context can be manipulated +// for execution control (setip, single-step, func-eval, etc) +// Returns NULL if not available. +CONTEXT * GetManagedStoppedCtx(Thread * pThread); + +// Get the context for a thread live in or around managed code. +// Caller guarantees this is active. +// This ctx is just for a 'live' thread. This means that the ctx may include +// from a M2U hijack or from a Native patch (like . +// Never NULL. +CONTEXT * GetManagedLiveCtx(Thread * pThread); + + +#undef UtilMessageBoxCatastrophic +#undef UtilMessageBoxCatastrophicNonLocalized +#undef UtilMessageBoxCatastrophicVA +#undef UtilMessageBoxCatastrophicNonLocalizedVA +#undef UtilMessageBox +#undef UtilMessageBoxNonLocalized +#undef UtilMessageBoxVA +#undef UtilMessageBoxNonLocalizedVA +#undef WszMessageBox +#define UtilMessageBoxCatastrophic __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define UtilMessageBoxCatastrophicNonLocalized __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define UtilMessageBoxCatastrophicVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define UtilMessageBoxCatastrophicNonLocalizedVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define UtilMessageBox __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define UtilMessageBoxNonLocalized __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define UtilMessageBoxVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define UtilMessageBoxNonLocalizedVA __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") +#define WszMessageBox __error("Use g_pDebugger->MessageBox from inside the left side of the debugger") + + +/* ------------------------------------------------------------------------ * + * Module classes + * ------------------------------------------------------------------------ */ + +// Once a module / appdomain is unloaded, all Right-side objects (such as breakpoints) +// in that appdomain will get neutered and will thus be prevented from accessing +// the unloaded appdomain. +// +// @dbgtodo jmc - This is now purely relegated to the LS. Eventually completely get rid of this +// by moving fields off to Module or getting rid of the fields completely. +typedef DPTR(class DebuggerModule) PTR_DebuggerModule; +class DebuggerModule +{ + public: + DebuggerModule(Module * pRuntimeModule, DomainFile * pDomainFile, AppDomain * pAppDomain); + + // Do we have any optimized code in the module? + // JMC-probes aren't emitted in optimized code, + bool HasAnyOptimizedCode(); + + // If the debugger updates things to allow/disallow optimized code, then we have to track that. + void MarkAllowedOptimizedCode(); + void UnmarkAllowedOptimizedCode(); + + + BOOL ClassLoadCallbacksEnabled(void); + void EnableClassLoadCallbacks(BOOL f); + + AppDomain* GetAppDomain(); + + Module * GetRuntimeModule(); + + + // <TODO> (8/12/2002) + // Currently we create a new DebuggerModules for each appdomain a shared + // module lives in. We then pretend there aren't any shared modules. + // This is bad. We need to move away from this. + // Once we stop lying, then every module will be it's own PrimaryModule. :) + // + // Currently, Module* is 1:n w/ DebuggerModule. + // We add a notion of PrimaryModule so that: + // Module* is 1:1 w/ DebuggerModule::GetPrimaryModule(); + // This should help transition towards exposing shared modules. + // If the Runtime module is shared, then this gives a common DM. + // If the runtime module is not shared, then this is an identity function. + // + // The runtime has the notion of "DomainFile", which is 1:1 with DebuggerModule + // and thus 1:1 with CordbModule. The CordbModule hash table on the RS now uses + // the DomainFile as the key instead of DebuggerModule. This is a temporary + // workaround to facilitate the removal of DebuggerModule. + // </TODO> + DebuggerModule * GetPrimaryModule(); + DomainFile * GetDomainFile() + { + LIMITED_METHOD_DAC_CONTRACT; + return m_pRuntimeDomainFile; + } + + // Called by DebuggerModuleTable to set our primary module + void SetPrimaryModule(DebuggerModule * pPrimary); + + void SetCanChangeJitFlags(bool fCanChangeJitFlags); + + private: + BOOL m_enableClassLoadCallbacks; + + // First step in moving away from hiding shared modules. + DebuggerModule* m_pPrimaryModule; + + PTR_Module m_pRuntimeModule; + PTR_DomainFile m_pRuntimeDomainFile; + + AppDomain* m_pAppDomain; + + bool m_fHasOptimizedCode; + + void PickPrimaryModule(); + + // Can we change jit flags on the module? + // This is true during the Module creation + bool m_fCanChangeJitFlags; + + +}; + +/* ------------------------------------------------------------------------ * + * Hash to hold pending func evals by thread id + * ------------------------------------------------------------------------ */ + +struct DebuggerPendingFuncEval +{ + FREEHASHENTRY entry; + PTR_Thread pThread; + PTR_DebuggerEval pDE; +}; + +typedef DPTR(struct DebuggerPendingFuncEval) PTR_DebuggerPendingFuncEval; + +/* ------------------------------------------------------------------------ * + * DebuggerRCThread class -- the Runtime Controller thread. + * ------------------------------------------------------------------------ */ + +#define DRCT_CONTROL_EVENT 0 +#define DRCT_RSEA 1 +#define DRCT_FAVORAVAIL 2 +#define DRCT_COUNT_INITIAL 3 + +#define DRCT_DEBUGGER_EVENT 3 +#define DRCT_COUNT_FINAL 4 + + + + + + +// Canary is used as way to have a runtime failure for the SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE +// contract violation. +// Have a macro which checks the canary and then uses the Suppress macro. +// We need this check to be a macro in order to chain to the Suppress_allocation macro. +#define CHECK_IF_CAN_TAKE_HELPER_LOCKS_IN_THIS_SCOPE(pHR, pCanary) \ + { \ + HelperCanary * __pCanary = (pCanary); \ + if (!__pCanary->AreLocksAvailable()) { \ + (*pHR) = CORDBG_E_HELPER_MAY_DEADLOCK; \ + } else { \ + (*pHR) = S_OK; \ + } \ + } \ + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE \ + ; \ + + +// Mechanics for cross-thread call to helper thread (called "Favor"). +class HelperThreadFavor +{ + // Only let RCThread access these fields. + friend class DebuggerRCThread; + + HelperThreadFavor(); + // No dtor because we intentionally leak all shutdown. + void Init(); + +protected: + // Stuff for having the helper thread do function calls for a thread + // that blew its stack + FAVORCALLBACK m_fpFavor; + void *m_pFavorData; + HANDLE m_FavorReadEvent; + Crst m_FavorLock; + + HANDLE m_FavorAvailableEvent; +}; + + +// The *LazyInit classes represents storage that the debugger doesn't need until after it has started up. +// This is effectively an extension to the debugger class; but for perf reasons, we only +// want to instantiate it if we're actually debugging. + +// Fields that are a logical extension of RCThread +class RCThreadLazyInit +{ + // Only let RCThread access these fields. + friend class DebuggerRCThread; + +public: + RCThreadLazyInit() { } + ~RCThreadLazyInit() { } + + void Init() { } +protected: + + + + HelperCanary m_Canary; +}; + +// Fields that are a logical extension of Debugger +class DebuggerLazyInit +{ + friend class Debugger; +public: + DebuggerLazyInit(); + ~DebuggerLazyInit(); + +protected: + void Init(); + + DebuggerPendingFuncEvalTable *m_pPendingEvals; + + // The "debugger data lock" is a very small leaf lock used to protect debugger internal data structures (such + // as DJIs, DMIs, module table). It is a GC-unsafe-anymode lock and so it can't trigger a GC while being held. + // It also can't issue any callbacks into the EE or anycode that it does not directly control. + // This is a separate lock from the the larger Debugger-lock / Controller lock, which allows regions under those + // locks to access debugger datastructures w/o blocking each other. + Crst m_DebuggerDataLock; + HANDLE m_CtrlCMutex; + HANDLE m_exAttachEvent; + HANDLE m_exUnmanagedAttachEvent; + + BOOL m_DebuggerHandlingCtrlC; + + // Used by MapAndBindFunctionBreakpoints. Note that this is thread-safe + // only b/c we access it from within the DebuggerController::Lock + SIZE_T_UNORDERED_ARRAY m_BPMappingDuplicates; + + UnorderedPtrArray m_pMemBlobs; + + // Hang RCThread fields off DebuggerLazyInit to avoid an extra pointer. + RCThreadLazyInit m_RCThread; +}; +typedef DPTR(DebuggerLazyInit) PTR_DebuggerLazyInit; + +class DebuggerRCThread +{ +public: + DebuggerRCThread(Debugger * pDebugger); + virtual ~DebuggerRCThread(); + void CloseIPCHandles(); + + // + // You create a new instance of this class, call Init() to set it up, + // then call Start() start processing events. Stop() terminates the + // thread and deleting the instance cleans all the handles and such + // up. + // + HRESULT Init(void); + HRESULT Start(void); + HRESULT AsyncStop(void); + + // + // These are used by this thread to send IPC events to the Debugger + // Interface side. + // + DebuggerIPCEvent* GetIPCEventSendBuffer() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + +#ifdef LOGGING + if(IsRCThreadReady()) { + LOG((LF_CORDB, LL_EVERYTHING, "RCThread is ready\n")); + } +#endif + + _ASSERTE(m_pDCB != NULL); + // In case this turns into a continuation event + GetRCThreadSendBuffer()->next = NULL; + LOG((LF_CORDB,LL_EVERYTHING, "GIPCESBuffer: got event 0x%x\n", GetRCThreadSendBuffer())); + + return GetRCThreadSendBuffer(); + } + + DebuggerIPCEvent *GetIPCEventSendBufferContinuation( + DebuggerIPCEvent *eventCur) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(eventCur != NULL); + PRECONDITION(eventCur->next == NULL); + } + CONTRACTL_END; + + DebuggerIPCEvent *dipce = (DebuggerIPCEvent *) new (nothrow) BYTE [CorDBIPC_BUFFER_SIZE]; + dipce->next = NULL; + + LOG((LF_CORDB,LL_INFO1000000, "About to GIPCESBC 0x%x\n",dipce)); + + if (dipce != NULL) + { + eventCur->next = dipce; + } +#ifdef _DEBUG + else + { + _ASSERTE( !"GetIPCEventSendBufferContinuation failed to allocate mem!" ); + } +#endif //_DEBUG + + return dipce; + } + + // Send an IPCEvent once we're ready for sending. This should be done inbetween + // SENDIPCEVENT_BEGIN & SENDIPCEVENT_END. See definition of SENDIPCEVENT_BEGIN + // for usage pattern + HRESULT SendIPCEvent(); + + HRESULT EnsureRuntimeOffsetsInit(IpcTarget i); // helper function for SendIPCEvent + void NeedRuntimeOffsetsReInit(IpcTarget i); + + DebuggerIPCEvent* GetIPCEventReceiveBuffer() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + _ASSERTE(m_pDCB != NULL); + + return GetRCThreadReceiveBuffer(); + } + + HRESULT SendIPCReply(); + + // + // Handle Favors - get the Helper thread to do a function call for us + // because our thread can't (eg, we don't have the stack space) + // DoFavor will call (*fp)(pData) and block until fp returns. + // pData can store parameters, return value, and a this ptr (if we + // need to call a member function) + // + void DoFavor(FAVORCALLBACK fp, void * pData); + + // + // Convience routines + // + PTR_DebuggerIPCControlBlock GetDCB() + { + LIMITED_METHOD_DAC_CONTRACT; + // This may be called before we init or after we shutdown. + + return m_pDCB; + } + + void WatchForStragglers(void); + + HRESULT SetupRuntimeOffsets(DebuggerIPCControlBlock *pDCB); + + bool HandleRSEA(); + void MainLoop(); + void TemporaryHelperThreadMainLoop(); + + HANDLE GetHelperThreadCanGoEvent(void) {LIMITED_METHOD_CONTRACT; return m_helperThreadCanGoEvent; } + + void EarlyHelperThreadDeath(void); + + void RightSideDetach(void); + + // + // + // + void ThreadProc(void); + static DWORD WINAPI ThreadProcStatic(LPVOID parameter); + static DWORD WINAPI ThreadProcRemote(LPVOID parameter); + + DWORD GetRCThreadId() + { + LIMITED_METHOD_CONTRACT; + + return m_pDCB->m_helperThreadId; + } + + // Return true if the Helper Thread up & initialized. + bool IsRCThreadReady(); + + HRESULT ReDaclEvents(PSECURITY_DESCRIPTOR securityDescriptor); +private: + + // The transport based communication protocol keeps the send and receive buffers outside of the DCB + // to keep the DCB size down (since we send it over the wire). + DebuggerIPCEvent * GetRCThreadReceiveBuffer() + { +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + return reinterpret_cast<DebuggerIPCEvent *>(&m_receiveBuffer[0]); +#else + return reinterpret_cast<DebuggerIPCEvent *>(&m_pDCB->m_receiveBuffer[0]); +#endif + } + + // The transport based communication protocol keeps the send and receive buffers outside of the DCB + // to keep the DCB size down (since we send it over the wire). + DebuggerIPCEvent * GetRCThreadSendBuffer() + { +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) + return reinterpret_cast<DebuggerIPCEvent *>(&m_sendBuffer[0]); +#else // FEATURE_DBGIPC_TRANSPORT_VM + return reinterpret_cast<DebuggerIPCEvent *>(&m_pDCB->m_sendBuffer[0]); +#endif // FEATURE_DBGIPC_TRANSPORT_VM + } + + FAVORCALLBACK GetFavorFnPtr() { return m_favorData.m_fpFavor; } + void * GetFavorData() { return m_favorData.m_pFavorData; } + + void SetFavorFnPtr(FAVORCALLBACK fp, void * pData) + { + m_favorData.m_fpFavor = fp; + m_favorData.m_pFavorData = pData; + } + Crst * GetFavorLock() { return &m_favorData.m_FavorLock; } + + HANDLE GetFavorReadEvent() { return m_favorData.m_FavorReadEvent; } + HANDLE GetFavorAvailableEvent() { return m_favorData.m_FavorAvailableEvent; } + + HelperThreadFavor m_favorData; + + + HelperCanary * GetCanary() { return &GetLazyData()->m_Canary; } + + + friend class Debugger; + HRESULT VerifySecurityOnRSCreatedEvents(HANDLE sse, HANDLE lsea, HANDLE lser); + Debugger* m_debugger; + + // IPC_TARGET_* define default targets - if we ever want to do + // multiple right sides, we'll have to switch to a OUTOFPROC + iTargetProcess scheme + PTR_DebuggerIPCControlBlock m_pDCB; + +#ifdef FEATURE_DBGIPC_TRANSPORT_VM + // These buffers move here out of the DebuggerIPCControlBlock since the block is not shared memory when + // using the transport, but we do send its contents over the wire (and these buffers would greatly impact + // the number of bytes sent without being useful in any way). + BYTE m_receiveBuffer[CorDBIPC_BUFFER_SIZE]; + BYTE m_sendBuffer[CorDBIPC_BUFFER_SIZE]; +#endif // FEATURE_DBGIPC_TRANSPORT_VM + + HANDLE m_thread; + bool m_run; + + HANDLE m_threadControlEvent; + HANDLE m_helperThreadCanGoEvent; + bool m_rgfInitRuntimeOffsets[IPC_TARGET_COUNT]; + bool m_fDetachRightSide; + + RCThreadLazyInit * GetLazyData(); +#ifdef _DEBUG + // Tracking to ensure that the helper thread only calls New() on the interop-safe heap. + // We need a very light-weight way to track the helper b/c we need to check everytime somebody + // calls operator new, which may occur during shutdown paths. + static EEThreadId s_DbgHelperThreadId; + + friend void AssertAllocationAllowed(); + +public: + // The OS ThreadId of the helper as determined from the CreateThread call. + DWORD m_DbgHelperThreadOSTid; +private: +#endif + +}; + +typedef DPTR(DebuggerRCThread) PTR_DebuggerRCThread; + +/* ------------------------------------------------------------------------ * + * Debugger Method Info struct and hash table + * ------------------------------------------------------------------------ */ + +// class DebuggerMethodInfo: Struct to hold all the information +// necessary for a given function. +// +// m_module, m_token: Method that this DMI applies to +// +const bool bOriginalToInstrumented = true; +const bool bInstrumentedToOriginal = false; + +class DebuggerMethodInfo +{ + // This is the most recent version of the function based on the latest update and is + // set in UpdateFunction. When a function is jitted, the version is copied from here + // and stored in the corresponding DebuggerJitInfo structure so can always know the + // version of a particular jitted function. + SIZE_T m_currentEnCVersion; + +public: + PTR_Module m_module; + mdMethodDef m_token; + + PTR_DebuggerMethodInfo m_prevMethodInfo; + PTR_DebuggerMethodInfo m_nextMethodInfo; + + + // Enumerate DJIs + // Expected usage: + // DMI.InitDJIIterator(&it); + // while(!it.IsAtEnd()) { + // f(it.Current()); it.Next(); + // } + class DJIIterator + { + friend class DebuggerMethodInfo; + + DebuggerJitInfo* m_pCurrent; + Module* m_pLoaderModuleFilter; + public: + DJIIterator(); + + bool IsAtEnd(); + DebuggerJitInfo * Current(); + void Next(BOOL fFirst = FALSE); + + }; + + // Ensure the DJI cache is completely up to date. (This is heavy weight). + void CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module * pModuleFilter = NULL); + + // Get an iterator for all native blobs (accounts for Generics, Enc, + Prejiiting). + // Must be stopped when we do this. This could be heavy weight. + // This will call CreateDJIsForNativeBlobs() to ensure we have all DJIs available. + // You may optionally pass pLoaderModuleFilter to restrict the DJIs iterated to + // exist only on MethodDescs whose loader module matches the filter (pass NULL not + // to filter by loader module). + void IterateAllDJIs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, DJIIterator * pEnum); + +private: + // The linked list of JIT's of this version of the method. This will ALWAYS + // contain one element except for code in generic classes or generic methods, + // which may get JITted more than once under different type instantiations. + // + // We find the appropriate JitInfo by searching the list (nearly always this + // will return the first element of course). + // + // The JitInfos contain back pointers to this MethodInfo. They should never be associated + // with any other MethodInfo. + // + // USE ACCESSOR FUNCTION GetLatestJitInfo(), as it does lazy init of this field. + // + + PTR_DebuggerJitInfo m_latestJitInfo; + +public: + + PTR_DebuggerJitInfo GetLatestJitInfo(MethodDesc *fd); + + DebuggerJitInfo * GetLatestJitInfo_NoCreate(); + + + // Find the DJI corresponding to the specified MD and native start address. + DebuggerJitInfo * FindJitInfo(MethodDesc * pMD, TADDR addrNativeStartAddr); + + // Creating the Jit-infos. + DebuggerJitInfo *FindOrCreateInitAndAddJitInfo(MethodDesc* fd); + DebuggerJitInfo *CreateInitAndAddJitInfo(MethodDesc* fd, TADDR startAddr); + + + void DeleteJitInfo(DebuggerJitInfo *dji); + void DeleteJitInfoList(void); + + // Return true iff this has been jitted. + // Since we can create DMIs freely, a DMI's existence doesn't mean that the method was jitted. + bool HasJitInfos(); + + // Return true iff this has been EnCed since the last time the function was jitted. + bool HasMoreRecentEnCVersion(); + + + // Return true iif this is a JMC function, else false. + bool IsJMCFunction(); + void SetJMCStatus(bool fStatus); + + + DebuggerMethodInfo(Module *module, mdMethodDef token); + ~DebuggerMethodInfo(); + + // A profiler can remap the IL. We track the "instrumented" IL map here. + void SetInstrumentedILMap(COR_IL_MAP * pMap, SIZE_T cEntries); + bool HasInstrumentedILMap() {return m_fHasInstrumentedILMap; } + + // TranslateToInstIL will take offOrig, and translate it to the + // correct IL offset if this code happens to be instrumented + ULONG32 TranslateToInstIL(const InstrumentedILOffsetMapping * pMapping, ULONG32 offOrig, bool fOrigToInst); + + + // We don't always have a debugger module. (Ex: we're tracking debug info, + // but no debugger's attached). So this may return NULL alot. + // If we can, we should use the RuntimeModule when ever possible. + DebuggerModule* GetPrimaryModule(); + + // We always have a runtime module. + Module * GetRuntimeModule(); + + // Set the latest EnC version number for this method + // This doesn't mean we have a DJI for this version yet. + void SetCurrentEnCVersion(SIZE_T currentEnCVersion) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(currentEnCVersion >= CorDB_DEFAULT_ENC_FUNCTION_VERSION); + m_currentEnCVersion = currentEnCVersion; + } + + SIZE_T GetCurrentEnCVersion() + { + LIMITED_METHOD_CONTRACT; + SUPPORTS_DAC; + + return m_currentEnCVersion; + } + +#ifdef DACCESS_COMPILE + void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); +#endif + +protected: + // JMC info. Each method can have its own JMC setting. + bool m_fJMCStatus; + + // "Instrumented" IL map set by the profiler. + // @dbgtodo execution control - remove this when we do execution control from out-of-proc + bool m_fHasInstrumentedILMap; +}; + +// ------------------------------------------------------------------------ * +// Executable code memory management for the debugger heap. +// +// Rather than allocating memory that needs to be executable on the process heap (which +// is forbidden on some flavors of SELinux and is generally a bad idea), we use the +// allocator below. It will handle allocating and managing the executable memory in a +// different part of the address space (not on the heap). +// ------------------------------------------------------------------------ */ + +#define DBG_MAX_EXECUTABLE_ALLOC_SIZE 48 + +// Forward declaration +struct DebuggerHeapExecutableMemoryPage; + +// ------------------------------------------------------------------------ */ +// DebuggerHeapExecutableMemoryChunk +// +// Each DebuggerHeapExecutableMemoryPage is divided into 64 of these chunks. +// The first chunk is a BookkeepingChunk used for bookkeeping information +// for the page, and the remaining ones are DataChunks and are handed out +// by the allocator when it allocates memory. +// ------------------------------------------------------------------------ */ +union DECLSPEC_ALIGN(64) DebuggerHeapExecutableMemoryChunk { + + struct DataChunk + { + char data[DBG_MAX_EXECUTABLE_ALLOC_SIZE]; + + DebuggerHeapExecutableMemoryPage *startOfPage; + + // The chunk number within the page. + uint8_t chunkNumber; + + } data; + + struct BookkeepingChunk + { + DebuggerHeapExecutableMemoryPage *nextPage; + + uint64_t pageOccupancy; + + } bookkeeping; + + char _alignpad[64]; +}; + +static_assert(sizeof(DebuggerHeapExecutableMemoryChunk) == 64, "DebuggerHeapExecutableMemoryChunk is expect to be 64 bytes."); + +// ------------------------------------------------------------------------ */ +// DebuggerHeapExecutableMemoryPage +// +// We allocate the size of DebuggerHeapExecutableMemoryPage each time we need +// more memory and divide each page into DebuggerHeapExecutableMemoryChunks for +// use. The pages are self describing; the first chunk contains information +// about which of the other chunks are used/free as well as a pointer to +// the next page. +// ------------------------------------------------------------------------ */ +struct DECLSPEC_ALIGN(4096) DebuggerHeapExecutableMemoryPage +{ + inline DebuggerHeapExecutableMemoryPage* GetNextPage() + { + return chunks[0].bookkeeping.nextPage; + } + + inline void SetNextPage(DebuggerHeapExecutableMemoryPage* nextPage) + { + chunks[0].bookkeeping.nextPage = nextPage; + } + + inline uint64_t GetPageOccupancy() const + { + return chunks[0].bookkeeping.pageOccupancy; + } + + inline void SetPageOccupancy(uint64_t newOccupancy) + { + // Can't unset first bit of occupancy! + ASSERT((newOccupancy & 0x8000000000000000) != 0); + + chunks[0].bookkeeping.pageOccupancy = newOccupancy; + } + + inline void* GetPointerToChunk(int chunkNum) const + { + return (char*)this + chunkNum * sizeof(DebuggerHeapExecutableMemoryChunk); + } + + DebuggerHeapExecutableMemoryPage() + { + SetPageOccupancy(0x8000000000000000); // only the first bit is set. + for (uint8_t i = 1; i < sizeof(chunks)/sizeof(chunks[0]); i++) + { + ASSERT(i != 0); + chunks[i].data.startOfPage = this; + chunks[i].data.chunkNumber = i; + } + } + +private: + DebuggerHeapExecutableMemoryChunk chunks[64]; +}; + +// ------------------------------------------------------------------------ */ +// DebuggerHeapExecutableMemoryAllocator class +// Handles allocation and freeing (and all necessary bookkeeping) for +// executable memory that the DebuggerHeap class needs. This is especially +// useful on systems (like SELinux) where having executable code on the +// heap is explicity disallowed for security reasons. +// ------------------------------------------------------------------------ */ + +class DebuggerHeapExecutableMemoryAllocator +{ +public: + DebuggerHeapExecutableMemoryAllocator() + : m_pages(NULL) + , m_execMemAllocMutex(CrstDebuggerHeapExecMemLock, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)) + { } + + ~DebuggerHeapExecutableMemoryAllocator(); + + void* Allocate(DWORD numberOfBytes); + int Free(void* addr); + +private: + enum class ChangePageUsageAction {ALLOCATE, FREE}; + + DebuggerHeapExecutableMemoryPage* AddNewPage(); + bool CheckPageForAvailability(DebuggerHeapExecutableMemoryPage* page, /* _Out_ */ int* chunkToUse); + void* ChangePageUsage(DebuggerHeapExecutableMemoryPage* page, int chunkNumber, ChangePageUsageAction action); + +private: + // Linked list of pages that have been allocated + DebuggerHeapExecutableMemoryPage* m_pages; + Crst m_execMemAllocMutex; +}; + +// ------------------------------------------------------------------------ * +// DebuggerHeap class +// For interop debugging, we need a heap that: +// - does not take any outside looks +// - returns memory which could be executed. +// ------------------------------------------------------------------------ */ + +#ifdef FEATURE_INTEROP_DEBUGGING + #define USE_INTEROPSAFE_HEAP +#endif + +class DebuggerHeap +{ +public: + DebuggerHeap(); + ~DebuggerHeap(); + + bool IsInit(); + void Destroy(); + HRESULT Init(BOOL fExecutable); + + void *Alloc(DWORD size); + void *Realloc(void *pMem, DWORD newSize, DWORD oldSize); + void Free(void *pMem); + + +protected: +#ifdef USE_INTEROPSAFE_HEAP + HANDLE m_hHeap; +#endif + BOOL m_fExecutable; + +private: + DebuggerHeapExecutableMemoryAllocator *m_execMemAllocator; +}; + +class DebuggerJitInfo; + +#if defined(WIN64EXCEPTIONS) +const int PARENT_METHOD_INDEX = -1; +#endif // WIN64EXCEPTIONS + +class CodeRegionInfo +{ +public: + CodeRegionInfo() : + m_addrOfHotCode(NULL), + m_addrOfColdCode(NULL), + m_sizeOfHotCode(0), + m_sizeOfColdCode(0) + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + } + + static CodeRegionInfo GetCodeRegionInfo(DebuggerJitInfo * dji, + MethodDesc * md = NULL, + PTR_CORDB_ADDRESS_TYPE addr = PTR_NULL); + + // Fills in the CodeRegoinInfo fields from the start address. + void InitializeFromStartAddress(PCODE addr) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + SUPPORTS_DAC; + } + CONTRACTL_END; + + m_addrOfHotCode = addr; + g_pEEInterface->GetMethodRegionInfo(addr, + &m_addrOfColdCode, + (size_t *) &m_sizeOfHotCode, + (size_t *) &m_sizeOfColdCode); + } + + // Converts an offset within a method to a code address + PCODE OffsetToAddress(SIZE_T offset) + { + LIMITED_METHOD_CONTRACT; + + if (m_addrOfHotCode != NULL) + { + if (offset < m_sizeOfHotCode) + { + return m_addrOfHotCode + offset; + } + else + { + _ASSERTE(m_addrOfColdCode); + _ASSERTE(offset <= m_sizeOfHotCode + m_sizeOfColdCode); + + return m_addrOfColdCode + (offset - m_sizeOfHotCode); + } + } + else + { + return NULL; + } + } + + // Converts a code address to an offset within the method + SIZE_T AddressToOffset(const BYTE *addr) + { + LIMITED_METHOD_CONTRACT; + + PCODE address = (PCODE)addr; + + if ((address >= m_addrOfHotCode) && + (address < m_addrOfHotCode + m_sizeOfHotCode)) + { + return address - m_addrOfHotCode; + } + else if ((address >= m_addrOfColdCode) && + (address < m_addrOfColdCode + m_sizeOfColdCode)) + { + return address - m_addrOfColdCode + m_sizeOfHotCode; + } + + _ASSERTE(!"addressToOffset called with invalid address"); + return NULL; + } + + // Determines whether the address lies within the method + bool IsMethodAddress(const BYTE *addr) + { + LIMITED_METHOD_CONTRACT; + + PCODE address = (PCODE)addr; + return (((address >= m_addrOfHotCode) && + (address < m_addrOfHotCode + m_sizeOfHotCode)) || + ((address >= m_addrOfColdCode) && + (address < m_addrOfColdCode + m_sizeOfColdCode))); + } + + // Determines whether the offset is in the hot section + bool IsOffsetHot(SIZE_T offset) + { + LIMITED_METHOD_CONTRACT; + + return (offset < m_sizeOfHotCode); + } + + PCODE getAddrOfHotCode() {LIMITED_METHOD_DAC_CONTRACT; return m_addrOfHotCode;} + PCODE getAddrOfColdCode() {LIMITED_METHOD_DAC_CONTRACT; return m_addrOfColdCode;} + SIZE_T getSizeOfHotCode() {LIMITED_METHOD_DAC_CONTRACT; return m_sizeOfHotCode;} + SIZE_T getSizeOfColdCode() {LIMITED_METHOD_DAC_CONTRACT; return m_sizeOfColdCode;} + SIZE_T getSizeOfTotalCode(){LIMITED_METHOD_DAC_CONTRACT; return m_sizeOfHotCode + m_sizeOfColdCode; } + +private: + + PCODE m_addrOfHotCode; + PCODE m_addrOfColdCode; + SIZE_T m_sizeOfHotCode; + SIZE_T m_sizeOfColdCode; +}; + +/* ------------------------------------------------------------------------ * + * Debugger JIT Info struct + * ------------------------------------------------------------------------ */ + +// class DebuggerJitInfo: Struct to hold all the JIT information +// necessary for a given function. +// - DJIs are 1:1 w/ native codeblobs. They're almost 1:1 w/ Native Method Descs. +// except that a MethodDesc only refers to the most recent EnC version of a method. +// - If 2 DJIs are different, they refer to different code-blobs. +// - DJIs are lazily created, and so you can't safely enumerate them b/c +// you can't rely on whether they're created or not. + + +// +// MethodDesc* m_fd: MethodDesc of the method that this DJI applies to +// +// CORDB_ADDRESS m_addrOfCode: Address of the code. This will be read by +// the right side (via ReadProcessMemory) to grab the actual native start +// address of the jitted method. +// +// SIZE_T m_sizeOfCode: Pseudo-private variable: use the GetSkzeOfCode +// method to get this value. +// +// bool m_jitComplete: Set to true once JITComplete has been called. +// +// DebuggerILToNativeMap* m_sequenceMap: This is the sequence map, which +// is actually a collection of IL-Native pairs, where each IL corresponds +// to a line of source code. Each pair is refered to as a sequence map point. +// +// SIZE_T m_lastIL: last nonEPILOG instruction +// +// unsigned int m_sequenceMapCount: Count of the DebuggerILToNativeMaps +// in m_sequenceMap. +// +// bool m_sequenceMapSorted: Set to true once m_sequenceMapSorted is sorted +// into ascending IL order (Debugger::setBoundaries, SortMap). +// + +class DebuggerJitInfo +{ +public: + PTR_MethodDesc m_fd; + + // Loader module is used to control life-time of DebufferJitInfo. Ideally, we would refactor the code to use LoaderAllocator here + // instead because of it is what the VM actually uses to track the life time. It would make the debugger interface less chatty. + PTR_Module m_pLoaderModule; + + bool m_jitComplete; + +#ifdef EnC_SUPPORTED + // If this is true, then we've plastered the method with DebuggerEncBreakpoints + // and the method has been EnC'd + bool m_encBreakpointsApplied; +#endif //EnC_SUPPORTED + + PTR_DebuggerMethodInfo m_methodInfo; + + CORDB_ADDRESS m_addrOfCode; + SIZE_T m_sizeOfCode; + + CodeRegionInfo m_codeRegionInfo; + + PTR_DebuggerJitInfo m_prevJitInfo; + PTR_DebuggerJitInfo m_nextJitInfo; + +protected: + // The jit maps are lazy-initialized. + // They are always sorted. + ULONG m_lastIL; + PTR_DebuggerILToNativeMap m_sequenceMap; + unsigned int m_sequenceMapCount; + PTR_DebuggerILToNativeMap m_callsiteMap; + unsigned int m_callsiteMapCount; + bool m_sequenceMapSorted; + + PTR_NativeVarInfo m_varNativeInfo; + unsigned int m_varNativeInfoCount; + + bool m_fAttemptInit; + +#ifndef DACCESS_COMPILE + void LazyInitBounds(); +#else + void LazyInitBounds() { LIMITED_METHOD_DAC_CONTRACT; } +#endif + +public: + unsigned int GetSequenceMapCount() + { + SUPPORTS_DAC; + + LazyInitBounds(); + return m_sequenceMapCount; + } + + //@todo: this method could return NULL, but some callers are not handling the case + PTR_DebuggerILToNativeMap GetSequenceMap() + { + SUPPORTS_DAC; + + LazyInitBounds(); + return m_sequenceMap; + } + + unsigned int GetCallsiteMapCount() + { + SUPPORTS_DAC; + + LazyInitBounds(); + return m_callsiteMapCount; + } + + PTR_DebuggerILToNativeMap GetCallSiteMap() + { + SUPPORTS_DAC; + + LazyInitBounds(); + return m_callsiteMap; + } + + PTR_NativeVarInfo GetVarNativeInfo() + { + SUPPORTS_DAC; + + LazyInitBounds(); + return m_varNativeInfo; + } + + unsigned int GetVarNativeInfoCount() + { + SUPPORTS_DAC; + + LazyInitBounds(); + return m_varNativeInfoCount; + } + + + // The version number of this jitted code + SIZE_T m_encVersion; + +#if defined(WIN64EXCEPTIONS) + DWORD *m_rgFunclet; + int m_funcletCount; +#endif // WIN64EXCEPTIONS + +#ifndef DACCESS_COMPILE + + DebuggerJitInfo(DebuggerMethodInfo *minfo, MethodDesc *fd); + ~DebuggerJitInfo(); + +#endif // #ifdef DACCESS_COMPILE + + class ILToNativeOffsetIterator; + + // Usage of ILToNativeOffsetIterator: + // + // ILToNativeOffsetIterator it; + // dji->InitILToNativeOffsetIterator(&it, ilOffset); + // while (!it.IsAtEnd()) + // { + // nativeOffset = it.Current(&fExact); + // it.Next(); + // } + struct ILOffset + { + friend class DebuggerJitInfo; + friend class DebuggerJitInfo::ILToNativeOffsetIterator; + + private: + SIZE_T m_ilOffset; +#ifdef WIN64EXCEPTIONS + int m_funcletIndex; +#endif + }; + + struct NativeOffset + { + friend class DebuggerJitInfo; + friend class DebuggerJitInfo::ILToNativeOffsetIterator; + + private: + SIZE_T m_nativeOffset; + BOOL m_fExact; + }; + + class ILToNativeOffsetIterator + { + friend class DebuggerJitInfo; + + public: + ILToNativeOffsetIterator(); + + bool IsAtEnd(); + SIZE_T Current(BOOL* pfExact); + SIZE_T CurrentAssertOnlyOne(BOOL* pfExact); + void Next(); + + private: + void Init(DebuggerJitInfo* dji, SIZE_T ilOffset); + + DebuggerJitInfo* m_dji; + ILOffset m_currentILOffset; + NativeOffset m_currentNativeOffset; + }; + + void InitILToNativeOffsetIterator(ILToNativeOffsetIterator &it, SIZE_T ilOffset); + + DebuggerILToNativeMap *MapILOffsetToMapEntry(SIZE_T ilOffset, BOOL *exact=NULL, BOOL fWantFirst = TRUE); + void MapILRangeToMapEntryRange(SIZE_T ilStartOffset, SIZE_T ilEndOffset, + DebuggerILToNativeMap **start, + DebuggerILToNativeMap **end); + NativeOffset MapILOffsetToNative(ILOffset ilOffset); + + // MapSpecialToNative maps a CordDebugMappingResult to a native + // offset so that we can get the address of the prolog & epilog. which + // determines which epilog or prolog, if there's more than one. + SIZE_T MapSpecialToNative(CorDebugMappingResult mapping, + SIZE_T which, + BOOL *pfAccurate); +#if defined(WIN64EXCEPTIONS) + void MapSpecialToNative(int funcletIndex, DWORD* pPrologEndOffset, DWORD* pEpilogStartOffset); + SIZE_T MapILOffsetToNativeForSetIP(SIZE_T offsetILTo, int funcletIndexFrom, EHRangeTree* pEHRT, BOOL* pExact); +#endif // _WIN64 + + // MapNativeOffsetToIL Takes a given nativeOffset, and maps it back + // to the corresponding IL offset, which it returns. If mapping indicates + // that a the native offset corresponds to a special region of code (for + // example, the epilog), then the return value will be specified by + // ICorDebugILFrame::GetIP (see cordebug.idl) + DWORD MapNativeOffsetToIL(SIZE_T nativeOffsetToMap, + CorDebugMappingResult *mapping, + DWORD *which, + BOOL skipPrologs=FALSE); + + // If a method has multiple copies of code (because of EnC or code-pitching), + // this returns the DJI corresponding to 'pbAddr' + DebuggerJitInfo *GetJitInfoByAddress(const BYTE *pbAddr ); + + void Init(TADDR newAddress); + +#if defined(WIN64EXCEPTIONS) + enum GetFuncletIndexMode + { + GFIM_BYOFFSET, + GFIM_BYADDRESS, + }; + + void InitFuncletAddress(); + DWORD GetFuncletOffsetByIndex(int index); + int GetFuncletIndex(CORDB_ADDRESS offset, GetFuncletIndexMode mode); + int GetFuncletCount() {return m_funcletCount;} +#endif // WIN64EXCEPTIONS + + void SetVars(ULONG32 cVars, ICorDebugInfo::NativeVarInfo *pVars); + void SetBoundaries(ULONG32 cMap, ICorDebugInfo::OffsetMapping *pMap); + + ICorDebugInfo::SourceTypes GetSrcTypeFromILOffset(SIZE_T ilOffset); + +#ifdef DACCESS_COMPILE + void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); +#endif + + // Debug support + CHECK Check() const; + CHECK Invariant() const; +}; + +#if !defined(DACCESS_COMPILE) +// @dbgtodo Microsoft inspection: get rid of this class when IPC events are eliminated. It's been copied to +// dacdbistructures +/* + * class MapSortIL: A template class that will sort an array of DebuggerILToNativeMap. + * This class is intended to be instantiated on the stack / in temporary storage, and used to reorder the sequence map. + */ +class MapSortIL : public CQuickSort<DebuggerILToNativeMap> +{ + public: + //Constructor + MapSortIL(DebuggerILToNativeMap *map, + int count) + : CQuickSort<DebuggerILToNativeMap>(map, count) {} + + inline int CompareInternal(DebuggerILToNativeMap *first, + DebuggerILToNativeMap *second) + { + LIMITED_METHOD_CONTRACT; + + if (first->nativeStartOffset == second->nativeStartOffset) + return 0; + else if (first->nativeStartOffset < second->nativeStartOffset) + return -1; + else + return 1; + } + + //Comparison operator + int Compare(DebuggerILToNativeMap *first, + DebuggerILToNativeMap *second) + { + LIMITED_METHOD_CONTRACT; + + const DWORD call_inst = (DWORD)ICorDebugInfo::CALL_INSTRUCTION; + + //PROLOGs go first + if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG + && second->ilOffset == (ULONG) ICorDebugInfo::PROLOG) + { + return CompareInternal(first, second); + } else if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG) + { + return -1; + } else if (second->ilOffset == (ULONG) ICorDebugInfo::PROLOG) + { + return 1; + } + // call_instruction goes at the very very end of the table. + else if ((first->source & call_inst) == call_inst + && (second->source & call_inst) == call_inst) + { + return CompareInternal(first, second); + } else if ((first->source & call_inst) == call_inst) + { + return 1; + } else if ((second->source & call_inst) == call_inst) + { + return -1; + } + //NO_MAPPING go last + else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING + && second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING) + { + return CompareInternal(first, second); + } else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING) + { + return 1; + } else if (second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING) + { + return -1; + } + //EPILOGs go next-to-last + else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG + && second->ilOffset == (ULONG) ICorDebugInfo::EPILOG) + { + return CompareInternal(first, second); + } else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG) + { + return 1; + } else if (second->ilOffset == (ULONG) ICorDebugInfo::EPILOG) + { + return -1; + } + //normal offsets compared otherwise + else if (first->ilOffset < second->ilOffset) + return -1; + else if (first->ilOffset == second->ilOffset) + return CompareInternal(first, second); + else + return 1; + } +}; + +/* + * class MapSortNative: A template class that will sort an array of DebuggerILToNativeMap by the nativeStartOffset field. + * This class is intended to be instantiated on the stack / in temporary storage, and used to reorder the sequence map. + */ +class MapSortNative : public CQuickSort<DebuggerILToNativeMap> +{ + public: + //Constructor + MapSortNative(DebuggerILToNativeMap *map, + int count) + : CQuickSort<DebuggerILToNativeMap>(map, count) + { + WRAPPER_NO_CONTRACT; + } + + + //Returns -1,0,or 1 if first's nativeStartOffset is less than, equal to, or greater than second's + int Compare(DebuggerILToNativeMap *first, + DebuggerILToNativeMap *second) + { + LIMITED_METHOD_CONTRACT; + + if (first->nativeStartOffset < second->nativeStartOffset) + return -1; + else if (first->nativeStartOffset == second->nativeStartOffset) + return 0; + else + return 1; + } +}; +#endif //!DACCESS_COMPILE + +/* ------------------------------------------------------------------------ * + * Import flares from assembly file + * We rely on flares having unique addresses, and so we need to keeps them + * from getting folded by the linker (Since they are identical code). + * ------------------------------------------------------------------------ */ + +extern "C" void __stdcall SignalHijackStartedFlare(void); +extern "C" void __stdcall ExceptionForRuntimeHandoffStartFlare(void); +extern "C" void __stdcall ExceptionForRuntimeHandoffCompleteFlare(void); +extern "C" void __stdcall SignalHijackCompleteFlare(void); +extern "C" void __stdcall ExceptionNotForRuntimeFlare(void); +extern "C" void __stdcall NotifyRightSideOfSyncCompleteFlare(void); +extern "C" void __stdcall NotifySecondChanceReadyForDataFlare(void); + +/* ------------------------------------------------------------------------ * + * Debugger class + * ------------------------------------------------------------------------ */ + + +// Forward declare some parameter marshalling structs +struct ShouldAttachDebuggerParams; +struct EnsureDebuggerAttachedParams; +struct SendMDANotificationParams; + +// class Debugger: This class implements DebugInterface to provide +// the hooks to the Runtime directly. +// + +class Debugger : public DebugInterface +{ + VPTR_VTABLE_CLASS(Debugger, DebugInterface); +public: + +#ifndef DACCESS_COMPILE + Debugger(); + virtual ~Debugger(); +#else + virtual ~Debugger() {} +#endif + + // If 0, then not yet initialized. If non-zero, then LS is initialized. + LONG m_fLeftSideInitialized; + + // This flag controls the window where SetDesiredNGENCompilerFlags is allowed, + // which is until Debugger::StartupPhase2 is complete. Typically it would be + // set during the CreateProcess debug event but it could be set other times such + // as module load for clr.dll. + SVAL_DECL(BOOL, s_fCanChangeNgenFlags); + + friend class DebuggerLazyInit; +#ifdef TEST_DATA_CONSISTENCY + friend class DataTest; +#endif + + // Checks if the JitInfos table has been allocated, and if not does so. + HRESULT inline CheckInitMethodInfoTable(); + HRESULT inline CheckInitModuleTable(); + HRESULT CheckInitPendingFuncEvalTable(); + +#ifndef DACCESS_COMPILE + DWORD GetRCThreadId() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (m_pRCThread) + return m_pRCThread->GetRCThreadId(); + else + return 0; + } +#endif + + // + // Methods exported from the Runtime Controller to the Runtime. + // (These are the methods specified by DebugInterface.) + // + HRESULT Startup(void); + + HRESULT StartupPhase2(Thread * pThread); + + void InitializeLazyDataIfNecessary(); + + void LazyInit(); // will throw + HRESULT LazyInitWrapper(); // calls LazyInit and converts to HR. + + // Helper on startup to notify debugger + void RaiseStartupNotification(); + + // Send a raw managed debug event over the managed pipeline. + void SendRawEvent(const DebuggerIPCEvent * pManagedEvent); + + // 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 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 + + void SetEEInterface(EEDebugInterface* i); + void StopDebugger(void); + BOOL IsStopped(void) + { + LIMITED_METHOD_CONTRACT; + // implements DebugInterface but also is called internally + return m_stopped; + } + + + + void ThreadCreated(Thread* pRuntimeThread); + void ThreadStarted(Thread* pRuntimeThread); + void DetachThread(Thread *pRuntimeThread); + + BOOL SuspendComplete(); + + void LoadModule(Module* pRuntimeModule, + LPCWSTR pszModuleName, + DWORD dwModuleName, + Assembly *pAssembly, + AppDomain *pAppDomain, + DomainFile * pDomainFile, + BOOL fAttaching); + void LoadModuleFinished(Module* pRuntimeModule, AppDomain * pAppDomain); + DebuggerModule * AddDebuggerModule(DomainFile * pDomainFile); + + + void UnloadModule(Module* pRuntimeModule, + AppDomain *pAppDomain); + void DestructModule(Module *pModule); + + void RemoveModuleReferences(Module * pModule); + + + void SendUpdateModuleSymsEventAndBlock(Module * pRuntimeModule, AppDomain * pAppDomain); + void SendRawUpdateModuleSymsEvent(Module * pRuntimeModule, AppDomain * pAppDomain); + + BOOL LoadClass(TypeHandle th, + mdTypeDef classMetadataToken, + Module* classModule, + AppDomain *pAppDomain); + void UnloadClass(mdTypeDef classMetadataToken, + Module* classModule, + AppDomain *pAppDomain); + + void SendClassLoadUnloadEvent (mdTypeDef classMetadataToken, + DebuggerModule *classModule, + Assembly *pAssembly, + AppDomain *pAppDomain, + BOOL fIsLoadEvent); + BOOL SendSystemClassLoadUnloadEvent (mdTypeDef classMetadataToken, + Module *classModule, + BOOL fIsLoadEvent); + + void SendCatchHandlerFound(Thread *pThread, + FramePointer fp, + SIZE_T nOffset, + DWORD dwFlags); + + LONG NotifyOfCHFFilter(EXCEPTION_POINTERS* pExceptionPointers, PVOID pCatchStackAddr); + + + bool FirstChanceNativeException(EXCEPTION_RECORD *exception, + T_CONTEXT *context, + DWORD code, + Thread *thread); + + bool IsJMCMethod(Module* pModule, mdMethodDef tkMethod); + + int GetMethodEncNumber(MethodDesc * pMethod); + + + bool FirstChanceManagedException(Thread *pThread, SIZE_T currentIP, SIZE_T currentSP); + + void FirstChanceManagedExceptionCatcherFound(Thread *pThread, + MethodDesc *pMD, TADDR pMethodAddr, + BYTE *currentSP, + EE_ILEXCEPTION_CLAUSE *pEHClause); + + LONG LastChanceManagedException(EXCEPTION_POINTERS * pExceptionInfo, + Thread *pThread, + BOOL jitAttachRequested); + + void ManagedExceptionUnwindBegin(Thread *pThread); + + void DeleteInterceptContext(void *pContext); + + void ExceptionFilter(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack); + void ExceptionHandle(MethodDesc *fd, TADDR pMethodAddr, SIZE_T offset, BYTE *pStack); + + int NotifyUserOfFault(bool userBreakpoint, DebuggerLaunchSetting dls); + + SIZE_T GetArgCount(MethodDesc* md, BOOL *fVarArg = NULL); + + void FuncEvalComplete(Thread *pThread, DebuggerEval *pDE); + + DebuggerMethodInfo *CreateMethodInfo(Module *module, mdMethodDef md); + void JITComplete(MethodDesc* fd, TADDR newAddress); + + HRESULT RequestFavor(FAVORCALLBACK fp, void * pData); + +#ifdef EnC_SUPPORTED + HRESULT UpdateFunction(MethodDesc* pFD, SIZE_T encVersion); + HRESULT AddFunction(MethodDesc* md, SIZE_T enCVersion); + HRESULT UpdateNotYetLoadedFunction(mdMethodDef token, Module * pModule, SIZE_T enCVersion); + + HRESULT AddField(FieldDesc* fd, SIZE_T enCVersion); + HRESULT RemapComplete(MethodDesc *pMd, TADDR addr, SIZE_T nativeOffset); + + HRESULT MapILInfoToCurrentNative(MethodDesc *pMD, + SIZE_T ilOffset, + TADDR nativeFnxStart, + SIZE_T *nativeOffset); +#endif // EnC_SUPPORTED + + void 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 + ); + + void getBoundariesHelper(MethodDesc * ftn, + unsigned int *cILOffsets, DWORD **pILOffsets); + void getBoundaries(MethodDesc * ftn, + unsigned int *cILOffsets, DWORD **pILOffsets, + ICorDebugInfo::BoundaryTypes* implictBoundaries); + + void getVars(MethodDesc * ftn, + ULONG32 *cVars, ICorDebugInfo::ILVarInfo **vars, + bool *extendOthers); + + DebuggerMethodInfo *GetOrCreateMethodInfo(Module *pModule, mdMethodDef token); + + PTR_DebuggerMethodInfoTable GetMethodInfoTable() { return m_pMethodInfos; } + + // Gets the DJI for 'fd' + // If 'pbAddr' is non-NULL and if the method has multiple copies of code + // (because of EnC or code-pitching), this returns the DJI corresponding + // to 'pbAddr' + DebuggerJitInfo *GetJitInfo(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo = NULL); + + // Several ways of getting a DJI. DJIs are 1:1 w/ Native Code blobs. + // Caller must guarantee good parameters. + // DJIs can be lazily created; so the only way these will fail is in an OOM case. + DebuggerJitInfo *GetJitInfoFromAddr(TADDR addr); + + // EnC trashes the methoddesc to point to the latest version. Thus given a method-desc, + // we can get the most recent DJI. + DebuggerJitInfo *GetLatestJitInfoFromMethodDesc(MethodDesc * pMethodDesc); + + + HRESULT GetILToNativeMapping(MethodDesc *pMD, ULONG32 cMap, ULONG32 *pcMap, + COR_DEBUG_IL_TO_NATIVE_MAP map[]); + + HRESULT GetILToNativeMappingIntoArrays( + MethodDesc * pMD, + USHORT cMapMax, + USHORT * pcMap, + UINT ** prguiILOffset, + UINT ** prguiNativeOffset); + + PRD_TYPE GetPatchedOpcode(CORDB_ADDRESS_TYPE *ip); + BOOL CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address, /*OUT*/ PRD_TYPE *pOpcode); + + void TraceCall(const BYTE *address); + + bool ThreadsAtUnsafePlaces(void); + + + void PollWaitingForHelper(); + + void IncThreadsAtUnsafePlaces(void) + { + LIMITED_METHOD_CONTRACT; + InterlockedIncrement(&m_threadsAtUnsafePlaces); + } + + void DecThreadsAtUnsafePlaces(void) + { + LIMITED_METHOD_CONTRACT; + InterlockedDecrement(&m_threadsAtUnsafePlaces); + } + + static StackWalkAction AtSafePlaceStackWalkCallback(CrawlFrame *pCF, + VOID* data); + bool IsThreadAtSafePlaceWorker(Thread *thread); + bool IsThreadAtSafePlace(Thread *thread); + + CorDebugUserState GetFullUserState(Thread *pThread); + + + void Terminate(); + void Continue(); + + bool HandleIPCEvent(DebuggerIPCEvent* event); + + DebuggerModule * LookupOrCreateModule(VMPTR_DomainFile vmDomainFile); + DebuggerModule * LookupOrCreateModule(DomainFile * pDomainFile); + DebuggerModule * LookupOrCreateModule(Module * pModule, AppDomain * pAppDomain); + + HRESULT GetAndSendInterceptCommand(DebuggerIPCEvent *event); + + //HRESULT GetAndSendJITFunctionData(DebuggerRCThread* rcThread, + // mdMethodDef methodToken, + // void* functionModuleToken); + HRESULT GetFuncData(mdMethodDef funcMetadataToken, + DebuggerModule* pDebuggerModule, + SIZE_T nVersion, + DebuggerIPCE_FuncData *data); + + + // The following four functions convert between type handles and the data that is + // shipped for types to and from the right-side. + // + // I'm heading toward getting rid of the first two - they are almost never used. + static HRESULT ExpandedTypeInfoToTypeHandle(DebuggerIPCE_ExpandedTypeData *data, + unsigned int genericArgsCount, + DebuggerIPCE_BasicTypeData *genericArgs, + TypeHandle *pRes); + static HRESULT BasicTypeInfoToTypeHandle(DebuggerIPCE_BasicTypeData *data, + TypeHandle *pRes); + void TypeHandleToBasicTypeInfo(AppDomain *pAppDomain, + TypeHandle th, + DebuggerIPCE_BasicTypeData *res); + + // TypeHandleToExpandedTypeInfo returns different DebuggerIPCE_ExpandedTypeData objects + // depending on whether the object value that the TypeData corresponds to is + // boxed or not. Different parts of the API transfer objects in slightly different ways. + // AllBoxed: + // For GetAndSendObjectData all values are boxed, + // + // StructsBoxed: + // When returning results from FuncEval only "true" structs + // get boxed, i.e. primitives are unboxed. + // + // NoSpecialBoxing: + // TypeHandleToExpandedTypeInfo is also used to report type parameters, + // and in this case none of the types are considered boxed ( + enum AreValueTypesBoxed { NoValueTypeBoxing, OnlyPrimitivesUnboxed, AllBoxed }; + + void TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed, + AppDomain *pAppDomain, + TypeHandle th, + DebuggerIPCE_ExpandedTypeData *res); + + class TypeDataWalk + { + DebuggerIPCE_TypeArgData *m_curdata; + unsigned int m_remaining; + + public: + TypeDataWalk(DebuggerIPCE_TypeArgData *pData, unsigned int nData) + { + m_curdata = pData; + m_remaining = nData; + } + + + // These are for type arguments in the funceval case. + // They throw COMPLUS exceptions if they fail, so can only be used during funceval. + void ReadTypeHandles(unsigned int nTypeArgs, TypeHandle *pRes); + TypeHandle ReadInstantiation(Module *pModule, mdTypeDef tok, unsigned int nTypeArgs); + TypeHandle ReadTypeHandle(); + + BOOL Finished() { LIMITED_METHOD_CONTRACT; return m_remaining == 0; } + DebuggerIPCE_TypeArgData *ReadOne() { LIMITED_METHOD_CONTRACT; if (m_remaining) { m_remaining--; return m_curdata++; } else return NULL; } + + }; + + + + HRESULT GetMethodDescData(MethodDesc *pFD, + DebuggerJitInfo *pJITInfo, + DebuggerIPCE_JITFuncData *data); + + void GetAndSendTransitionStubInfo(CORDB_ADDRESS_TYPE *stubAddress); + + void SendBreakpoint(Thread *thread, T_CONTEXT *context, + DebuggerBreakpoint *breakpoint); + + void SendStep(Thread *thread, T_CONTEXT *context, + DebuggerStepper *stepper, + CorDebugStepReason reason); + + void LockAndSendEnCRemapEvent(DebuggerJitInfo * dji, SIZE_T currentIP, SIZE_T *resumeIP); + void LockAndSendEnCRemapCompleteEvent(MethodDesc *pFD); + void SendEnCUpdateEvent(DebuggerIPCEventType eventType, + Module * pModule, + mdToken memberToken, + mdTypeDef classToken, + SIZE_T enCVersion); + void LockAndSendBreakpointSetError(PATCH_UNORDERED_ARRAY * listUnbindablePatches); + + // helper for SendException + void SendExceptionEventsWorker( + Thread * pThread, + bool firstChance, + bool fIsInterceptable, + bool continuable, + SIZE_T currentIP, + FramePointer framePointer, + bool atSafePlace); + + // Main function to send an exception event, handle jit-attach if needed, etc + HRESULT SendException(Thread *pThread, + bool fFirstChance, + SIZE_T currentIP, + SIZE_T currentSP, + bool fContinuable, + bool fAttaching, + bool fForceNonInterceptable, + EXCEPTION_POINTERS * pExceptionInfo); + + // Top-level function to handle sending a user-breakpoint, jit-attach, sync, etc. + void SendUserBreakpoint(Thread * thread); + + // Send the user breakpoint and block waiting for a continue. + void SendUserBreakpointAndSynchronize(Thread * pThread); + + // Just send the actual event. + void SendRawUserBreakpoint(Thread *thread); + + + + void SendInterceptExceptionComplete(Thread *thread); + + HRESULT AttachDebuggerForBreakpoint(Thread *thread, + __in_opt WCHAR *wszLaunchReason); + + + void ThreadIsSafe(Thread *thread); + + void UnrecoverableError(HRESULT errorHR, + unsigned int errorCode, + const char *errorFile, + unsigned int errorLine, + bool exitThread); + + BOOL IsSynchronizing(void) + { + LIMITED_METHOD_CONTRACT; + + return m_trappingRuntimeThreads; + } + + // + // The debugger mutex is used to protect any "global" Left Side + // data structures. The RCThread takes it when handling a Right + // Side event, and Runtime threads take it when processing + // debugger events. + // +#ifdef _DEBUG + int m_mutexCount; +#endif + + // Helper function + HRESULT AttachDebuggerForBreakpointOnHelperThread(Thread *pThread); + + // helper function to send Exception IPC event and Exception_CallBack2 event + HRESULT SendExceptionHelperAndBlock( + Thread *pThread, + OBJECTHANDLE exceptionHandle, + bool continuable, + FramePointer framePointer, + SIZE_T nOffset, + CorDebugExceptionCallbackType eventType, + DWORD dwFlags); + + + // Helper function to send out LogMessage only. Can be either on helper thread or manager thread. + void SendRawLogMessage( + Thread *pThread, + AppDomain *pAppDomain, + int iLevel, + SString * pCategory, + SString * pMessage); + + + // Helper function to send MDA notification + void SendRawMDANotification(SendMDANotificationParams * params); + static void SendMDANotificationOnHelperThreadProxy(SendMDANotificationParams * params); + + // Returns a bitfield reflecting the managed debugging state at the time of + // the jit attach. + CLR_DEBUGGING_PROCESS_FLAGS GetAttachStateFlags(); + + // Records that this thread is about to trigger jit attach and + // resolves the race for which thread gets to trigger it + BOOL PreJitAttach(BOOL willSendManagedEvent, BOOL willLaunchDebugger, BOOL explicitUserRequest); + + // Blocks until the debugger completes jit attach + void WaitForDebuggerAttach(); + + // Cleans up after jit attach is complete + void PostJitAttach(); + + // Main worker function to initiate, handle, and wait for a Jit-attach. + void JitAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest); + +private: + void DoNotCallDirectlyPrivateLock(void); + void DoNotCallDirectlyPrivateUnlock(void); + + // 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 + HRESULT LaunchJitDebuggerAndNativeAttach(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo); + + // Helper to serialize metadata that has been updated by the profiler into + // a buffer so that it can be read out-of-proc + BYTE* SerializeModuleMetaData(Module * pModule, DWORD * countBytes); + + /// Wrapps fusion Module FusionCopyPDBs. + HRESULT CopyModulePdb(Module* pRuntimeModule); + + // 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 IterateAppDomainsForPdbs(); + +#ifndef DACCESS_COMPILE +public: + // Helper function to initialize JDI structure + void InitDebuggerLaunchJitInfo(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo); + + // Helper function to retrieve JDI structure + JIT_DEBUG_INFO * GetDebuggerLaunchJitInfo(void); + +private: + static JIT_DEBUG_INFO s_DebuggerLaunchJitInfo; + static EXCEPTION_RECORD s_DebuggerLaunchJitInfoExceptionRecord; + static CONTEXT s_DebuggerLaunchJitInfoContext; + + static void AcquireDebuggerLock(Debugger *c) + { + WRAPPER_NO_CONTRACT; + c->DoNotCallDirectlyPrivateLock(); + } + + static void ReleaseDebuggerLock(Debugger *c) + { + WRAPPER_NO_CONTRACT; + c->DoNotCallDirectlyPrivateUnlock(); + } +#else // DACCESS_COMPILE + static void AcquireDebuggerLock(Debugger *c); + static void ReleaseDebuggerLock(Debugger *c); +#endif // DACCESS_COMPILE + + +public: + // define type for DebuggerLockHolder + typedef DacHolder<Debugger *, Debugger::AcquireDebuggerLock, Debugger::ReleaseDebuggerLock> DebuggerLockHolder; + + void LockForEventSending(DebuggerLockHolder *dbgLockHolder); + void UnlockFromEventSending(DebuggerLockHolder *dbgLockHolder); + void SyncAllThreads(DebuggerLockHolder *dbgLockHolder); + void SendSyncCompleteIPCEvent(); + + // Helper for sending a single pre-baked IPC event and blocking on the continue. + // See definition of SENDIPCEVENT_BEGIN for usage pattern. + void SendSimpleIPCEventAndBlock(); + + void SendCreateProcess(DebuggerLockHolder * pDbgLockHolder); + + void IncrementClassLoadCallbackCount(void) + { + LIMITED_METHOD_CONTRACT; + InterlockedIncrement(&m_dClassLoadCallbackCount); + } + + void DecrementClassLoadCallbackCount(void) + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_dClassLoadCallbackCount > 0); + InterlockedDecrement(&m_dClassLoadCallbackCount); + } + + +#ifdef _DEBUG_IMPL + bool ThreadHoldsLock(void) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (g_fProcessDetach) + return true; + + BEGIN_GETTHREAD_ALLOWED; + if (g_pEEInterface->GetThread()) + { + return (GetThreadIdHelper(g_pEEInterface->GetThread()) == m_mutexOwner); + } + else + { + return (GetCurrentThreadId() == m_mutexOwner); + } + END_GETTHREAD_ALLOWED; + } +#endif // _DEBUG_IMPL + +#ifdef FEATURE_INTEROP_DEBUGGING + static VOID M2UHandoffHijackWorker( + T_CONTEXT *pContext, + EXCEPTION_RECORD *pExceptionRecord); + + LONG FirstChanceSuspendHijackWorker( + T_CONTEXT *pContext, + EXCEPTION_RECORD *pExceptionRecord); + static void GenericHijackFunc(void); + static void SecondChanceHijackFunc(void); + static void SecondChanceHijackFuncWorker(void); + static void SignalHijackStarted(void); + static void ExceptionForRuntimeHandoffStart(void); + static void ExceptionForRuntimeHandoffComplete(void); + static void SignalHijackComplete(void); + static void ExceptionNotForRuntime(void); + static void NotifyRightSideOfSyncComplete(void); + static void NotifySecondChanceReadyForData(void); +#endif // FEATURE_INTEROP_DEBUGGING + + void UnhandledHijackWorker(T_CONTEXT * pContext, EXCEPTION_RECORD * pRecord); + + // + // InsertToMethodInfoList puts the given DMI onto the DMI list. + // + HRESULT InsertToMethodInfoList(DebuggerMethodInfo *dmi); + + + // MapBreakpoints will map any and all breakpoints (except EnC + // patches) from previous versions of the method into the current version. + HRESULT MapAndBindFunctionPatches( DebuggerJitInfo *pJiNew, + MethodDesc * fd, + CORDB_ADDRESS_TYPE * addrOfCode); + + // MPTDJI takes the given patch (and djiFrom, if you've got it), and + // does the IL mapping forwards to djiTo. Returns + // CORDBG_E_CODE_NOT_AVAILABLE if there isn't a mapping, which means that + // no patch was placed. + HRESULT MapPatchToDJI(DebuggerControllerPatch *dcp, DebuggerJitInfo *djiTo); + + HRESULT LaunchDebuggerForUser(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, + BOOL useManagedBPForManagedAttach, BOOL explicitUserRequest); + + void SendLogMessage (int iLevel, + SString * pSwitchName, + SString * pMessage); + + void SendLogSwitchSetting (int iLevel, + int iReason, + __in_z LPCWSTR pLogSwitchName, + __in_z LPCWSTR pParentSwitchName); + + bool IsLoggingEnabled (void) + { + LIMITED_METHOD_CONTRACT; + + if (m_LoggingEnabled) + return true; + return false; + } + + // send a custom debugger notification to the RS + void SendCustomDebuggerNotification(Thread * pThread, DomainFile * pDomain, mdTypeDef classToken); + + // Send an MDA notification. This ultimately translates to an ICorDebugMDA object on the Right-Side. + void SendMDANotification( + Thread * pThread, // may be NULL. Lets us send on behalf of other threads. + SString * szName, + SString * szDescription, + SString * szXML, + CorDebugMDAFlags flags, + BOOL bAttach + ); + + + void EnableLogMessages (bool fOnOff) {LIMITED_METHOD_CONTRACT; m_LoggingEnabled = fOnOff;} + bool GetILOffsetFromNative (MethodDesc *PFD, const BYTE *pbAddr, + DWORD nativeOffset, DWORD *ilOffset); + + DWORD GetHelperThreadID(void ); + + + HRESULT SetIP( bool fCanSetIPOnly, + Thread *thread, + Module *module, + mdMethodDef mdMeth, + DebuggerJitInfo* dji, + SIZE_T offsetILTo, + BOOL fIsIL); + + // Helper routines used by Debugger::SetIP + + // 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. + BOOL IsVarArgsFunction(unsigned int nEntries, PTR_NativeVarInfo varNativeInfo); + + HRESULT ShuffleVariablesGet(DebuggerJitInfo *dji, + SIZE_T offsetFrom, + T_CONTEXT *pCtx, + SIZE_T **prgVal1, + SIZE_T **prgVal2, + BYTE ***prgpVCs); + + HRESULT ShuffleVariablesSet(DebuggerJitInfo *dji, + SIZE_T offsetTo, + T_CONTEXT *pCtx, + SIZE_T **prgVal1, + SIZE_T **prgVal2, + BYTE **rgpVCs); + + HRESULT GetVariablesFromOffset(MethodDesc *pMD, + UINT varNativeInfoCount, + ICorDebugInfo::NativeVarInfo *varNativeInfo, + SIZE_T offsetFrom, + T_CONTEXT *pCtx, + SIZE_T *rgVal1, + SIZE_T *rgVal2, + UINT uRgValSize, // number of element of the preallocated rgVal1 and rgVal2 + BYTE ***rgpVCs); + + HRESULT SetVariablesAtOffset(MethodDesc *pMD, + UINT varNativeInfoCount, + ICorDebugInfo::NativeVarInfo *varNativeInfo, + SIZE_T offsetTo, + T_CONTEXT *pCtx, + SIZE_T *rgVal1, + SIZE_T *rgVal2, + BYTE **rgpVCs); + + BOOL IsThreadContextInvalid(Thread *pThread); + + // notification for SQL fiber debugging support + void CreateConnection(CONNID dwConnectionId, __in_z WCHAR *wzName); + void DestroyConnection(CONNID dwConnectionId); + void ChangeConnection(CONNID dwConnectionId); + + // + // This function is used to identify the helper thread. + // + bool ThisIsHelperThread(void); + + HRESULT ReDaclEvents(PSECURITY_DESCRIPTOR securityDescriptor); + +#ifdef DACCESS_COMPILE + virtual void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); + virtual void EnumMemoryRegionsIfFuncEvalFrame(CLRDataEnumMemoryFlags flags, Frame * pFrame); +#endif + + BOOL ShouldAutoAttach(); + BOOL FallbackJITAttachPrompt(); + HRESULT SetFiberMode(bool isFiberMode); + + HRESULT AddAppDomainToIPC (AppDomain *pAppDomain); + HRESULT RemoveAppDomainFromIPC (AppDomain *pAppDomain); + HRESULT UpdateAppDomainEntryInIPC (AppDomain *pAppDomain); + + void SendCreateAppDomainEvent(AppDomain * pAppDomain); + void SendExitAppDomainEvent (AppDomain *pAppDomain); + + // Notify the debugger that an assembly has been loaded + void LoadAssembly(DomainAssembly * pDomainAssembly); + + // Notify the debugger that an assembly has been unloaded + void UnloadAssembly(DomainAssembly * pDomainAssembly); + + HRESULT FuncEvalSetup(DebuggerIPCE_FuncEvalInfo *pEvalInfo, BYTE **argDataArea, DebuggerEval **debuggerEvalKey); + HRESULT FuncEvalSetupReAbort(Thread *pThread, Thread::ThreadAbortRequester requester); + HRESULT FuncEvalAbort(DebuggerEval *debuggerEvalKey); + HRESULT FuncEvalRudeAbort(DebuggerEval *debuggerEvalKey); + HRESULT FuncEvalCleanup(DebuggerEval *debuggerEvalKey); + + HRESULT SetReference(void *objectRefAddress, VMPTR_OBJECTHANDLE vmObjectHandle, void *newReference); + HRESULT SetValueClass(void *oldData, void *newData, DebuggerIPCE_BasicTypeData *type); + + HRESULT SetILInstrumentedCodeMap(MethodDesc *fd, + BOOL fStartJit, + ULONG32 cILMapEntries, + COR_IL_MAP rgILMapEntries[]); + + void EarlyHelperThreadDeath(void); + + void ShutdownBegun(void); + + void LockDebuggerForShutdown(void); + + void DisableDebugger(void); + + // Pid of the left side process that this Debugger instance is in. + DWORD GetPid(void) { return m_processId; } + + HRESULT NameChangeEvent(AppDomain *pAppDomain, Thread *pThread); + + // send an event to the RS indicating that there's a Ctrl-C or Ctrl-Break + BOOL SendCtrlCToDebugger(DWORD dwCtrlType); + + // Allows the debugger to keep an up to date list of special threads + HRESULT UpdateSpecialThreadList(DWORD cThreadArrayLength, DWORD *rgdwThreadIDArray); + + // Updates the pointer for the debugger services + void SetIDbgThreadControl(IDebuggerThreadControl *pIDbgThreadControl); + +#ifndef DACCESS_COMPILE + static void AcquireDebuggerDataLock(Debugger *pDebugger); + + static void ReleaseDebuggerDataLock(Debugger *pDebugger); + +#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. + static void AcquireDebuggerDataLock(Debugger *pDebugger); + + // unimplemented--nothing to do here + static void ReleaseDebuggerDataLock(Debugger *pDebugger); + +#endif // DACCESS_COMPILE + + // define type for DebuggerDataLockHolder + typedef DacHolder<Debugger *, Debugger::AcquireDebuggerDataLock, Debugger::ReleaseDebuggerDataLock> DebuggerDataLockHolder; + +#ifdef _DEBUG + // Use for asserts + bool HasDebuggerDataLock() + { + // If no lazy data yet, then can't possibly have the debugger-data lock. + if (!g_pDebugger->HasLazyData()) + { + return false; + } + return (g_pDebugger->GetDebuggerDataLock()->OwnedByCurrentThread()) != 0; + } +#endif + + + // For Just-My-Code (aka Just-User-Code). + // The jit injects probes in debuggable managed methods that look like: + // if (*pFlag != 0) call JIT_DbgIsJustMyCode. + // pFlag is unique per-method constant determined by GetJMCFlagAddr. + // JIT_DbgIsJustMyCode will get the ip & fp and call OnMethodEnter. + + // pIP is an ip within the method, right after the prolog. +#ifndef DACCESS_COMPILE + virtual void OnMethodEnter(void * pIP); + virtual DWORD* GetJMCFlagAddr(Module * pModule); +#endif + + // GetJMCFlagAddr provides a unique flag for each module. UpdateModuleJMCFlag + // will go through all modules with user-code and set their flag to fStatus. + void UpdateAllModuleJMCFlag(bool fStatus); + void UpdateModuleJMCFlag(Module * pRuntime, bool fStatus); + + // Set the default JMC status of the specified module. This function + // also finds all the DMIs in the specified module and update their + // JMC status as well. + void SetModuleDefaultJMCStatus(Module * pRuntimeModule, bool fStatus); + +#ifndef DACCESS_COMPILE + static DWORD GetThreadIdHelper(Thread *pThread); +#endif // DACCESS_COMPILE + +private: + DebuggerJitInfo *GetJitInfoWorker(MethodDesc *fd, const BYTE *pbAddr, DebuggerMethodInfo **pMethInfo); + + // Save the necessary information for the debugger to recognize an IP in one of the thread redirection + // functions. + void InitializeHijackFunctionAddress(); + + void InitDebugEventCounting(); + void DoHelperThreadDuty(); + + typedef enum + { + ATTACH_YES, + ATTACH_NO, + ATTACH_TERMINATE + } ATTACH_ACTION; + + // Returns true if the debugger is not attached and DbgJITDebugLaunchSetting + // is set to either ATTACH_DEBUGGER or ASK_USER and the user request attaching. + ATTACH_ACTION ShouldAttachDebugger(bool fIsUserBreakpoint); + ATTACH_ACTION ShouldAttachDebuggerProxy(bool fIsUserBreakpoint); + friend void ShouldAttachDebuggerStub(ShouldAttachDebuggerParams * p); + friend struct ShouldAttachDebuggerParams; + + void TrapAllRuntimeThreads(); + void ReleaseAllRuntimeThreads(AppDomain *pAppDomain); + +#ifndef DACCESS_COMPILE + // @dbgtodo inspection - eventually, all replies should be removed because requests will be DAC-ized. + // Do not call this function unless you are getting ThreadId from RS + void InitIPCReply(DebuggerIPCEvent *ipce, + DebuggerIPCEventType type) + { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(ipce != NULL); + ipce->type = type; + ipce->hr = S_OK; + + ipce->processId = m_processId; + // AppDomain, Thread, are already initialized + } + + void InitIPCEvent(DebuggerIPCEvent *ipce, + DebuggerIPCEventType type, + Thread *pThread, + AppDomain* pAppDomain) + { + WRAPPER_NO_CONTRACT; + + InitIPCEvent(ipce, type, pThread, VMPTR_AppDomain::MakePtr(pAppDomain)); + } + + // Let this function to figure out the unique Id that we will use for Thread. + void InitIPCEvent(DebuggerIPCEvent *ipce, + DebuggerIPCEventType type, + Thread *pThread, + VMPTR_AppDomain vmAppDomain) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(ipce != NULL); + ipce->type = type; + ipce->hr = S_OK; + ipce->processId = m_processId; + ipce->vmAppDomain = vmAppDomain; + ipce->vmThread.SetRawPtr(pThread); + } + + void InitIPCEvent(DebuggerIPCEvent *ipce, + DebuggerIPCEventType type) + { + WRAPPER_NO_CONTRACT; + + _ASSERTE((type == DB_IPCE_SYNC_COMPLETE) || + (type == DB_IPCE_TEST_CRST) || + (type == DB_IPCE_TEST_RWLOCK)); + + Thread *pThread = g_pEEInterface->GetThread(); + AppDomain *pAppDomain = NULL; + + if (pThread) + { + pAppDomain = pThread->GetDomain(); + } + + InitIPCEvent(ipce, + type, + pThread, + VMPTR_AppDomain::MakePtr(pAppDomain)); + } +#endif // DACCESS_COMPILE + + HRESULT GetFunctionInfo(Module *pModule, + mdToken functionToken, + BYTE **pCodeStart, + unsigned int *pCodeSize, + mdToken *pLocalSigToken); + + // Allocate a buffer and send it to the right side + HRESULT GetAndSendBuffer(DebuggerRCThread* rcThread, ULONG bufSize); + + // Allocate a buffer in the left-side for use by the right-side + HRESULT AllocateRemoteBuffer( ULONG bufSize, void **ppBuffer ); + + // Releases a previously requested remote bufer and send reply + HRESULT SendReleaseBuffer(DebuggerRCThread* rcThread, void *pBuffer); + +public: + // Release previously requested remmote buffer + HRESULT ReleaseRemoteBuffer(void *pBuffer, bool removeFromBlobList); + +private: +#ifdef EnC_SUPPORTED + // Apply an EnC edit and send the result event to the RS + HRESULT ApplyChangesAndSendResult(DebuggerModule * pDebuggerModule, + DWORD cbMetadata, + BYTE *pMetadata, + DWORD cbIL, + BYTE *pIL); +#endif // EnC_SUPPORTED + + bool GetCompleteDebuggerLaunchString(SString * pStrArgsBuf); + + // Launch a debugger for jit-attach + void EnsureDebuggerAttached(Thread * pThread, EXCEPTION_POINTERS * pExceptionInfo, BOOL willSendManagedEvent, BOOL explicitUserRequest); + HRESULT EDAHelper(PROCESS_INFORMATION * pProcessInfo); + HRESULT EDAHelperProxy(PROCESS_INFORMATION * pProcessInfo); + friend void EDAHelperStub(EnsureDebuggerAttachedParams * p); + DebuggerLaunchSetting GetDbgJITDebugLaunchSetting(); + +public: + HRESULT InitAppDomainIPC(void); + HRESULT TerminateAppDomainIPC(void); + + bool ResumeThreads(AppDomain* pAppDomain); + + static DWORD WaitForSingleObjectHelper(HANDLE handle, DWORD dwMilliseconds); + + void ProcessAnyPendingEvals(Thread *pThread); + + bool HasLazyData(); + RCThreadLazyInit * GetRCThreadLazyData(); + + // The module table is lazy init, and may be NULL. Callers must check. + DebuggerModuleTable * GetModuleTable(); + + DebuggerHeap *GetInteropSafeHeap(); + DebuggerHeap *GetInteropSafeHeap_NoThrow(); + DebuggerHeap *GetInteropSafeExecutableHeap(); + DebuggerHeap *GetInteropSafeExecutableHeap_NoThrow(); + DebuggerLazyInit *GetLazyData(); + HelperCanary * GetCanary(); + void MarkDebuggerAttachedInternal(); + void MarkDebuggerUnattachedInternal(); + + HANDLE GetAttachEvent() { return GetLazyData()->m_exAttachEvent; } + +private: +#ifndef DACCESS_COMPILE + void StartCanaryThread(); +#endif + DebuggerPendingFuncEvalTable *GetPendingEvals() { return GetLazyData()->m_pPendingEvals; } + SIZE_T_UNORDERED_ARRAY * GetBPMappingDuplicates() { return &GetLazyData()->m_BPMappingDuplicates; } + HANDLE GetUnmanagedAttachEvent() { return GetLazyData()->m_exUnmanagedAttachEvent; } + BOOL GetDebuggerHandlingCtrlC() { return GetLazyData()->m_DebuggerHandlingCtrlC; } + void SetDebuggerHandlingCtrlC(BOOL f) { GetLazyData()->m_DebuggerHandlingCtrlC = f; } + HANDLE GetCtrlCMutex() { return GetLazyData()->m_CtrlCMutex; } + UnorderedPtrArray* GetMemBlobs() { return &GetLazyData()->m_pMemBlobs; } + + + PTR_DebuggerRCThread m_pRCThread; + DWORD m_processId; // our pid + BOOL m_trappingRuntimeThreads; + BOOL m_stopped; + BOOL m_unrecoverableError; + BOOL m_ignoreThreadDetach; + PTR_DebuggerMethodInfoTable m_pMethodInfos; + + + // This is the main debugger lock. It is a large lock and used to synchronize complex operations + // such as sending IPC events, debugger sycnhronization, and attach / detach. + // The debugger effectively can't make any radical state changes without holding this lock. + // + // + Crst m_mutex; // The main debugger lock. + + // Flag to track if the debugger Crst needs to go into "Shutdown for Finalizer" mode. + // This means that only special shutdown threads (helper / finalizer / shutdown) can + // take the lock, and all others will just block forever if they take it. + bool m_fShutdownMode; + + // + // Flag to track if the VM has told the debugger that it should block all threads + // as soon as possible as it goes thru the debugger. As of this writing, this is + // done via the debugger Crst, anyone attempting to take the lock will block forever. + // + bool m_fDisabled; + +#ifdef _DEBUG + // Ownership tracking for debugging. + DWORD m_mutexOwner; + + // Tid that last called LockForEventSending. + DWORD m_tidLockedForEventSending; +#endif + LONG m_threadsAtUnsafePlaces; + Volatile<BOOL> m_jitAttachInProgress; + + // True if after the jit attach we plan to send a managed non-catchup + // debug event + BOOL m_attachingForManagedEvent; + BOOL m_launchingDebugger; + BOOL m_userRequestedDebuggerLaunch; + + BOOL m_LoggingEnabled; + AppDomainEnumerationIPCBlock *m_pAppDomainCB; + + LONG m_dClassLoadCallbackCount; + + // Lazily initialized array of debugger modules + // @dbgtodo module - eventually, DebuggerModule should go away, + // and all such information should be stored in either the VM's module class or in the RS. + DebuggerModuleTable *m_pModules; + + // DacDbiInterfaceImpl needs to be able to write to private fields in the debugger class. + friend class DacDbiInterfaceImpl; + + // Set OOP by RS to request a sync after a debug event. + // Clear by LS when we sync. + Volatile<BOOL> m_RSRequestedSync; + + // send first chance/handler found callbacks for exceptions outside of JMC to the LS + Volatile<BOOL> m_sendExceptionsOutsideOfJMC; + + // represents different thead redirection functions recognized by the debugger + enum HijackFunction + { + kUnhandledException = 0, + kRedirectedForGCThreadControl, + kRedirectedForDbgThreadControl, + kRedirectedForUserSuspend, + kRedirectedForYieldTask, +#if defined(HAVE_GCCOVER) && defined(_TARGET_AMD64_) + kRedirectedForGCStress, +#endif // HAVE_GCCOVER && _TARGET_AMD64_ + kMaxHijackFunctions, + }; + + // static array storing the range of the thread redirection functions + static MemoryRange s_hijackFunction[kMaxHijackFunctions]; + + // Currently DAC doesn't support static array members. This field is used to work around this limitation. + ARRAY_PTR_MemoryRange m_rgHijackFunction; + +public: + + + IDebuggerThreadControl *m_pIDbgThreadControl; + + + // Sometimes we force all exceptions to be non-interceptable. + // There are currently three cases where we set this field to true: + // + // 1) NotifyOfCHFFilter() + // - If the CHF filter is the first handler we encounter in the first pass, then there is no + // managed stack frame at which we can intercept the exception anyway. + // + // 2) LastChanceManagedException() + // - If Watson is launched for an unhandled exception, then the exception cannot be intercepted. + // + // 3) SecondChanceHijackFuncWorker() + // - The RS hijack the thread to this function to prevent the OS from killing the process at + // the end of the first pass. (When a debugger is attached, the OS does not run a second pass.) + // This function ensures that the debugger gets a second chance notification. + BOOL m_forceNonInterceptable; + + // When we are doing an early attach, the RS shim should not queue all the fake attach events for + // the process, the appdomain, and the thread. Otherwise we'll get duplicate events when these + // entities are actually created. This flag is used to mark whether we are doing an early attach. + // There are still time windows where we can get duplicate events, but this flag closes down the + // most common scenario. + SVAL_DECL(BOOL, s_fEarlyAttach); + +private: + Crst * GetDebuggerDataLock() { SUPPORTS_DAC; return &GetLazyData()-> m_DebuggerDataLock; } + + // This is lazily inititalized. It's just a wrapper around a handle so we embed it here. + DebuggerHeap m_heap; + DebuggerHeap m_executableHeap; + + PTR_DebuggerLazyInit m_pLazyData; + + + // A list of all defines that affect layout of MD types + typedef enum _Target_Defines + { + DEFINE__DEBUG = 1, + } _Target_Defines; + + // A bitfield that has bits set at build time corresponding + // to which defines are active + static const int _defines = 0 +#ifdef _DEBUG + | DEFINE__DEBUG +#endif + ; + +public: + DWORD m_defines; + DWORD m_mdDataStructureVersion; +}; + + + +extern "C" { +void STDCALL FuncEvalHijack(void); +void * STDCALL FuncEvalHijackWorker(DebuggerEval *pDE); + +void STDCALL ExceptionHijack(void); +void STDCALL ExceptionHijackEnd(void); +void STDCALL ExceptionHijackWorker(T_CONTEXT * pContext, EXCEPTION_RECORD * pRecord, EHijackReason::EHijackReason reason, void * pData); + +void RedirectedHandledJITCaseForGCThreadControl_Stub(); +void RedirectedHandledJITCaseForGCThreadControl_StubEnd(); + +void RedirectedHandledJITCaseForDbgThreadControl_Stub(); +void RedirectedHandledJITCaseForDbgThreadControl_StubEnd(); + +void RedirectedHandledJITCaseForUserSuspend_Stub(); +void RedirectedHandledJITCaseForUserSuspend_StubEnd(); + +void RedirectedHandledJITCaseForYieldTask_Stub(); +void RedirectedHandledJITCaseForYieldTask_StubEnd(); +#if defined(HAVE_GCCOVER) && defined(_TARGET_AMD64_) +void RedirectedHandledJITCaseForGCStress_Stub(); +void RedirectedHandledJITCaseForGCStress_StubEnd(); +#endif // HAVE_GCCOVER && _TARGET_AMD64_ +}; + + +// CNewZeroData is the allocator used by the all the hash tables that the helper thread could possibly alter. It uses +// the interop safe allocator. +class CNewZeroData +{ +public: +#ifndef DACCESS_COMPILE + static BYTE *Alloc(int iSize, int iMaxSize) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(g_pDebugger != NULL); + } + CONTRACTL_END; + + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + if (pHeap == NULL) + { + return NULL; + } + + BYTE *pb = (BYTE *) pHeap->Alloc(iSize); + if (pb == NULL) + { + return NULL; + } + + memset(pb, 0, iSize); + return pb; + } + static void Free(BYTE *pPtr, int iSize) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(g_pDebugger != NULL); + } + CONTRACTL_END; + + + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + _ASSERTE(pHeap != NULL); // should already exist + + pHeap->Free(pPtr); + } + static BYTE *Grow(BYTE *&pPtr, int iCurSize) + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + PRECONDITION(g_pDebugger != NULL); + } + CONTRACTL_END; + + void *p; + + DebuggerHeap* pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + _ASSERTE(pHeap != NULL); // should already exist + + PREFIX_ASSUME( iCurSize >= 0 ); + S_UINT32 iNewSize = S_UINT32( iCurSize ) + S_UINT32( GrowSize(iCurSize) ); + if( iNewSize.IsOverflow() ) + { + return NULL; + } + p = pHeap->Realloc(pPtr, iNewSize.Value(), iCurSize); + if (p == NULL) + { + return NULL; + } + + memset((BYTE*)p+iCurSize, 0, GrowSize(iCurSize)); + return (pPtr = (BYTE *)p); + } + + // A hashtable may recycle memory. We need to zero it out again. + static void Clean(BYTE * pData, int iSize) + { + LIMITED_METHOD_CONTRACT; + + memset(pData, 0, iSize); + } +#endif // DACCESS_COMPILE + + static int RoundSize(int iSize) + { + LIMITED_METHOD_CONTRACT; + + return (iSize); + } + static int GrowSize(int iCurSize) + { + LIMITED_METHOD_CONTRACT; + int newSize = (3 * iCurSize) / 2; + return (newSize < 256) ? 256 : newSize; + } +}; + +class DebuggerPendingFuncEvalTable : private CHashTableAndData<CNewZeroData> +{ + public: + virtual ~DebuggerPendingFuncEvalTable() = default; + + private: + + BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2) + { + LIMITED_METHOD_DAC_CONTRACT; + +#if defined(DACCESS_COMPILE) + // This function hasn't been tested yet in the DAC build. Make sure the DACization is correct. + DacNotImpl(); +#endif // DACCESS_COMPILE + + Thread * pThread1 = reinterpret_cast<Thread *>(k1); + Thread * pThread2 = dac_cast<PTR_DebuggerPendingFuncEval>(const_cast<HASHENTRY *>(pc2))->pThread; + + return (pThread1 != pThread2); + } + + ULONG HASH(Thread* pThread) + { + LIMITED_METHOD_CONTRACT; + return (ULONG)((SIZE_T)pThread); // only use low 32-bits if 64-bit + } + + + SIZE_T KEY(Thread * pThread) + { + LIMITED_METHOD_CONTRACT; + return (SIZE_T)pThread; + } + + public: + +#ifndef DACCESS_COMPILE + DebuggerPendingFuncEvalTable() : CHashTableAndData<CNewZeroData>(11) + { + WRAPPER_NO_CONTRACT; + + SUPPRESS_ALLOCATION_ASSERTS_IN_THIS_SCOPE; + NewInit(11, sizeof(DebuggerPendingFuncEval), 11); + } + + void AddPendingEval(Thread *pThread, DebuggerEval *pDE) + { + WRAPPER_NO_CONTRACT; + + _ASSERTE((pThread != NULL) && (pDE != NULL)); + + DebuggerPendingFuncEval *pfe = (DebuggerPendingFuncEval*)Add(HASH(pThread)); + pfe->pThread = pThread; + pfe->pDE = pDE; + } + + void RemovePendingEval(Thread* pThread) + { + WRAPPER_NO_CONTRACT; + + _ASSERTE(pThread != NULL); + + DebuggerPendingFuncEval *entry = (DebuggerPendingFuncEval*)Find(HASH(pThread), KEY(pThread)); + Delete(HASH(pThread), (HASHENTRY*)entry); + } + +#endif // #ifndef DACCESS_COMPILE + + DebuggerPendingFuncEval *GetPendingEval(Thread* pThread) + { + WRAPPER_NO_CONTRACT; + + DebuggerPendingFuncEval *entry = (DebuggerPendingFuncEval*)Find(HASH(pThread), KEY(pThread)); + return entry; + } +}; + +struct DebuggerModuleEntry +{ + FREEHASHENTRY entry; + PTR_DebuggerModule module; +}; + +typedef DPTR(struct DebuggerModuleEntry) PTR_DebuggerModuleEntry; + +class DebuggerModuleTable : private CHashTableAndData<CNewZeroData> +{ +#ifdef DACCESS_COMPILE + public: + virtual ~DebuggerModuleTable() = default; +#endif + + private: + + BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2) + { + LIMITED_METHOD_DAC_CONTRACT; + +#if defined(DACCESS_COMPILE) + // This function hasn't been tested yet in the DAC build. Make sure the DACization is correct. + DacNotImpl(); +#endif // DACCESS_COMPILE + + Module * pModule1 = reinterpret_cast<Module *>(k1); + Module * pModule2 = + dac_cast<PTR_DebuggerModuleEntry>(const_cast<HASHENTRY *>(pc2))->module->GetRuntimeModule(); + + return (pModule1 != pModule2); + } + + ULONG HASH(Module* module) + { + LIMITED_METHOD_CONTRACT; + return (ULONG)((SIZE_T)module); // only use low 32-bits if 64-bit + } + + SIZE_T KEY(Module * pModule) + { + LIMITED_METHOD_CONTRACT; + return (SIZE_T)pModule; + } + +#ifdef _DEBUG + bool ThreadHoldsLock(); +#endif + +public: + +#ifndef DACCESS_COMPILE + + DebuggerModuleTable(); + virtual ~DebuggerModuleTable(); + + void AddModule(DebuggerModule *module); + + void RemoveModule(Module* module, AppDomain *pAppDomain); + + + void Clear(); + + // + // RemoveModules removes any module loaded into the given appdomain from the hash. This is used when we send an + // ExitAppdomain event to ensure that there are no leftover modules in the hash. This can happen when we have shared + // modules that aren't properly accounted for in the CLR. We miss sending UnloadModule events for those modules, so + // we clean them up with this method. + // + void RemoveModules(AppDomain *pAppDomain); +#endif // #ifndef DACCESS_COMPILE + + DebuggerModule *GetModule(Module* module); + + // We should never look for a NULL Module * + DebuggerModule *GetModule(Module* module, AppDomain* pAppDomain); + DebuggerModule *GetFirstModule(HASHFIND *info); + DebuggerModule *GetNextModule(HASHFIND *info); +}; + +// struct DebuggerMethodInfoKey: Key for each of the method info hash table entries. +// Module * m_pModule: This and m_token make up the key +// mdMethodDef m_token: This and m_pModule make up the key +// +// Note: This is used for hashing, so the structure must be totally blittable. +typedef DPTR(struct DebuggerMethodInfoKey) PTR_DebuggerMethodInfoKey; +struct DebuggerMethodInfoKey +{ + PTR_Module pModule; + mdMethodDef token; +} ; + +// struct DebuggerMethodInfoEntry: Entry for the JIT info hash table. +// FREEHASHENTRY entry: Needed for use by the hash table +// DebuggerMethodInfo * ji: The actual DebuggerMethodInfo to +// hash. Note that DMI's will be hashed by MethodDesc. +typedef DPTR(struct DebuggerMethodInfoEntry) PTR_DebuggerMethodInfoEntry; +struct DebuggerMethodInfoEntry +{ + FREEHASHENTRY entry; + DebuggerMethodInfoKey key; + SIZE_T nVersion; + SIZE_T nVersionLastRemapped; + PTR_DebuggerMethodInfo mi; + +#ifdef DACCESS_COMPILE + void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); +#endif +}; + +// class DebuggerMethodInfoTable: Hash table to hold all the non-JIT related +// info for each method we see. The JIT infos live in a seperate table +// keyed by MethodDescs - there may be multiple +// JITted realizations of each MethodDef, e.g. under different generic +// assumptions. Hangs off of the Debugger object. +// INVARIANT: There is only one DebuggerMethodInfo per method +// in the table. Note that DMI's will be hashed by MethodDesc. +// +class DebuggerMethodInfoTable : private CHashTableAndData<CNewZeroData> +{ + VPTR_BASE_CONCRETE_VTABLE_CLASS(DebuggerMethodInfoTable); + + public: + virtual ~DebuggerMethodInfoTable() = default; + + private: + BOOL Cmp(SIZE_T k1, const HASHENTRY * pc2) + { + LIMITED_METHOD_DAC_CONTRACT; + + // This is the inverse of the KEY() function. + DebuggerMethodInfoKey * pDjik = reinterpret_cast<DebuggerMethodInfoKey *>(k1); + + DebuggerMethodInfoEntry * pDjie = dac_cast<PTR_DebuggerMethodInfoEntry>(const_cast<HASHENTRY *>(pc2)); + + return (pDjik->pModule != pDjie->key.pModule) || + (pDjik->token != pDjie->key.token); + } + + ULONG HASH(DebuggerMethodInfoKey* pDjik) + { + LIMITED_METHOD_DAC_CONTRACT; + return HashPtr( pDjik->token, pDjik->pModule ); + } + + SIZE_T KEY(DebuggerMethodInfoKey * pDjik) + { + // This is casting a host pointer to a SIZE_T. So that key is restricted to the host address space. + // This key is just passed to Cmp(), which will cast it back to a DebuggerMethodInfoKey*. + LIMITED_METHOD_DAC_CONTRACT; + return (SIZE_T)pDjik; + } + +//#define _DEBUG_DMI_TABLE + +#ifdef _DEBUG_DMI_TABLE +public: + ULONG CheckDmiTable(); + +#define CHECK_DMI_TABLE (CheckDmiTable()) +#define CHECK_DMI_TABLE_DEBUGGER (m_pMethodInfos->CheckDmiTable()) + +#else + +#define CHECK_DMI_TABLE +#define CHECK_DMI_TABLE_DEBUGGER + +#endif // _DEBUG_DMI_TABLE + + public: + +#ifndef DACCESS_COMPILE + + DebuggerMethodInfoTable(); + + HRESULT AddMethodInfo(Module *pModule, + mdMethodDef token, + DebuggerMethodInfo *mi); + + HRESULT OverwriteMethodInfo(Module *pModule, + mdMethodDef token, + DebuggerMethodInfo *mi, + BOOL fOnlyIfNull); + + // pModule is being unloaded - remove any entries that belong to it. Why? + // (a) Correctness: the module can be reloaded at the same address, + // which will cause accidental matches with our hashtable (indexed by + // {Module*,mdMethodDef} + // (b) Perf: don't waste the memory! + void ClearMethodsOfModule(Module *pModule); + void DeleteEntryDMI(DebuggerMethodInfoEntry *entry); + +#endif // #ifndef DACCESS_COMPILE + + DebuggerMethodInfo *GetMethodInfo(Module *pModule, mdMethodDef token); + DebuggerMethodInfo *GetFirstMethodInfo(HASHFIND *info); + DebuggerMethodInfo *GetNextMethodInfo(HASHFIND *info); + +#ifdef DACCESS_COMPILE + void EnumMemoryRegions(CLRDataEnumMemoryFlags flags); +#endif +}; + +class DebuggerEvalBreakpointInfoSegment +{ +public: + // DebuggerEvalBreakpointInfoSegment contains just the breakpoint + // instruction and a pointer to the associated DebuggerEval. It makes + // it easy to go from the instruction to the corresponding DebuggerEval + // object. It has been separated from the rest of the DebuggerEval + // because it needs to be in a section of memory that's executable, + // while the rest of DebuggerEval does not. By having it separate, we + // don't need to have the DebuggerEval contents in executable memory. + BYTE m_breakpointInstruction[CORDbg_BREAK_INSTRUCTION_SIZE]; + DebuggerEval *m_associatedDebuggerEval; + + DebuggerEvalBreakpointInfoSegment(DebuggerEval* dbgEval) + : m_associatedDebuggerEval(dbgEval) + { + ASSERT(dbgEval != NULL); + } +}; + +/* ------------------------------------------------------------------------ * + * DebuggerEval class + * + * Note that arguments get passsed in a block allocated when + * the func-eval is set up. The setup phase passes the total count of arguments. + * + * In some situations type arguments must also be passed, e.g. + * when performing a "newarr" operation or calling a generic function with a + * "funceval". In the setup phase we pass a count of the number of + * nodes in the "flattened" type expressions for the type arguments, if any. + * e.g. for calls to non-generic code this is 0. + * - for "newobj List<int>" this is 1: there is one type argument "int". + * - for "newobj Dict<string,int>" this is 2: there are two + * type arguments "string" and "int". + * - for "newobj Dict<string,List<int>>" this is 3: there are two + type arguments but the second contains two nodes (one for List and one for int). + * The type argument will get placed in the allocated argument block, + * the order being determined by the order they occur in the tree, i.e. + * left-to-right, top-to-bottom in the type expressions tree, e.g. for + * type arguments <string,List<int>> you get string followed by List followed by int. + * ------------------------------------------------------------------------ */ + +class DebuggerEval +{ +public: + + // + // Used as a bit field. + // + enum FUNC_EVAL_ABORT_TYPE + { + FE_ABORT_NONE = 0, + FE_ABORT_NORMAL = 1, + FE_ABORT_RUDE = 2 + }; + + T_CONTEXT m_context; + Thread *m_thread; + DebuggerIPCE_FuncEvalType m_evalType; + mdMethodDef m_methodToken; + mdTypeDef m_classToken; + ADID m_appDomainId; // Safe even if AD unloaded + PTR_DebuggerModule m_debuggerModule; // Only valid if AD is still around + RSPTR_CORDBEVAL m_funcEvalKey; + bool m_successful; // Did the eval complete successfully + Debugger::AreValueTypesBoxed m_retValueBoxing; // Is the return value boxed? + unsigned int m_argCount; + unsigned int m_genericArgsCount; + unsigned int m_genericArgsNodeCount; + SIZE_T m_stringSize; + BYTE *m_argData; + MethodDesc *m_md; + PCODE m_targetCodeAddr; + ARG_SLOT m_result[NUMBER_RETURNVALUE_SLOTS]; + TypeHandle m_resultType; + SIZE_T m_arrayRank; + FUNC_EVAL_ABORT_TYPE m_aborting; // Has an abort been requested, and what type. + bool m_aborted; // Was this eval aborted + bool m_completed; // Is the eval complete - successfully or by aborting + bool m_evalDuringException; + bool m_rethrowAbortException; + Thread::ThreadAbortRequester m_requester; // For aborts, what kind? + VMPTR_OBJECTHANDLE m_vmObjectHandle; + TypeHandle m_ownerTypeHandle; + DebuggerEvalBreakpointInfoSegment* m_bpInfoSegment; + + DebuggerEval(T_CONTEXT * pContext, DebuggerIPCE_FuncEvalInfo * pEvalInfo, bool fInException); + + // This constructor is only used when setting up an eval to re-abort a thread. + DebuggerEval(T_CONTEXT * pContext, Thread * pThread, Thread::ThreadAbortRequester requester); + + bool Init() + { + _ASSERTE(DbgIsExecutable(&m_bpInfoSegment->m_breakpointInstruction, sizeof(m_bpInfoSegment->m_breakpointInstruction))); + return true; + } + + // The m_argData buffer holds both the type arg data (for generics) and the main argument data. + // + // For DB_IPCE_FET_NEW_STRING it holds the data specifying the string to create. + DebuggerIPCE_TypeArgData *GetTypeArgData() + { + LIMITED_METHOD_CONTRACT; + return (DebuggerIPCE_TypeArgData *) (m_argData); + } + + DebuggerIPCE_FuncEvalArgData *GetArgData() + { + LIMITED_METHOD_CONTRACT; + return (DebuggerIPCE_FuncEvalArgData*) (m_argData + m_genericArgsNodeCount * sizeof(DebuggerIPCE_TypeArgData)); + } + + WCHAR *GetNewStringArgData() + { + LIMITED_METHOD_CONTRACT; + _ASSERTE(m_evalType == DB_IPCE_FET_NEW_STRING); + return (WCHAR*)m_argData; + } + + ~DebuggerEval() + { + WRAPPER_NO_CONTRACT; + + // Clean up any temporary buffers used to send the argument type information. These were allocated + // in respnse to a GET_BUFFER message + DebuggerIPCE_FuncEvalArgData *argData = GetArgData(); + for (unsigned int i = 0; i < m_argCount; i++) + { + if (argData[i].fullArgType != NULL) + { + _ASSERTE(g_pDebugger != NULL); + g_pDebugger->ReleaseRemoteBuffer((BYTE*)argData[i].fullArgType, true); + } + } + + // Clean up the array of argument information. This was allocated as part of Func Eval setup. + if (m_argData) + { + DeleteInteropSafe(m_argData); + } + +#ifdef _DEBUG + // Set flags to strategic values in case we access deleted memory. + m_completed = false; + m_rethrowAbortException = true; +#endif + } +}; + +/* ------------------------------------------------------------------------ * + * New/delete overrides to use the debugger's private heap + * ------------------------------------------------------------------------ */ + +class InteropSafe {}; +#define interopsafe (*(InteropSafe*)NULL) + +class InteropSafeExecutable {}; +#define interopsafeEXEC (*(InteropSafeExecutable*)NULL) + +#ifndef DACCESS_COMPILE +inline void * __cdecl operator new(size_t n, const InteropSafe&) +{ + CONTRACTL + { + THROWS; // throw on OOM + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(g_pDebugger != NULL); + void *result = g_pDebugger->GetInteropSafeHeap()->Alloc((DWORD)n); + if (result == NULL) { + ThrowOutOfMemory(); + } + return result; +} + +inline void * __cdecl operator new[](size_t n, const InteropSafe&) +{ + CONTRACTL + { + THROWS; // throw on OOM + GC_NOTRIGGER; + } + CONTRACTL_END; + _ASSERTE(g_pDebugger != NULL); + void *result = g_pDebugger->GetInteropSafeHeap()->Alloc((DWORD)n); + if (result == NULL) { + ThrowOutOfMemory(); + } + return result; +} + +inline void * __cdecl operator new(size_t n, const InteropSafe&, const NoThrow&) throw() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + if (pHeap == NULL) + { + return NULL; + } + void *result = pHeap->Alloc((DWORD)n); + return result; +} + +inline void * __cdecl operator new[](size_t n, const InteropSafe&, const NoThrow&) throw() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + if (pHeap == NULL) + { + return NULL; + } + void *result = pHeap->Alloc((DWORD)n); + return result; +} + +// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that +// this delete operator will be invoked automatically to destroy the object. +inline void __cdecl operator delete(void *p, const InteropSafe&) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (p != NULL) + { + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting + pHeap->Free(p); + } +} + +// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that +// this delete operator will be invoked automatically to destroy the object. +inline void __cdecl operator delete[](void *p, const InteropSafe&) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (p != NULL) + { + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting + + pHeap->Free(p); + } +} + +// +// Interop safe delete to match the interop safe new's above. There is no C++ syntax for actually invoking those interop +// safe delete operators above, so we use this method to accomplish the same thing. +// +template<class T> void DeleteInteropSafe(T *p) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Don't stop a thread that may hold the Interop-safe heap lock. + // It may be in preemptive, but it's still "inside" the CLR and so inside the "Can't-Stop-Region" + CantStopHolder hHolder; + + if (p != NULL) + { + p->~T(); + + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeHeap_NoThrow(); + _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting + + pHeap->Free(p); + } +} + +inline void * __cdecl operator new(size_t n, const InteropSafeExecutable&) +{ + CONTRACTL + { + THROWS; // throw on OOM + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(g_pDebugger != NULL); + void *result = g_pDebugger->GetInteropSafeExecutableHeap()->Alloc((DWORD)n); + if (result == NULL) { + ThrowOutOfMemory(); + } + return result; +} + +inline void * __cdecl operator new(size_t n, const InteropSafeExecutable&, const NoThrow&) throw() +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeExecutableHeap_NoThrow(); + if (pHeap == NULL) + { + return NULL; + } + void *result = pHeap->Alloc((DWORD)n); + return result; +} + +// Note: there is no C++ syntax for manually invoking this, but if a constructor throws an exception I understand that +// this delete operator will be invoked automatically to destroy the object. +inline void __cdecl operator delete(void *p, const InteropSafeExecutable&) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (p != NULL) + { + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeExecutableHeap_NoThrow(); + _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting + pHeap->Free(p); + } +} + +// +// Interop safe delete to match the interop safe new's above. There is no C++ syntax for actually invoking those interop +// safe delete operators above, so we use this method to accomplish the same thing. +// +template<class T> void DeleteInteropSafeExecutable(T *p) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + // Don't stop a thread that may hold the Interop-safe heap lock. + // It may be in preemptive, but it's still "inside" the CLR and so inside the "Can't-Stop-Region" + CantStopHolder hHolder; + + if (p != NULL) + { + p->~T(); + + _ASSERTE(g_pDebugger != NULL); + DebuggerHeap * pHeap = g_pDebugger->GetInteropSafeExecutableHeap_NoThrow(); + _ASSERTE(pHeap != NULL); // should have had heap around if we're deleting + + pHeap->Free(p); + } +} +#endif // DACCESS_COMPILE + + +#if _DEBUG +#define DBG_RUNTIME_MAX ((DB_IPCE_RUNTIME_LAST&0xff)+1) +#define DBG_DEBUGGER_MAX ((DB_IPCE_DEBUGGER_LAST&0xff)+1) + +#define DbgLog(event) DbgLogHelper(event) +void DbgLogHelper(DebuggerIPCEventType event); +#else +#define DbgLog(event) +#endif // _DEBUG + +//----------------------------------------------------------------------------- +// Helpers for cleanup +// These are various utility functions, mainly where we factor out code. +//----------------------------------------------------------------------------- +void GetPidDecoratedName(__out_ecount(cBufSizeInChars) WCHAR * pBuf, + int cBufSizeInChars, + const WCHAR * pPrefix); + +// Specify type of Win32 event +enum EEventResetType { + kManualResetEvent = TRUE, + kAutoResetEvent = FALSE +}; + +HANDLE CreateWin32EventOrThrow( + LPSECURITY_ATTRIBUTES lpEventAttributes, + EEventResetType eType, + BOOL bInitialState +); + +HANDLE OpenWin32EventOrThrow( + DWORD dwDesiredAccess, + BOOL bInheritHandle, + LPCWSTR lpName +); + +// @todo - should this be moved into where we defined IPCWriterInterface? +// Holder for security Attribute +// Old code: +// hr = g_pIPCManagerInterface->GetSecurityAttributes(GetCurrentProcessId(), &pSA); +// .... foo(pSa)... +// g_pIPCManagerInterface->DestroySecurityAttributes(pSA); +// +// new code: +// { +// SAHolder x(g_pIPCManagerInterface, GetCurrentProcessId()); +// .... foo(x.GetSA()) .. +// } // calls dtor +class IPCHostSecurityAttributeHolder +{ +public: + IPCHostSecurityAttributeHolder(DWORD pid); + ~IPCHostSecurityAttributeHolder(); + + SECURITY_ATTRIBUTES * GetHostSA(); + +protected: + SECURITY_ATTRIBUTES *m_pSA; // the resource we're protecting. +}; + +#define SENDIPCEVENT_RAW_BEGIN_EX(pDbgLockHolder, gcxStmt) \ + { \ + Debugger::DebuggerLockHolder *__pDbgLockHolder = pDbgLockHolder; \ + gcxStmt; \ + g_pDebugger->LockForEventSending(__pDbgLockHolder); + +#define SENDIPCEVENT_RAW_END_EX \ + g_pDebugger->UnlockFromEventSending(__pDbgLockHolder); \ + } + +#define SENDIPCEVENT_RAW_BEGIN(pDbgLockHolder) \ + SENDIPCEVENT_RAW_BEGIN_EX(pDbgLockHolder, GCX_PREEMP_EEINTERFACE_TOGGLE_COND(CORDebuggerAttached())) + +#define SENDIPCEVENT_RAW_END SENDIPCEVENT_RAW_END_EX + +// Suspend-aware SENDIPCEVENT macros: +// Check whether __thread has been suspended by the debugger via SetDebugState(). +// If this thread has been suspended, it shouldn't send any event to the RS because the +// debugger may not be expecting it. Instead, just leave the lock and retry. +// When we leave, we'll enter coop mode first and get suspended if a suspension is in progress. +// Afterwards, we'll transition back into preemptive mode, and we'll block because this thread +// has been suspended by the debugger (see code:Thread::RareEnablePreemptiveGC). +#define SENDIPCEVENT_BEGIN_EX(pDebugger, thread, gcxStmt) \ + { \ + FireEtwDebugIPCEventStart(); \ + bool __fRetry = true; \ + do \ + { \ + { \ + Debugger::DebuggerLockHolder __dbgLockHolder(pDebugger, FALSE); \ + Debugger::DebuggerLockHolder *__pDbgLockHolder = &__dbgLockHolder; \ + gcxStmt; \ + g_pDebugger->LockForEventSending(__pDbgLockHolder); \ + /* Check if the thread has been suspended by the debugger via SetDebugState(). */ \ + if (thread != NULL && thread->HasThreadStateNC(Thread::TSNC_DebuggerUserSuspend)) \ + { \ + /* Just leave the lock and retry (see comment above for explanation */ \ + } \ + else \ + { \ + __fRetry = false; \ + +#define SENDIPCEVENT_END_EX \ + ; \ + } \ + g_pDebugger->UnlockFromEventSending(__pDbgLockHolder); \ + } /* ~gcxStmt & ~DebuggerLockHolder */ \ + } while (__fRetry); \ + FireEtwDebugIPCEventEnd(); \ + } + + +// The typical SENDIPCEVENT - toggles the GC mode... +#define SENDIPCEVENT_BEGIN(pDebugger, thread) \ + SENDIPCEVENT_BEGIN_EX(pDebugger, thread, GCX_PREEMP_EEINTERFACE_TOGGLE_IFTHREAD_COND(CORDebuggerAttached())) + +// Convenience macro to match SENDIPCEVENT_BEGIN +#define SENDIPCEVENT_END SENDIPCEVENT_END_EX + + +// Use this if you need to access the DebuggerLockHolder set up by SENDIPCEVENT_BEGIN. +// This is valid only between the SENDIPCEVENT_BEGIN / SENDIPCEVENT_END macros +#define SENDIPCEVENT_PtrDbgLockHolder __pDbgLockHolder + + +// Common contract for sending events. +// Used inbetween SENDIPCEVENT_BEGIN & _END. +// +// Can't GC trigger b/c if we're sycning we'll deadlock: +// - We'll block at the GC toggle (b/c we're syncing). +// - But we're holding the LockForEventSending "lock", so we'll block the helper trying to send a +// SuspendComplete +// +// @todo- we could also assert that: +// - m_tidLockedForEventSending = GetCurrentThreadId(); +#define SENDEVENT_CONTRACT_ITEMS \ + GC_NOTRIGGER; \ + MODE_PREEMPTIVE; \ + PRECONDITION(g_pDebugger->ThreadHoldsLock()); \ + PRECONDITION(!g_pDebugger->IsStopped()); \ + + +//----------------------------------------------------------------------------- +// Sample usage for sending IPC _Notification_ events. +// This is different then SendIPCReply (which is used to reply to events +// initiated by the RS). +//----------------------------------------------------------------------------- + +// Thread *pThread = g_pEEInterface->GetThread(); +// SENDIPCEVENT_BEGIN(g_pDebugger, pThread); // or use "this" if inside a Debugger method +// _ASSERTE(ThreadHoldsLock()); // we now hold the debugger lock. +// // debugger may have detached while we were blocked above. +// +// if (CORDebuggerAttached()) { +// // Send as many IPC events as we wish. +// SendIPCEvent(....); +// SendIPCEvent(....); +// SendIPCEvent(....); +// +// if (we sent an event) { +// TrapAllRuntimeThreads(); +// } +// } +// +// // We block here while the debugger responds to the event. +// SENDIPCEVENT_END; + +// Or if we just want to send a single IPC event and block, we can do this: +// +// < ... Init IPC Event ...> +// SendSimpleIPCEventAndBlock(); <-- this will block +// +// Note we don't have to call SENDIPCEVENT_BEGIN / END in this case. + +// @todo - further potential cleanup to the IPC sending: +// - Make SendIPCEvent + TrapAllRuntimeThreads check for CORDebuggerAttached() so that we +// can always call them after SENDIPCEVENT_BEGIN +// - Assert that SendIPCEVent is only called inbetween a Begin/End pair +// - count if we actually send any IPCEvents inbetween a Begin/End pair, and then have +// SendIPCEvent_END call TrapAllRuntimeThreads automatically for us. + + +// Include all of the inline stuff now. +#include "debugger.inl" + + +// +// +// +// The below contract defines should only be used (A) if they apply, and (B) they are the LEAST +// definitive for the function you are contracting. The below defines represent the baseline contract +// for each case. +// +// e.g. If a function FOO() throws, always, you should use THROWS, not any of the below. +// +// +// +#if _DEBUG + +#define MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT \ + if (!m_pRCThread->IsRCThreadReady()) { THROWS; } else { NOTHROW; } + +#define MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT \ + if (!m_pRCThread->IsRCThreadReady() || (GetThread() != NULL)) { GC_TRIGGERS; } else { GC_NOTRIGGER; } + +#define GC_TRIGGERS_FROM_GETJITINFO if (GetThreadNULLOk() != NULL) { GC_TRIGGERS; } else { GC_NOTRIGGER; } + +// +// The DebuggerDataLock lock is UNSAFE_ANYMODE, which means that we cannot +// take a GC while someone is holding it. Unfortunately this means that +// we cannot contract for a "possible" GC trigger statically, and must +// rely on runtime coverage to find any code path that may cause a GC. +// +#define CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT WRAPPER(GC_TRIGGERS) + +#else + +#define MAY_DO_HELPER_THREAD_DUTY_THROWS_CONTRACT +#define MAY_DO_HELPER_THREAD_DUTY_GC_TRIGGERS_CONTRACT +#define CALLED_IN_DEBUGGERDATALOCK_HOLDER_SCOPE_MAY_GC_TRIGGERS_CONTRACT + +#define GC_TRIGGERS_FROM_GETJITINFO + +#endif + +// Returns true if the specified IL offset has a special meaning (eg. prolog, etc.) +bool DbgIsSpecialILOffset(DWORD offset); + +#if !defined(_TARGET_X86_) +void FixupDispatcherContext(T_DISPATCHER_CONTEXT* pDispatcherContext, T_CONTEXT* pContext, T_CONTEXT* pOriginalContext, PEXCEPTION_ROUTINE pUnwindPersonalityRoutine = NULL); +#endif + +#endif /* DEBUGGER_H_ */ |