summaryrefslogtreecommitdiff
path: root/src/debug/di/dbgtransportmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/dbgtransportmanager.cpp')
-rw-r--r--src/debug/di/dbgtransportmanager.cpp1912
1 files changed, 1912 insertions, 0 deletions
diff --git a/src/debug/di/dbgtransportmanager.cpp b/src/debug/di/dbgtransportmanager.cpp
new file mode 100644
index 0000000000..e5a74b5761
--- /dev/null
+++ b/src/debug/di/dbgtransportmanager.cpp
@@ -0,0 +1,1912 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+
+#include "stdafx.h"
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+#include "coreclrremotedebugginginterfaces.h"
+
+#ifdef FEATURE_DBGIPC_TRANSPORT_DI
+
+//
+// Provides access to various process enumeration and control facilities for a remote machine.
+//
+
+// The one and only instance of the DbgTransportManager in the process.
+DbgTransportManager *g_pDbgTransportManager = NULL;
+
+DbgTransportManager::DbgTransportManager()
+{
+ memset(this, 0, sizeof(*this));
+}
+
+// Startup/shutdown calls. These are ref-counted (cordbg, for instance, is constructed in such a way that
+// the command shell will attempt to load mscordbi and initialize an associated DbgTransportManager
+// multiple times).
+HRESULT DbgTransportManager::Init()
+{
+ if (InterlockedIncrement(&m_lRefCount) == 1)
+ {
+ m_sLock.Init("DbgTransportManager Lock", RSLock::cLockFlat, RSLock::LL_DBG_TRANSPORT_MANAGER_LOCK);
+ m_pTargets = NULL;
+ }
+
+ return S_OK;
+}
+
+void DbgTransportManager::Shutdown()
+{
+ if (InterlockedDecrement(&m_lRefCount) == 0)
+ {
+ m_sLock.Destroy();
+
+ while (m_pTargets)
+ {
+ TargetRef *pTargetRef = m_pTargets;
+ m_pTargets = pTargetRef->m_pNext;
+
+ pTargetRef->m_pTarget->Shutdown();
+ delete pTargetRef->m_pTarget;
+
+ delete pTargetRef;
+ }
+ }
+}
+
+// Attempt to connect to a debugging proxy on the machine at the given address and with the specified port
+// number. If the port number is given as zero use the port stored in user debugger configuration. On success
+// a pointer to a DbgTransportTarget object will be returned.
+HRESULT DbgTransportManager::ConnectToTarget(DWORD dwIPAddress, USHORT usPort, DbgTransportTarget **ppTarget)
+{
+ RSLockHolder lock(&m_sLock);
+
+ // Look for an existing target with matching IP address and port number.
+ TargetRef *pTargetRef = m_pTargets;
+ while (pTargetRef)
+ {
+ // Matches must have identical IP address and port number and must also be in a good connection state
+ // (otherwise we're looking at a target that hit a network error and is just waiting until outstanding
+ // references to it have been released -- in these circumstances we allow a new target to be allocated
+ // in order to re-attempt connection to the proxy).
+ if (pTargetRef->m_dwIPAddress == dwIPAddress &&
+ pTargetRef->m_usPort == usPort &&
+ !pTargetRef->m_pTarget->IsProxyConnectionBad())
+ {
+ pTargetRef->m_dwRefCount++;
+ *ppTarget = pTargetRef->m_pTarget;
+ return S_OK;
+ }
+
+ pTargetRef = pTargetRef->m_pNext;
+ }
+
+ // If we get here there wasn't an appropriate existing entry, so create one.
+
+ // First the reference structure used to track the target.
+ pTargetRef = new (nothrow) TargetRef();
+ if (pTargetRef == NULL)
+ return E_OUTOFMEMORY;
+
+ // Then the target object itself.
+ DbgTransportTarget *pTarget = new (nothrow) DbgTransportTarget();
+ if (pTargetRef == NULL)
+ {
+ delete pTargetRef;
+ return E_OUTOFMEMORY;
+ }
+
+ // Initialize the target (this will attempt a connection to the proxy immediately).
+ HRESULT hr = pTarget->Init(dwIPAddress, usPort);
+ if (FAILED(hr))
+ {
+ pTarget->Shutdown();
+ delete pTarget;
+ delete pTargetRef;
+ return hr;
+ }
+
+ // Everything's good, go ahead and initialize and link in the target reference.
+ pTargetRef->m_dwRefCount = 1;
+ pTargetRef->m_pTarget = pTarget;
+ pTargetRef->m_dwIPAddress = dwIPAddress;
+ pTargetRef->m_usPort = usPort;
+
+ pTargetRef->m_pNext = m_pTargets;
+ m_pTargets = pTargetRef;
+
+ *ppTarget = pTarget;
+ return S_OK;
+}
+
+// Add another reference to a target already acquired by ConnectToTarget (used by clients when they want
+// to hand a target out to independent code).
+void DbgTransportManager::ReferenceTarget(DbgTransportTarget *pTarget)
+{
+ RSLockHolder lock(&m_sLock);
+
+ // We need to locate the target reference for this target.
+ TargetRef *pTargetRef = m_pTargets;
+ while (pTargetRef)
+ {
+ if (pTargetRef->m_pTarget == pTarget)
+ {
+ pTargetRef->m_dwRefCount++;
+ return;
+ }
+ pTargetRef = pTargetRef->m_pNext;
+ }
+
+ // Shouldn't get here.
+ _ASSERTE(FALSE);
+}
+
+// Release reference to a DbgTransportTarget. If this is the last active reference then the connection to the
+// proxy will be severed and the object deallocated.
+void DbgTransportManager::ReleaseTarget(DbgTransportTarget *pTarget)
+{
+ RSLockHolder lock(&m_sLock);
+
+ // We need to locate the target reference for this target (and the previous reference so we can perform
+ // the fixup to remove the entry from the queue if this was the last reference to the target).
+ TargetRef *pTargetRef = m_pTargets;
+ TargetRef *pLastRef = NULL;
+ while (pTargetRef)
+ {
+ if (pTargetRef->m_pTarget == pTarget)
+ {
+ pTargetRef->m_dwRefCount--;
+ if (pTargetRef->m_dwRefCount == 0)
+ {
+ // This was the last reference to this particular target. Remove it from the queue and
+ // deallocate it.
+ if (pLastRef)
+ pLastRef->m_pNext = pTargetRef->m_pNext;
+ else
+ m_pTargets = pTargetRef->m_pNext;
+
+ delete pTargetRef;
+
+ pTarget->Shutdown();
+ delete pTarget;
+ }
+ return;
+ }
+
+ pLastRef = pTargetRef;
+ pTargetRef = pTargetRef->m_pNext;
+ }
+
+ // Shouldn't get here.
+ _ASSERTE(FALSE);
+}
+
+DbgTransportTarget::DbgTransportTarget()
+{
+ memset(this, 0, sizeof(*this));
+}
+
+// Initialization routine called only by the DbgTransportManager.
+HRESULT DbgTransportTarget::Init(DWORD dwIPAddress, USHORT usPort)
+{
+ m_ullLastUpdate = 0;
+ m_fShutdown = false;
+
+ // Target platform is initially unknown. This gets set when the proxy replies to our initial GetSystemInfo
+ // message.
+ m_ePlatform = DTP_Unknown;
+
+ // If a port number hasn't been specified query the debugger configuration for the current user, this will
+ // give us the default.
+ if (usPort == 0)
+ {
+ DbgConfiguration sDbgConfig;
+ if (!GetDebuggerConfiguration(&sDbgConfig))
+ {
+ DbgTransportLog(LC_Always, "Failed to locate debugger configuration");
+ return CORDBG_E_REMOTE_INVALID_CONFIG;
+ }
+ _ASSERTE(sDbgConfig.m_fEnabled); // Debugging is always enabled on right side.
+ m_usProxyPort = sDbgConfig.m_usProxyPort;
+ }
+ else
+ m_usProxyPort = usPort;
+
+ // Do the same for IP address except the fallback is an environment variable (and after that 127.0.0.1 for
+ // local debugging).
+ if (dwIPAddress == 0)
+ {
+ LPWSTR wszProxyIP = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgTransportProxyAddress);
+ if (wszProxyIP != NULL)
+ {
+ int cbReq = WszWideCharToMultiByte(CP_UTF8, 0, wszProxyIP, -1, 0, 0, 0, 0);
+ char *szProxyIP = new (nothrow) char[cbReq + 1];
+ if (szProxyIP != NULL)
+ {
+ WszWideCharToMultiByte(CP_UTF8, 0, wszProxyIP, -1, szProxyIP, cbReq + 1, 0,0);
+ m_dwProxyIP = DBGIPC_NTOHL(inet_addr(szProxyIP));
+ }
+
+ REGUTIL::FreeConfigString(wszProxyIP);
+ }
+ if (m_dwProxyIP == 0)
+ m_dwProxyIP = DBGIPC_NTOHL(inet_addr("127.0.0.1"));
+ }
+ else
+ m_dwProxyIP = dwIPAddress;
+
+ // Allocate the connection manager and initialize it.
+ m_pConnectionManager = AllocateSecConnMgr();
+ if (m_pConnectionManager == NULL)
+ return E_OUTOFMEMORY;
+
+ SecConnStatus eStatus = m_pConnectionManager->Initialize();
+ if (eStatus != SCS_Success)
+ {
+ DbgTransportLog(LC_Always, "Failed to initialize connection manager with %u", eStatus);
+ switch (eStatus)
+ {
+ case SCS_OutOfMemory:
+ return E_OUTOFMEMORY;
+ case SCS_InvalidConfiguration:
+ return CORDBG_E_REMOTE_INVALID_CONFIG;
+ default:
+ return E_FAIL;
+ }
+ }
+
+ m_sLock.Init("DbgTransportTarget Lock", RSLock::cLockFlat, RSLock::LL_DBG_TRANSPORT_TARGET_LOCK);
+ m_fInitLock = true;
+
+ // Outgoing requests are identified with a monotonically increasing ID starting from 1.
+ m_dwNextRequestID = 1;
+
+ // We store a singly-linked list of requests to the proxy that haven't been replied yet.
+ m_pRequestList = NULL;
+
+ // Attempt to contact the proxy and form a connection to it.
+ eStatus = m_pConnectionManager->AllocateConnection(m_dwProxyIP, m_usProxyPort, &m_pConnection);
+ if (eStatus == SCS_Success)
+ eStatus = m_pConnection->Connect();
+ if (eStatus != SCS_Success)
+ {
+ DbgTransportLog(LC_Always, "Failed to connect to proxy with %u", eStatus);
+ switch (eStatus)
+ {
+ case SCS_OutOfMemory:
+ return E_OUTOFMEMORY;
+ case SCS_UnknownTarget:
+ return CORDBG_E_REMOTE_UNKNOWN_TARGET;
+ case SCS_NoListener:
+ return CORDBG_E_REMOTE_NO_LISTENER;
+ case SCS_NetworkFailure:
+ return CORDBG_E_REMOTE_NETWORK_FAILURE;
+ case SCS_MismatchedCerts:
+ return CORDBG_E_REMOTE_MISMATCHED_CERTS;
+ default:
+ return E_ABORT;
+ }
+ }
+
+ // Create a thread used to monitor remote process state.
+ m_hProcessEventThread = CreateThread(NULL, 0, ProcessEventWorkerStatic, this, 0, NULL);
+ if (m_hProcessEventThread == NULL)
+ return E_OUTOFMEMORY;
+
+ // Send the initial message to the proxy which informs it of our protocol version and queries the target
+ // platform and protocol version. This must be done after the thread above is started since we rely on
+ // this thread to process replies.
+ DWORD dwProxyMajorVersion;
+ DWORD dwProxyMinorVersion;
+ HRESULT hr = MakeProxyRequest(DPMT_GetSystemInfo,
+ &dwProxyMajorVersion,
+ &dwProxyMinorVersion,
+ &m_ePlatform);
+ if (FAILED(hr))
+ {
+ DbgTransportLog(LC_Always, "GetSystemInfo request to proxy failed with %08X", hr);
+ return hr;
+ }
+
+ // Check that we can deal with the proxy's protocol.
+ if (dwProxyMajorVersion != kCurrentMajorVersion)
+ {
+ DbgTransportLog(LC_Always, "Don't understand proxy protocol v%u.%u",
+ dwProxyMajorVersion, dwProxyMinorVersion);
+ return CORDBG_E_REMOTE_MISMATCHED_PROTOCOLS;
+ }
+
+ m_fProxyConnectionBad = false;
+
+ return S_OK;
+}
+
+// Shutdown routine called only by the DbgTransportManager.
+void DbgTransportTarget::Shutdown()
+{
+ DbgTransportLog(LC_Always, "DbgTransportTarget shutting down");
+
+ m_fShutdown = true;
+ m_fProxyConnectionBad = true;
+
+ if (m_hProcessEventThread)
+ {
+ // Unwedge the process event thread if it's blocked in a Receive().
+ m_pConnection->CancelReceive();
+
+ // Wait for the process event thread to see the shutdown status and close itself down.
+ WaitForSingleObject(m_hProcessEventThread, INFINITE);
+ CloseHandle(m_hProcessEventThread);
+ }
+
+ // Cleanup process list.
+ DeallocateProcessList(m_pProcessList);
+
+ if (m_pConnection)
+ m_pConnection->Destroy();
+
+ if (m_pConnectionManager)
+ m_pConnectionManager->Destroy();
+
+ if (m_fInitLock)
+ m_sLock.Destroy();
+}
+
+// Indicates when the connection to a proxy has failed: this target object will remain until the last
+// reference to it is released (DbgTransportManager::ReleaseTransport) but will fail all further requests. The
+// manager will then allow a new attempt to create a connection to the proxy to be made (wrapped in a new
+// DbgTransportTarget).
+bool DbgTransportTarget::IsProxyConnectionBad()
+{
+ return m_fProxyConnectionBad;
+}
+
+// Fill caller allocated table at pdwProcesses with the pids of processes currently alive on the target
+// system. Return the number of slots filled in *pcProcesses. The size of the table is given by cSlots. If
+// more than this number of processes are alive then *pcProcesses is set to the total number and E_ABORT
+// returned.
+HRESULT DbgTransportTarget::EnumProcesses(DWORD *pdwProcesses, DWORD cSlots, DWORD *pcProcesses)
+{
+ if (m_fProxyConnectionBad)
+ return E_ABORT;
+
+ *pcProcesses = 0;
+
+ // Get an up-to-date process list from the proxy.
+ UpdateProcessList();
+
+ // Must access the process list under the lock.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // Populate the output table from the new process list.
+ DWORD i = 0;
+ DWORD cSlotsLeft = cSlots;
+
+ // Fill the output table with as many process IDs as we have (or until we run out of slots). Carry on
+ // to the end of the process regardless so we can report how many processes there actually are.
+ for (ProcessEntry *pProcess = m_pProcessList; pProcess; pProcess = pProcess->m_pNext)
+ {
+ // Entries for dead processes can persist until an associated transport is released. Don't report
+ // these.
+ if (pProcess->m_fExited)
+ continue;
+
+ if (cSlotsLeft)
+ {
+ pdwProcesses[i] = pProcess->m_dwPID;
+ cSlotsLeft--;
+ }
+
+ i++;
+ }
+
+ // Return total count to caller.
+ *pcProcesses = i;
+
+ } // Leave lock
+
+ return *pcProcesses > cSlots ? E_ABORT : S_OK;
+}
+
+// Given a PID attempt to find or create a DbgTransportSession instance to manage a connection to a runtime in
+// that process. Returns E_UNEXPECTED if the process can't be found. Also returns a handle that can be waited
+// on for process termination.
+HRESULT DbgTransportTarget::GetTransportForProcess(DWORD dwPID,
+ DbgTransportSession **ppTransport,
+ HANDLE *phProcessHandle)
+{
+ if (m_fProxyConnectionBad)
+ return E_ABORT;
+
+ // Get an up-to-date process list from the proxy.
+ UpdateProcessList();
+
+ // Process list can only be examined under the lock.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // Scan each process in the list.
+ ProcessEntry *pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ if (pProcess->m_dwPID == dwPID)
+ {
+ // We've found a match.
+ if (pProcess->m_fExited)
+ {
+ // But it was for a dead process. Don't report this one (though we know the process is dead so
+ // return E_UNEXPECTED).
+ return E_UNEXPECTED;
+ }
+
+ RetryTransport:
+ // If we already know about runtimes in this process then attempt to attach to the first one.
+ // CORECLRTODO: In the next version we'll wire up the additional logic to enable the caller to
+ // indicate which runtime they want to target within a single process.
+ if (pProcess->m_pRuntimes)
+ {
+ RuntimeEntry *pRuntime = pProcess->m_pRuntimes;
+
+ // If we have a runtime entry already then the LS is already present and we know the port
+ // to connect to. If there's already a transport in place then we can (and must) use that.
+ // Otherwise we can allocate and initialize one based on the port information.
+ DbgTransportSession *pTransport = pRuntime->m_pDbgTransport;
+ if (pTransport == NULL)
+ {
+ // No transport yet, allocate one.
+ pTransport = new (nothrow) DbgTransportSession();
+ if (pTransport == NULL)
+ return E_OUTOFMEMORY;
+
+ // Initialize it (this immediately starts the remote connection process).
+ HRESULT hr = pTransport->Init(m_dwProxyIP, pRuntime->m_usPort, pProcess->m_hExitedEvent);
+ if (FAILED(hr))
+ {
+ lock.Release();
+ pTransport->Shutdown();
+ delete pTransport;
+ return hr;
+ }
+
+ pRuntime->m_pDbgTransport = pTransport;
+ }
+
+ // One more caller knows about this transport instance. (Which in turn is another reason
+ // the process can't be deleted yet).
+ pRuntime->m_cTransportRef++;
+ pProcess->m_cProcessRef++;
+
+ *ppTransport = pTransport;
+ if (!DuplicateHandle(GetCurrentProcess(),
+ pProcess->m_hExitedEvent,
+ GetCurrentProcess(),
+ phProcessHandle,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ lock.Release();
+ return HRESULT_FROM_GetLastError();
+ }
+ return S_OK;
+ }
+
+ // If we get here we've found the process record but there's no known runtime yet. The proxy
+ // will send us an event if either a runtime starts up or the process dies, so we'll wait on
+ // both of these. We can't wait with the lock held so increment the ref count on the process
+ // (to keep the entry and the events we're about to wait on valid) and drop the lock first.
+
+ pProcess->m_cProcessRef++;
+ lock.Release();
+
+ // We need to send an early attach notification to the proxy so that when the next runtime
+ // starts up and registers it will know to suspend itself until we attach (i.e. this is the
+ // early attach). Obviously we're racing with runtime startup here but that's by definition.
+ bool fProcessExited;
+ HRESULT hr = MakeProxyRequest(DPMT_EarlyAttach, pProcess->m_pruidProcess, &fProcessExited);
+ if (FAILED(hr))
+ {
+ lock.Acquire();
+ pProcess->m_cProcessRef--;
+ return hr;
+ }
+
+ // The process might have managed to exit before we even built a process entry for it on this
+ // side. In that case a process termination might have been missed. Checking for the exit
+ // status again with the EarlyAttach request above closes the hole (we establish a process
+ // entry and lock it in place so any termination events from that point on will be caught,
+ // then we fire an EarlyAttach and check the current status).
+ if (fProcessExited)
+ {
+ lock.Acquire();
+ pProcess->m_cProcessRef--;
+ pProcess->m_fExited = true;
+ SetEvent(pProcess->m_hExitedEvent);
+ return E_UNEXPECTED;
+ }
+
+ DbgTransportLog(LC_Always, "Waiting on runtime starting or process termination for %08X(%u, %u)",
+ pProcess, pProcess->m_dwPID, pProcess->m_pruidProcess);
+
+ HANDLE rgEvents[] = { pProcess->m_hRuntimeStartedEvent, pProcess->m_hExitedEvent };
+ DWORD dwResult = WaitForMultipleObjectsEx(2, rgEvents, FALSE, INFINITE, FALSE);
+ _ASSERTE(dwResult == WAIT_OBJECT_0 || dwResult == (WAIT_OBJECT_0 + 1));
+
+ DbgTransportLog(LC_Always, " %s", dwResult == WAIT_OBJECT_0 ? "Runtime started" : "Process terminated");
+
+ // Take the lock again and determine what our status is.
+ lock.Acquire();
+
+ // We have no further need to keep this process record alive (once we drop the lock).
+ _ASSERTE(pProcess->m_cProcessRef > 0);
+ pProcess->m_cProcessRef--;
+
+ // If the process terminated then exit with E_UNEXPECTED. Note that this might be a zombie
+ // entry marked with m_fExited = true in this case, but rather than duplicate entry cleanup
+ // code we'll let the next process list update flush this record (now that the ref count has
+ // been decremented).
+ if (dwResult == (WAIT_OBJECT_0 + 1))
+ return E_UNEXPECTED;
+
+ // We should have at least one runtime entry now; just jump back to the code that knows how to
+ // re-use or allocate a transport on it.
+ _ASSERTE(pProcess->m_pRuntimes);
+ goto RetryTransport;
+ }
+
+ pProcess = pProcess->m_pNext;
+ }
+ } // Leave lock
+
+ // Didn't find a process with a matching PID.
+ return E_UNEXPECTED;
+}
+
+// Returns true if the given PID identifies a running process which is hosting at least one CoreCLR
+// runtime.
+bool DbgTransportTarget::IsManagedProcess(DWORD dwPID)
+{
+ // Maybe we already know the process is managed.
+ {
+ RSLockHolder lock(&m_sLock);
+ ProcessEntry *pProcess = LocateProcessByPID(dwPID);
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:6011) // Prefast doesn't understand the guard to avoid de-referencing a NULL pointer below.
+#endif // _PREFAST_
+ if (pProcess && pProcess->m_pRuntimes)
+ return true;
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif // _PREFAST_
+ } // Leave lock
+
+ // Get an up-to-date process list from the proxy in case we've haven't done this for a while and a runtime
+ // has started up in the meantime.
+ UpdateProcessList();
+
+ // Try once again.
+ {
+ RSLockHolder lock(&m_sLock);
+ ProcessEntry *pProcess = LocateProcessByPID(dwPID);
+ return pProcess ? pProcess->m_pRuntimes != NULL : false;
+ } // Leave lock
+}
+
+// Release another reference to the transport associated with dwPID. Once all references are gone (modulo the
+// manager's own weak reference) clean up the transport and deallocate it.
+void DbgTransportTarget::ReleaseTransport(DbgTransportSession *pTransport)
+{
+ DbgTransportSession *pTransportToShutdown = NULL;
+
+ // Process list can only be examined under the lock.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // Scan all processes we know about.
+ ProcessEntry *pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ // Scan each runtime we know about in the current process.
+ RuntimeEntry *pRuntime = pProcess->m_pRuntimes;
+ while (pRuntime)
+ {
+ if (pRuntime->m_pDbgTransport == pTransport)
+ {
+ // Found it.
+
+ // Decrement the transport ref count. This is also one less reason to hold onto the
+ // process record.
+ _ASSERTE(pRuntime->m_cTransportRef > 0 && pProcess->m_cProcessRef > 0);
+ pRuntime->m_cTransportRef--;
+ pProcess->m_cProcessRef--;
+
+ // If nobody references this transport any more we can shut it down and delete it. Don't
+ // do this under the lock however.
+ if (pRuntime->m_cTransportRef == 0)
+ {
+ pTransportToShutdown = pRuntime->m_pDbgTransport;
+ pRuntime->m_pDbgTransport = NULL;
+ }
+
+ lock.Release();
+
+ // If we made the transport inaccessible above we can shut it down and deallocate it now.
+ if (pTransportToShutdown)
+ {
+ pTransportToShutdown->Shutdown();
+ delete pTransportToShutdown;
+ }
+
+ return;
+ }
+
+ pRuntime = pRuntime->m_pNext;
+ }
+
+ pProcess = pProcess->m_pNext;
+ }
+ } // Leave lock
+
+ _ASSERTE(!"Failed to find ProcessEntry to release transport reference");
+}
+
+// Run the command line given on the remote machine to create a process. Return the PID of this process. When
+// and if the process starts a runtime and registers with the proxy it will be told to halt and wait for a
+// debugger attach.
+HRESULT DbgTransportTarget::CreateProcess(LPCWSTR wszCommand,
+ LPCWSTR wszArgs,
+ LPCWSTR wszCurrentDirectory,
+ LPVOID pvEnvironment,
+ DWORD *pdwPID)
+{
+ if (m_fProxyConnectionBad)
+ return E_ABORT;
+
+ DWORD cchCommand = wszCommand ? wcslen(wszCommand) : 0;
+ DWORD cchArgs = wszArgs ? wcslen(wszArgs) : 0;
+
+ // Proxy expects the command line as a single string.
+ LPWSTR wszCommandLine = (LPWSTR)_alloca((cchCommand + 1 + cchArgs + 1) * sizeof(WCHAR));
+ wszCommandLine[0] = W('\0');
+ if (wszCommand)
+ {
+ wcscat(wszCommandLine, wszCommand);
+ wcscat(wszCommandLine, W(" "));
+ }
+ if (wszArgs)
+ wcscat(wszCommandLine, wszArgs);
+
+ // Check how big a UTF8 version of the command line would be.
+ int cbReqd = WszWideCharToMultiByte(CP_UTF8, 0, wszCommandLine, -1, 0, 0, 0, 0);
+
+ LPSTR szCommandLine = (LPSTR)_alloca(cbReqd);
+
+ // Do the conversion from 16-bit.
+ WszWideCharToMultiByte(CP_UTF8, 0, wszCommandLine, -1, szCommandLine, cbReqd, 0, 0);
+
+ // If a default directory is supplied then convert it to UTF8.
+ LPSTR szCurrentDirectory = NULL;
+ if (wszCurrentDirectory)
+ {
+ cbReqd = WszWideCharToMultiByte(CP_UTF8, 0, wszCurrentDirectory, -1, 0, 0, 0, 0);
+ szCurrentDirectory = (LPSTR)_alloca(cbReqd);
+ WszWideCharToMultiByte(CP_UTF8, 0, wszCurrentDirectory, -1, szCurrentDirectory, cbReqd, 0, 0);
+ }
+
+ // Prepare to format an attribute block containing all the launch parameters we'll send to the proxy.
+ // There are two phases: first we plan how much space will be required in the block then we allocate the
+ // block and fill it in.
+ DbgAttributeBlockWriter sAttrWriter;
+
+ sAttrWriter.ScheduleStringValue(szCommandLine);
+
+ if (szCurrentDirectory)
+ sAttrWriter.ScheduleStringValue(szCurrentDirectory);
+
+ // Determine how large the environment block is (if it's supplied).
+ DWORD cbEnvironment = 0;
+ if (pvEnvironment)
+ {
+ char *szEnv = (char *)pvEnvironment;
+
+ // The environment is a series of nul-terminated strings followed by a final nul.
+ while (*szEnv)
+ {
+ DWORD cbString = strlen(szEnv) + 1;
+ cbEnvironment += cbString;
+ szEnv += cbString;
+ }
+
+ // Account for final nul.
+ cbEnvironment++;
+ }
+
+ if (cbEnvironment)
+ sAttrWriter.ScheduleValue(cbEnvironment);
+
+ // By now we know how large an attribute block we need.
+ DWORD cbAttributeBlock = sAttrWriter.GetRequiredBufferSize();
+ BYTE *pbAttributeBlock = new (nothrow) BYTE[cbAttributeBlock];
+ if (pbAttributeBlock == NULL)
+ return E_OUTOFMEMORY;
+
+ // Initialize the attribute block.
+ sAttrWriter.BeginFormatting((char*)pbAttributeBlock);
+
+ sAttrWriter.AddStringValue(DAT_CommandLine, szCommandLine);
+ if (szCurrentDirectory)
+ sAttrWriter.AddStringValue(DAT_DefaultDirectory, szCurrentDirectory);
+ if (cbEnvironment)
+ sAttrWriter.AddValue(DAT_Environment, (char*)pvEnvironment, cbEnvironment);
+
+ // Allocate a new process entry up front (but don't link it into the list until we know we've created the
+ // remote process).
+ ProcessEntry *pProcess = new (nothrow) ProcessEntry();
+ if (pProcess == NULL)
+ {
+ delete [] pbAttributeBlock;
+ return E_OUTOFMEMORY;
+ }
+ memset(pProcess, 0, sizeof(ProcessEntry));
+
+ strncpy(pProcess->m_szCommandLine, szCommandLine, kMaxCommandLine);
+ pProcess->m_szCommandLine[kMaxCommandLine - 1] = '\0';
+
+ pProcess->m_hExitedEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (pProcess->m_hExitedEvent == NULL)
+ {
+ delete [] pbAttributeBlock;
+ delete pProcess;
+ return E_OUTOFMEMORY;
+ }
+
+ pProcess->m_hRuntimeStartedEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (pProcess->m_hRuntimeStartedEvent == NULL)
+ {
+ delete [] pbAttributeBlock;
+ delete pProcess;
+ return E_OUTOFMEMORY;
+ }
+
+ // Send the launch request to the proxy. It will reply with a PID or an hresult on failure.
+ HRESULT hr = MakeProxyRequest(DPMT_LaunchProcess,
+ pbAttributeBlock,
+ &pProcess->m_dwPID,
+ &pProcess->m_pruidProcess);
+
+ delete [] pbAttributeBlock;
+
+ if (SUCCEEDED(hr))
+ {
+ // The remote process has been created.
+ *pdwPID = pProcess->m_dwPID;
+
+ // Take the lock and check whether we already have an entry for this process (this can happen due to
+ // an EnumProcesses from another thread).
+ {
+ RSLockHolder lock(&m_sLock);
+ ProcessEntry *pSearchProcess = m_pProcessList;
+ while (pSearchProcess)
+ {
+ if (pSearchProcess->m_pruidProcess == pProcess->m_pruidProcess)
+ break;
+ pSearchProcess = pSearchProcess->m_pNext;
+ }
+
+ if (pSearchProcess)
+ {
+ // Someone else has already made the update. Discard our unneeded copy.
+ delete pProcess;
+ }
+ else
+ {
+ // No current entry for this process, link it in.
+ pProcess->m_pNext = m_pProcessList;
+ m_pProcessList = pProcess;
+ }
+ } // Leave lock
+ }
+ else
+ {
+ // The process was not created, throw away the process entry we'd prepared.
+ delete pProcess;
+ }
+
+ return hr;
+}
+
+// Kill the process identified by PID.
+void DbgTransportTarget::KillProcess(DWORD dwPID)
+{
+ if (m_fProxyConnectionBad)
+ return;
+
+ PRUID pruidTarget = 0;
+
+ // Look up the process by PID so we can find the corresponding PRUID (which is the proxy's version of the
+ // PID).
+ {
+ RSLockHolder lock(&m_sLock);
+ ProcessEntry *pProcess = LocateProcessByPID(dwPID);
+ if (pProcess)
+ pruidTarget = pProcess->m_pruidProcess;
+ } // Leave lock
+
+ if (pruidTarget)
+ {
+ HRESULT hr = MakeProxyRequest(DPMT_TerminateProcess, pruidTarget);
+ if (FAILED(hr))
+ {
+ // Network failure could prevent our terminate from getting through. We don't currently support
+ // rebuilding a network connection and retrying, so report the process as dead to prevent a hang
+ // in the debugger on this end.
+ RSLockHolder lock(&m_sLock);
+ ProcessEntry *pProcess = LocateProcessByPID(dwPID);
+ if (pProcess)
+ {
+ pProcess->m_fExited = true;
+ SetEvent(pProcess->m_hExitedEvent);
+ }
+ } // Leave lock
+ }
+}
+
+// Ask the remote debugger proxy for an updated list of processes and reflect these changes into our local
+// process list. Any failure will leave the current process list state unchanged.
+void DbgTransportTarget::UpdateProcessList()
+{
+ // As an optimization, don't update the process list more than once a second.
+ if ((CLRGetTickCount64() - m_ullLastUpdate) <= 1000)
+ return;
+ m_ullLastUpdate = CLRGetTickCount64();
+
+ // Send message to the proxy asking for a list of processes and CoreCLR instances. By the time the
+ // MakeProxyRequest request call below completes the process/runtime database will have been updated.
+ MakeProxyRequest(DPMT_EnumProcesses);
+}
+
+// Locate a process entry by PID. Assumes the lock is already held.
+DbgTransportTarget::ProcessEntry *DbgTransportTarget::LocateProcessByPID(DWORD dwPID)
+{
+ _ASSERTE(m_sLock.HasLock());
+
+ ProcessEntry *pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ if (pProcess->m_dwPID == dwPID)
+ return pProcess;
+ pProcess = pProcess->m_pNext;
+ }
+ return NULL;
+}
+
+DbgTransportTarget::RuntimeEntry::~RuntimeEntry()
+{
+ // If there's still a transport attached to the remote runtime shut it down and delete it.
+ if (m_pDbgTransport)
+ {
+ m_pDbgTransport->Shutdown();
+ delete m_pDbgTransport;
+ }
+}
+
+DbgTransportTarget::ProcessEntry::~ProcessEntry()
+{
+ // Clean up any records for runtimes hosted within this process.
+ while (m_pRuntimes)
+ {
+ RuntimeEntry *pDelRuntime = m_pRuntimes;
+ m_pRuntimes = m_pRuntimes->m_pNext;
+ delete pDelRuntime;
+ }
+
+ if (m_hExitedEvent)
+ CloseHandle(m_hExitedEvent);
+
+ if (m_hRuntimeStartedEvent)
+ CloseHandle(m_hRuntimeStartedEvent);
+}
+
+// Deallocate all resources associated with a process list.
+void DbgTransportTarget::DeallocateProcessList(ProcessEntry *pProcessList)
+{
+ while (pProcessList)
+ {
+ ProcessEntry *pDelProcess = pProcessList;
+ pProcessList = pProcessList->m_pNext;
+ delete pDelProcess;
+ }
+}
+
+// Format and send a request to the proxy then wait on a reply (if appropriate) and return results to the
+// caller.
+HRESULT DbgTransportTarget::MakeProxyRequest(DbgProxyMessageType eType, ...)
+{
+ va_list args;
+
+ va_start(args, eType);
+
+ // We allocate space for some request related context on the stack. For the duration of the request (until
+ // a reply is received) this context is linked on a global request queue so the process event thread can
+ // handle matching replies to requests.
+ Request sRequest;
+ sRequest.m_pNext = NULL;
+ sRequest.m_hrResult = S_OK;
+
+ // DPMT_TerminateProcess requests don't expect a reply.
+ if (eType != DPMT_TerminateProcess)
+ {
+ sRequest.m_hCompletionEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, not signalled
+ if (sRequest.m_hCompletionEvent == NULL)
+ return E_OUTOFMEMORY;
+ }
+ else
+ sRequest.m_hCompletionEvent = NULL;
+
+ // Space for the message header.
+ DbgProxyMessageHeader sMessage;
+
+ // Format common message fields.
+ memset(&sMessage, 0, sizeof(sMessage));
+ sMessage.m_eType = eType;
+
+ // Based on request type fill in the remainder of the request fields and record the addresses of the
+ // caller's output buffer(s) if any.
+ BYTE *pbAttributeBlock = NULL;
+ DWORD cbAttributeBlock = 0;
+ switch (eType)
+ {
+ case DPMT_GetSystemInfo:
+ DbgTransportLog(LC_Always, "Sending 'GetSystemInfo'");
+ sMessage.VariantData.GetSystemInfo.m_uiMajorVersion = kCurrentMajorVersion;
+ sMessage.VariantData.GetSystemInfo.m_uiMinorVersion = kCurrentMinorVersion;
+ sRequest.OutputBuffers.GetSystemInfo.m_pdwMajorVersion = va_arg(args, DWORD*);
+ sRequest.OutputBuffers.GetSystemInfo.m_pdwMinorVersion = va_arg(args, DWORD*);
+ sRequest.OutputBuffers.GetSystemInfo.m_pePlatform = va_arg(args, DbgTargetPlatform*);
+ break;
+
+ case DPMT_EnumProcesses:
+ DbgTransportLog(LC_Always, "Sending 'EnumProcesses'");
+ break;
+
+ case DPMT_LaunchProcess:
+ {
+ DbgTransportLog(LC_Always, "Sending 'LaunchProcess'");
+ pbAttributeBlock = va_arg(args, BYTE*);
+ DbgAttributeBlockReader sAttrReader((char*)pbAttributeBlock);
+ cbAttributeBlock = sAttrReader.GetBlockSize();
+ sMessage.VariantData.LaunchProcess.m_cbAttributeBlock = cbAttributeBlock;
+ sRequest.OutputBuffers.LaunchProcess.m_pdwPID = va_arg(args, DWORD*);
+ sRequest.OutputBuffers.LaunchProcess.m_ppruidProcess = va_arg(args, PRUID*);
+ break;
+ }
+
+ case DPMT_EarlyAttach:
+ DbgTransportLog(LC_Always, "Sending 'EarlyAttach'");
+ sMessage.VariantData.EarlyAttach.m_pruidProcess = va_arg(args, DWORD);
+ sRequest.OutputBuffers.EarlyAttach.m_pfProcessExited = va_arg(args, bool*);
+ break;
+
+ case DPMT_TerminateProcess:
+ DbgTransportLog(LC_Always, "Sending 'TerminateProcess'");
+ sMessage.VariantData.TerminateProcess.m_pruidProcess = va_arg(args, DWORD);
+ break;
+
+ default:
+ _ASSERTE(!"Illegal message type for MakeProxyRequest");
+ return E_FAIL;
+ }
+
+ // We must hold the lock in order to send messages, allocate request IDs or touch the request queue.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // While under the lock we can check the connection state without races. We either see the connection
+ // state is bad and abort the operation now or we successfully queue the request (in which case it is
+ // the process event thread's responsibility to abort the request if an error occurs).
+ if (m_fProxyConnectionBad)
+ {
+ if (sRequest.m_hCompletionEvent)
+ CloseHandle(sRequest.m_hCompletionEvent);
+ return E_ABORT;
+ }
+
+ // Allocate a request ID and add the request to the queue (except for messages that don't expect a
+ // reply).
+ if (sRequest.m_hCompletionEvent != NULL)
+ {
+ // Allocate a unique ID for this request. This will allow us to match the reply that comes back.
+ sRequest.m_dwID = sMessage.m_uiRequestID = m_dwNextRequestID++;
+
+ // The request queue is not ordered, so just place the new request at the head.
+ sRequest.m_pNext = m_pRequestList;
+ m_pRequestList = &sRequest;
+ }
+ else
+ sMessage.m_uiRequestID = 0;
+
+ // Now the type and request ID have been filled in we can calculate the value of the magic field used
+ // as an extra layer of validation in the message format.
+ sMessage.m_uiMagic = DBGPROXY_MAGIC_VALUE(&sMessage);
+
+ // Send the message header.
+ if (!m_pConnection->Send((unsigned char*)&sMessage, sizeof(sMessage)))
+ {
+ DbgTransportLog(LC_Always, "DbgTransportTarget::MakeProxyRequest(): Send() failed");
+ if (sRequest.m_hCompletionEvent)
+ {
+ m_pRequestList = sRequest.m_pNext;
+ CloseHandle(sRequest.m_hCompletionEvent);
+ }
+
+ return E_ABORT;
+ }
+
+ // Launch requests have additional data (an attribute block).
+ if (eType == DPMT_LaunchProcess)
+ {
+ _ASSERTE(pbAttributeBlock && cbAttributeBlock);
+
+ if (!m_pConnection->Send(pbAttributeBlock, cbAttributeBlock))
+ {
+ DbgTransportLog(LC_Always, "DbgTransportTarget::MakeProxyRequest(): Send() failed");
+
+ m_pRequestList = sRequest.m_pNext;
+ CloseHandle(sRequest.m_hCompletionEvent);
+
+ return E_ABORT;
+ }
+ }
+
+ } // Leave lock
+
+ // We're done if we don't expect a reply.
+ if (sRequest.m_hCompletionEvent == NULL)
+ return S_OK;
+
+ // Now wait on the completion event (this will be signalled by the process event thread once it has
+ // matched a reply to our request successfully).
+ WaitForSingleObject(sRequest.m_hCompletionEvent, INFINITE);
+
+ // No more need for the completionm event.
+ CloseHandle(sRequest.m_hCompletionEvent);
+
+ // Return the completion result from the transmission record.
+ return sRequest.m_hrResult;
+}
+
+// Static entry point for the process event thread.
+DWORD WINAPI DbgTransportTarget::ProcessEventWorkerStatic(LPVOID lpvContext)
+{
+ // Just dispatch straight to the version that's an instance method.
+ ((DbgTransportTarget*)lpvContext)->ProcessEventWorker();
+ return 0;
+}
+
+// Instance method version of the worker, called from ProcessEventWorkerStatic().
+void DbgTransportTarget::ProcessEventWorker()
+{
+ // Loop handling requests until we're told to shutdown or hit a network error.
+ while (!m_fShutdown)
+ {
+ // How the worker reacts to the incoming data is driven by shared state set up by other threads in
+ // this process calling MakeProxyRequest(). These calls cause a message to be sent directly to the
+ // proxy but also set up state so that this thread will know how to dispatch replies from the proxy
+ // back to the originating thread.
+
+ // There are three sizes of message that we can receive: most messages will fit in the common message
+ // header but DPMT_RuntimeStarted needs something a little larger (an additional DbgProxyRuntimeInfo
+ // structure) and DPMT_ProcessList includes an variable sized list of process and runtime records.
+ // Allocate storage for a common header on the stack and always receive the header into this. We'll
+ // handle any extra data required on a case by case basis (which is easy since there's no requirement
+ // to actually assemble the incoming message into a single contiguous buffer at any point).
+ DbgProxyMessageHeader sMessage;
+
+ if (!m_pConnection->Receive((unsigned char *)&sMessage, sizeof(sMessage)))
+ {
+ DbgTransportLog(LC_Always, "DbgTransportTarget: Receive() failed");
+ goto NetworkError;
+ }
+
+ // Validate the magic number in the header that we use as an additional check of the messages's
+ // integrity (this makes it much harder to launch a network attack based on sending random data).
+ if (sMessage.m_uiMagic != DBGPROXY_MAGIC_VALUE(&sMessage))
+ {
+ DbgTransportLog(LC_Always, "DbgTransportTarget: message failed magic number test");
+ goto NetworkError;
+ }
+
+ // Most of the incoming messages are replies to requests that we have on our request queue. Locate the
+ // original request (keyed off the ID returned in the reply).
+ Request *pRequest = NULL;
+ if (sMessage.m_eType != DPMT_RuntimeStarted &&
+ sMessage.m_eType != DPMT_ProcessTerminated)
+ {
+ pRequest = LocateOriginalRequest(&sMessage);
+ if (pRequest == NULL)
+ {
+ DbgTransportLog(LC_Always, "DbgTransportTarget: can't find request for reply %u",
+ (unsigned)sMessage.m_uiRequestID);
+ goto NetworkError;
+ }
+ }
+
+ // Process the rest of the message based on the type.
+ switch (sMessage.m_eType)
+ {
+ case DPMT_SystemInfo:
+ DbgTransportLog(LC_Always, "Received 'SystemInfo'");
+ PREFIX_ASSUME(pRequest != NULL);
+
+ *pRequest->OutputBuffers.GetSystemInfo.m_pdwMajorVersion =
+ sMessage.VariantData.SystemInfo.m_uiMajorVersion;
+ *pRequest->OutputBuffers.GetSystemInfo.m_pdwMinorVersion =
+ sMessage.VariantData.SystemInfo.m_uiMinorVersion;
+ *pRequest->OutputBuffers.GetSystemInfo.m_pePlatform =
+ sMessage.VariantData.SystemInfo.m_ePlatform;
+ break;
+
+ case DPMT_ProcessList:
+ DbgTransportLog(LC_Always, "Received 'ProcessList(%u, %u)'",
+ (unsigned)sMessage.VariantData.ProcessList.m_uiProcessRecords,
+ (unsigned)sMessage.VariantData.ProcessList.m_uiRuntimeRecords);
+ PREFIX_ASSUME(pRequest != NULL);
+
+ ProcessProcessList(&sMessage, pRequest);
+ if (FAILED(pRequest->m_hrResult))
+ goto NetworkError;
+ break;
+
+ case DPMT_ProcessLaunched:
+ DbgTransportLog(LC_Always, "Received 'ProcessLaunched'");
+ PREFIX_ASSUME(pRequest != NULL);
+
+ // We successfully launched a process remotely (or an error code indicating why we could not).
+ // On success copy PID and PRUID of the new process back to the requester.
+ switch (sMessage.VariantData.ProcessLaunched.m_eResult)
+ {
+ case DPLR_Success:
+ pRequest->m_hrResult = S_OK;
+ *pRequest->OutputBuffers.LaunchProcess.m_pdwPID =
+ sMessage.VariantData.ProcessLaunched.m_uiPID;
+ *pRequest->OutputBuffers.LaunchProcess.m_ppruidProcess =
+ sMessage.VariantData.ProcessLaunched.m_pruidProcess;
+ break;
+ case DPLR_OutOfMemory:
+ pRequest->m_hrResult = E_OUTOFMEMORY;
+ break;
+ case DPLR_Denied:
+ pRequest->m_hrResult = E_ACCESSDENIED;
+ break;
+ case DPLR_NotFound:
+ pRequest->m_hrResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+ break;
+ case DPLR_UnspecifiedError:
+ pRequest->m_hrResult = E_FAIL;
+ break;
+ default:
+ _ASSERTE(!"Unknown ProcessLaunched result code");
+ pRequest->m_hrResult = E_FAIL;
+ }
+ break;
+
+ case DPMT_RuntimeStarted:
+ {
+ DbgTransportLog(LC_Always, "Received 'RuntimeStarted'");
+
+ // RuntimeStarted sends a process info block as well as runtime info (in case the process is new
+ // as well).
+
+ // Read DbgProxyProcessInfo structure.
+ DbgProxyProcessInfo sProcessInfo;
+ if (!m_pConnection->Receive((unsigned char *)&sProcessInfo, sizeof(sProcessInfo)))
+ {
+ DbgTransportLog(LC_Always, "DbgTransportTarget: Receive() failed");
+ goto NetworkError;
+ }
+
+ // Read DbgProxyRuntimeInfo structure.
+ DbgProxyRuntimeInfo sRuntimeInfo;
+ if (!m_pConnection->Receive((unsigned char *)&sRuntimeInfo, sizeof(sRuntimeInfo)))
+ {
+ DbgTransportLog(LC_Always, "DbgTransportTarget: Receive() failed");
+ goto NetworkError;
+ }
+
+ // A runtime has started up in some process on the target machine.
+ // Add a runtime record to our database (if it's not already there).
+ if (!ProcessRuntimeStarted(&sProcessInfo, &sRuntimeInfo))
+ goto NetworkError;
+ break;
+ }
+
+ case DPMT_ProcessTerminated:
+ {
+ DbgTransportLog(LC_Always, "Received 'ProcessTerminated'");
+
+ // A process has terminated on the target machine.
+ // See if we were tracking the process on our side and if so either delete the entry (if it's
+ // not being used) or fire the process termination event.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ ProcessEntry *pProcess = m_pProcessList;
+ ProcessEntry *pLastProcess = NULL;
+ while (pProcess)
+ {
+ if (pProcess->m_pruidProcess == sMessage.VariantData.ProcessTerminated.m_pruidProcess)
+ {
+ // Found a matching entry. Is it in use?
+ if (pProcess->m_cProcessRef > 0)
+ {
+ // Can't delete the entry. Signal the process exited event (which will move some
+ // users off).
+ SetEvent(pProcess->m_hExitedEvent);
+ pProcess->m_fExited = true;
+ }
+ else
+ {
+ // Nobody's using this process entry, we can unlink and deallocate it.
+ if (pLastProcess)
+ pLastProcess->m_pNext = pProcess->m_pNext;
+ else
+ m_pProcessList = pProcess->m_pNext;
+ delete pProcess;
+ }
+ break;
+ }
+ pLastProcess = pProcess;
+ pProcess = pProcess->m_pNext;
+ }
+ } // Leave lock
+ break;
+ }
+
+ case DPMT_EarlyAttachDone:
+ DbgTransportLog(LC_Always, "Received 'EarlyAttachDone(%s)'",
+ sMessage.VariantData.EarlyAttachDone.m_fProcessExited ? "dead" : "alive");
+ PREFIX_ASSUME(pRequest != NULL);
+
+ // The only thing we need to do here is pass back the indication of whether the process
+ // managed to exit before the attach was registered.
+ *pRequest->OutputBuffers.EarlyAttach.m_pfProcessExited =
+ sMessage.VariantData.EarlyAttachDone.m_fProcessExited;
+ break;
+
+ default:
+ _ASSERTE(!"Invalid message typr");
+ }
+
+ // If this was a reply then the original request has been updated at this point. We only need to
+ // remove it from the global queue and signal the completion event to unblock the requesting thread.
+ if (pRequest)
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // Look for the previous item in the queue.
+ Request *pSearchRequest = m_pRequestList;
+ while (pSearchRequest)
+ {
+ if (pSearchRequest->m_pNext == pRequest)
+ {
+ pSearchRequest->m_pNext = pRequest->m_pNext;
+ break;
+ }
+ pSearchRequest = pSearchRequest->m_pNext;
+ }
+
+ // No match, maybe the transmission was at the head of the queue.
+ if (pSearchRequest == NULL)
+ {
+ _ASSERTE(m_pRequestList == pRequest);
+ m_pRequestList = pRequest->m_pNext;
+ }
+
+ // Now complete the request to the caller.
+ SetEvent(pRequest->m_hCompletionEvent);
+ } // Leave lock
+
+ // Loop round for the next message.
+ }
+
+ NetworkError:
+
+ // We get here if we were asked to shutdown or hit a network error.
+ m_fProxyConnectionBad = true;
+
+ // Abort any outstanding requests.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ Request *pRequest = m_pRequestList;
+ while (pRequest)
+ {
+ Request *pAbortRequest = pRequest;
+ pRequest = pRequest->m_pNext;
+
+ pAbortRequest->m_hrResult = E_ABORT;
+ SetEvent(pAbortRequest->m_hCompletionEvent);
+ }
+ } // Leave lock
+
+ // If this isn't shutdown (i.e. we got here as the result of a network error) run through the process list
+ // and report them all as terminated (better than having the debugger time-out some request and then hang
+ // forever as it tries to terminate the process itself).
+ if (!m_fShutdown)
+ {
+ for (ProcessEntry *pProcess = m_pProcessList; pProcess; pProcess = pProcess->m_pNext)
+ {
+ if (!pProcess->m_fExited)
+ {
+ pProcess->m_fExited = true;
+ SetEvent(pProcess->m_hExitedEvent);
+ }
+ }
+ }
+}
+
+// If this message is a reply to a right-side request locate that request. Otherwise return NULL.
+DbgTransportTarget::Request *DbgTransportTarget::LocateOriginalRequest(DbgProxyMessageHeader *pMessage)
+{
+ // Search the request queue for a matching ID.
+ Request *pRequest;
+ {
+ RSLockHolder lock(&m_sLock);
+
+ pRequest = m_pRequestList;
+ while (pRequest)
+ {
+ if (pRequest->m_dwID == pMessage->m_uiRequestID)
+ break;
+ pRequest = pRequest->m_pNext;
+ }
+ } // Leave lock
+
+ return pRequest;
+}
+
+// Process an incoming ProcessList message.
+void DbgTransportTarget::ProcessProcessList(DbgProxyMessageHeader *pMessage,
+ Request *pRequest)
+{
+ // The message header is followed by a sequence of DbgProxyProcessInfo records then a sequence of
+ // DbgProxyRuntimeInfo records. The lengths of both of these sequences are provided in the header.
+ DWORD cProcessRecords = pMessage->VariantData.ProcessList.m_uiProcessRecords;
+ DWORD cRuntimeRecords = pMessage->VariantData.ProcessList.m_uiRuntimeRecords;
+
+ // Impose some reasonable bounds to catch badly formed replies (and so we don't have to worry about
+ // integer overflow in the allocation code that follows).
+ if (cProcessRecords > 1024 || cRuntimeRecords > 1024)
+ {
+ _ASSERTE(!"Badly formed ProcessList message");
+ pRequest->m_hrResult = E_UNEXPECTED;
+ return;
+ }
+
+ // Allocate space for all the process and runtime records in one contiguous block.
+ DWORD cbRecordBuffer = (cProcessRecords * sizeof(DbgProxyProcessInfo)) +
+ (cRuntimeRecords * sizeof(DbgProxyRuntimeInfo));
+ BYTE *pbRecordBuffer = new (nothrow) BYTE[cbRecordBuffer];
+ if (pbRecordBuffer == NULL)
+ {
+ DbgTransportLog(LC_Always, "Failed to allocate memory for %u process records and %u runtime records",
+ cProcessRecords, cRuntimeRecords);
+ pRequest->m_hrResult = E_OUTOFMEMORY;
+ return;
+ }
+
+ DbgProxyProcessInfo *pProcessRecords = (DbgProxyProcessInfo*)pbRecordBuffer;
+ DbgProxyRuntimeInfo *pRuntimeRecords = (DbgProxyRuntimeInfo*)(pProcessRecords + cProcessRecords);
+
+ // If we've gotten to this point we believe the message looks valid and we have all resources
+ // allocated necessary to receive the response for this portion of the reply.
+ if (!m_pConnection->Receive(pbRecordBuffer, cbRecordBuffer))
+ {
+ DbgTransportLog(LC_Always, "ProcessProcessList: Receive() failed");
+ pRequest->m_hrResult = E_FAIL;
+ return;
+ }
+
+ // Now parse the records and make any necessary updates to the cached process/runtime state we already
+ // have.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // First we walk the current list of processes and mark each entry as potentially removeable. This is
+ // part of the algorithm to detect processes which have terminated, see the next stage for the rest.
+ ProcessEntry *pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ pProcess->m_fRemove = true;
+ pProcess = pProcess->m_pNext;
+ }
+
+ // Next we traverse the incoming list of process records, determining which we already know about
+ // (and marking the corresponding ProcessEntry) and those that are new (for which we create new
+ // ProcessEntry structures).
+ for (DWORD i = 0; i < cProcessRecords; i++)
+ {
+ // See if we can locate an existing ProcessEntry (i.e. one with the same PRUID as the current
+ // process record).
+ pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ if (pProcess->m_pruidProcess == pProcessRecords[i].m_pruidProcess)
+ {
+ _ASSERTE(pProcess->m_dwPID == pProcessRecords[i].m_uiPID);
+
+ // We've found a match so we indicate that this ProcessEntry is still live.
+ pProcess->m_fRemove = false;
+ break;
+ }
+
+ pProcess = pProcess->m_pNext;
+ }
+
+ // If we didn't find a matching ProcessEntry create one now.
+ if (pProcess == NULL)
+ {
+ pProcess = new (nothrow) ProcessEntry();
+ if (pProcess == NULL)
+ goto FailedUpdate;
+
+ pProcess->m_pNext = m_pProcessList;
+ pProcess->m_dwPID = pProcessRecords[i].m_uiPID;
+ pProcess->m_pruidProcess = pProcessRecords[i].m_pruidProcess;
+ strcpy_s(pProcess->m_szCommandLine, kMaxCommandLine, pProcessRecords[i].m_szCommandLine);
+ pProcess->m_pRuntimes = NULL;
+ pProcess->m_hExitedEvent = NULL;
+ pProcess->m_hRuntimeStartedEvent = NULL;
+ pProcess->m_fExited = false;
+ pProcess->m_fRemove = false;
+ pProcess->m_cProcessRef = 0;
+
+ // Allocate event used to signal this process' termination.
+ pProcess->m_hExitedEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (pProcess->m_hExitedEvent == NULL)
+ {
+ delete pProcess;
+ goto FailedUpdate;
+ }
+
+ // Allocate event used to signal the first runtime has started within this process.
+ pProcess->m_hRuntimeStartedEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (pProcess->m_hRuntimeStartedEvent == NULL)
+ {
+ delete pProcess;
+ goto FailedUpdate;
+ }
+
+ // Link the new process entry to the database.
+ m_pProcessList = pProcess;
+ }
+ }
+
+ // Now walk the updated ProcessEntry structures. Each that wasn't marked as being present in the
+ // incoming process infos indicates a dead process. We either remove such entries or, if they're being
+ // used currently (have a connected debugger session etc.), we simply mark them as dead so they won't
+ // appear in process enumerations and we'll delete them as soon as their use count falls to zero.
+ pProcess = m_pProcessList;
+ ProcessEntry *pLastProcess = NULL;
+ while (pProcess)
+ {
+ if (pProcess->m_fRemove)
+ {
+ // The process has terminated. Can we release the ProcessEntry yet?
+ if (pProcess->m_cProcessRef == 0)
+ {
+ // Nobody is using the process, we can get rid of it.
+
+ // Unlink the entry from the list.
+ if (pLastProcess)
+ pLastProcess->m_pNext = pProcess->m_pNext;
+ else
+ m_pProcessList = pProcess->m_pNext;
+
+ // Since we've removed this entry from the list the last entry remains the same for the
+ // next iteration of the loop. We have to extract the next entry from the current one
+ // before we deallocate it.
+ ProcessEntry *pDeleteEntry = pProcess;
+ pProcess = pProcess->m_pNext;
+
+ // Finally we can delete the current entry (this automatically takes care of any runtime
+ // entries and other process entry owned resources).
+ delete pDeleteEntry;
+
+ continue;
+ }
+ else
+ {
+ // Process is in use. Simply mark it as exited for now.
+ pProcess->m_fExited = true;
+ SetEvent(pProcess->m_hExitedEvent);
+ }
+ }
+
+ pLastProcess = pProcess;
+ pProcess = pProcess->m_pNext;
+ }
+
+ // Next we walk the incoming runtime records. Here we're just looking for new runtimes to add since we
+ // don't currently support shutting down a runtime without terminating the process.
+ for (DWORD i = 0; i < cRuntimeRecords; i++)
+ {
+ // First find the parent ProcessEntry record. This must exist (if the proxy sent a runtime record
+ // it must send the parent process record).
+ pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ if (pProcess->m_pruidProcess == pRuntimeRecords[i].m_pruidProcess)
+ {
+ pProcess->m_fRemove = false;
+ break;
+ }
+
+ pProcess = pProcess->m_pNext;
+ }
+ PREFIX_ASSUME(pProcess != NULL);
+
+ // Walk the list of RuntimeEntry records associated with this ProcessEntry to see if we already
+ // have this entry.
+ RuntimeEntry *pRuntime = pProcess->m_pRuntimes;
+ while (pRuntime)
+ {
+ if (pRuntime->m_pruidRuntime == pRuntimeRecords[i].m_pruidRuntime)
+ {
+ // We already have this entry.
+ _ASSERTE(pRuntime->m_usPort == pRuntimeRecords[i].m_usPort);
+ break;
+ }
+
+ pRuntime = pRuntime->m_pNext;
+ }
+
+ // If this is a new runtime add a corresponding RuntimeEntry.
+ if (pRuntime == NULL)
+ {
+ pRuntime = new (nothrow) RuntimeEntry();
+ if (pRuntime == NULL)
+ goto FailedUpdate;
+
+ pRuntime->m_pruidRuntime = pRuntimeRecords[i].m_pruidRuntime;
+ pRuntime->m_usPort = pRuntimeRecords[i].m_usPort;
+ pRuntime->m_pDbgTransport = NULL;
+ pRuntime->m_cTransportRef = 0;
+
+ // Link the runtime entry onto the process.
+ pRuntime->m_pNext = pProcess->m_pRuntimes;
+ pProcess->m_pRuntimes = pRuntime;
+
+ // Since there's at least one runtime for this process make sure the runtime started event is set.
+ SetEvent(pProcess->m_hRuntimeStartedEvent);
+ }
+ }
+ }
+
+ // We're done, all incoming data has been consumed.
+ delete [] pbRecordBuffer;
+ return;
+
+ FailedUpdate:
+ pRequest->m_hrResult = E_OUTOFMEMORY;
+ delete [] pbRecordBuffer;
+}
+
+// Process an incoming RuntimeStarted datagram.
+bool DbgTransportTarget::ProcessRuntimeStarted(DbgProxyProcessInfo *pProcessInfo,
+ DbgProxyRuntimeInfo *pRuntimeInfo)
+{
+ // A runtime has started up in some process on the target machine.
+ // Add a runtime record to our database (if it's not already there).
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // Search for the parent process in our list.
+ ProcessEntry *pProcess = m_pProcessList;
+ while (pProcess)
+ {
+ if (pProcess->m_pruidProcess == pRuntimeInfo->m_pruidProcess)
+ break;
+ pProcess = pProcess->m_pNext;
+ }
+
+ DbgTransportLog(LC_Always, "Processing 'RuntimeStarted' for (%u, %u, %u)",
+ (unsigned)pProcessInfo->m_uiPID,
+ (PRUID)pRuntimeInfo->m_pruidProcess,
+ (PRUID)pRuntimeInfo->m_pruidRuntime);
+
+ // If we haven't recorded the process yet create an entry for it.
+ if (pProcess == NULL)
+ {
+ pProcess = new (nothrow) ProcessEntry();
+ if (pProcess == NULL)
+ return false;
+
+ DbgTransportLog(LC_Always, " No existing process record, created %08X", pProcess);
+
+ memset(pProcess, 0, sizeof(ProcessEntry));
+ pProcess->m_dwPID = pProcessInfo->m_uiPID;
+ pProcess->m_pruidProcess = pProcessInfo->m_pruidProcess;
+ strcpy(pProcess->m_szCommandLine, pProcessInfo->m_szCommandLine);
+
+ pProcess->m_hExitedEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (pProcess->m_hExitedEvent == NULL)
+ {
+ delete pProcess;
+ return false;
+ }
+
+ pProcess->m_hRuntimeStartedEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (pProcess->m_hRuntimeStartedEvent == NULL)
+ {
+ delete pProcess;
+ return false;
+ }
+
+ // Link the new process entry into the list.
+ pProcess->m_pNext = m_pProcessList;
+ m_pProcessList = pProcess;
+ }
+ else
+ DbgTransportLog(LC_Always, " Found existing process record %08X", pProcess);
+
+ // Now we have a process record we can look for a runtime entry.
+ RuntimeEntry *pRuntime = pProcess->m_pRuntimes;
+ while (pRuntime)
+ {
+ if (pRuntime->m_pruidRuntime == pRuntimeInfo->m_pruidRuntime)
+ break;
+ pRuntime = pRuntime->m_pNext;
+ }
+
+ // If we didn't have an entry for this runtime create one now.
+ if (pRuntime == NULL)
+ {
+ pRuntime = new (nothrow) RuntimeEntry();
+ if (pRuntime == NULL)
+ return false;
+
+ DbgTransportLog(LC_Always, " No existing runtime record, created %08X", pRuntime);
+
+ pRuntime->m_pruidRuntime = pRuntimeInfo->m_pruidRuntime;
+ pRuntime->m_usPort = pRuntimeInfo->m_usPort;
+ pRuntime->m_pDbgTransport = NULL;
+ pRuntime->m_cTransportRef = 0;
+
+ // Link the runtime entry onto the process.
+ pRuntime->m_pNext = pProcess->m_pRuntimes;
+ pProcess->m_pRuntimes = pRuntime;
+
+ // Since there's at least one runtime for this process make sure the runtime started event is set.
+ SetEvent(pProcess->m_hRuntimeStartedEvent);
+ }
+ else
+ DbgTransportLog(LC_Always, " Found existing runtime record %08X", pRuntime);
+ } // Leave lock
+
+ return true;
+}
+
+// A version of EnumProcesses used when we're controlled by the Visual Studio debugger. This API is exposed to
+// our port supplier implementation via the ICoreClrDebugTarget interface implemented by the
+// CoreClrDebugTarget class implemented in DbgTransportManager.cpp.
+HRESULT DbgTransportTarget::EnumProcessesForVS(DWORD *pcProcs, CoreClrDebugProcInfo **ppProcs)
+{
+ *pcProcs = 0;
+ *ppProcs = NULL;
+
+ // Update our process and runtime view from the remote target.
+ UpdateProcessList();
+
+ // Acquire the lock to get a consistent view of current state.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ ProcessEntry *pProcess;
+ DWORD cProcesses = 0;
+
+ // Count the number of processes we know about.
+ for (pProcess = m_pProcessList; pProcess; pProcess = pProcess->m_pNext)
+ {
+ // Skip reporting of processes that have already exited (and we're waiting to clean up).
+ if (pProcess->m_fExited)
+ continue;
+ cProcesses++;
+ }
+
+ // We're done if there aren't any.
+ if (cProcesses == 0)
+ return S_OK;
+
+ // Otherwise allocate an array large enough to hold information about each process.
+ CoreClrDebugProcInfo *pProcInfos = new (nothrow) CoreClrDebugProcInfo[cProcesses];
+ if (pProcInfos == NULL)
+ return E_OUTOFMEMORY;
+
+ // Iterate over the processes again, this time filling in the data for each one in the output array.
+ DWORD idxCurrentProc = 0;
+ for (pProcess = m_pProcessList; pProcess; pProcess = pProcess->m_pNext)
+ {
+ // Skip reporting of processes that have already exited (and we're waiting to clean up).
+ if (pProcess->m_fExited)
+ continue;
+
+ pProcInfos[idxCurrentProc].m_dwPID = pProcess->m_dwPID;
+ pProcInfos[idxCurrentProc].m_dwInternalID = pProcess->m_pruidProcess;
+
+ if (MultiByteToWideChar(CP_UTF8, 0, pProcess->m_szCommandLine, -1, pProcInfos[idxCurrentProc].m_wszName, kMaxCommandLine) == 0)
+ {
+ delete [] pProcInfos;
+ return E_OUTOFMEMORY;
+ }
+
+ idxCurrentProc++;
+ }
+
+ *pcProcs = cProcesses;
+ *ppProcs = pProcInfos;
+ }
+
+ return S_OK;
+}
+
+// A similar API for VS that enumerates runtimes running within the given process. Returns S_FALSE if
+// there are none.
+HRESULT DbgTransportTarget::EnumRuntimesForVS(PRUID pruidProcess, DWORD *pcRuntimes, CoreClrDebugRuntimeInfo **ppRuntimes)
+{
+ *pcRuntimes = 0;
+ *ppRuntimes = NULL;
+
+ // Update our process and runtime view from the remote target.
+ UpdateProcessList();
+
+ // Acquire the lock to get a consistent view of current state.
+ {
+ RSLockHolder lock(&m_sLock);
+
+ // Look for the process record with the matching PRUID.
+ ProcessEntry *pProcess;
+ for (pProcess = m_pProcessList; pProcess; pProcess = pProcess->m_pNext)
+ {
+ if (pProcess->m_fExited)
+ continue;
+ if (pProcess->m_pruidProcess == pruidProcess)
+ break;
+ }
+
+ // We couldn't find a match -- the process must have terminated so it certainly doesn't have any
+ // runtimes.
+ if (pProcess == NULL)
+ return S_FALSE;
+
+ // Count the runtime instances running in the process.
+ DWORD cRuntimes = 0;
+ RuntimeEntry *pRuntime;
+ for (pRuntime = pProcess->m_pRuntimes; pRuntime; pRuntime = pRuntime->m_pNext)
+ cRuntimes++;
+
+ // We're done if there aren't any.
+ if (cRuntimes == 0)
+ return S_OK;
+
+ // Otherwise allocate an array large enough to hold information about each runtime.
+ CoreClrDebugRuntimeInfo *pRuntimeInfos = new (nothrow) CoreClrDebugRuntimeInfo[cRuntimes];
+ if (pRuntimeInfos == NULL)
+ return E_OUTOFMEMORY;
+
+ // Iterate over the runtimes again, this time filling in data for each one in the output array.
+ DWORD idxCurrentRuntime = 0;
+ for (pRuntime = pProcess->m_pRuntimes; pRuntime; pRuntime = pRuntime->m_pNext)
+ {
+ pRuntimeInfos[idxCurrentRuntime].m_dwInternalID = pRuntime->m_pruidRuntime;
+ idxCurrentRuntime++;
+ }
+
+ *pcRuntimes = cRuntimes;
+ *ppRuntimes = pRuntimeInfos;
+ }
+
+ return S_OK;
+}
+
+
+// When we're being driven by the Visual Studio debugger CoreCLR supplies an entity known as a port supplier
+// to handle interactions between VS and the remote system when setting up debug sessions. The port supplier
+// implements the connection to the remote proxy by talking to DbgTransportTarget instances controlled via the
+// following class through a psuedo-COM interface, ICoreClrDebugTarget, described in
+// debug\inc\CoreClrRemoteDebuggingInterfaces.h.
+class CoreClrDebugTarget : public ICoreClrDebugTarget
+{
+public:
+ CoreClrDebugTarget(DWORD dwAddress) :
+ m_lRefCount(1),
+ m_dwAddress(dwAddress),
+ m_pTarget(NULL)
+ {
+ }
+
+ ~CoreClrDebugTarget()
+ {
+ if (m_pTarget)
+ g_pDbgTransportManager->ReleaseTarget(m_pTarget);
+ }
+
+ STDMETHODIMP_(void) AddRef()
+ {
+ InterlockedIncrement(&m_lRefCount);
+ }
+
+ STDMETHODIMP_(void) Release()
+ {
+ LONG lRef = InterlockedDecrement(&m_lRefCount);
+ if (lRef == 0)
+ delete this;
+ }
+
+ // Enumerate all processes on the target system (whether they have managed code or not). The memory
+ // returned is deallocated via FreeMemory().
+ STDMETHODIMP EnumProcesses(DWORD *pcProcs, CoreClrDebugProcInfo **ppProcs)
+ {
+ return m_pTarget->EnumProcessesForVS(pcProcs, ppProcs);
+ }
+
+ // Enumerate all runtimes running in the given process on the target system. The memory returned is
+ // deallocated via FreeMemory().
+ STDMETHODIMP EnumRuntimes(DWORD dwInternalProcessID, DWORD *pcRuntimes, CoreClrDebugRuntimeInfo **ppRuntimes)
+ {
+ return m_pTarget->EnumRuntimesForVS((PRUID)dwInternalProcessID, pcRuntimes, ppRuntimes);
+ }
+
+ // Free memory allocated via EnumProcesses or EnumRuntimes.
+ STDMETHODIMP_(void) FreeMemory(void *pMemory)
+ {
+ delete [] (BYTE*)pMemory;
+ }
+
+ // Non-exported method used by CreateCoreClrDebugTarget below to connect an instance of DbgTransportTarget
+ // to the remote system's proxy process.
+ HRESULT Init()
+ {
+ HRESULT hr = g_pDbgTransportManager->ConnectToTarget(m_dwAddress, 0, &m_pTarget);
+ if (FAILED(hr))
+ return hr;
+
+ return S_OK;
+ }
+
+private:
+ LONG m_lRefCount; // COM-style ref count
+ DWORD m_dwAddress; // IPv4 address of target system
+ DbgTransportTarget *m_pTarget; // Currently connected DbgTransportTarget
+};
+
+// Function exported by mscordbi_mac* to allow the port supplier used by Visual Studio to query remote system
+// state.
+extern "C" HRESULT __stdcall CreateCoreClrDebugTarget(DWORD dwAddress, ICoreClrDebugTarget **ppTarget)
+{
+ HRESULT hr;
+
+ *ppTarget = NULL;
+
+ // Allocate a new object implementing ICoreClrDebugTarget.
+ CoreClrDebugTarget *pTarget = new (nothrow) CoreClrDebugTarget(dwAddress);
+ if (pTarget == NULL)
+ return E_OUTOFMEMORY;
+
+ // Attempt to connect it to the remote system.
+ hr = pTarget->Init();
+ if (FAILED(hr))
+ {
+ delete pTarget;
+ return hr;
+ }
+
+ *ppTarget = static_cast<ICoreClrDebugTarget*>(pTarget);
+ return S_OK;
+}
+
+#endif // FEATURE_DBGIPC_TRANSPORT_DI