diff options
Diffstat (limited to 'src/debug/di/rsmain.cpp')
-rw-r--r-- | src/debug/di/rsmain.cpp | 2530 |
1 files changed, 2530 insertions, 0 deletions
diff --git a/src/debug/di/rsmain.cpp b/src/debug/di/rsmain.cpp new file mode 100644 index 0000000000..8eefc09f4a --- /dev/null +++ b/src/debug/di/rsmain.cpp @@ -0,0 +1,2530 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// File: RsMain.cpp +// + +// Random RS utility stuff, plus root ICorCordbug implementation +// +//***************************************************************************** +#include "stdafx.h" +#include "primitives.h" +#include "safewrap.h" + +#include "check.h" + +#include <tlhelp32.h> +#include "wtsapi32.h" + +#ifndef SM_REMOTESESSION +#define SM_REMOTESESSION 0x1000 +#endif + +#include "corpriv.h" +#include "../../dlls/mscorrc/resource.h" +#include <limits.h> + + +// The top level Cordb object is built around the Shim +#include "shimpriv.h" + +//----------------------------------------------------------------------------- +// For debugging ease, cache some global values. +// Include these in retail & free because that's where we need them the most!! +// Optimized builds may not let us view locals & parameters. So Having these +// cached as global values should let us inspect almost all of +// the interesting parts of the RS even in a Retail build! +//----------------------------------------------------------------------------- + +RSDebuggingInfo g_RSDebuggingInfo_OutOfProc = {0 }; // set to NULL +RSDebuggingInfo * g_pRSDebuggingInfo = &g_RSDebuggingInfo_OutOfProc; + + +#ifdef _DEBUG +// For logs, we can print the string name for the debug codes. +const char * GetDebugCodeName(DWORD dwCode) +{ + if (dwCode < 1 || dwCode > 9) + { + return "!Invalid Debug Event Code!"; + } + + static const char * const szNames[] = { + "(1) EXCEPTION_DEBUG_EVENT", + "(2) CREATE_THREAD_DEBUG_EVENT", + "(3) CREATE_PROCESS_DEBUG_EVENT", + "(4) EXIT_THREAD_DEBUG_EVENT", + "(5) EXIT_PROCESS_DEBUG_EVENT", + "(6) LOAD_DLL_DEBUG_EVENT", + "(7) UNLOAD_DLL_DEBUG_EVENT", + "(8) OUTPUT_DEBUG_STRING_EVENT" + "(9) RIP_EVENT",// <-- only on Win9X + }; + + return szNames[dwCode - 1]; +} + +#endif + + +//----------------------------------------------------------------------------- +// Per-thread state for Debug builds... +//----------------------------------------------------------------------------- +#ifdef RSCONTRACTS +DWORD DbgRSThread::s_TlsSlot = TLS_OUT_OF_INDEXES; +LONG DbgRSThread::s_Total = 0; + +DbgRSThread::DbgRSThread() +{ + m_cInsideRS = 0; + m_fIsInCallback = false; + m_fIsUnrecoverableErrorCallback = false; + + m_cTotalDbgApiLocks = 0; + for(int i = 0; i < RSLock::LL_MAX; i++) + { + m_cLocks[i] = 0; + } + + // Initialize Identity info + m_Cookie = COOKIE_VALUE; + m_tid = GetCurrentThreadId(); +} + +// NotifyTakeLock & NotifyReleaseLock are called by RSLock to update the per-thread locking context. +// This will assert if the operation is unsafe (ie, violates lock order). +void DbgRSThread::NotifyTakeLock(RSLock * pLock) +{ + if (pLock->HasLock()) + { + return; + } + + int iLevel = pLock->GetLevel(); + + // Is it safe to take this lock? + // Must take "bigger" locks first. We shouldn't hold any locks at our current level either. + // If this lock is re-entrant and we're double-taking it, we would have returned already. + // And the locking model on the RS forbids taking multiple locks at the same level. + for(int i = iLevel; i >= 0; i --) + { + bool fHasLowerLock = m_cLocks[i] > 0; + CONSISTENCY_CHECK_MSGF(!fHasLowerLock, ( + "RSLock violation. Trying to take lock '%s (%d)', but already have smaller lock at level %d'\n", + pLock->Name(), iLevel, + i)); + } + + // Update the counts + _ASSERTE(m_cLocks[iLevel] == 0); + m_cLocks[iLevel]++; + + if (pLock->IsDbgApiLock()) + m_cTotalDbgApiLocks++; +} + +void DbgRSThread::NotifyReleaseLock(RSLock * pLock) +{ + if (pLock->HasLock()) + { + return; + } + + int iLevel = pLock->GetLevel(); + m_cLocks[iLevel]--; + _ASSERTE(m_cLocks[iLevel] == 0); + + if (pLock->IsDbgApiLock()) + m_cTotalDbgApiLocks--; + + _ASSERTE(m_cTotalDbgApiLocks >= 0); +} + +void DbgRSThread::TakeVirtualLock(RSLock::ERSLockLevel level) +{ + m_cLocks[level]++; +} + +void DbgRSThread::ReleaseVirtualLock(RSLock::ERSLockLevel level) +{ + m_cLocks[level]--; + _ASSERTE(m_cLocks[level] >= 0); +} + + +// Get a DbgRSThread for the current OS thread id; lazily create if needed. +DbgRSThread * DbgRSThread::GetThread() +{ + _ASSERTE(DbgRSThread::s_TlsSlot != TLS_OUT_OF_INDEXES); + + void * p2 = TlsGetValue(DbgRSThread::s_TlsSlot); + if (p2 == NULL) + { + // We lazily create for threads that haven't gone through DllMain + // Since this is per-thread, we don't need to lock. + p2 = DbgRSThread::Create(); + } + DbgRSThread * p = reinterpret_cast<DbgRSThread*> (p2); + + _ASSERTE(p->m_Cookie == COOKIE_VALUE); + + return p; +} + + + +#endif // RSCONTRACTS + + + + + + +#ifdef _DEBUG +LONG CordbCommonBase::s_TotalObjectCount = 0; +LONG CordbCommonBase::s_CordbObjectUID = 0; + + +LONG CordbCommonBase::m_saDwInstance[enumMaxDerived]; +LONG CordbCommonBase::m_saDwAlive[enumMaxDerived]; +PVOID CordbCommonBase::m_sdThis[enumMaxDerived][enumMaxThis]; + +#endif + +#ifdef _DEBUG_IMPL +// Mem tracking +LONG Cordb::s_DbgMemTotalOutstandingCordb = 0; +LONG Cordb::s_DbgMemTotalOutstandingInternalRefs = 0; +#endif + +#ifdef TRACK_OUTSTANDING_OBJECTS +void *Cordb::s_DbgMemOutstandingObjects[MAX_TRACKED_OUTSTANDING_OBJECTS] = { NULL }; +LONG Cordb::s_DbgMemOutstandingObjectMax = 0; +#endif + +// Default implementation for neutering left-side resources. +void CordbBase::NeuterLeftSideResources() +{ + LIMITED_METHOD_CONTRACT; + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + Neuter(); +} + +// Default implementation for neutering. +// All derived objects should eventually chain to this. +void CordbBase::Neuter() +{ + // Neutering occurs under the process lock. Neuter can be called twice + // and so locking protects against races in double-delete. + // @dbgtodo - , some CordbBase objects (Cordb, CordbProcessEnum), + // don't have process affinity these should eventually be hoisted to the shim, + // and then we can enforce. + CordbProcess * pProcess = GetProcess(); + if (pProcess != NULL) + { + _ASSERTE(pProcess->ThreadHoldsProcessLock()); + } + CordbCommonBase::Neuter(); +} + +//----------------------------------------------------------------------------- +// NeuterLists +//----------------------------------------------------------------------------- + +NeuterList::NeuterList() +{ + m_pHead = NULL; +} + +NeuterList::~NeuterList() +{ + // Our owner should have neutered us before deleting us. + // Thus we should be empty. + CONSISTENCY_CHECK_MSGF(m_pHead == NULL, ("NeuterList not empty on shutdown. this=0x%p", this)); +} + +// Wrapper around code:NeuterList::UnsafeAdd +void NeuterList::Add(CordbProcess * pProcess, CordbBase * pObject) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + UnsafeAdd(pProcess, pObject); +} + +// +// Add an object to be neutered. +// +// Arguments: +// pProcess - process that holds lock that will protect the neuter list +// pObject - object to add +// +// Returns: +// Throws on error. +// +// Notes: +// This will add it to the list and maintain an internal reference to it. +// This will take the process lock. +// +void NeuterList::UnsafeAdd(CordbProcess * pProcess, CordbBase * pObject) +{ + _ASSERTE(pObject != NULL); + + // Lock if needed. + RSLock * pLock = (pProcess != NULL) ? pProcess->GetProcessLock() : NULL; + RSLockHolder lockHolder(pLock, FALSE); + if (pLock != NULL) lockHolder.Acquire(); + + + Node * pNode = new Node(); // throws on error. + pNode->m_pObject.Assign(pObject); + pNode->m_pNext = m_pHead; + + m_pHead = pNode; +} + +// Neuter everything on the list and clear it +// +// Arguments: +// pProcess - process tree that this neuterlist belongs in +// ticket - neuter ticket proving caller ensured we're safe to neuter. +// +// Assumptions: +// Caller ensures we're safe to neuter (required to obtain NeuterTicket) +// +// Notes: +// This will release all internal references and empty the list. +void NeuterList::NeuterAndClear(CordbProcess * pProcess) +{ + RSLock * pLock = (pProcess != NULL) ? pProcess->GetProcessLock() : NULL; + (void)pLock; //prevent "unused variable" error from GCC + _ASSERTE((pLock == NULL) || pLock->HasLock()); + + while (m_pHead != NULL) + { + Node * pTemp = m_pHead; + m_pHead = m_pHead->m_pNext; + + pTemp->m_pObject->Neuter(); + delete pTemp; // will implicitly release + } +} + +// Only neuter objects that are marked. +// Removes neutered objects from the list. +void NeuterList::SweepAllNeuterAtWillObjects(CordbProcess * pProcess) +{ + _ASSERTE(pProcess != NULL); + RSLock * pLock = pProcess->GetProcessLock(); + RSLockHolder lockHolder(pLock); + + Node ** ppLast = &m_pHead; + Node * pCur = m_pHead; + + while (pCur != NULL) + { + CordbBase * pObject = pCur->m_pObject; + if (pObject->IsNeuterAtWill() || pObject->IsNeutered()) + { + // Delete + pObject->Neuter(); + + Node * pNext = pCur->m_pNext; + delete pCur; // dtor will implicitly release the internal ref to pObject + pCur = *ppLast = pNext; + } + else + { + // Move to next. + ppLast = &pCur->m_pNext; + pCur = pCur->m_pNext; + } + } +} + +//----------------------------------------------------------------------------- +// Neuters all objects in the list and empties the list. +// +// Notes: +// See also code:LeftSideResourceCleanupList::SweepNeuterLeftSideResources, +// which only neuters objects that have been marked as NeuterAtWill (external +// ref count has gone to 0). +void LeftSideResourceCleanupList::NeuterLeftSideResourcesAndClear(CordbProcess * pProcess) +{ + // Traversal protected under Process-lock. + // SG-lock must already be held to do neutering. + // Stop-Go lock is bigger than Process-lock. + // Neutering requires the Stop-Go lock (until we get rid of IPC events) + // But we want to be able to add to the Neuter list under the Process-lock. + // So we just need to protected m_pHead under process-lock. + + // "Privatize" the list under the lock. + _ASSERTE(pProcess != NULL); + RSLock * pLock = pProcess->GetProcessLock(); + + Node * pCur = NULL; + { + RSLockHolder lockHolder(pLock); // only acquire lock if we have one + pCur = m_pHead; + m_pHead = NULL; + } + + // @dbgtodo - eventually everything can be under the process lock. + _ASSERTE(!pLock->HasLock()); // Can't hold Process lock while calling NeuterLeftSideResources + + // Now we're operating on local data, so traversing doesn't need to be under the lock. + while (pCur != NULL) + { + Node * pTemp = pCur; + pCur = pCur->m_pNext; + + pTemp->m_pObject->NeuterLeftSideResources(); + delete pTemp; // will implicitly release + } + +} + +//----------------------------------------------------------------------------- +// Only neuter objects that are marked. Removes neutered objects from the list. +// +// Arguments: +// pProcess - non-null process owning the objects in the list +// +// Notes: +// this cleans up left-side resources held by objects in the list. +// It may send IPC events to do this. +void LeftSideResourceCleanupList::SweepNeuterLeftSideResources(CordbProcess * pProcess) +{ + _ASSERTE(pProcess != NULL); + + // Must be safe to send IPC events. + _ASSERTE(pProcess->GetStopGoLock()->HasLock()); // holds this for neutering + _ASSERTE(pProcess->GetSynchronized()); + + RSLock * pLock = pProcess->GetProcessLock(); + + // Lock while we "privatize" the head. + RSLockHolder lockHolder(pLock); + Node * pHead = m_pHead; + m_pHead = NULL; + lockHolder.Release(); + + Node ** ppLast = &pHead; + Node * pCur = pHead; + + // Can't hold the process-lock while calling Neuter. + while (pCur != NULL) + { + CordbBase * pObject = pCur->m_pObject; + if (pObject->IsNeuterAtWill() || pObject->IsNeutered()) + { + // HeavyNueter can not be done under the process-lock because + // it may take the Stop-Go lock and send events. + pObject->NeuterLeftSideResources(); + + // Delete + Node * pNext = pCur->m_pNext; + delete pCur; // dtor will implicitly release the internal ref to pObject + pCur = *ppLast = pNext; + } + else + { + // Move to next. + ppLast = &pCur->m_pNext; + pCur = pCur->m_pNext; + } + } + + // Now link back in. m_pHead may have changed while we were unlocked. + // The list does not need to be ordered. + + lockHolder.Acquire(); + *ppLast = m_pHead; + m_pHead = pHead; +} + + + +/* ------------------------------------------------------------------------- * + * CordbBase class + * ------------------------------------------------------------------------- */ + +// Do any initialization necessary for both CorPublish and CorDebug +// This includes enabling logging and adding the SEDebug priv. +void CordbCommonBase::InitializeCommon() +{ + static bool IsInitialized = false; + if( IsInitialized ) + { + return; + } + +#ifdef STRESS_LOG + { + bool fStressLog = false; + +#ifdef _DEBUG + // default for stress log is on debug build + fStressLog = true; +#endif // DEBUG + + // StressLog will turn on stress logging for the entire runtime. + // RSStressLog is only used here and only effects just the RS. + fStressLog = + (REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_StressLog, fStressLog) != 0) || + (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_RSStressLog) != 0); + + if (fStressLog == true) + { + unsigned facilities = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogFacility, LF_ALL); + unsigned level = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::EXTERNAL_LogLevel, LL_INFO1000); + unsigned bytesPerThread = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_StressLogSize, STRESSLOG_CHUNK_SIZE * 2); + unsigned totalBytes = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_TotalStressLogSize, STRESSLOG_CHUNK_SIZE * 1024); + StressLog::Initialize(facilities, level, bytesPerThread, totalBytes, GetModuleInst()); + } + } + +#endif // STRESS_LOG + +#ifdef LOGGING + InitializeLogging(); +#endif + + // Add debug privilege. This will let us call OpenProcess() on anything, regardless of ACL. + AddDebugPrivilege(); + + IsInitialized = true; +} + +// Adjust the permissions of this process to ensure that we have +// the debugging priviledge. If we can't make the adjustment, it +// only means that we won't be able to attach to a service under +// NT, so we won't treat that as a critical failure. +// This also will let us call OpenProcess() on anything, regardless of DACL. This allows an +// Admin debugger to attach to a debuggee in the guest account. +// This code was taken directly from code in the Win32 debugger, given to us by Matt Hendel. +// Ideally, the debugger would set this (and we wouldn't mess with privileges at all). However, we've been +// setting this since V1.0 and removing it may be a breaking change. +void CordbCommonBase::AddDebugPrivilege() +{ + HANDLE hToken; + TOKEN_PRIVILEGES Privileges; + BOOL fSucc; + + LUID SeDebugLuid = {0, 0}; + + fSucc = LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &SeDebugLuid); + DWORD err = GetLastError(); + + if (!fSucc) + { + STRESS_LOG1(LF_CORDB, LL_INFO1000, "Unable to adjust permissions of this process to include SE_DEBUG. Lookup failed %d\n", err); + return; + } + + + // Retrieve a handle of the access token + fSucc = OpenProcessToken(GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + &hToken); + + if (fSucc) + { + Privileges.PrivilegeCount = 1; + Privileges.Privileges[0].Luid = SeDebugLuid; + Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + AdjustTokenPrivileges(hToken, + FALSE, + &Privileges, + sizeof(TOKEN_PRIVILEGES), + (PTOKEN_PRIVILEGES) NULL, + (PDWORD) NULL); + err = GetLastError(); + // The return value of AdjustTokenPrivileges cannot be tested. + if (err != ERROR_SUCCESS) + { + STRESS_LOG1(LF_CORDB, LL_INFO1000, + "Unable to adjust permissions of this process to include SE_DEBUG. Adjust failed %d\n", err); + } + else + { + LOG((LF_CORDB, LL_INFO1000, "Adjusted process permissions to include SE_DEBUG.\n")); + } + CloseHandle(hToken); + } +} + + +namespace +{ + + // + // DefaultManagedCallback2 + // + // In the event that the debugger is of an older version than the Right Side & Left Side, the Right Side may issue + // new callbacks that the debugger is not expecting. In this case, we need to provide a default behavior for those + // new callbacks, if for nothing else than to force the debugger to Continue(). + // + class DefaultManagedCallback2 : public ICorDebugManagedCallback2 + { + public: + DefaultManagedCallback2(ICorDebug* pDebug); + virtual ~DefaultManagedCallback2() { } + virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pInterface); + virtual ULONG __stdcall AddRef(); + virtual ULONG __stdcall Release(); + COM_METHOD FunctionRemapOpportunity(ICorDebugAppDomain* pAppDomain, + ICorDebugThread* pThread, + ICorDebugFunction* pOldFunction, + ICorDebugFunction* pNewFunction, + ULONG32 oldILOffset); + COM_METHOD FunctionRemapComplete(ICorDebugAppDomain *pAppDomain, + ICorDebugThread *pThread, + ICorDebugFunction *pFunction); + + COM_METHOD CreateConnection(ICorDebugProcess *pProcess, + CONNID dwConnectionId, + __in_z WCHAR* pConnectionName); + COM_METHOD ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId); + COM_METHOD DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId); + + COM_METHOD Exception(ICorDebugAppDomain *pAddDomain, + ICorDebugThread *pThread, + ICorDebugFrame *pFrame, + ULONG32 nOffset, + CorDebugExceptionCallbackType eventType, + DWORD dwFlags ); + + COM_METHOD ExceptionUnwind(ICorDebugAppDomain *pAddDomain, + ICorDebugThread *pThread, + CorDebugExceptionUnwindCallbackType eventType, + DWORD dwFlags ); + COM_METHOD MDANotification( + ICorDebugController * pController, + ICorDebugThread *pThread, + ICorDebugMDA * pMDA + ) { return E_NOTIMPL; } + + private: + // not implemented + DefaultManagedCallback2(const DefaultManagedCallback2&); + DefaultManagedCallback2& operator=(const DefaultManagedCallback2&); + + ICorDebug* m_pDebug; + LONG m_refCount; + }; + + + + + DefaultManagedCallback2::DefaultManagedCallback2(ICorDebug* pDebug) : m_pDebug(pDebug), m_refCount(0) + { + } + + HRESULT + DefaultManagedCallback2::QueryInterface(REFIID iid, void** pInterface) + { + if (IID_ICorDebugManagedCallback2 == iid) + { + *pInterface = static_cast<ICorDebugManagedCallback2*>(this); + } + else if (IID_IUnknown == iid) + { + *pInterface = static_cast<IUnknown*>(this); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + this->AddRef(); + return S_OK; + } + + ULONG + DefaultManagedCallback2::AddRef() + { + return InterlockedIncrement(&m_refCount); + } + + ULONG + DefaultManagedCallback2::Release() + { + ULONG ulRef = InterlockedDecrement(&m_refCount); + if (0 == ulRef) + { + delete this; + } + + return ulRef; + } + + HRESULT + DefaultManagedCallback2::FunctionRemapOpportunity(ICorDebugAppDomain* pAppDomain, + ICorDebugThread* pThread, + ICorDebugFunction* pOldFunction, + ICorDebugFunction* pNewFunction, + ULONG32 oldILOffset) + { + + // + // In theory, this function should never be reached. To get here, we'd have to have a debugger which doesn't + // support edit and continue somehow turn on edit & continue features. + // + _ASSERTE(!"Edit & Continue callback reached when debugger doesn't support Edit And Continue"); + + + // If you ignore this assertion, or you're in a retail build, there are two options as far as how to proceed + // from this point + // o We can do nothing, and let the debugee process hang, or + // o We can silently ignore the FunctionRemapOpportunity, and tell the debugee to Continue running. + // + // For now, we'll silently ignore the function remapping. + pAppDomain->Continue(false); + pAppDomain->Release(); + + return S_OK; + } + + + HRESULT + DefaultManagedCallback2::FunctionRemapComplete(ICorDebugAppDomain *pAppDomain, + ICorDebugThread *pThread, + ICorDebugFunction *pFunction) + { + // + // In theory, this function should never be reached. To get here, we'd have to have a debugger which doesn't + // support edit and continue somehow turn on edit & continue features. + // + _ASSERTE(!"Edit & Continue callback reached when debugger doesn't support Edit And Continue"); + return E_NOTIMPL; + } + + // + // <TODO> + // These methods are current left unimplemented. + // + // Create/Change/Destroy Connection *should* force the Process/AppDomain/Thread to Continue(). Currently the + // arguments to these functions don't provide the relevant Process/AppDomain/Thread, so there is no way to figure + // out which Threads should be forced to Continue(). + // + // </TODO> + // + HRESULT + DefaultManagedCallback2::CreateConnection(ICorDebugProcess *pProcess, + CONNID dwConnectionId, + __in_z WCHAR* pConnectionName) + { + _ASSERTE(!"DefaultManagedCallback2::CreateConnection not implemented"); + return E_NOTIMPL; + } + + HRESULT + DefaultManagedCallback2::ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId) + { + _ASSERTE(!"DefaultManagedCallback2::ChangeConnection not implemented"); + return E_NOTIMPL; + } + + HRESULT + DefaultManagedCallback2::DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId) + { + _ASSERTE(!"DefaultManagedCallback2::DestroyConnection not implemented"); + return E_NOTIMPL; + } + + HRESULT + DefaultManagedCallback2::Exception(ICorDebugAppDomain *pAppDomain, + ICorDebugThread *pThread, + ICorDebugFrame *pFrame, + ULONG32 nOffset, + CorDebugExceptionCallbackType eventType, + DWORD dwFlags ) + { + // + // Just ignore and continue the process. + // + pAppDomain->Continue(false); + return S_OK; + } + + HRESULT + DefaultManagedCallback2::ExceptionUnwind(ICorDebugAppDomain *pAppDomain, + ICorDebugThread *pThread, + CorDebugExceptionUnwindCallbackType eventType, + DWORD dwFlags ) + { + // + // Just ignore and continue the process. + // + pAppDomain->Continue(false); + return S_OK; + } + + // + // DefaultManagedCallback3 + // + // In the event that the debugger is of an older version than the Right Side & Left Side, the Right Side may issue + // new callbacks that the debugger is not expecting. In this case, we need to provide a default behavior for those + // new callbacks, if for nothing else than to force the debugger to Continue(). + // + class DefaultManagedCallback3 : public ICorDebugManagedCallback3 + { + public: + DefaultManagedCallback3(ICorDebug* pDebug); + virtual ~DefaultManagedCallback3() { } + virtual HRESULT __stdcall QueryInterface(REFIID iid, void** pInterface); + virtual ULONG __stdcall AddRef(); + virtual ULONG __stdcall Release(); + COM_METHOD CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain); + private: + // not implemented + DefaultManagedCallback3(const DefaultManagedCallback3&); + DefaultManagedCallback3& operator=(const DefaultManagedCallback3&); + + ICorDebug* m_pDebug; + LONG m_refCount; + }; + + DefaultManagedCallback3::DefaultManagedCallback3(ICorDebug* pDebug) : m_pDebug(pDebug), m_refCount(0) + { + } + + HRESULT + DefaultManagedCallback3::QueryInterface(REFIID iid, void** pInterface) + { + if (IID_ICorDebugManagedCallback3 == iid) + { + *pInterface = static_cast<ICorDebugManagedCallback3*>(this); + } + else if (IID_IUnknown == iid) + { + *pInterface = static_cast<IUnknown*>(this); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + this->AddRef(); + return S_OK; + } + + ULONG + DefaultManagedCallback3::AddRef() + { + return InterlockedIncrement(&m_refCount); + } + + ULONG + DefaultManagedCallback3::Release() + { + ULONG ulRef = InterlockedDecrement(&m_refCount); + if (0 == ulRef) + { + delete this; + } + + return ulRef; + } + + HRESULT + DefaultManagedCallback3::CustomNotification(ICorDebugThread * pThread, ICorDebugAppDomain * pAppDomain) + { + // + // Just ignore and continue the process. + // + pAppDomain->Continue(false); + return S_OK; + } +} + +/* ------------------------------------------------------------------------- * + * Cordb class + * ------------------------------------------------------------------------- */ + + +Cordb::Cordb(CorDebugInterfaceVersion iDebuggerVersion) + : CordbBase(NULL, 0, enumCordb), + m_processes(11), + m_initialized(false), + m_debuggerSpecifiedVersion(iDebuggerVersion) +#ifdef FEATURE_CORESYSTEM + , + m_targetCLR(0) +#endif +{ + g_pRSDebuggingInfo->m_Cordb = this; + +#ifdef _DEBUG_IMPL + // Memory leak detection + InterlockedIncrement(&s_DbgMemTotalOutstandingCordb); +#endif +} + +Cordb::~Cordb() +{ + LOG((LF_CORDB, LL_INFO10, "C::~C Terminating Cordb object.\n")); + g_pRSDebuggingInfo->m_Cordb = NULL; +} + +void Cordb::Neuter() +{ + if (this->IsNeutered()) + { + return; + } + + + RSLockHolder lockHolder(&m_processListMutex); + m_pProcessEnumList.NeuterAndClear(NULL); + + + HRESULT hr = S_OK; + EX_TRY // @dbgtodo push this up. + { + // Iterating needs to be done under the processList lock (small), while neutering + // needs to be able to take the process lock (big). + RSPtrArray<CordbProcess> list; + m_processes.TransferToArray(&list); // throws + + // can't hold list lock while calling CordbProcess::Neuter (which + // will take the Process-lock). + lockHolder.Release(); + + list.NeuterAndClear(); + // List dtor calls release on each element + } + EX_CATCH_HRESULT(hr); + SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr); + + CordbCommonBase::Neuter(); + + // Implicit release from smart ptr. +} + +#ifdef _DEBUG_IMPL +void CheckMemLeaks() +{ + // Memory leak detection. + LONG l = InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingCordb); + if (l == 0) + { + // If we just released our final Cordb root object, then we expect no internal references at all. + // Note that there may still be external references (and thus not all objects may have been + // deleted yet). + bool fLeakedInternal = (Cordb::s_DbgMemTotalOutstandingInternalRefs > 0); + + // Some Cordb objects (such as CordbValues) may not be rooted, and thus we can't neuter + // them and thus an external ref may keep them alive. Since these objects may have internal refs, + // This means that external refs can keep internal refs. + // Thus this assert must be tempered if unrooted objects are leaked. (But that means we can always + // assert the tempered version; regardless of bugs in Cordbg). + CONSISTENCY_CHECK_MSGF(!fLeakedInternal, + ("'%d' Outstanding internal references at final Cordb::Terminate\n", + Cordb::s_DbgMemTotalOutstandingInternalRefs)); + + DWORD dLeakCheck = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgLeakCheck); + if (dLeakCheck > 0) + { + // We have 1 ref for this Cordb root object. All other refs should have been deleted. + CONSISTENCY_CHECK_MSGF(Cordb::s_TotalObjectCount == 1, ("'%d' total cordbBase objects are leaked.\n", + Cordb::s_TotalObjectCount-1)); + } + } +} +#endif + +// This shuts down ICorDebug. +// All CordbProcess objects owned by this Cordb object must have either: +// - returned for a Detach() call +// - returned from dispatching the ExitProcess() callback. +// In both cases, CordbProcess::NeuterChildren has been called, although the Process object itself +// may not yet be neutered. This condition will ensure that the CordbProcess objects don't need +// any resources that we're about to release. +HRESULT Cordb::Terminate() +{ + LOG((LF_CORDB, LL_INFO10000, "[%x] Terminating Cordb\n", GetCurrentThreadId())); + + if (!m_initialized) + return E_FAIL; + + FAIL_IF_NEUTERED(this); + + // We can't terminate the debugging services from within a callback. + // Caller is supposed to be out of all callbacks when they call this. + // This also avoids a deadlock because we'll shutdown the RCET, which would block if we're + // in the RCET. + if (m_rcEventThread->IsRCEventThread()) + { + STRESS_LOG0(LF_CORDB, LL_INFO10, "C::T: failed on RCET\n"); + _ASSERTE(!"Gross API Misuse: Debugger shouldn't call ICorDebug::Terminate from within a managed callback."); + return CORDBG_E_CANT_CALL_ON_THIS_THREAD; + } + + // @todo - do we need to throw some switch to prevent new processes from being added now? + + // VS must stop all debugging before terminating. Fail if we have any non-neutered processes + // (b/c those processes should have been either shutdown or detached). + // We are in an undefined state if this check fails. + // Process are removed from this list before Process::Detach() returns and before the ExitProcess callback is dispatched. + // Thus any processes in this list should be live or have an unrecoverable error. + { + RSLockHolder ch(&m_processListMutex); + + HASHFIND hfDT; + CordbProcess * pProcess; + + for (pProcess= (CordbProcess*) m_processes.FindFirst(&hfDT); + pProcess != NULL; + pProcess = (CordbProcess*) m_processes.FindNext(&hfDT)) + { + _ASSERTE(pProcess->IsSafeToSendEvents() || pProcess->m_unrecoverableError); + if (pProcess->IsSafeToSendEvents() && !pProcess->m_unrecoverableError) + { + CONSISTENCY_CHECK_MSGF(false, ("Gross API misuses. Callling terminate with live process:0x%p\n", pProcess)); + STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::Terminate b/c of non-neutered process '%p'\n", pProcess); + // This is very bad. + // GROSS API MISUSES - Debugger is calling ICorDebug::Terminate while there + // are still outstanding (non-neutered) ICorDebugProcess. + // ICorDebug is now in an undefined state. + // We will also leak memory b/c we're leaving the EventThreads up (which will in turn + // keep a reference to this Cordb object). + return ErrWrapper(CORDBG_E_ILLEGAL_SHUTDOWN_ORDER); + } + } + } + + // @todo- ideally, we'd wait for all threads to get outside of ICorDebug before we proceed. + // That's tough to implement in practice; but we at least wait for both ET to exit. As these + // guys dispatch callbacks, that means at least we'll wait until VS is outside of any callback. + // + // Stop the event handling threads. + // + if (m_rcEventThread != NULL) + { + // Stop may do significant work b/c if it drains the worker queue. + m_rcEventThread->Stop(); + delete m_rcEventThread; + m_rcEventThread = NULL; + } + + +#ifdef _DEBUG + // @todo - this disables thread-safety asserts on the process-list-hash. We clearly + // can't hold the lock while neutering it. (lock violation since who knows what neuter may do) + // @todo- we may have races beteen Cordb::Terminate and Cordb::CreateProcess as both + // modify the process list. This is mitigated since Terminate is supposed to be the last method called. + m_processes.DebugSetRSLock(NULL); +#endif + + // + // We expect the debugger to neuter all processes before calling Terminate(), so do not neuter them here. + // + +#ifdef _DEBUG + { + HASHFIND find; + _ASSERTE(m_processes.FindFirst(&find) == NULL); // should be emptied by neuter + } +#endif //_DEBUG + + // Officially mark us as neutered. + this->Neuter(); + + m_processListMutex.Destroy(); + + // + // Release the callbacks + // + m_managedCallback.Clear(); + m_managedCallback2.Clear(); + m_managedCallback3.Clear(); + m_unmanagedCallback.Clear(); + + // The Shell may still have outstanding references, so we don't want to shutdown logging yet. + // But everything should be neutered anyways. + + m_initialized = FALSE; + + + // After this, all outstanding Cordb objects should be neutered. + LOG((LF_CORDB, LL_EVERYTHING, "Cordb finished terminating.\n")); + +#if defined(_DEBUG) + // + // Assert that there are no outstanding object references within the debugging + // API itself. + // + CheckMemLeaks(); +#endif + + return S_OK; +} + +HRESULT Cordb::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebug) + *pInterface = static_cast<ICorDebug*>(this); + else if (id == IID_IUnknown) + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebug*>(this)); + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + + + +// +// Initialize -- setup the ICorDebug object by creating any objects +// that the object needs to operate and starting the two needed IPC +// threads. +// +HRESULT Cordb::Initialize(void) +{ + HRESULT hr = S_OK; + + FAIL_IF_NEUTERED(this); + + if (!m_initialized) + { + CordbCommonBase::InitializeCommon(); + + // Since logging wasn't active when we called CordbBase, do it now. + LOG((LF_CORDB, LL_EVERYTHING, "Memory: CordbBase object allocated: this=%p, count=%d, RootObject\n", this, s_TotalObjectCount)); + LOG((LF_CORDB, LL_INFO10, "Initializing ICorDebug...\n")); + + // Ensure someone hasn't messed up the IPC buffer size + _ASSERTE(sizeof(DebuggerIPCEvent) <= CorDBIPC_BUFFER_SIZE); + + // + // Init things that the Cordb will need to operate + // + m_processListMutex.Init("Process-List Lock", RSLock::cLockReentrant, RSLock::LL_PROCESS_LIST_LOCK); + +#ifdef _DEBUG + m_processes.DebugSetRSLock(&m_processListMutex); +#endif + + // + // Create the runtime controller event listening thread + // + m_rcEventThread = new (nothrow) CordbRCEventThread(this); + + if (m_rcEventThread == NULL) + { + hr = E_OUTOFMEMORY; + } + else + { + // This stuff only creates events & starts the thread + hr = m_rcEventThread->Init(); + + if (SUCCEEDED(hr)) + hr = m_rcEventThread->Start(); + + if (FAILED(hr)) + { + delete m_rcEventThread; + m_rcEventThread = NULL; + } + } + + if (FAILED(hr)) + goto exit; + + m_initialized = TRUE; + } + +exit: + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Throw if no more process can be debugged with this Cordb object. +// +// Notes: +// This is highly dependent on the wait sets in the Win32 & RCET threads. +// @dbgtodo- this will end up in the shim. + +void Cordb::EnsureAllowAnotherProcess() +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + RSLockHolder ch(&m_processListMutex); + + // Cordb, Win32, and RCET all have process sets, but Cordb's is the + // best count of total debuggees. The RCET set is volatile (processes + // are added / removed when they become synchronized), and Win32's set + // doesn't include all processes. + int cCurProcess = GetProcessList()->GetCount(); + + // In order to accept another debuggee, we must have a free slot in all + // wait sets. Currently, we don't expose the size of those sets, but + // we know they're MAXIMUM_WAIT_OBJECTS. Note that we lose one slot + // to the control event. + if (cCurProcess >= MAXIMUM_WAIT_OBJECTS - 1) + { + ThrowHR(CORDBG_E_TOO_MANY_PROCESSES); + } +} + +//--------------------------------------------------------------------------------------- +// +// Add process to the list. +// +// Notes: +// AddProcess -- add a process object to this ICorDebug's hash of processes. +// This also tells this ICorDebug's runtime controller thread that the +// process set has changed so it can update its list of wait events. +// +void Cordb::AddProcess(CordbProcess* process) +{ + // At this point, we should have already checked that we + // can have another debuggee. + STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::AddProcess %08x...\n", process); + + if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL)) + { + ThrowHR(E_FAIL); + } + + + + RSLockHolder lockHolder(&m_processListMutex); + + // Once we add another process, all outstanding process-enumerators become invalid. + m_pProcessEnumList.NeuterAndClear(NULL); + + GetProcessList()->AddBaseOrThrow(process); + m_rcEventThread->ProcessStateChanged(); +} + +// +// RemoveProcess -- remove a process object from this ICorDebug's hash of +// processes. This also tells this ICorDebug's runtime controller thread +// that the process set has changed so it can update its list of wait events. +// +void Cordb::RemoveProcess(CordbProcess* process) +{ + STRESS_LOG1(LF_CORDB, LL_INFO10, "Cordb::RemoveProcess %08x...\n", process); + + LockProcessList(); + GetProcessList()->RemoveBase((ULONG_PTR)process->m_id); + + m_rcEventThread->ProcessStateChanged(); + + UnlockProcessList(); +} + +// +// LockProcessList -- Lock the process list. +// +void Cordb::LockProcessList(void) +{ + m_processListMutex.Lock(); +} + +// +// UnlockProcessList -- Unlock the process list. +// +void Cordb::UnlockProcessList(void) +{ + m_processListMutex.Unlock(); +} + +#ifdef _DEBUG +// Return true iff this thread owns the ProcessList lock +bool Cordb::ThreadHasProcessListLock() +{ + return m_processListMutex.HasLock(); +} +#endif + + +// Get the hash that has the process. +CordbSafeHashTable<CordbProcess> *Cordb::GetProcessList() +{ + // If we're accessing the hash, we'd better be locked. + _ASSERTE(ThreadHasProcessListLock()); + + return &m_processes; +} + + +HRESULT Cordb::SendIPCEvent(CordbProcess * pProcess, + DebuggerIPCEvent * pEvent, + SIZE_T eventSize) +{ + HRESULT hr = S_OK; + + LOG((LF_CORDB, LL_EVERYTHING, "SendIPCEvent in Cordb called\n")); + EX_TRY + { + hr = m_rcEventThread->SendIPCEvent(pProcess, pEvent, eventSize); + } + EX_CATCH_HRESULT(hr) + return hr; +} + + +void Cordb::ProcessStateChanged(void) +{ + m_rcEventThread->ProcessStateChanged(); +} + + +HRESULT Cordb::WaitForIPCEventFromProcess(CordbProcess* process, + CordbAppDomain *pAppDomain, + DebuggerIPCEvent* event) +{ + return m_rcEventThread->WaitForIPCEventFromProcess(process, + pAppDomain, + event); +} + +#ifdef FEATURE_CORECLR +HRESULT Cordb::SetTargetCLR(HMODULE hmodTargetCLR) +{ + if (m_initialized) + return E_FAIL; + +#ifdef FEATURE_CORESYSTEM + m_targetCLR = hmodTargetCLR; +#endif + + // @REVIEW: are we happy with this workaround? It allows us to use the existing + // infrastructure for instance name decoration, but it really doesn't fit + // the same model because coreclr.dll isn't in this process and hmodTargetCLR + // is the debuggee target, not the coreclr.dll to bind utilcode to.. + + CoreClrCallbacks cccallbacks; + cccallbacks.m_hmodCoreCLR = hmodTargetCLR; + cccallbacks.m_pfnIEE = NULL; + cccallbacks.m_pfnGetCORSystemDirectory = NULL; + cccallbacks.m_pfnGetCLRFunction = NULL; + InitUtilcode(cccallbacks); + + return S_OK; +} +#endif // FEATURE_CORECLR + +//----------------------------------------------------------- +// ICorDebug +//----------------------------------------------------------- + +// Set the handler for callbacks on managed events +// This can not be NULL. +// If we're debugging V2.0 apps, pCallback must implement ICDManagedCallback2 +// @todo- what if somebody calls this after we've already initialized? (eg, changes +// the callback underneath us) +HRESULT Cordb::SetManagedHandler(ICorDebugManagedCallback *pCallback) +{ + if (!m_initialized) + return E_FAIL; + + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pCallback, ICorDebugManagedCallback*); + + m_managedCallback.Clear(); + m_managedCallback2.Clear(); + m_managedCallback3.Clear(); + + // For SxS, V2.0 debuggers must implement ManagedCallback2 to handle v2.0 debug events. + // For Single-CLR, A v1.0 debugger may actually geta V2.0 debuggee. + pCallback->QueryInterface(IID_ICorDebugManagedCallback2, (void **)&m_managedCallback2); + if (m_managedCallback2 == NULL) + { + if (GetDebuggerVersion() >= CorDebugVersion_2_0) + { + // This will leave our internal callbacks null, which future operations (Create/Attach) will + // use to know that we're not sufficiently initialized. + return E_NOINTERFACE; + } + else + { + // This should only be used in a single-CLR shimming scenario. + m_managedCallback2.Assign(new (nothrow) DefaultManagedCallback2(this)); + + if (m_managedCallback2 == NULL) + { + return E_OUTOFMEMORY; + } + } + } + + + pCallback->QueryInterface(IID_ICorDebugManagedCallback3, (void **)&m_managedCallback3); + if (m_managedCallback3 == NULL) + { + m_managedCallback3.Assign(new (nothrow) DefaultManagedCallback3(this)); + } + + if (m_managedCallback3 == NULL) + { + return E_OUTOFMEMORY; + } + + m_managedCallback.Assign(pCallback); + return S_OK; +} + +HRESULT Cordb::SetUnmanagedHandler(ICorDebugUnmanagedCallback *pCallback) +{ + if (!m_initialized) + return E_FAIL; + + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pCallback, ICorDebugUnmanagedCallback*); + + m_unmanagedCallback.Assign(pCallback); + + return S_OK; +} + +// CreateProcess() isn't supported on Windows CoreCLR. +// It is currently supported on Mac CoreCLR, but that may change. +bool Cordb::IsCreateProcessSupported() +{ +#if defined(FEATURE_CORECLR) && !defined(FEATURE_DBGIPC_TRANSPORT_DI) + return false; +#else + return true; +#endif +} + +// Given everything we know about our configuration, can we support interop-debugging +bool Cordb::IsInteropDebuggingSupported() +{ + // We explicitly refrain from checking the unmanaged callback. See comment in + // ICorDebug::SetUnmanagedHandler for details. +#ifdef FEATURE_INTEROP_DEBUGGING + +#if defined(FEATURE_CORECLR) && !defined(FEATURE_CORESYSTEM) + // Interop debugging is only supported internally on CoreCLR. + // Check if the special reg key is set. If not, then we don't allow interop debugging. + if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgEnableMixedModeDebugging) == 0) + { + return false; + } +#endif // FEATURE_CORECLR + + return true; +#else + return false; +#endif +} + + +//--------------------------------------------------------------------------------------- +// +// Implementation of ICorDebug::CreateProcess. +// Creates a process. +// +// Arguments: +// The following arguments are passed thru unmodified to the OS CreateProcess API and +// are defined by that API. +// lpApplicationName +// lpCommandLine +// lpProcessAttributes +// lpThreadAttributes +// bInheritHandles +// dwCreationFlags +// lpCurrentDirectory +// lpStartupInfo +// lpProcessInformation +// debuggingFlags +// +// ppProcess - Space to fill in for the resulting process, returned as a valid pointer +// on any success HRESULT. +// +// Return Value: +// Normal HRESULT semantics. +// +//--------------------------------------------------------------------------------------- +HRESULT Cordb::CreateProcess(LPCWSTR lpApplicationName, + __in_z LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + PVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation, + CorDebugCreateProcessFlags debuggingFlags, + ICorDebugProcess **ppProcess) +{ + return CreateProcessCommon(NULL, + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation, + debuggingFlags, + ppProcess); +} + +HRESULT Cordb::CreateProcessCommon(ICorDebugRemoteTarget * pRemoteTarget, + LPCWSTR lpApplicationName, + __in_z LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + PVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation, + CorDebugCreateProcessFlags debuggingFlags, + ICorDebugProcess ** ppProcess) +{ + // If you hit this assert, it means that you are attempting to create a process without specifying the version + // number. + _ASSERTE(CorDebugInvalidVersion != m_debuggerSpecifiedVersion); + + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess**); + + HRESULT hr = S_OK; + + EX_TRY + { + if (!m_initialized) + { + ThrowHR(E_FAIL); + } + + // Check that we support the debugger version + CheckCompatibility(); + + #ifdef FEATURE_INTEROP_DEBUGGING + // DEBUG_PROCESS (=0x1) means debug this process & all future children. + // DEBUG_ONLY_THIS_PROCESS =(0x2) means just debug the immediate process. + // If we want to support DEBUG_PROCESS, then we need to have the RS sniff for new CREATE_PROCESS + // events and spawn new CordbProcess for them. + switch(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)) + { + // 1) managed-only debugging + case 0: + break; + + // 2) failure - returns E_NOTIMPL. (as this would involve debugging all of our children processes). + case DEBUG_PROCESS: + ThrowHR(E_NOTIMPL); + + // 3) Interop-debugging. + // Note that MSDN (at least as of Jan 2003) is wrong about this flag. MSDN claims + // DEBUG_ONLY_THIS_PROCESS w/o DEBUG_PROCESS should be ignored. + // But it really should do launch as a debuggee (but not auto-attach to child processes). + case DEBUG_ONLY_THIS_PROCESS: + // Emprically, this is the common case for native / interop-debugging. + break; + + // 4) Interop. + // The spec for ICorDebug::CreateProcess says this is the one to use for interop-debugging. + case DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS: + // Win2k does not honor these flags properly. So we just use + // It treats (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS) as if it were DEBUG_PROCESS. + // We'll just always touch up the flags, even though WinXP and above is fine here. + // Per win2k issue, strip off DEBUG_PROCESS, so that we're just left w/ DEBUG_ONLY_THIS_PROCESS. + dwCreationFlags &= ~(DEBUG_PROCESS); + break; + + default: + __assume(0); + } + + #endif // FEATURE_INTEROP_DEBUGGING + + // Must have a managed-callback by now. + if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL)) + { + ThrowHR(E_FAIL); + } + + if (!IsCreateProcessSupported()) + { + ThrowHR(E_NOTIMPL); + } + + if (!IsInteropDebuggingSupported() && + ((dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)) != 0)) + { + ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED); + } + + // Check that we can even accept another debuggee before trying anything. + EnsureAllowAnotherProcess(); + + } EX_CATCH_HRESULT(hr); + if (FAILED(hr)) + { + return hr; + } + + hr = ShimProcess::CreateProcess(this, + pRemoteTarget, + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation, + debuggingFlags + ); + + LOG((LF_CORDB, LL_EVERYTHING, "Handle in Cordb::CreateProcess is: %.I64x\n", lpProcessInformation->hProcess)); + + if (SUCCEEDED(hr)) + { + LockProcessList(); + + CordbProcess * pProcess = GetProcessList()->GetBase(lpProcessInformation->dwProcessId); + + UnlockProcessList(); + + PREFIX_ASSUME(pProcess != NULL); + + pProcess->ExternalAddRef(); + *ppProcess = (ICorDebugProcess *)pProcess; + } + + return hr; +} + + +HRESULT Cordb::CreateProcessEx(ICorDebugRemoteTarget * pRemoteTarget, + LPCWSTR lpApplicationName, + __in_z LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + PVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation, + CorDebugCreateProcessFlags debuggingFlags, + ICorDebugProcess ** ppProcess) +{ + if (pRemoteTarget == NULL) + { + return E_INVALIDARG; + } + + return CreateProcessCommon(pRemoteTarget, + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation, + debuggingFlags, + ppProcess); +} + + +//--------------------------------------------------------------------------------------- +// +// Attachs to an existing process. +// +// Arguments: +// dwProcessID - The PID to attach to +// fWin32Attach - Flag to tell whether to attach as the Win32 debugger or not. +// ppProcess - Space to fill in for the resulting process, returned as a valid pointer +// on any success HRESULT. +// +// Return Value: +// Normal HRESULT semantics. +// +//--------------------------------------------------------------------------------------- +HRESULT Cordb::DebugActiveProcess(DWORD dwProcessId, + BOOL fWin32Attach, + ICorDebugProcess **ppProcess) +{ + return DebugActiveProcessCommon(NULL, dwProcessId, fWin32Attach, ppProcess); +} + +HRESULT Cordb::DebugActiveProcessCommon(ICorDebugRemoteTarget * pRemoteTarget, + DWORD dwProcessId, + BOOL fWin32Attach, + ICorDebugProcess ** ppProcess) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **); + + HRESULT hr = S_OK; + + EX_TRY + { + if (!m_initialized) + { + ThrowHR(E_FAIL); + } + + // Must have a managed-callback by now. + if ((m_managedCallback == NULL) || (m_managedCallback2 == NULL) || (m_managedCallback3 == NULL)) + { + ThrowHR(E_FAIL); + } + + // See the comment in Cordb::CreateProcess + _ASSERTE(CorDebugInvalidVersion != m_debuggerSpecifiedVersion); + + // Check that we support the debugger version + CheckCompatibility(); + + // Check that we can even accept another debuggee before trying anything. + EnsureAllowAnotherProcess(); + + // Check if we're allowed to do interop. + bool fAllowInterop = IsInteropDebuggingSupported(); + + if (!fAllowInterop && fWin32Attach) + { + ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED); + } + + } EX_CATCH_HRESULT(hr) + if (FAILED(hr)) + { + return hr; + } + + hr = ShimProcess::DebugActiveProcess( + this, + pRemoteTarget, + dwProcessId, + fWin32Attach == TRUE); + + // If that worked, then there will be a process object... + if (SUCCEEDED(hr)) + { + LockProcessList(); + CordbProcess * pProcess = GetProcessList()->GetBase(dwProcessId); + + if (pProcess != NULL) + { + // Add a reference now so process won't go away + pProcess->ExternalAddRef(); + } + UnlockProcessList(); + + if (pProcess == NULL) + { + // This can happen if we add the process into process hash in + // SendDebugActiveProcessEvent and then process exit + // before we attemp to retrieve it again from GetBase. + // + *ppProcess = NULL; + return S_FALSE; + } + +#if defined(FEATURE_DBGIPC_TRANSPORT_DI) + // This is where we queue the managed attach event in Whidbey. In the new architecture, the Windows + // pipeline gets a loader breakpoint when native attach is completed, and that's where we queue the + // managed attach event. See how we handle the loader breakpoint in code:ShimProcess::DefaultEventHandler. + // However, the Mac debugging transport gets no such breakpoint, and so we need to do this here. + // + // @dbgtodo Mac - Ideally we should hide this in our pipeline implementation, or at least move + // this to the shim. + _ASSERTE(!fWin32Attach); + { + pProcess->Lock(); + hr = pProcess->QueueManagedAttach(); + pProcess->Unlock(); + } +#endif // FEATURE_DBGIPC_TRANSPORT_DI + + *ppProcess = (ICorDebugProcess*) pProcess; + } + + return hr; +} + +// Make sure we want to support the debugger that's using us +void Cordb::CheckCompatibility() +{ + // Get the debugger version specified by the startup APIs and convert it to a CLR major version number + CorDebugInterfaceVersion debuggerVersion = GetDebuggerVersion(); + DWORD clrMajor; + if (debuggerVersion <= CorDebugVersion_1_0 || debuggerVersion == CorDebugVersion_1_1) + clrMajor = 1; + else if (debuggerVersion <= CorDebugVersion_2_0) + clrMajor = 2; + else if (debuggerVersion <= CorDebugVersion_4_0) + clrMajor = 4; + else + clrMajor = 5; // some unrecognized future version + + if(!CordbProcess::IsCompatibleWith(clrMajor)) + { + // Carefully choose our error-code to get an appropriate error-message from VS 2008 + // If GetDebuggerVersion is >= 4, we could consider using the more-appropriate (but not + // added until V4) HRESULT CORDBG_E_UNSUPPORTED_FORWARD_COMPAT that is used by + // OpenVirtualProcess, but it's probably simpler to keep ICorDebug APIs returning + // consistent error codes. + ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL); + } +} + +HRESULT Cordb::DebugActiveProcessEx(ICorDebugRemoteTarget * pRemoteTarget, + DWORD dwProcessId, + BOOL fWin32Attach, + ICorDebugProcess ** ppProcess) +{ + if (pRemoteTarget == NULL) + { + return E_INVALIDARG; + } + + return DebugActiveProcessCommon(pRemoteTarget, dwProcessId, fWin32Attach, ppProcess); +} + + +HRESULT Cordb::GetProcess(DWORD dwProcessId, ICorDebugProcess **ppProcess) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess**); + + if (!m_initialized) + { + return E_FAIL; + } + + LockProcessList(); + CordbProcess *p = GetProcessList()->GetBase(dwProcessId); + UnlockProcessList(); + + if (p == NULL) + return E_INVALIDARG; + + p->ExternalAddRef(); + *ppProcess = static_cast<ICorDebugProcess*> (p); + + return S_OK; +} + +HRESULT Cordb::EnumerateProcesses(ICorDebugProcessEnum **ppProcesses) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppProcesses, ICorDebugProcessEnum **); + + HRESULT hr = S_OK; + EX_TRY + { + if (!m_initialized) + { + ThrowHR(E_FAIL); + } + + // Locking here just means that the enumerator gets initialized against a consistent + // process-list. If we add/remove processes w/ an outstanding enumerator, things + // could still get out of sync. + RSLockHolder lockHolder(&this->m_processListMutex); + + RSInitHolder<CordbHashTableEnum> pEnum; + CordbHashTableEnum::BuildOrThrow( + this, + &m_pProcessEnumList, + GetProcessList(), + IID_ICorDebugProcessEnum, + pEnum.GetAddr()); + + + pEnum.TransferOwnershipExternal(ppProcesses); + } + EX_CATCH_HRESULT(hr); + return hr; +} + + +// +// Note: the following defs and structs are copied from various NT headers. I wasn't able to include those headers (like +// ntexapi.h) due to loads of redef problems and other conflicts with headers that we already pull in. +// +typedef LONG NTSTATUS; + +typedef BOOL (*NTQUERYSYSTEMINFORMATION)(SYSTEM_INFORMATION_CLASS SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength); + +// Implementation of ICorDebug::CanLaunchOrAttach +// @dbgtodo- this all goes away in V3. +// @dbgtodo- this should go away in Dev11. +HRESULT Cordb::CanLaunchOrAttach(DWORD dwProcessId, BOOL fWin32DebuggingEnabled) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + EX_TRY + { + EnsureCanLaunchOrAttach(fWin32DebuggingEnabled); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Throw an expcetion if we can't launch/attach. +// +// Arguments: +// fWin32DebuggingEnabled - true if interop-debugging, else false +// +// Return Value: +// None. If this returns, then it's safe to launch/attach. +// Else this throws an exception on failure. +// +// Assumptions: +// +// Notes: +// It should always be safe to launch/attach except in exceptional cases. +// @dbgtodo- this all goes away in V3. +// @dbgtodo- this should go away in Dev11. +// +void Cordb::EnsureCanLaunchOrAttach(BOOL fWin32DebuggingEnabled) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + if (!m_initialized) + { + ThrowHR(E_FAIL); + } + + EnsureAllowAnotherProcess(); + + if (!IsInteropDebuggingSupported() && fWin32DebuggingEnabled) + { + ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED); + } + + // Made it this far, we succeeded. +} + +HRESULT Cordb::CreateObjectV1(REFIID id, void **object) +{ + return CreateObject(CorDebugVersion_1_0, id, object); +} + +#if defined(FEATURE_DBGIPC_TRANSPORT_DI) +// CoreCLR activates debugger objects via direct COM rather than the shim (just like V1). For now we share the +// same debug engine version as V2, though this may change in the future. +HRESULT Cordb::CreateObjectTelesto(REFIID id, void ** pObject) +{ + return CreateObject(CorDebugVersion_2_0, id, pObject); +} +#endif // FEATURE_DBGIPC_TRANSPORT_DI + +// Static +// Used to create an instance for a ClassFactory (thus an external ref). +HRESULT Cordb::CreateObject(CorDebugInterfaceVersion iDebuggerVersion, REFIID id, void **object) +{ + if (id != IID_IUnknown && id != IID_ICorDebug) + return (E_NOINTERFACE); + + Cordb *db = new (nothrow) Cordb(iDebuggerVersion); + + if (db == NULL) + return (E_OUTOFMEMORY); + + *object = static_cast<ICorDebug*> (db); + db->ExternalAddRef(); + + return (S_OK); +} + + +// This is the version of the ICorDebug APIs that the debugger believes it's consuming. +// If this is a different version than that of the debuggee, we have the option of shimming +// behavior. +CorDebugInterfaceVersion +Cordb::GetDebuggerVersion() const +{ + return m_debuggerSpecifiedVersion; +} + +//*********************************************************************** +// ICorDebugTMEnum (Thread and Module enumerator) +//*********************************************************************** +CordbEnumFilter::CordbEnumFilter(CordbBase * pOwnerObj, NeuterList * pOwnerList) + : CordbBase (pOwnerObj->GetProcess(), 0), + m_pOwnerObj(pOwnerObj), + m_pOwnerNeuterList(pOwnerList), + m_pFirst (NULL), + m_pCurrent (NULL), + m_iCount (0) +{ + _ASSERTE(m_pOwnerNeuterList != NULL); + + HRESULT hr = S_OK; + EX_TRY + { + m_pOwnerNeuterList->Add(pOwnerObj->GetProcess(), this); + } + EX_CATCH_HRESULT(hr); + SetUnrecoverableIfFailed(GetProcess(), hr); + +} + +CordbEnumFilter::CordbEnumFilter(CordbEnumFilter *src) + : CordbBase (src->GetProcess(), 0), + m_pOwnerObj(src->m_pOwnerObj), + m_pOwnerNeuterList(src->m_pOwnerNeuterList), + m_pFirst (NULL), + m_pCurrent (NULL) +{ + _ASSERTE(m_pOwnerNeuterList != NULL); + + HRESULT hr = S_OK; + EX_TRY + { + m_pOwnerNeuterList->Add(src->GetProcess(), this); + } + EX_CATCH_HRESULT(hr); + SetUnrecoverableIfFailed(GetProcess(), hr); + + + + int iCountSanityCheck = 0; + EnumElement *pElementCur = NULL; + EnumElement *pElementNew = NULL; + EnumElement *pElementNewPrev = NULL; + + m_iCount = src->m_iCount; + + pElementCur = src->m_pFirst; + + while (pElementCur != NULL) + { + pElementNew = new (nothrow) EnumElement; + if (pElementNew == NULL) + { + // Out of memory. Clean up and bail out. + goto Error; + } + + if (pElementNewPrev == NULL) + { + m_pFirst = pElementNew; + } + else + { + pElementNewPrev->SetNext(pElementNew); + } + + pElementNewPrev = pElementNew; + + // Copy the element, including the AddRef part + pElementNew->SetData(pElementCur->GetData()); + IUnknown *iu = (IUnknown *)pElementCur->GetData(); + iu->AddRef(); + + if (pElementCur == src->m_pCurrent) + m_pCurrent = pElementNew; + + pElementCur = pElementCur->GetNext(); + iCountSanityCheck++; + } + + _ASSERTE(iCountSanityCheck == m_iCount); + + return; +Error: + // release all the allocated memory before returning + pElementCur = m_pFirst; + + while (pElementCur != NULL) + { + pElementNewPrev = pElementCur; + pElementCur = pElementCur->GetNext(); + + ((ICorDebugModule *)pElementNewPrev->GetData())->Release(); + delete pElementNewPrev; + } +} + +CordbEnumFilter::~CordbEnumFilter() +{ + _ASSERTE(this->IsNeutered()); + + _ASSERTE(m_pFirst == NULL); +} + +void CordbEnumFilter::Neuter() +{ + EnumElement *pElement = m_pFirst; + EnumElement *pPrevious = NULL; + + while (pElement != NULL) + { + pPrevious = pElement; + pElement = pElement->GetNext(); + delete pPrevious; + } + + // Null out the head in case we get neutered again. + m_pFirst = NULL; + m_pCurrent = NULL; + + CordbBase::Neuter(); +} + + + +HRESULT CordbEnumFilter::QueryInterface(REFIID id, void **ppInterface) +{ + // if we QI with the IID of the base type, we can't just return a pointer ICorDebugEnum directly, because + // the cast is ambiguous. This happens because CordbEnumFilter implements both ICorDebugModuleEnum and + // ICorDebugThreadEnum, both of which derive in turn from ICorDebugEnum. This produces a diamond inheritance + // graph. Thus we need a double cast. It doesn't really matter whether we pick ICorDebugThreadEnum or + // ICorDebugModuleEnum, because it will be backed by the same object regardless. + if (id == IID_ICorDebugEnum) + *ppInterface = static_cast<ICorDebugEnum *>(static_cast<ICorDebugThreadEnum *>(this)); + else if (id == IID_ICorDebugModuleEnum) + *ppInterface = (ICorDebugModuleEnum*)this; + else if (id == IID_ICorDebugThreadEnum) + *ppInterface = (ICorDebugThreadEnum*)this; + else if (id == IID_IUnknown) + *ppInterface = this; + else + { + *ppInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +HRESULT CordbEnumFilter::Skip(ULONG celt) +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this); + { + while ((celt-- > 0) && (m_pCurrent != NULL)) + { + m_pCurrent = m_pCurrent->GetNext(); + } + } + PUBLIC_API_END(hr); + return hr; +} + +HRESULT CordbEnumFilter::Reset() +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this); + { + m_pCurrent = m_pFirst; + } + PUBLIC_API_END(hr); + return hr; +} + +HRESULT CordbEnumFilter::Clone(ICorDebugEnum **ppEnum) +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this); + { + ValidateOrThrow(ppEnum); + + CordbEnumFilter * pClone = new CordbEnumFilter(this); + + // Ambigous conversion from CordbEnumFilter to ICorDebugEnum, so + // we explicitly convert it through ICorDebugThreadEnum. + pClone->ExternalAddRef(); + (*ppEnum) = static_cast<ICorDebugThreadEnum *> (pClone); + } + PUBLIC_API_END(hr); + return hr; +} + +HRESULT CordbEnumFilter::GetCount(ULONG *pcelt) +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this); + { + ValidateOrThrow(pcelt); + *pcelt = (ULONG)m_iCount; + } + PUBLIC_API_END(hr); + return hr; +} + +HRESULT CordbEnumFilter::Next(ULONG celt, + ICorDebugModule *objects[], + ULONG *pceltFetched) +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this); + { + hr = NextWorker(celt, objects, pceltFetched); + } + PUBLIC_API_END(hr); + return hr; +} + +HRESULT CordbEnumFilter::NextWorker(ULONG celt, ICorDebugModule *objects[], ULONG *pceltFetched) +{ + // <TODO> + // + // nickbe 11/20/2002 10:43:39 + // This function allows you to enumerate threads that "belong" to a + // particular AppDomain. While this operation makes some sense, it makes + // very little sense to + // (a) enumerate the list of threads in the enter process + // (b) build up a hand-rolled singly linked list (grrr) + // </TODO> + VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorDebugModule *, + celt, true, true); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *); + + if ((pceltFetched == NULL) && (celt != 1)) + { + return E_INVALIDARG; + } + + if (celt == 0) + { + if (pceltFetched != NULL) + { + *pceltFetched = 0; + } + return S_OK; + } + + HRESULT hr = S_OK; + + ULONG count = 0; + + while ((m_pCurrent != NULL) && (count < celt)) + { + objects[count] = (ICorDebugModule *)m_pCurrent->GetData(); + m_pCurrent = m_pCurrent->GetNext(); + count++; + } + + if (pceltFetched != NULL) + { + *pceltFetched = count; + } + + // + // If we reached the end of the enumeration, but not the end + // of the number of requested items, we return S_FALSE. + // + if (count < celt) + { + return S_FALSE; + } + + return hr; +} + + +HRESULT CordbEnumFilter::Next(ULONG celt, + ICorDebugThread *objects[], + ULONG *pceltFetched) +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this); + { + hr = NextWorker(celt, objects, pceltFetched); + } + PUBLIC_API_END(hr); + return hr; +} + +HRESULT CordbEnumFilter::NextWorker(ULONG celt, ICorDebugThread *objects[], ULONG *pceltFetched) +{ + // @TODO remove this class + VALIDATE_POINTER_TO_OBJECT_ARRAY(objects, ICorDebugThread *, celt, true, true); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *); + + if ((pceltFetched == NULL) && (celt != 1)) + { + return E_INVALIDARG; + } + + if (celt == 0) + { + if (pceltFetched != NULL) + { + *pceltFetched = 0; + } + return S_OK; + } + + HRESULT hr = S_OK; + + ULONG count = 0; + + while ((m_pCurrent != NULL) && (count < celt)) + { + objects[count] = (ICorDebugThread *)m_pCurrent->GetData(); + m_pCurrent = m_pCurrent->GetNext(); + count++; + } + + if (pceltFetched != NULL) + { + *pceltFetched = count; + } + + // + // If we reached the end of the enumeration, but not the end + // of the number of requested items, we return S_FALSE. + // + if (count < celt) + { + return S_FALSE; + } + + return hr; +} + + + +HRESULT CordbEnumFilter::Init (ICorDebugModuleEnum * pModEnum, CordbAssembly *pAssembly) +{ + INTERNAL_API_ENTRY(GetProcess()); + + ICorDebugModule *pCorModule = NULL; + CordbModule *pModule = NULL; + ULONG ulDummy = 0; + + HRESULT hr = pModEnum->Next(1, &pCorModule, &ulDummy); + + // + // Next returns E_FAIL if there is no next item, along with + // the count being 0. Convert that to just being S_OK. + // + if ((hr == E_FAIL) && (ulDummy == 0)) + { + hr = S_OK; + } + + if (FAILED (hr)) + return hr; + + EnumElement *pPrevious = NULL; + EnumElement *pElement = NULL; + + while (ulDummy != 0) + { + pModule = (CordbModule *)(ICorDebugModule *)pCorModule; + // Is this module part of the assembly for which we're enumerating? + if (pModule->m_pAssembly == pAssembly) + { + pElement = new (nothrow) EnumElement; + if (pElement == NULL) + { + // Out of memory. Clean up and bail out. + hr = E_OUTOFMEMORY; + goto Error; + } + + pElement->SetData ((void *)pCorModule); + m_iCount++; + + if (m_pFirst == NULL) + { + m_pFirst = pElement; + } + else + { + PREFIX_ASSUME(pPrevious != NULL); + pPrevious->SetNext (pElement); + } + pPrevious = pElement; + } + else + ((ICorDebugModule *)pModule)->Release(); + + hr = pModEnum->Next(1, &pCorModule, &ulDummy); + + // + // Next returns E_FAIL if there is no next item, along with + // the count being 0. Convert that to just being S_OK. + // + if ((hr == E_FAIL) && (ulDummy == 0)) + { + hr = S_OK; + } + + if (FAILED (hr)) + goto Error; + } + + m_pCurrent = m_pFirst; + + return S_OK; + +Error: + // release all the allocated memory before returning + pElement = m_pFirst; + + while (pElement != NULL) + { + pPrevious = pElement; + pElement = pElement->GetNext(); + + ((ICorDebugModule *)pPrevious->GetData())->Release(); + delete pPrevious; + } + + return hr; +} + +HRESULT CordbEnumFilter::Init (ICorDebugThreadEnum *pThreadEnum, CordbAppDomain *pAppDomain) +{ + INTERNAL_API_ENTRY(GetProcess()); + + ICorDebugThread *pCorThread = NULL; + CordbThread *pThread = NULL; + ULONG ulDummy = 0; + + HRESULT hr = pThreadEnum->Next(1, &pCorThread, &ulDummy); + + // + // Next returns E_FAIL if there is no next item, but we want to consider this + // ok in this context. + // + if ((hr == E_FAIL) && (ulDummy == 0)) + { + hr = S_OK; + } + + if (FAILED(hr)) + { + return hr; + } + + EnumElement *pPrevious = NULL; + EnumElement *pElement = NULL; + + while (ulDummy > 0) + { + pThread = (CordbThread *)(ICorDebugThread *) pCorThread; + + // Is this module part of the appdomain for which we're enumerating? + // Note that this is rather inefficient (we call into the left side for every AppDomain), + // but the whole idea of enumerating the threads of an AppDomain is pretty bad, + // and we don't expect this to be used much if at all. + CordbAppDomain* pThreadDomain; + hr = pThread->GetCurrentAppDomain( &pThreadDomain ); + if( FAILED(hr) ) + { + goto Error; + } + + if (pThreadDomain == pAppDomain) + { + pElement = new (nothrow) EnumElement; + if (pElement == NULL) + { + // Out of memory. Clean up and bail out. + hr = E_OUTOFMEMORY; + goto Error; + } + + pElement->SetData ((void *)pCorThread); + m_iCount++; + + if (m_pFirst == NULL) + { + m_pFirst = pElement; + } + else + { + PREFIX_ASSUME(pPrevious != NULL); + pPrevious->SetNext (pElement); + } + + pPrevious = pElement; + } + else + { + ((ICorDebugThread *)pThread)->Release(); + } + + // get the next thread in the thread list + hr = pThreadEnum->Next(1, &pCorThread, &ulDummy); + + // + // Next returns E_FAIL if there is no next item, along with + // the count being 0. Convert that to just being S_OK. + // + if ((hr == E_FAIL) && (ulDummy == 0)) + { + hr = S_OK; + } + + if (FAILED (hr)) + goto Error; + } + + m_pCurrent = m_pFirst; + + return S_OK; + +Error: + // release all the allocated memory before returning + pElement = m_pFirst; + + while (pElement != NULL) + { + pPrevious = pElement; + pElement = pElement->GetNext(); + + ((ICorDebugThread *)pPrevious->GetData())->Release(); + delete pPrevious; + } + + return hr; +} + |