summaryrefslogtreecommitdiff
path: root/src/debug/shared/dbgtransportsession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/shared/dbgtransportsession.cpp')
-rw-r--r--src/debug/shared/dbgtransportsession.cpp2786
1 files changed, 2786 insertions, 0 deletions
diff --git a/src/debug/shared/dbgtransportsession.cpp b/src/debug/shared/dbgtransportsession.cpp
new file mode 100644
index 0000000000..14b509aa9c
--- /dev/null
+++ b/src/debug/shared/dbgtransportsession.cpp
@@ -0,0 +1,2786 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+#include "dbgtransportsession.h"
+
+#if (!defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_VM)) || (defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_DI))
+
+// This is the entry type for the IPC event queue owned by the transport.
+// Each entry contains the multiplexing type of the IPC event plus the
+// IPC event itself.
+struct DbgEventBufferEntry
+{
+public:
+ IPCEventType m_type;
+ BYTE m_event[CorDBIPC_BUFFER_SIZE]; // buffer for the IPC event
+};
+
+//
+// Provides a robust and secure transport session between a debugger and a debuggee that are potentially on
+// different machines.
+//
+// See DbgTransportSession.h for further detailed comments.
+//
+
+#ifndef RIGHT_SIDE_COMPILE
+// The one and only transport instance for the left side. Allocated and initialized during EE startup (from
+// Debugger::Startup() in debugger.cpp).
+DbgTransportSession *g_pDbgTransport = NULL;
+
+#include "ddmarshalutil.h"
+#endif // !RIGHT_SIDE_COMPILE
+
+// No real work done in the constructor. Use Init() instead.
+DbgTransportSession::DbgTransportSession()
+{
+ m_ref = 1;
+ m_eState = SS_Closed;
+}
+
+DbgTransportSession::~DbgTransportSession()
+{
+ DbgTransportLog(LC_Proxy, "DbgTransportSession::~DbgTransportSession() called");
+
+ // No other threads are now using session resources. We're free to deallocate them as we wish (if they
+ // were allocated in the first place).
+ if (m_hTransportThread)
+ CloseHandle(m_hTransportThread);
+ if (m_rghEventReadyEvent[IPCET_OldStyle])
+ CloseHandle(m_rghEventReadyEvent[IPCET_OldStyle]);
+ if (m_rghEventReadyEvent[IPCET_DebugEvent])
+ CloseHandle(m_rghEventReadyEvent[IPCET_DebugEvent]);
+ if (m_pEventBuffers)
+ delete [] m_pEventBuffers;
+
+#ifdef RIGHT_SIDE_COMPILE
+ if (m_hSessionOpenEvent)
+ CloseHandle(m_hSessionOpenEvent);
+
+ if (m_hProcessExited)
+ CloseHandle(m_hProcessExited);
+#endif // RIGHT_SIDE_COMPILE
+
+ if (m_fInitStateLock)
+ m_sStateLock.Destroy();
+}
+
+// Allocates initial resources (including starting the transport thread). The session will start in the
+// SS_Opening state. That is, the RS will immediately start trying to Connect() a connection while the LS will
+// perform an accept()/Accept() to wait for a connection request. The RS needs an IP address and port number
+// to initiate connections. These should be given in host byte order. The LS, on the other hand, requires the
+// addresses of a couple of runtime data structures to service certain debugger requests that may be delivered
+// once the session is established.
+#ifdef RIGHT_SIDE_COMPILE
+HRESULT DbgTransportSession::Init(DWORD pid, HANDLE hProcessExited)
+#else // RIGHT_SIDE_COMPILE
+HRESULT DbgTransportSession::Init(DebuggerIPCControlBlock *pDCB, AppDomainEnumerationIPCBlock *pADB)
+#endif // RIGHT_SIDE_COMPILE
+{
+ _ASSERTE(m_eState == SS_Closed);
+
+ // Start with a blank slate so that Shutdown() on a partially initialized instance will only do the
+ // cleanup necessary.
+ memset(this, 0, sizeof(*this));
+
+ // Because of the above memset the embeded classes/structs need to be reinitialized especially
+ // the two way pipe; it expects the in/out handles to be -1 instead of 0.
+ m_ref = 1;
+ m_pipe = TwoWayPipe();
+ m_sStateLock = DbgTransportLock();
+
+ // Initialize all per-session state variables.
+ InitSessionState();
+
+#ifdef RIGHT_SIDE_COMPILE
+ // The RS randomly allocates a session ID which is sent to the LS in the SessionRequest message. In the
+ // case of network errors during session formation this allows the LS to tell SessionRequest re-sends from
+ // a new request from a different RS.
+ HRESULT hr = CoCreateGuid(&m_sSessionID);
+ if (FAILED(hr))
+ return hr;
+#endif // RIGHT_SIDE_COMPILE
+
+
+#ifdef RIGHT_SIDE_COMPILE
+ m_pid = pid;
+
+ if (!DuplicateHandle(GetCurrentProcess(),
+ hProcessExited,
+ GetCurrentProcess(),
+ &m_hProcessExited,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ m_fDebuggerAttached = false;
+#else // RIGHT_SIDE_COMPILE
+ m_pDCB = pDCB;
+ m_pADB = pADB;
+#endif // RIGHT_SIDE_COMPILE
+
+ m_sStateLock.Init();
+ m_fInitStateLock = true;
+
+#ifdef RIGHT_SIDE_COMPILE
+ m_hSessionOpenEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL); // Manual reset, not signalled
+ if (m_hSessionOpenEvent == NULL)
+ return E_OUTOFMEMORY;
+#else // RIGHT_SIDE_COMPILE
+ DWORD pid = GetCurrentProcessId();
+ if (!m_pipe.CreateServer(pid)) {
+ return E_OUTOFMEMORY;
+ }
+#endif // RIGHT_SIDE_COMPILE
+
+ // Allocate some buffers to receive incoming events. The initial number is chosen arbitrarily, tune as
+ // necessary. This array will need to grow if it fills with unread events (it takes our client a little
+ // time to process each incoming receive). In general, however, one side will not send an unbounded stream
+ // of events to the other without waiting for some kind of response. More usual are small bursts of events
+ // to represent variable sized data (such as a stack trace).
+ m_cEventBuffers = 10;
+ m_pEventBuffers = (DbgEventBufferEntry *)new (nothrow) BYTE[m_cEventBuffers * sizeof(DbgEventBufferEntry)];
+ if (m_pEventBuffers == NULL)
+ return E_OUTOFMEMORY;
+
+ m_rghEventReadyEvent[IPCET_OldStyle] = WszCreateEvent(NULL, FALSE, FALSE, NULL); // Auto reset, not signalled
+ if (m_rghEventReadyEvent[IPCET_OldStyle] == NULL)
+ return E_OUTOFMEMORY;
+
+ m_rghEventReadyEvent[IPCET_DebugEvent] = WszCreateEvent(NULL, FALSE, FALSE, NULL); // Auto reset, not signalled
+ if (m_rghEventReadyEvent[IPCET_DebugEvent] == NULL)
+ return E_OUTOFMEMORY;
+
+ // Start the transport thread which handles forming and re-forming connections, driving the session
+ // state to SS_Open and receiving and initially processing all incoming traffic.
+ AddRef();
+ m_hTransportThread = CreateThread(NULL, 0, TransportWorkerStatic, this, 0, NULL);
+ if (m_hTransportThread == NULL)
+ {
+ Release();
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+// Drive the session to the SS_Closed state, which will deallocate all remaining transport resources
+// (including terminating the transport thread). If this is the RS and the session state is SS_Open at the
+// time of this call a graceful disconnect will be attempted (which tells the LS to go back to SS_Opening to
+// look for a new RS rather than interpreting the disconnection as a temporary error and going into
+// SS_Resync). On either side the session will no longer be functional after this call returns (though Init()
+// may be called again to start over from the beginning).
+void DbgTransportSession::Shutdown()
+{
+ DbgTransportLog(LC_Proxy, "DbgTransportSession::Shutdown() called");
+
+ // The transport thread is allocated last in Init() (since it uses all the other resources that Init()
+ // prepares). Don't do any transport related stuff unless this was allocated (which can happen if
+ // Shutdown() is called after an Init() failure).
+
+ if (m_hTransportThread)
+ {
+ // From SS_Open state try a graceful disconnect.
+ if (m_eState == SS_Open)
+ {
+ DbgTransportLog(LC_Session, "Sending 'SessionClose'");
+ DBG_TRANSPORT_INC_STAT(SentSessionClose);
+ Message sMessage;
+ sMessage.Init(MT_SessionClose);
+ SendMessage(&sMessage, false);
+ }
+
+ // Must take the state lock to make a state transition.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Remember previous state and transition to SS_Closed.
+ SessionState ePreviousState = m_eState;
+ m_eState = SS_Closed;
+
+ if (ePreviousState != SS_Closed)
+ {
+ m_pipe.Disconnect();
+ }
+
+ } // Leave m_sStateLock
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Signal the m_hSessionOpenEvent now to quickly error out any callers of WaitForSessionToOpen().
+ SetEvent(m_hSessionOpenEvent);
+#endif // RIGHT_SIDE_COMPILE
+ }
+
+ // The transport instance is no longer valid
+ Release();
+}
+
+#ifndef RIGHT_SIDE_COMPILE
+
+// Cleans up the named pipe connection so no tmp files are left behind. Does only
+// the minimum and must be safe to call at any time. Called during PAL ExitProcess,
+// TerminateProcess and for unhandled native exceptions and asserts.
+void DbgTransportSession::AbortConnection()
+{
+ m_pipe.Disconnect();
+}
+
+// API used only by the LS to drive the transport into a state where it won't accept connections. This is used
+// when no proxy is detected at startup but it's too late to shutdown all of the debugging system easily. It's
+// mainly paranoia to increase the protection of your system when the proxy isn't started.
+void DbgTransportSession::Neuter()
+{
+ // Simply set the session state to SS_Closed. The transport thread will switch itself off if it ever gets
+ // a connection but the rest of the transport resources remain valid (so the debugger helper thread won't
+ // AV on a deallocated handle, which might happen if we simply called Shutdown()).
+ m_eState = SS_Closed;
+}
+
+#else // RIGHT_SIDE_COMPILE
+
+// Used by debugger side (RS) to cleanup the target (LS) named pipes
+// and semaphores when the debugger detects the debuggee process exited.
+void DbgTransportSession::CleanupTargetProcess()
+{
+ m_pipe.CleanupTargetProcess();
+}
+
+// On the RS it may be useful to wait and see if the session can reach the SS_Open state. If the target
+// runtime has terminated for some reason then we'll never reach the open state. So the method below gives the
+// RS a way to try and establish a connection for a reasonable amount of time and to time out otherwise. They
+// could then call Shutdown on the session and report an error back to the rest of the debugger. The method
+// returns true if the session opened within the time given (in milliseconds) and false otherwise.
+bool DbgTransportSession::WaitForSessionToOpen(DWORD dwTimeout)
+{
+ DWORD dwRet = WaitForSingleObject(m_hSessionOpenEvent, dwTimeout);
+ if (m_eState == SS_Closed)
+ return false;
+
+ if (dwRet == WAIT_TIMEOUT)
+ DbgTransportLog(LC_Proxy, "DbgTransportSession::WaitForSessionToOpen(%u) timed out", dwTimeout);
+
+ return dwRet == WAIT_OBJECT_0;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// A valid ticket is returned if no other client is currently acting as the debugger.
+// If the caller passes in a valid ticket, this function will return true without invalidating the ticket.
+//
+// Arguments:
+// pTicket - out parameter; set to a valid ticket if the client has successfully registered as the debugger
+//
+// Return Value:
+// Return true if the client has successfully registered as the debugger.
+//
+
+bool DbgTransportSession::UseAsDebugger(DebugTicket * pTicket)
+{
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ if (m_fDebuggerAttached)
+ {
+ if (pTicket->IsValid())
+ {
+ // The client already holds a valid ticket.
+ return true;
+ }
+ else
+ {
+ // Another client of this session has already indicated that it's using this session to debug.
+ _ASSERTE(!pTicket->IsValid());
+ return false;
+ }
+ }
+ else
+ {
+ m_fDebuggerAttached = true;
+ pTicket->SetValid();
+ return true;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// A valid ticket is required in order for this function to succeed. After this function succeeds,
+// another client can request to be the debugger.
+//
+// Arguments:
+// pTicket - the client's ticket; must be valid for this function to succeed
+//
+// Return Value:
+// Return true if the client has successfully unregistered as the debugger.
+// Return false if no client is currently acting as the debugger or if the client's ticket is invalid.
+//
+
+bool DbgTransportSession::StopUsingAsDebugger(DebugTicket * pTicket)
+{
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ if (m_fDebuggerAttached && pTicket->IsValid())
+ {
+ // The caller is indeed the owner of the debug ticket.
+ m_fDebuggerAttached = false;
+ pTicket->SetInvalid();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+#endif // RIGHT_SIDE_COMPILE
+
+// Sends a pre-initialized event to the other side.
+HRESULT DbgTransportSession::SendEvent(DebuggerIPCEvent *pEvent)
+{
+ DbgTransportLog(LC_Events, "Sending '%s'", IPCENames::GetName(pEvent->type));
+ DBG_TRANSPORT_INC_STAT(SentEvent);
+
+ return SendEventWorker(pEvent, IPCET_OldStyle);
+}
+
+// Sends a pre-initialized event to the other side, but pretend that this is coming from the native pipeline.
+// See code:IPCEventType for more information.
+HRESULT DbgTransportSession::SendDebugEvent(DebuggerIPCEvent * pEvent)
+{
+ DbgTransportLog(LC_Events, "Sending '%s' as DEBUG_EVENT", IPCENames::GetName(pEvent->type));
+ DBG_TRANSPORT_INC_STAT(SentEvent);
+
+ return SendEventWorker(pEvent, IPCET_DebugEvent);
+}
+
+// Retrieves the auto-reset handle which is signalled by the session each time a new event is received from
+// the other side.
+HANDLE DbgTransportSession::GetIPCEventReadyEvent()
+{
+ return m_rghEventReadyEvent[IPCET_OldStyle];
+}
+
+// Retrieves the auto-reset handle which is signalled by the session each time a new event (disguised as a
+// debug event) is received from the other side.
+HANDLE DbgTransportSession::GetDebugEventReadyEvent()
+{
+ return m_rghEventReadyEvent[IPCET_DebugEvent];
+}
+
+// Copies the last event received from the other side into the provided buffer. This should only be called
+// (once) after the event returned from GetIPCEEventReadyEvent()/GetDebugEventReadyEvent() has been signalled.
+void DbgTransportSession::GetNextEvent(DebuggerIPCEvent *pEvent, DWORD cbEvent)
+{
+ _ASSERTE(cbEvent <= CorDBIPC_BUFFER_SIZE);
+
+ // Must acquire the state lock to synchronize us wrt to the transport thread (clients already guarantee
+ // they serialize calls to this and waiting on m_rghEventReadyEvent).
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // There must be at least one valid event waiting (this call does not block).
+ _ASSERTE(m_cValidEventBuffers);
+
+ // Copy the first valid event into the client's buffer.
+ memcpy(pEvent, &m_pEventBuffers[m_idxEventBufferHead].m_event, cbEvent);
+
+ // Move the index of the head of the valid list forward (which may in fact move it back to the start of
+ // the array since the list is circular). This reduces the number of valid entries by one. Note that these
+ // two adjustments do not affect the tail of the list in any way. In the limit case the head will end up
+ // pointing to the same event as the tail (and m_cValidEventBuffers will be zero).
+ m_idxEventBufferHead = (m_idxEventBufferHead + 1) % m_cEventBuffers;
+ m_cValidEventBuffers--;
+ _ASSERTE(((m_idxEventBufferHead + m_cValidEventBuffers) % m_cEventBuffers) == m_idxEventBufferTail);
+
+ // If there's at least one more valid event we can signal event ready now.
+ if (m_cValidEventBuffers)
+ {
+ SetEvent(m_rghEventReadyEvent[m_pEventBuffers[m_idxEventBufferHead].m_type]);
+ }
+}
+
+
+
+void MarshalDCBTransportToDCB(DebuggerIPCControlBlockTransport* pIn, DebuggerIPCControlBlock* pOut)
+{
+ pOut->m_DCBSize = pIn->m_DCBSize;
+ pOut->m_verMajor = pIn->m_verMajor;
+ pOut->m_verMinor = pIn->m_verMinor;
+ pOut->m_checkedBuild = pIn->m_checkedBuild;
+ pOut->m_bHostingInFiber = pIn->m_bHostingInFiber;
+ pOut->padding2 = pIn->padding2;
+ pOut->padding3 = pIn->padding3;
+
+ pOut->m_leftSideProtocolCurrent = pIn->m_leftSideProtocolCurrent;
+ pOut->m_leftSideProtocolMinSupported = pIn->m_leftSideProtocolMinSupported;
+
+ pOut->m_rightSideProtocolCurrent = pIn->m_rightSideProtocolCurrent;
+ pOut->m_rightSideProtocolMinSupported = pIn->m_rightSideProtocolMinSupported;
+
+ pOut->m_errorHR = pIn->m_errorHR;
+ pOut->m_errorCode = pIn->m_errorCode;
+
+#if defined(DBG_TARGET_WIN64)
+ pOut->padding4 = pIn->padding4;
+#endif // DBG_TARGET_WIN64
+
+
+ //
+ //pOut->m_rightSideEventAvailable
+ //pOut->m_rightSideEventRead
+ //pOut->m_paddingObsoleteLSEA
+ //pOut->m_paddingObsoleteLSER
+ //pOut->m_rightSideProcessHandle
+ //pOut->m_leftSideUnmanagedWaitEvent
+
+ pOut->m_realHelperThreadId = pIn->m_realHelperThreadId;
+ pOut->m_helperThreadId = pIn->m_helperThreadId;
+ pOut->m_temporaryHelperThreadId = pIn->m_temporaryHelperThreadId;
+ pOut->m_CanaryThreadId = pIn->m_CanaryThreadId;
+ pOut->m_pRuntimeOffsets = pIn->m_pRuntimeOffsets;
+ pOut->m_helperThreadStartAddr = pIn->m_helperThreadStartAddr;
+ pOut->m_helperRemoteStartAddr = pIn->m_helperRemoteStartAddr;
+ pOut->m_specialThreadList = pIn->m_specialThreadList;
+
+ //
+ //pOut->m_receiveBuffer
+ //pOut->m_sendBuffer
+
+ pOut->m_specialThreadListLength = pIn->m_specialThreadListLength;
+ pOut->m_shutdownBegun = pIn->m_shutdownBegun;
+ pOut->m_rightSideIsWin32Debugger = pIn->m_rightSideIsWin32Debugger;
+ pOut->m_specialThreadListDirty = pIn->m_specialThreadListDirty;
+
+ pOut->m_rightSideShouldCreateHelperThread = pIn->m_rightSideShouldCreateHelperThread;
+
+}
+
+void MarshalDCBToDCBTransport(DebuggerIPCControlBlock* pIn, DebuggerIPCControlBlockTransport* pOut)
+{
+ pOut->m_DCBSize = pIn->m_DCBSize;
+ pOut->m_verMajor = pIn->m_verMajor;
+ pOut->m_verMinor = pIn->m_verMinor;
+ pOut->m_checkedBuild = pIn->m_checkedBuild;
+ pOut->m_bHostingInFiber = pIn->m_bHostingInFiber;
+ pOut->padding2 = pIn->padding2;
+ pOut->padding3 = pIn->padding3;
+
+ pOut->m_leftSideProtocolCurrent = pIn->m_leftSideProtocolCurrent;
+ pOut->m_leftSideProtocolMinSupported = pIn->m_leftSideProtocolMinSupported;
+
+ pOut->m_rightSideProtocolCurrent = pIn->m_rightSideProtocolCurrent;
+ pOut->m_rightSideProtocolMinSupported = pIn->m_rightSideProtocolMinSupported;
+
+ pOut->m_errorHR = pIn->m_errorHR;
+ pOut->m_errorCode = pIn->m_errorCode;
+
+#if defined(DBG_TARGET_WIN64)
+ pOut->padding4 = pIn->padding4;
+#endif // DBG_TARGET_WIN64
+
+ pOut->m_realHelperThreadId = pIn->m_realHelperThreadId;
+ pOut->m_helperThreadId = pIn->m_helperThreadId;
+ pOut->m_temporaryHelperThreadId = pIn->m_temporaryHelperThreadId;
+ pOut->m_CanaryThreadId = pIn->m_CanaryThreadId;
+ pOut->m_pRuntimeOffsets = pIn->m_pRuntimeOffsets;
+ pOut->m_helperThreadStartAddr = pIn->m_helperThreadStartAddr;
+ pOut->m_helperRemoteStartAddr = pIn->m_helperRemoteStartAddr;
+ pOut->m_specialThreadList = pIn->m_specialThreadList;
+
+ pOut->m_specialThreadListLength = pIn->m_specialThreadListLength;
+ pOut->m_shutdownBegun = pIn->m_shutdownBegun;
+ pOut->m_rightSideIsWin32Debugger = pIn->m_rightSideIsWin32Debugger;
+ pOut->m_specialThreadListDirty = pIn->m_specialThreadListDirty;
+
+ pOut->m_rightSideShouldCreateHelperThread = pIn->m_rightSideShouldCreateHelperThread;
+}
+
+
+
+#ifdef RIGHT_SIDE_COMPILE
+// Read and write memory on the LS from the RS.
+HRESULT DbgTransportSession::ReadMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer)
+{
+ DbgTransportLog(LC_Requests, "Sending 'ReadMemory(0x%08X, %u)'", pbRemoteAddress, cbBuffer);
+ DBG_TRANSPORT_INC_STAT(SentReadMemory);
+
+ Message sMessage;
+ sMessage.Init(MT_ReadMemory, NULL, 0, pbBuffer, (DWORD)cbBuffer);
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = pbRemoteAddress;
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = (DWORD)cbBuffer;
+
+ HRESULT hr = SendRequestMessageAndWait(&sMessage);
+ if (FAILED(hr))
+ return hr;
+
+ // If we reached here the send was successful but the actual memory operation may not have been (due to
+ // unmapped memory or page protections etc.). So the final result comes back to us in the reply.
+ return sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_hrResult;
+}
+
+HRESULT DbgTransportSession::WriteMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer)
+{
+ DbgTransportLog(LC_Requests, "Sending 'WriteMemory(0x%08X, %u)'", pbRemoteAddress, cbBuffer);
+ DBG_TRANSPORT_INC_STAT(SentWriteMemory);
+
+ Message sMessage;
+ sMessage.Init(MT_WriteMemory, pbBuffer, (DWORD)cbBuffer);
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer = pbRemoteAddress;
+ sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer = (DWORD)cbBuffer;
+
+ HRESULT hr = SendRequestMessageAndWait(&sMessage);
+ if (FAILED(hr))
+ return hr;
+
+ // If we reached here the send was successful but the actual memory operation may not have been (due to
+ // unmapped memory or page protections etc.). So the final result comes back to us in the reply.
+ return sMessage.m_sHeader.TypeSpecificData.MemoryAccess.m_hrResult;
+}
+
+HRESULT DbgTransportSession::VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context)
+{
+ DbgTransportLog(LC_Requests, "Sending 'VirtualUnwind'");
+ DBG_TRANSPORT_INC_STAT(SentVirtualUnwind);
+
+ Message sMessage;
+ sMessage.Init(MT_VirtualUnwind, context, contextSize, context, contextSize);
+ return SendRequestMessageAndWait(&sMessage);
+}
+
+// Read and write the debugger control block on the LS from the RS.
+HRESULT DbgTransportSession::GetDCB(DebuggerIPCControlBlock *pDCB)
+{
+ DbgTransportLog(LC_Requests, "Sending 'GetDCB'");
+ DBG_TRANSPORT_INC_STAT(SentGetDCB);
+
+ Message sMessage;
+ DebuggerIPCControlBlockTransport dcbt;
+ sMessage.Init(MT_GetDCB, NULL, 0, (PBYTE)&dcbt, sizeof(DebuggerIPCControlBlockTransport));
+ HRESULT ret = SendRequestMessageAndWait(&sMessage);
+
+ MarshalDCBTransportToDCB(&dcbt, pDCB);
+ return ret;
+}
+
+HRESULT DbgTransportSession::SetDCB(DebuggerIPCControlBlock *pDCB)
+{
+ DbgTransportLog(LC_Requests, "Sending 'SetDCB'");
+ DBG_TRANSPORT_INC_STAT(SentSetDCB);
+
+ DebuggerIPCControlBlockTransport dcbt;
+ MarshalDCBToDCBTransport(pDCB, &dcbt);
+
+ Message sMessage;
+ sMessage.Init(MT_SetDCB, (PBYTE)&dcbt, sizeof(DebuggerIPCControlBlockTransport));
+ return SendRequestMessageAndWait(&sMessage);
+
+}
+
+// Read the AppDomain control block on the LS from the RS.
+HRESULT DbgTransportSession::GetAppDomainCB(AppDomainEnumerationIPCBlock *pADB)
+{
+ DbgTransportLog(LC_Requests, "Sending 'GetAppDomainCB'");
+ DBG_TRANSPORT_INC_STAT(SentGetAppDomainCB);
+
+ Message sMessage;
+ sMessage.Init(MT_GetAppDomainCB, NULL, 0, (PBYTE)pADB, sizeof(AppDomainEnumerationIPCBlock));
+ return SendRequestMessageAndWait(&sMessage);
+}
+
+#endif // RIGHT_SIDE_COMPILE
+
+// Worker function for code:DbgTransportSession::SendEvent and code:DbgTransportSession::SendDebugEvent.
+HRESULT DbgTransportSession::SendEventWorker(DebuggerIPCEvent * pEvent, IPCEventType type)
+{
+ DWORD cbEvent = GetEventSize(pEvent);
+ _ASSERTE(cbEvent <= CorDBIPC_BUFFER_SIZE);
+
+ Message sMessage;
+ sMessage.Init(MT_Event, (PBYTE)pEvent, cbEvent);
+
+ // Store the event type in the header as well, it's sometimes useful for debugging.
+ sMessage.m_sHeader.TypeSpecificData.Event.m_eIPCEventType = type;
+ sMessage.m_sHeader.TypeSpecificData.Event.m_eType = pEvent->type;
+
+ return SendMessage(&sMessage, false);
+}
+
+// Sends a pre-formatted message (including the data block, if any). The fWaitsForReply indicates whether the
+// caller is going to block until some sort of reply message is received (for instance an event that must be
+// ack'd or a request such as MT_GetDCB that needs a reply). SendMessage() uses this to determine whether it
+// needs to buffer the message before placing it on the send queue (since it may need to resend the message
+// after a transitory network failure).
+HRESULT DbgTransportSession::SendMessage(Message *pMessage, bool fWaitsForReply)
+{
+ // Serialize the whole operation under the state lock. In particular we need to make allocating the
+ // message ID atomic wrt placing the message on the connection (to ensure our IDs are seen in order by the
+ // other side). We also need to hold the lock while manipulating the send queue (to prevent corruption)
+ // and while determining whether to send immediately or not depending on the session state (to avoid
+ // posting a send on a closed and possibly recycled socket).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Perform any last updates to the header or data block here since we might be about to encrypt them.
+
+ // Give this message a unique ID (useful both to track which messages need to be resent on a network
+ // failure and to match replies to the original message).
+ pMessage->m_sHeader.m_dwId = m_dwNextMessageId++;
+
+ // Use this message send to piggyback an acknowledgement of the last message we processed from the
+ // other side (this will allow the other side to discard one or more buffered messages from its send
+ // queue).
+ pMessage->m_sHeader.m_dwLastSeenId = m_dwLastMessageIdSeen;
+
+ // If the caller isn't waiting around for a reply we must make a copy of the message to place on the
+ // send queue.
+ pMessage->m_pOrigMessage = pMessage;
+ Message *pMessageCopy = NULL;
+ PBYTE pDataBlockCopy = NULL;
+ if (!fWaitsForReply)
+ {
+ // Allocate a new message (includes an embedded message header).
+ pMessageCopy = new (nothrow) Message();
+ if (pMessageCopy == NULL)
+ return E_OUTOFMEMORY;
+
+ // Allocate a new data block if one is being used.
+ if (pMessage->m_pbDataBlock)
+ {
+ pDataBlockCopy = new (nothrow) BYTE[pMessage->m_cbDataBlock];
+ if (pDataBlockCopy == NULL)
+ {
+ delete pMessageCopy;
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ // Copy the message descriptor over.
+ memcpy(pMessageCopy, pMessage, sizeof(Message));
+
+ // And the data block if applicable.
+ if (pDataBlockCopy)
+ memcpy(pDataBlockCopy, pMessage->m_pbDataBlock, pMessage->m_cbDataBlock);
+
+ // The message copy still points to the wrong data block (if there is one).
+ pMessageCopy->m_pbDataBlock = pDataBlockCopy;
+
+ // Point the copy back to the original message.
+ pMessageCopy->m_pOrigMessage = pMessage;
+
+ // From now on we'll use the copy.
+ pMessage = pMessageCopy;
+ }
+
+ // Check the session state.
+ if (m_eState == SS_Closed)
+ {
+ // SS_Closed is bad news, we'll never recover from that so error the send immediately.
+ if (pMessageCopy)
+ delete pMessageCopy;
+ if (pDataBlockCopy)
+ delete [] pDataBlockCopy;
+
+ return E_ABORT;
+ }
+
+ // Don't queue session management messages. We always recreate these if we need to re-send them.
+ if (pMessage->m_sHeader.m_eType > MT_SessionClose)
+ {
+ // Regardless of session state we always queue the message for at least as long as it takes us to
+ // be sure the other side has received the message.
+ if (m_pSendQueueLast == NULL)
+ {
+ // Queue is currently empty.
+ m_pSendQueueFirst = pMessage;
+ m_pSendQueueLast = pMessage;
+ pMessage->m_pNext = NULL;
+ }
+ else
+ {
+ // Place on end of queue.
+ m_pSendQueueLast->m_pNext = pMessage;
+ m_pSendQueueLast = pMessage;
+ pMessage->m_pNext = NULL;
+ }
+ }
+
+ // If the state is SS_Open we can send the message now.
+ if (m_eState == SS_Open)
+ {
+ // Send the message header block followed by the data block if it's provided. Any network error will
+ // be reported internally by SendBlock and result in a transition to the SS_Resync_NC state (and an
+ // eventual resend of the data).
+ if (SendBlock((PBYTE)&pMessage->m_sHeader, sizeof(MessageHeader)) && pMessage->m_pbDataBlock)
+ SendBlock(pMessage->m_pbDataBlock, pMessage->m_cbDataBlock);
+ }
+
+ // If the state wasn't open there's nothing more to be done. The state will eventually transition to
+ // either SS_Open (in which case the transport thread will send all pending messages for us at the
+ // transition point) or SS_Closed (where the transport thread will drain the queue and discard each
+ // message, setting m_fAborted if necessary).
+
+ } // Leave m_sStateLock
+
+ return S_OK;
+}
+
+// Helper method for sending messages requiring a reply (such as MT_GetDCB) and waiting on the result.
+HRESULT DbgTransportSession::SendRequestMessageAndWait(Message *pMessage)
+{
+ // Allocate event to wait for reply on.
+ pMessage->m_hReplyEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, not signalled
+ if (pMessage->m_hReplyEvent == NULL)
+ return E_OUTOFMEMORY;
+
+ // Duplicate the handle to the event. It's necessary to have two handles to the same event because
+ // both this thread and the message pumping thread may be trying to access the handle at the same
+ // time (e.g. closing the handle). So we make a duplicate handle. This thread is responsible for
+ // closing hReplyEvent (the local variable) whereas the message pumping thread is responsible for
+ // closing the handle on the message.
+ HANDLE hReplyEvent = NULL;
+ if (!DuplicateHandle(GetCurrentProcess(),
+ pMessage->m_hReplyEvent,
+ GetCurrentProcess(),
+ &hReplyEvent,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ // Send the request.
+ HRESULT hr = SendMessage(pMessage, true);
+ if (FAILED(hr))
+ {
+ // In this case, we need to close both handles since the message is never put into the send queue.
+ // This thread is the only one who has access to the message.
+ CloseHandle(pMessage->m_hReplyEvent);
+ CloseHandle(hReplyEvent);
+ return hr;
+ }
+
+ // At this point, the message pumping thread may receive the reply any time. It may even receive the
+ // reply message even before we wait on the event. Keep this in mind.
+
+ // Wait for a reply (by the time this event is signalled the message header will have been overwritten by
+ // the reply and any output buffer provided will have been filled in).
+#if defined(RIGHT_SIDE_COMPILE)
+ HANDLE rgEvents[] = { hReplyEvent, m_hProcessExited };
+#else // !RIGHT_SIDE_COMPILE
+ HANDLE rgEvents[] = { hReplyEvent };
+#endif // RIGHT_SIDE_COMPILE
+
+ DWORD dwResult = WaitForMultipleObjectsEx(sizeof(rgEvents)/sizeof(rgEvents[0]), rgEvents, FALSE, INFINITE, FALSE);
+
+ if (dwResult == WAIT_OBJECT_0)
+ {
+ // This is the normal case. The message pumping thread receives a reply from the debuggee process.
+ // It signals the event to wake up this thread.
+ CloseHandle(hReplyEvent);
+
+ // Check whether the session aborted us due to a Shutdown().
+ if (pMessage->m_fAborted)
+ return E_ABORT;
+ }
+#if defined(RIGHT_SIDE_COMPILE)
+ else if (dwResult == (WAIT_OBJECT_0 + 1))
+ {
+ // This is the complicated case. This thread wakes up because the debuggee process is terminated.
+ // At the same time, the message pumping thread may be in the process of handling the reply message.
+ // We need to be careful here because there is a race condition.
+
+ // Remove the original message from the send queue. This is because in the case of a blocking message,
+ // the message can be allocated on the stack. Thus, the message becomes invalid when we return from
+ // this function. The message pumping thread may have beaten this thread to it. That's ok since
+ // RemoveMessageFromSendQueue() takes the state lock.
+ Message * pOriginalMessage = RemoveMessageFromSendQueue(pMessage->m_sHeader.m_dwId);
+ _ASSERTE((pOriginalMessage == NULL) || (pOriginalMessage == pMessage));
+
+ // If the message pumping thread has beaten this thread to removing the original message, then this
+ // thread must wait until the message pumping thread is done with the message before returning.
+ // Otherwise, the message may become invalid when the message pumping thread is accessing it.
+ // Fortunately, in this case, we know the message pumping thread is going to signal the event.
+ if (pOriginalMessage == NULL)
+ {
+ WaitForSingleObject(hReplyEvent, INFINITE);
+ }
+
+ CloseHandle(hReplyEvent);
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+#endif // RIGHT_SIDE_COMPILE
+ else
+ {
+ // Should never get here.
+ CloseHandle(hReplyEvent);
+ UNREACHABLE();
+ }
+
+ return S_OK;
+}
+
+// Sends a single contiguous buffer of host memory over the connection. The caller is responsible for holding
+// the state lock and ensuring the session state is SS_Open. Returns false if the send failed (the error will
+// have already caused the recovery logic to kick in, so handling it is not required, the boolean is just
+// returned so that any further blocks in the message are not sent).
+bool DbgTransportSession::SendBlock(PBYTE pbBuffer, DWORD cbBuffer)
+{
+ _ASSERTE(m_eState == SS_Opening || m_eState == SS_Resync || m_eState == SS_Open);
+ _ASSERTE(m_pipe.GetState() == TwoWayPipe::ServerConnected || m_pipe.GetState() == TwoWayPipe::ClientConnected);
+ _ASSERTE(cbBuffer > 0);
+
+ DBG_TRANSPORT_INC_STAT(SentBlocks);
+ DBG_TRANSPORT_ADD_STAT(SentBytes, cbBuffer);
+
+ //DbgTransportLog(LC_Proxy, "SendBlock(%08X, %u)", pbBuffer, cbBuffer);
+ bool fSuccess;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Send))
+ fSuccess = false;
+ else
+ fSuccess = (m_pipe.Write(pbBuffer, cbBuffer) == cbBuffer);
+
+ if (!fSuccess)
+ {
+ DbgTransportLog(LC_NetErrors, "Network error on Send()");
+ DBG_TRANSPORT_INC_STAT(SendErrors);
+ HandleNetworkError(true);
+ return false;
+ }
+
+ return true;
+}
+
+// Receives a single contiguous buffer of host memory over the connection. No state lock needs to be held
+// (receives are serialized by the fact they're only performed on the transport thread). Returns false if a
+// network error is encountered (which will automatically transition the session into the correct retry
+// state).
+bool DbgTransportSession::ReceiveBlock(PBYTE pbBuffer, DWORD cbBuffer)
+{
+ _ASSERTE(m_pipe.GetState() == TwoWayPipe::ServerConnected || m_pipe.GetState() == TwoWayPipe::ClientConnected);
+ _ASSERTE(cbBuffer > 0);
+
+ DBG_TRANSPORT_INC_STAT(ReceivedBlocks);
+ DBG_TRANSPORT_ADD_STAT(ReceivedBytes, cbBuffer);
+
+ //DbgTransportLog(LC_Proxy, "ReceiveBlock(%08X, %u)", pbBuffer, cbBuffer);
+
+ bool fSuccess;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Receive))
+ fSuccess = false;
+ else
+ fSuccess = (m_pipe.Read(pbBuffer, cbBuffer) == cbBuffer);
+
+ if (!fSuccess)
+ {
+ DbgTransportLog(LC_NetErrors, "Network error on Receive()");
+ DBG_TRANSPORT_INC_STAT(ReceiveErrors);
+ HandleNetworkError(false);
+ return false;
+ }
+
+ return true;
+}
+
+// Called upon encountering a network error (e.g. an error from Send() or Receive()). This handles pushing the
+// session state into SS_Resync_NC or SS_Opening_NC in order to start the recovery process.
+void DbgTransportSession::HandleNetworkError(bool fCallerHoldsStateLock)
+{
+ _ASSERTE(m_eState == SS_Open || m_eState == SS_Opening || m_eState == SS_Resync || !fCallerHoldsStateLock);
+
+ // Check the easy cases first which don't require us to take the lock (because we don't transition the
+ // state). These are the SS_Closed state (a network error doesn't matter when we're closing down the
+ // session anyway) and the SS_*_NC states (which indicate someone else beat us to it, closed the
+ // connection and has started recovery).
+ if (m_eState == SS_Closed ||
+ m_eState == SS_Opening_NC ||
+ m_eState == SS_Resync_NC)
+ return;
+
+ // We need the state lock to perform a state transition.
+ if (!fCallerHoldsStateLock)
+ m_sStateLock.Enter();
+
+ switch (m_eState)
+ {
+ case SS_Closed:
+ case SS_Opening_NC:
+ case SS_Resync_NC:
+ // Still need to cope with the no-op states handled above since we could have transitioned into them
+ // before we took the lock.
+ break;
+
+ case SS_Opening:
+ // All work to transition SS_Opening to SS_Open is performed by the transport thread, so we know we're
+ // on that thread. Consequently it's just enough to set the state to SS_Opening_NC and the thread will
+ // notice the change when the SendMessage() or ReceiveBlock() call completes.
+ m_eState = SS_Opening_NC;
+ break;
+
+ case SS_Resync:
+ // Likewise, all the work to transition SS_Resync to SS_Open is performed by the transport thread, so
+ // we know we're on that thread.
+ m_eState = SS_Resync_NC;
+ break;
+
+ case SS_Open:
+ // The state change to SS_Resync_NC will prompt the transport thread (which might be this thread) that
+ // it should discard the current connection and reform a new one. It will also cause sends to be
+ // queued instead of sent. In case we're not the transport thread and instead it is currently stuck in
+ // a Receive (I don't entirely trust the connection to immediately fail these on a network problem)
+ // we'll call CancelReceive() to abort the operation. The transport thread itself will handle the
+ // actual Destroy() (having one thread do this management greatly simplifies things).
+ m_eState = SS_Resync_NC;
+ m_pipe.Disconnect();
+ break;
+
+ default:
+ _ASSERTE(!"Unknown session state");
+ }
+
+ if (!fCallerHoldsStateLock)
+ m_sStateLock.Leave();
+}
+
+// Scan the send queue and discard any messages which have been processed by the other side according to the
+// specified ID). Messages waiting on a reply message (e.g. MT_GetDCB) will be retained until that reply is
+// processed. FlushSendQueue will take the state lock.
+void DbgTransportSession::FlushSendQueue(DWORD dwLastProcessedId)
+{
+ // Must access the send queue under the state lock.
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Note that message headers (and data blocks) may be encrypted. Use the cached fields in the Message
+ // structure to compare message IDs and types.
+
+ Message *pMsg = m_pSendQueueFirst;
+ Message *pLastMsg = NULL;
+ while (pMsg)
+ {
+ if (pMsg->m_sHeader.m_dwId <= dwLastProcessedId)
+ {
+ // Message has been seen and processed by other side.
+ // Check if we can discard it (i.e. it's not waiting on a reply message that needs the original
+ // request to hang around).
+#ifdef RIGHT_SIDE_COMPILE
+ MessageType eType = pMsg->m_sHeader.m_eType;
+ if (eType != MT_ReadMemory &&
+ eType != MT_WriteMemory &&
+ eType != MT_VirtualUnwind &&
+ eType != MT_GetDCB &&
+ eType != MT_SetDCB &&
+ eType != MT_GetAppDomainCB)
+#endif // RIGHT_SIDE_COMPILE
+ {
+#ifdef RIGHT_SIDE_COMPILE
+ _ASSERTE(eType == MT_Event);
+#endif // RIGHT_SIDE_COMPILE
+
+ // We can discard this message.
+
+ // Unlink it from the queue.
+ if (pLastMsg == NULL)
+ m_pSendQueueFirst = pMsg->m_pNext;
+ else
+ pLastMsg->m_pNext = pMsg->m_pNext;
+ if (m_pSendQueueLast == pMsg)
+ m_pSendQueueLast = pLastMsg;
+
+ Message *pDiscardMsg = pMsg;
+ pMsg = pMsg->m_pNext;
+
+ // If the message is a copy deallocate it (and the data block associated with it).
+ if (pDiscardMsg->m_pOrigMessage != pDiscardMsg)
+ {
+ if (pDiscardMsg->m_pbDataBlock)
+ delete [] pDiscardMsg->m_pbDataBlock;
+ delete pDiscardMsg;
+ }
+
+ continue;
+ }
+ }
+
+ pLastMsg = pMsg;
+ pMsg = pMsg->m_pNext;
+ }
+}
+
+#ifdef RIGHT_SIDE_COMPILE
+// Perform processing required to complete a request (such as MT_GetDCB) once a reply comes in. This includes
+// reading data from the connection into the output buffer, removing the original message from the send queue
+// and signalling the completion event. Returns true if no network error was encountered.
+bool DbgTransportSession::ProcessReply(MessageHeader *pHeader)
+{
+ // Locate original message on the send queue.
+ Message *pMsg = RemoveMessageFromSendQueue(pHeader->m_dwReplyId);
+
+ // This can happen if the thread blocked waiting for the replyl message has waken up because the debuggee
+ // process has terminated. See code:DbgTransportSession::SendRequestMessageAndWait() for more info.
+ if (pMsg == NULL)
+ {
+ return true;
+ }
+
+ // If there is a reply block but the caller hasn't specified a reply buffer.
+ // This combination is not used any more.
+ _ASSERTE(! ((pHeader->m_cbDataBlock != (DWORD)0) && (pMsg->m_pbReplyBlock == (PBYTE)NULL)) );
+
+ // If there was an output buffer provided then we copy the data block in the reply into it (perhaps
+ // decrypting it first). If the reply header indicates there is no data block then presumably the request
+ // failed (which should be indicated in the TypeSpecificData of the reply, ala MT_ReadMemory).
+ if (pMsg->m_pbReplyBlock && pHeader->m_cbDataBlock)
+ {
+ _ASSERTE(pHeader->m_cbDataBlock == pMsg->m_cbReplyBlock);
+ if (!ReceiveBlock(pMsg->m_pbReplyBlock, pMsg->m_cbReplyBlock))
+ {
+ // Whoops. We hit an error trying to read the reply data. We need to push the original message
+ // back on the queue and await a retry. Since this message must have been seen by the other side
+ // we don't need to put it on the queue in order (it will never be resent). Easiest just to put it
+ // on the head.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ pMsg->m_pNext = m_pSendQueueFirst;
+ m_pSendQueueFirst = pMsg;
+ if (m_pSendQueueLast == NULL)
+ m_pSendQueueLast = pMsg;
+ return false;
+ } // Leave m_sStateLock
+ }
+ }
+
+ // Copy TypeSpecificData from the reply back into the original message (it can contain additional status).
+ // Be careful to update the real original message (the version on the queue will be a copy if we're using
+ // a secure session).
+ pMsg->m_pOrigMessage->m_sHeader.TypeSpecificData = pHeader->TypeSpecificData;
+
+ // **** IMPORTANT NOTE ****
+ // We're about to cause a side-effect visible to our client. From here on out (until we update the
+ // session's idea of the last incoming message we processed back in the transport thread's main loop) we
+ // must avoid any failures. If we fail before the update the other side will re-send the message which is
+ // bad if we've already processed it. See the comment near the start of the SS_Open message dispatch logic
+ // for more details.
+ // **** IMPORTANT NOTE ****
+
+ // Signal the completion event.
+ SignalReplyEvent(pMsg);
+
+ return true;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Upon receiving a reply message, signal the event on the message to wake up the thread waiting for
+// the reply message and close the handle to the event.
+//
+// Arguments:
+// pMessage - the reply message to be processed
+//
+
+void DbgTransportSession::SignalReplyEvent(Message * pMessage)
+{
+ // Make a local copy of the event handle. As soon as we signal the event, the thread blocked waiting on
+ // the reply may wake up and trash the message. See code:DbgTransportSession::SendRequestMessageAndWait()
+ // for more info.
+ HANDLE hReplyEvent = pMessage->m_hReplyEvent;
+ _ASSERTE(hReplyEvent != NULL);
+
+ SetEvent(hReplyEvent);
+ CloseHandle(hReplyEvent);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given a message ID, find the matching message in the send queue. If there is no match, return NULL.
+// If there is a match, remove the message from the send queue and return it.
+//
+// Arguments:
+// dwMessageId - the ID of the message to retrieve
+//
+// Return Value:
+// NULL if the specified message cannot be found.
+// Otherwise return the specified message with the side effect that it's also removed from the send queue.
+//
+// Notes:
+// The caller is NOT responsible for taking the state lock. This function will do that.
+//
+
+DbgTransportSession::Message * DbgTransportSession::RemoveMessageFromSendQueue(DWORD dwMessageId)
+{
+ // Locate original message on the send queue.
+ Message *pMsg = NULL;
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ pMsg = m_pSendQueueFirst;
+ Message *pLastMsg = NULL;
+
+ while (pMsg)
+ {
+ if (dwMessageId == pMsg->m_sHeader.m_dwId)
+ {
+ // Found the original message that this is a reply to. Unlink it.
+ if (pLastMsg == NULL)
+ m_pSendQueueFirst = pMsg->m_pNext;
+ else
+ pLastMsg->m_pNext = pMsg->m_pNext;
+
+ if (m_pSendQueueLast == pMsg)
+ m_pSendQueueLast = pLastMsg;
+ break;
+ }
+
+ pLastMsg = pMsg;
+ pMsg = pMsg->m_pNext;
+ }
+ } // Leave m_sStateLock
+
+ // could be NULL
+ return pMsg;
+}
+#endif
+
+#ifndef RIGHT_SIDE_COMPILE
+
+#ifdef FEATURE_PAL
+__attribute__((noinline))
+__attribute__((optnone))
+static void
+ProbeMemory(__in_ecount(cbBuffer) volatile PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess)
+{
+ // Need an throw in this function to fool the C++ runtime into handling the
+ // possible h/w exception below.
+ if (pbBuffer == NULL)
+ {
+ throw PAL_SEHException();
+ }
+
+ // Simple one byte at a time probing
+ while (cbBuffer > 0)
+ {
+ volatile BYTE read = *pbBuffer;
+ if (fWriteAccess)
+ {
+ *pbBuffer = read;
+ }
+ ++pbBuffer;
+ --cbBuffer;
+ }
+}
+#endif // FEATURE_PAL
+
+// Check read and optionally write memory access to the specified range of bytes. Used to check
+// ReadProcessMemory and WriteProcessMemory requests.
+HRESULT DbgTransportSession::CheckBufferAccess(__in_ecount(cbBuffer) PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess)
+{
+ // check for integer overflow
+ if ((pbBuffer + cbBuffer) < pbBuffer)
+ {
+ return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
+ }
+
+ // VirtualQuery doesn't know much about memory allocated outside of PAL's VirtualAlloc
+ // that's why on Unix we can't rely on in to detect invalid memory reads
+#ifndef FEATURE_PAL
+ do
+ {
+ // Find the attributes of the largest set of pages with common attributes starting from our base address.
+ MEMORY_BASIC_INFORMATION sMemInfo;
+ VirtualQuery(pbBuffer, &sMemInfo, sizeof(sMemInfo));
+
+ DbgTransportLog(LC_Proxy, "CBA(%08X,%08X): State:%08X Protect:%08X BA:%08X RS:%08X",
+ pbBuffer, cbBuffer, sMemInfo.State, sMemInfo.Protect, sMemInfo.BaseAddress, sMemInfo.RegionSize);
+
+ // The memory must be committed (i.e. have physical pages or backing store).
+ if (sMemInfo.State != MEM_COMMIT)
+ return HRESULT_FROM_WIN32(ERROR_INVALID_ADDRESS);
+
+ // Check for compatible page protections. Lower byte of Protect has these (upper bytes have options we're
+ // not interested in, cache modes and the like.
+ DWORD dwProtect = sMemInfo.Protect & 0xff;
+
+ if (fWriteAccess &&
+ ((dwProtect & (PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY | PAGE_READWRITE | PAGE_WRITECOPY)) == 0))
+ return HRESULT_FROM_WIN32(ERROR_NOACCESS);
+ else if (!fWriteAccess &&
+ ((dwProtect & (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY | PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY)) == 0))
+ return HRESULT_FROM_WIN32(ERROR_NOACCESS);
+
+ // If the requested range is bigger than the region we have queried,
+ // we need to continue on to check the next region.
+ if ((pbBuffer + cbBuffer) > ((PBYTE)sMemInfo.BaseAddress + sMemInfo.RegionSize))
+ {
+ PBYTE pbRegionEnd = reinterpret_cast<PBYTE>(sMemInfo.BaseAddress) + sMemInfo.RegionSize;
+ cbBuffer = (DWORD)((pbBuffer + cbBuffer) - pbRegionEnd);
+ pbBuffer = pbRegionEnd;
+ }
+ else
+ {
+ // We are done. Set cbBuffer to 0 to exit this loop.
+ cbBuffer = 0;
+ }
+ }
+ while (cbBuffer > 0);
+#else
+ try
+ {
+ // Need to explicit h/w exception holder so to catch them in ProbeMemory
+ CatchHardwareExceptionHolder __catchHardwareException;
+
+ ProbeMemory(pbBuffer, cbBuffer, fWriteAccess);
+ }
+ catch(...)
+ {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_ADDRESS);
+ }
+#endif
+
+ // The specified region has passed all of our checks.
+ return S_OK;
+}
+
+#endif // !RIGHT_SIDE_COMPILE
+
+// Initialize all session state to correct starting values. Used during Init() and on the LS when we
+// gracefully close one session and prepare for another.
+void DbgTransportSession::InitSessionState()
+{
+ DBG_TRANSPORT_INC_STAT(Sessions);
+
+ m_dwMajorVersion = kCurrentMajorVersion;
+ m_dwMinorVersion = kCurrentMinorVersion;
+
+ memset(&m_sSessionID, 0, sizeof(m_sSessionID));
+
+ m_pSendQueueFirst = NULL;
+ m_pSendQueueLast = NULL;
+
+ m_dwNextMessageId = 1;
+ m_dwLastMessageIdSeen = 0;
+
+ m_eState = SS_Opening_NC;
+
+ m_cValidEventBuffers = 0;
+ m_idxEventBufferHead = 0;
+ m_idxEventBufferTail = 0;
+}
+
+// The entry point of the transport worker thread. This one's static, so we immediately dispatch to an
+// instance method version defined below for convenience in the implementation.
+DWORD WINAPI DbgTransportSession::TransportWorkerStatic(LPVOID pvContext)
+{
+ ((DbgTransportSession*)pvContext)->TransportWorker();
+
+ // Nobody looks at this result, the choice of 0 is arbitrary.
+ return 0;
+}
+
+// Macros used to simplify error and state transition handling within the transport worker loop. Errors are
+// classified as either transient or critical. Transient errors (typically those from network operations)
+// result in the connection being closed and rebuilt: we should eventually recover from them. Critical errors
+// are those that cause a transition to the SS_Closed state, which the session never recovers from. These are
+// normally due to protocol errors where we want to shut the transport down in case they are of malicious
+// origin.
+#define HANDLE_TRANSIENT_ERROR() do { \
+ HandleNetworkError(false); \
+ m_pipe.Disconnect(); \
+ goto ResetConnection; \
+} while (false)
+
+#define HANDLE_CRITICAL_ERROR() do { \
+ m_eState = SS_Closed; \
+ goto Shutdown; \
+} while (false)
+
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void DbgTransportSession::TransportWorker()
+{
+ _ASSERTE(m_eState == SS_Opening_NC);
+
+ // Loop until shutdown. Each loop iteration involves forming a connection (or waiting for one to form)
+ // followed by processing incoming messages on that connection until there's a failure (either here of
+ // from a send on another thread) or the session shuts down. The connection is then closed and discarded
+ // and we either go round the loop again (to recover our previous session state) or exit the method as
+ // part of shutdown.
+ ResetConnection:
+ while (m_eState != SS_Closed)
+ {
+ _ASSERTE(m_eState == SS_Opening_NC || m_eState == SS_Resync_NC || m_eState == SS_Closed);
+
+ DbgTransportLog(LC_Proxy, "Forming new connection");
+
+#ifdef RIGHT_SIDE_COMPILE
+ // The session is definitely not open at this point.
+ ResetEvent(m_hSessionOpenEvent);
+
+ // On the right side we initiate the connection via Connect(). A failure is dealt with by waiting a
+ // little while and retrying (the LS may take a little while to set up). If there's nobody listening
+ // the debugger will eventually get bored waiting for us and shutdown the session, which will
+ // terminate this loop.
+ ConnStatus eStatus;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Connect))
+ eStatus = SCS_NetworkFailure;
+ else
+ {
+ if (m_pipe.Connect(m_pid))
+ {
+ eStatus = SCS_Success;
+ }
+ else
+ {
+ //not really sure that this is the real failure
+ //TODO: we probably need to analyse GetErrorCode() here
+ eStatus = SCS_NoListener;
+ }
+ }
+
+ if (eStatus != SCS_Success)
+ {
+ DbgTransportLog(LC_Proxy, "AllocateConnection() failed with %u\n", eStatus);
+ DBG_TRANSPORT_INC_STAT(MiscErrors);
+ _ASSERTE(m_pipe.GetState() != TwoWayPipe::ClientConnected);
+ Sleep(1000);
+ continue;
+ }
+#else // RIGHT_SIDE_COMPILE
+ ConnStatus eStatus;
+ if (DBG_TRANSPORT_SHOULD_INJECT_FAULT(Accept))
+ eStatus = SCS_NetworkFailure;
+ else
+ {
+ DWORD pid = GetCurrentProcessId();
+ if ((m_pipe.GetState() == TwoWayPipe::Created || m_pipe.CreateServer(pid)) &&
+ m_pipe.WaitForConnection())
+ {
+ eStatus = SCS_Success;
+ }
+ else
+ {
+ //not really sure that this is the real failure
+ //TODO: we probably need to analyse GetErrorCode() here
+ eStatus = SCS_NoListener;
+ }
+ }
+
+ if (eStatus != SCS_Success)
+ {
+ DbgTransportLog(LC_Proxy, "Accept() failed with %u\n", eStatus);
+ DBG_TRANSPORT_INC_STAT(MiscErrors);
+ _ASSERTE(m_pipe.GetState() != TwoWayPipe::ServerConnected);
+ Sleep(1000);
+ continue;
+ }
+
+ // Note that when resynching a session we may let in a connection from a different debugger. That's
+ // OK, we'll reject his SessionRequest message in due course and drop the connection.
+#endif // RIGHT_SIDE_COMPILE
+
+ DBG_TRANSPORT_INC_STAT(Connections);
+
+ // We now have a connection. Transition to the next state (either SS_Opening or SS_Resync). The
+ // primary purpose of this state transition is to let other threads know that this thread might now be
+ // blocked on a Receive() on the newly formed connection (important if they want to transition the state
+ // to SS_Closed).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ if (m_eState == SS_Closed)
+ break;
+ else if (m_eState == SS_Opening_NC)
+ m_eState = SS_Opening;
+ else if (m_eState == SS_Resync_NC)
+ m_eState = SS_Resync;
+ else
+ _ASSERTE(!"Bad session state");
+ } // Leave m_sStateLock
+
+
+ // Now we have a connection in place. Start reading messages and processing them. Which messages are
+ // valid depends on whether we're in SS_Opening or SS_Resync (the state can change at any time
+ // asynchronously to us to either SS_Closed or SS_Resync_NC but we're guaranteed the connection stays
+ // valid (though not necessarily useful) until we notice this state change and Destroy() it ourself).
+ // We check the state after each network operation.
+
+ // During the SS_Opening and SS_Resync states we're guarantee to be the only thread posting sends, so
+ // we can break the rules and use SendBlock without acquiring the state lock. (We use SendBlock a lot
+ // during these phases because we're using simple Session* messages which don't require the extra
+ // processing SendMessage gives us such as encryption or placement on the send queue).
+
+ MessageHeader sSendHeader;
+ MessageHeader sReceiveHeader;
+
+ memset(&sSendHeader, 0, sizeof(MessageHeader));
+
+ if (m_eState == SS_Opening)
+ {
+#ifdef RIGHT_SIDE_COMPILE
+ // The right side actually starts things off by sending a SessionRequest message.
+
+ SessionRequestData sDataBlock;
+
+ sSendHeader.m_eType = MT_SessionRequest;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = kCurrentMinorVersion;
+
+ // The start of the data block always contains a session ID. This is a GUID randomly generated at
+ // Init() time.
+ sSendHeader.m_cbDataBlock = sizeof(SessionRequestData);
+ memcpy(&sDataBlock.m_sSessionID, &m_sSessionID, sizeof(m_sSessionID));
+
+ // Send the header block followed by the data block. For failures during SS_Opening we just close
+ // the connection and retry from the beginning (the failing send will already have caused a
+ // transition into SS_Opening_NC. No need to use the same resend logic that SS_Resync does, since
+ // no user messages have been sent and we can simply recreate the SessionRequest.
+ DbgTransportLog(LC_Session, "Sending 'SessionRequest'");
+ DBG_TRANSPORT_INC_STAT(SentSessionRequest);
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)) ||
+ !SendBlock((PBYTE)&sDataBlock, sSendHeader.m_cbDataBlock))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Wait for a reply.
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // This should be either a SessionAccept or SessionReject. Any other message type will be treated
+ // as a SessionReject (i.e. an unrecoverable failure that will leave the session in SS_Closed
+ // permanently).
+ if (sReceiveHeader.m_eType != MT_SessionAccept)
+ {
+ _ASSERTE(!"Unexpected response to SessionRequest");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // Validate the SessionAccept.
+ if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion != kCurrentMajorVersion ||
+ sReceiveHeader.m_cbDataBlock != (DWORD)0)
+ {
+ _ASSERTE(!"Malformed SessionAccept received");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // The LS might have negotiated the minor protocol version down.
+ m_dwMinorVersion = sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion;
+#else // RIGHT_SIDE_COMPILE
+
+ // On the left side we wait for a SessionRequest first.
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ if (sReceiveHeader.m_eType != MT_SessionRequest)
+ {
+ _ASSERTE(!"Unexpected message type");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // Validate the SessionRequest.
+ if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion != kCurrentMajorVersion ||
+ sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(SessionRequestData))
+ {
+ // Send a SessionReject message with the reason for rejection.
+ sSendHeader.m_eType = MT_SessionReject;
+ sSendHeader.TypeSpecificData.SessionReject.m_eReason = RR_IncompatibleVersion;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMinorVersion = kCurrentMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionReject(RR_IncompatibleVersion)'");
+ DBG_TRANSPORT_INC_STAT(SentSessionReject);
+
+ SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader));
+
+ // Go back into the opening state rather than closed because we want to give the RS a chance
+ // to correct the problem and try again.
+ HANDLE_TRANSIENT_ERROR();
+ }
+
+ // Read the data block.
+ SessionRequestData sDataBlock;
+ if (!ReceiveBlock((PBYTE)&sDataBlock, sizeof(SessionRequestData)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // If the RS only understands a lower minor protocol version than us then remember that fact.
+ if (sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion < m_dwMinorVersion)
+ m_dwMinorVersion = sReceiveHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion;
+
+ // Send a SessionAccept message back.
+ sSendHeader.m_eType = MT_SessionAccept;
+ sSendHeader.m_cbDataBlock = 0;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = m_dwMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionAccept'");
+ DBG_TRANSPORT_INC_STAT(SentSessionAccept);
+
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+#endif // RIGHT_SIDE_COMPILE
+
+ // Everything pans out, we have a session formed. But we must send messages that queued up
+ // before transitioning the state to open (otherwise a racing send could sneak in ahead).
+
+ // Must access the send queue under the state lock.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ Message *pMsg = m_pSendQueueFirst;
+ while (pMsg)
+ {
+ if (SendBlock((PBYTE)&pMsg->m_sHeader, sizeof(MessageHeader)) && pMsg->m_pbDataBlock)
+ SendBlock(pMsg->m_pbDataBlock, pMsg->m_cbDataBlock);
+ pMsg = pMsg->m_pNext;
+ }
+
+ // Check none of the sends failed.
+ if (m_eState != SS_Opening)
+ {
+ m_pipe.Disconnect();
+ continue;
+ }
+ } // Leave m_sStateLock
+
+ // Finally we can transition to SS_Open.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+ if (m_eState == SS_Closed)
+ break;
+ else if (m_eState == SS_Opening)
+ m_eState = SS_Open;
+ else
+ _ASSERTE(!"Bad session state");
+ } // Leave m_sStateLock
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Signal any WaitForSessionToOpen() waiters that we've gotten to SS_Open.
+ SetEvent(m_hSessionOpenEvent);
+#endif // RIGHT_SIDE_COMPILE
+
+ // We're ready to begin receiving normal incoming messages now.
+ }
+ else
+ {
+ // The SS_Resync case. Send a message indicating the last message we saw from the other side and
+ // wait for a similar message to arrive for us.
+
+ sSendHeader.m_eType = MT_SessionResync;
+ sSendHeader.m_dwLastSeenId = m_dwLastMessageIdSeen;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionResync'");
+ DBG_TRANSPORT_INC_STAT(SentSessionResync);
+
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+#ifndef RIGHT_SIDE_COMPILE
+ if (sReceiveHeader.m_eType == MT_SessionRequest)
+ {
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // This SessionRequest could be from a different debugger. In this case we should send a
+ // SessionReject to let them know we're not available and close the connection so we can
+ // re-listen for the original debugger.
+ // Or it could be the original debugger re-sending the SessionRequest because the connection
+ // died as we sent the SessionAccept.
+ // We distinguish the two cases by looking at the session ID in the request.
+ bool fRequestResend = false;
+
+ // Only read the data block if it matches our expectations of its size.
+ if (sReceiveHeader.m_cbDataBlock == (DWORD)sizeof(SessionRequestData))
+ {
+ SessionRequestData sDataBlock;
+ if (!ReceiveBlock((PBYTE)&sDataBlock, sizeof(SessionRequestData)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Check the session ID for a match.
+ if (memcmp(&sDataBlock.m_sSessionID, &m_sSessionID, sizeof(m_sSessionID)) == 0)
+ // OK, everything checks out and this is a valid re-send of a SessionRequest.
+ fRequestResend = true;
+ }
+
+ if (fRequestResend)
+ {
+ // The RS never got our SessionAccept. We must resend it.
+ memset(&sSendHeader, 0, sizeof(MessageHeader));
+ sSendHeader.m_eType = MT_SessionAccept;
+ sSendHeader.m_cbDataBlock = 0;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.VersionInfo.m_dwMinorVersion = m_dwMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionAccept'");
+ DBG_TRANSPORT_INC_STAT(SentSessionAccept);
+
+ if (!SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Now simply reset the connection. The RS should get the SessionAccept and transition to
+ // SS_Open then detect the connection loss and transition to SS_Resync_NC, which will
+ // finally sync the two sides.
+ HANDLE_TRANSIENT_ERROR();
+ }
+ else
+ {
+ // This is the case where we must reject the request.
+ memset(&sSendHeader, 0, sizeof(MessageHeader));
+ sSendHeader.m_eType = MT_SessionReject;
+ sSendHeader.TypeSpecificData.SessionReject.m_eReason = RR_AlreadyAttached;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMajorVersion = kCurrentMajorVersion;
+ sSendHeader.TypeSpecificData.SessionReject.m_dwMinorVersion = kCurrentMinorVersion;
+
+ DbgTransportLog(LC_Session, "Sending 'SessionReject(RR_AlreadyAttached)'");
+ DBG_TRANSPORT_INC_STAT(SentSessionReject);
+
+ SendBlock((PBYTE)&sSendHeader, sizeof(MessageHeader));
+
+ HANDLE_TRANSIENT_ERROR();
+ }
+ }
+#endif // !RIGHT_SIDE_COMPILE
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // Handle all other invalid message types by shutting down (it may be an attempt to subvert the
+ // protocol).
+ if (sReceiveHeader.m_eType != MT_SessionResync)
+ {
+ _ASSERTE(!"Unexpected message type during SS_Resync");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // We've got our resync message. Go through the send queue and resend any messages that haven't
+ // been processed by the other side. Those that have been processed can be discarded (unless
+ // they're waiting for another form of higher level acknowledgement, such as a reply message).
+
+ // Discard unneeded messages first.
+ FlushSendQueue(sReceiveHeader.m_dwLastSeenId);
+
+ // Must access the send queue under the state lock.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ Message *pMsg = m_pSendQueueFirst;
+ while (pMsg)
+ {
+ if (pMsg->m_sHeader.m_dwId > sReceiveHeader.m_dwLastSeenId)
+ {
+ // The other side never saw this message, re-send it.
+ DBG_TRANSPORT_INC_STAT(Resends);
+ if (SendBlock((PBYTE)&pMsg->m_sHeader, sizeof(MessageHeader)) && pMsg->m_pbDataBlock)
+ SendBlock(pMsg->m_pbDataBlock, pMsg->m_cbDataBlock);
+ }
+ pMsg = pMsg->m_pNext;
+ }
+
+ // Finished processing queued sends. We can transition to the SS_Open state now as long as there
+ // wasn't a send failure or an asynchronous Shutdown().
+ if (m_eState == SS_Resync)
+ m_eState = SS_Open;
+ else if (m_eState == SS_Closed)
+ break;
+ else if (m_eState == SS_Resync_NC)
+ {
+ m_pipe.Disconnect();
+ continue;
+ }
+ else
+ _ASSERTE(!"Bad session state");
+ } // Leave m_sStateLock
+ }
+
+ // Once we get here we should be in SS_Open (can't assert this because Shutdown() can throw the state
+ // into SS_Closed and we've just released SendMessage() calls on other threads that can transition us
+ // into SS_Resync).
+
+ // We now loop receiving messages and processing them until the state changes.
+ while (m_eState == SS_Open)
+ {
+ // temporary data block used in DCB messages
+ DebuggerIPCControlBlockTransport dcbt;
+
+ // temporary virtual stack unwind context buffer
+ CONTEXT frameContext;
+
+ // Read a message header block.
+ if (!ReceiveBlock((PBYTE)&sReceiveHeader, sizeof(MessageHeader)))
+ HANDLE_TRANSIENT_ERROR();
+
+ // Since we care about security here, perform some additional validation checks that make it
+ // harder for a malicious sender to attack with random message data.
+ if (sReceiveHeader.m_eType > MT_GetAppDomainCB ||
+ (sReceiveHeader.m_dwId <= m_dwLastMessageIdSeen &&
+ sReceiveHeader.m_dwId != (DWORD)0) ||
+ (sReceiveHeader.m_dwReplyId >= m_dwNextMessageId &&
+ sReceiveHeader.m_dwReplyId != (DWORD)0) ||
+ (sReceiveHeader.m_dwLastSeenId >= m_dwNextMessageId &&
+ sReceiveHeader.m_dwLastSeenId != (DWORD)0))
+ {
+ _ASSERTE(!"Incoming message header looks bogus");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ DbgTransportLogMessageReceived(&sReceiveHeader);
+
+ // Flush any entries in our send queue for messages that the other side has just confirmed
+ // processed with this message.
+ FlushSendQueue(sReceiveHeader.m_dwLastSeenId);
+
+#ifndef RIGHT_SIDE_COMPILE
+ // State variables to track whether this message needs a reply and if so whether it consists of a
+ // header only or a header and an optional data block.
+ bool fReplyRequired = false;
+ PBYTE pbOptReplyData = NULL;
+ DWORD cbOptReplyData = 0;
+ HRESULT hr = E_FAIL;
+
+ // if you change the lifetime of resultBuffer, make sure you change pbOptReplyData to match.
+ // In some cases pbOptReplyData will point at the memory held alive in resultBuffer
+ WriteBuffer resultBuffer;
+ ReadBuffer receiveBuffer;
+
+#endif // RIGHT_SIDE_COMPILE
+
+ // Dispatch based on message type.
+ //
+ // **** IMPORTANT NOTE ****
+ //
+ // We must be very careful wrt to updating m_dwLastMessageIdSeen here. If we update it too soon
+ // (we haven't finished receiving the entire message, for instance) then the other side won't
+ // re-send the message on failure and we'll lose it. If we update it too late we might have
+ // reported the message to our caller or produced any other side-effect we can't take back such as
+ // sending a reply and then hit an error and reset the connection before we had a chance to record
+ // the message as seen. In this case the other side will re-send the original message and we'll
+ // repeat our actions, which is also very bad.
+ //
+ // So we must be very disciplined here.
+ //
+ // First we must read the message in its entirety (i.e. receive the data block if there is one)
+ // without causing any side-effects. This ensures that any failure at this point will be handled
+ // correctly (by the other side re-sending us the same message).
+ //
+ // Then we process the message. At this point we are committed. The processing must always
+ // succeed, or have no side-effect (that we care about) or we must have an additional scheme to
+ // handle resynchronization in the event of failure. This ensures that we don't have the tricky
+ // situation where we can't cope with a re-send of the message (because we've started processing
+ // it) but can't report a failure to the other side (because we don't know how).
+ //
+ // Finally we must ensure that there is no error path between the completion of processing and
+ // updating the m_dwLastMessageIdSeen field. This ensures we don't accidently get re-sent a
+ // message we've processed completely (it's really just a sub-case of the rule above, but it's
+ // worth pointing out explicitly since it can be a subtle problem).
+ //
+ // Request messages (such as MT_GetDCB) are an interesting case in point here. They all require a
+ // reply and we can fail on the reply because we run out of system resources. This breaks the
+ // second rule above (we fail halfway through processing). We should really preallocate enough
+ // resources to send the reply before we begin processing of it but for now we don't since (a) the
+ // SendMessage system isn't currently set up to make this easy and (b) we happen to know that all
+ // the request types are effectively idempotent (even ReadMemory and WriteMemory since the RS is
+ // holding the LS still while it does these). So instead we must carefully distinguish the case
+ // where SendMessage fails without possibility of message transmission (e.g. out of memory) and
+ // those where it fails for a transient network failure (where it will re-send the reply on
+ // resync). This is easy enough to do since SendMessage returns a failure hresult for the first
+ // case and success (and a state transition) for the second. In the first case we don't update
+ // m_dwLastMessageIdSeen and instead wait for the request to be resent. In the second we make the
+ // update because we know the reply will get through eventually.
+ //
+ // **** IMPORTANT NOTE ****
+ switch (sReceiveHeader.m_eType)
+ {
+ case MT_SessionRequest:
+ case MT_SessionAccept:
+ case MT_SessionReject:
+ case MT_SessionResync:
+ // Illegal messages at this time, fail the transport entirely.
+ m_eState = SS_Closed;
+ break;
+
+ case MT_SessionClose:
+ // Close is legal on the LS and transitions to the SS_Opening_NC state. It's illegal on the RS
+ // and should shutdown the transport.
+#ifdef RIGHT_SIDE_COMPILE
+ m_eState = SS_Closed;
+ break;
+#else // RIGHT_SIDE_COMPILE
+ // We need to do some state cleanup here, since when we reform a connection (if ever, it will
+ // be with a new session).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // Check we're still in a good state before a clean restart.
+ if (m_eState != SS_Open)
+ {
+ m_eState = SS_Closed;
+ break;
+ }
+
+ m_pipe.Disconnect();
+
+ // We could add code to drain the send queue here (like we have for SS_Closed at the end of
+ // this method) but I'm pretty sure we can only get a graceful session close with no
+ // outstanding sends. So just assert the queue is empty instead. If the assert fires and it's
+ // not due to an issue we can add the logic here).
+ _ASSERTE(m_pSendQueueFirst == NULL);
+ _ASSERTE(m_pSendQueueLast == NULL);
+
+ // This will reset all session specific state and transition us to SS_Opening_NC.
+ InitSessionState();
+ } // Leave m_sStateLock
+
+ goto ResetConnection;
+#endif // RIGHT_SIDE_COMPILE
+
+ case MT_Event:
+ {
+ // Incoming debugger event.
+
+ if (sReceiveHeader.m_cbDataBlock > CorDBIPC_BUFFER_SIZE)
+ {
+ _ASSERTE(!"Oversized Event");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ // See if our array of buffered events has filled up. If so we'll need to re-allocate the
+ // array to expand it.
+ if (m_cValidEventBuffers == m_cEventBuffers)
+ {
+ // Allocate a larger array.
+ DWORD cNewEntries = m_cEventBuffers + 4;
+ DbgEventBufferEntry * pNewBuffers = (DbgEventBufferEntry *)new (nothrow) BYTE[cNewEntries * sizeof(DbgEventBufferEntry)];
+ if (pNewBuffers == NULL)
+ HANDLE_TRANSIENT_ERROR();
+
+ // We must take the lock to swap the new array in. Although this thread is the only one
+ // that can expand the array, a client thread may be in GetNextEvent() reading from the
+ // old version.
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ // When we copy old array contents over we place the head of the list at the start of
+ // the new array for simplicity. If the head happened to be at the start of the old
+ // array anyway, this is even simpler.
+ if (m_idxEventBufferHead == 0)
+ memcpy(pNewBuffers, m_pEventBuffers, m_cEventBuffers * sizeof(DbgEventBufferEntry));
+ else
+ {
+ // Otherwise we need to perform the copy in two segments: first we copy the head
+ // of the list (starts at a non-zero index and runs to the end of the old array)
+ // into the start of the new array.
+ DWORD cHeadEntries = m_cEventBuffers - m_idxEventBufferHead;
+
+ memcpy(pNewBuffers,
+ &m_pEventBuffers[m_idxEventBufferHead],
+ cHeadEntries * sizeof(DbgEventBufferEntry));
+
+ // Then we copy the remaining portion from the beginning of the old array upto to
+ // the index of the head.
+ memcpy(&pNewBuffers[cHeadEntries],
+ m_pEventBuffers,
+ m_idxEventBufferHead * sizeof(DbgEventBufferEntry));
+ }
+
+ // Delete the old array.
+ delete [] m_pEventBuffers;
+
+ // Swap the new array in.
+ m_pEventBuffers = pNewBuffers;
+ m_cEventBuffers = cNewEntries;
+
+ // The new array now has the head at index zero and the tail at the start of the
+ // new entries.
+ m_idxEventBufferHead = 0;
+ m_idxEventBufferTail = m_cValidEventBuffers;
+ }
+ }
+
+ // We have at least one free buffer at this point (no threading issues, the only thread that
+ // can add entries is this one).
+
+ // Receive event data into the tail buffer (we want to do this without holding the state lock
+ // and can do so safely since this is the only thread that can receive data and clients can do
+ // nothing that impacts the location of the tail of the buffer list).
+ if (!ReceiveBlock((PBYTE)&m_pEventBuffers[m_idxEventBufferTail].m_event, sReceiveHeader.m_cbDataBlock))
+ HANDLE_TRANSIENT_ERROR();
+
+ {
+ m_pEventBuffers[m_idxEventBufferTail].m_type = sReceiveHeader.TypeSpecificData.Event.m_eIPCEventType;
+
+ // We must take the lock to update the count of valid entries though, since clients can
+ // touch this field as well.
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ m_cValidEventBuffers++;
+ DWORD idxCurrentEvent = m_idxEventBufferTail;
+
+ // Update tail of the list (strictly speaking this needn't be done under the lock, but the
+ // code in GetNextEvent() does read it for an assert.
+ m_idxEventBufferTail = (m_idxEventBufferTail + 1) % m_cEventBuffers;
+
+ // If we just added the first valid event then wake up the client so they can call
+ // GetNextEvent().
+ if (m_cValidEventBuffers == 1)
+ SetEvent(m_rghEventReadyEvent[m_pEventBuffers[idxCurrentEvent].m_type]);
+ }
+ }
+ break;
+
+ case MT_ReadMemory:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ // The RS wants to read our memory. First check the range requested is both committed and
+ // readable. If that succeeds we simply set the optional reply block to match the request region
+ // (i.e. we send the memory directly).
+ fReplyRequired = true;
+
+ hr = CheckBufferAccess(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer,
+ false);
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult = hr;
+ if (SUCCEEDED(hr))
+ {
+ pbOptReplyData = sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer;
+ cbOptReplyData = sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer;
+ }
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_WriteMemory:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ // The RS wants to write our memory.
+ if (sReceiveHeader.m_cbDataBlock != sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer)
+ {
+ _ASSERTE(!"Inconsistent WriteMemory request");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ fReplyRequired = true;
+
+ // Check the range requested is both committed and writeable. If that succeeds we simply read
+ // the next incoming block into the destination buffer.
+ hr = CheckBufferAccess(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer,
+ true);
+ if (SUCCEEDED(hr))
+ {
+ if (!ReceiveBlock(sReceiveHeader.TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer))
+ HANDLE_TRANSIENT_ERROR();
+ }
+ else
+ {
+ sReceiveHeader.TypeSpecificData.MemoryAccess.m_hrResult = hr;
+
+ // We might be failing the write attempt but we still need to read the update data to
+ // drain it from the connection or we'll become unsynchronized (i.e. we'll treat the start
+ // of the write data as the next message header). So read and discard the data into a
+ // dummy buffer.
+ BYTE rgDummy[256];
+ DWORD cbBytesToRead = sReceiveHeader.TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer;
+ while (cbBytesToRead)
+ {
+ DWORD cbTransfer = min(cbBytesToRead, sizeof(rgDummy));
+ if (!ReceiveBlock(rgDummy, cbTransfer))
+ HANDLE_TRANSIENT_ERROR();
+ cbBytesToRead -= cbTransfer;
+ }
+ }
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_VirtualUnwind:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ if (sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(frameContext))
+ {
+ _ASSERTE(!"Inconsistent VirtualUnwind request");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ if (!ReceiveBlock((PBYTE)&frameContext, sizeof(frameContext)))
+ {
+ HANDLE_TRANSIENT_ERROR();
+ }
+
+ if (!PAL_VirtualUnwind(&frameContext, NULL))
+ {
+ HANDLE_TRANSIENT_ERROR();
+ }
+
+ fReplyRequired = true;
+ pbOptReplyData = (PBYTE)&frameContext;
+ cbOptReplyData = sizeof(frameContext);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_GetDCB:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ fReplyRequired = true;
+ MarshalDCBToDCBTransport(m_pDCB, &dcbt);
+ pbOptReplyData = (PBYTE)&dcbt;
+ cbOptReplyData = sizeof(DebuggerIPCControlBlockTransport);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_SetDCB:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ if (sReceiveHeader.m_cbDataBlock != (DWORD)sizeof(DebuggerIPCControlBlockTransport))
+ {
+ _ASSERTE(!"Inconsistent SetDCB request");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+ fReplyRequired = true;
+
+ if (!ReceiveBlock((PBYTE)&dcbt, sizeof(DebuggerIPCControlBlockTransport)))
+ HANDLE_TRANSIENT_ERROR();
+
+ MarshalDCBTransportToDCB(&dcbt, m_pDCB);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ case MT_GetAppDomainCB:
+#ifdef RIGHT_SIDE_COMPILE
+ if (!ProcessReply(&sReceiveHeader))
+ HANDLE_TRANSIENT_ERROR();
+#else // RIGHT_SIDE_COMPILE
+ fReplyRequired = true;
+ pbOptReplyData = (PBYTE)m_pADB;
+ cbOptReplyData = sizeof(AppDomainEnumerationIPCBlock);
+#endif // RIGHT_SIDE_COMPILE
+ break;
+
+ default:
+ _ASSERTE(!"Unknown message type");
+ HANDLE_CRITICAL_ERROR();
+ }
+
+#ifndef RIGHT_SIDE_COMPILE
+ // On the left side we may need to send a reply back.
+ if (fReplyRequired)
+ {
+ Message sReply;
+ sReply.Init(sReceiveHeader.m_eType, pbOptReplyData, cbOptReplyData);
+ sReply.m_sHeader.m_dwReplyId = sReceiveHeader.m_dwId;
+ sReply.m_sHeader.TypeSpecificData = sReceiveHeader.TypeSpecificData;
+
+#ifdef _DEBUG
+ DbgTransportLog(LC_Requests, "Sending '%s' reply", MessageName(sReceiveHeader.m_eType));
+#endif // _DEBUG
+
+ // We must be careful with the failure mode of SendMessage here to avoid the same request
+ // being processed too many or too few times. See the comment above starting with 'IMPORTANT
+ // NOTE' for more details. The upshot is that on SendMessage hresult failures (which indicate
+ // the message will never be sent), we don't update m_dwLastMessageIdSeen and simply wait for
+ // the request to be made again. When we get success, however, we must be careful to ensure
+ // that m_dwLastMessageIdSeen gets updated even if a network error is reported. Otherwise on
+ // the resync we'll both reprocess the request and re-send the original reply which is very
+ // very bad.
+ hr = SendMessage(&sReply, false);
+
+ if (FAILED(hr))
+ HANDLE_TRANSIENT_ERROR(); // Message will never be sent, other side will retry
+
+ // SendMessage doesn't report network errors (it simply queues the send and changes the
+ // session state). So check for a network error here specifically so we can get started on the
+ // resync. We must update m_dwLastMessageIdSeen first though, or the other side will retry the
+ // request.
+ if (m_eState != SS_Open)
+ {
+ _ASSERTE(sReceiveHeader.m_dwId > m_dwLastMessageIdSeen);
+ m_dwLastMessageIdSeen = sReceiveHeader.m_dwId;
+ HANDLE_TRANSIENT_ERROR();
+ }
+ }
+#endif // !RIGHT_SIDE_COMPILE
+
+ if (sReceiveHeader.m_dwId != (DWORD)0)
+ {
+ // We've now completed processing on the incoming message. Remember we've processed up to this
+ // message ID so that on a resync the other side doesn't send it to us again.
+ _ASSERTE(sReceiveHeader.m_dwId > m_dwLastMessageIdSeen);
+ m_dwLastMessageIdSeen = sReceiveHeader.m_dwId;
+ }
+ }
+ }
+
+ Shutdown:
+
+ _ASSERTE(m_eState == SS_Closed);
+
+#ifdef RIGHT_SIDE_COMPILE
+ // The session is definitely not open at this point.
+ ResetEvent(m_hSessionOpenEvent);
+#endif // RIGHT_SIDE_COMPILE
+
+ // Close the connection if we haven't done so already.
+ m_pipe.Disconnect();
+
+ // Drain any remaining entries in the send queue (aborting them when they need completions).
+ {
+ TransportLockHolder sLockHolder(&m_sStateLock);
+
+ Message *pMsg;
+ while ((pMsg = m_pSendQueueFirst) != NULL)
+ {
+ // Remove message from the queue.
+ m_pSendQueueFirst = pMsg->m_pNext;
+
+ // Determine whether the message needs to be deleted by us before we signal any completion (because
+ // once we signal the completion pMsg might become invalid immediately if it's not a copy).
+ bool fMustDelete = pMsg->m_pOrigMessage != pMsg;
+
+ // If there's a waiter (i.e. we don't own the message) it know that the operation didn't really
+ // complete, it was aborted.
+ if (!fMustDelete)
+ pMsg->m_pOrigMessage->m_fAborted = true;
+
+ // Determine how to complete the message.
+ switch (pMsg->m_sHeader.m_eType)
+ {
+ case MT_SessionRequest:
+ case MT_SessionAccept:
+ case MT_SessionReject:
+ case MT_SessionResync:
+ case MT_SessionClose:
+ _ASSERTE(!"Session management messages should not be on send queue");
+ break;
+
+ case MT_Event:
+ break;
+
+#ifdef RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ case MT_WriteMemory:
+ case MT_VirtualUnwind:
+ case MT_GetDCB:
+ case MT_SetDCB:
+ case MT_GetAppDomainCB:
+ // On the RS these are the original requests. Signal the completion event.
+ SignalReplyEvent(pMsg);
+ break;
+#else // RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ case MT_WriteMemory:
+ case MT_VirtualUnwind:
+ case MT_GetDCB:
+ case MT_SetDCB:
+ case MT_GetAppDomainCB:
+ // On the LS these are replies to the original request. Nobody's waiting on these.
+ break;
+#endif // RIGHT_SIDE_COMPILE
+
+ default:
+ _ASSERTE(!"Unknown message type");
+ }
+
+ // If the message was a copy, deallocate the resources now.
+ if (fMustDelete)
+ {
+ if (pMsg->m_pbDataBlock)
+ delete [] pMsg->m_pbDataBlock;
+ delete pMsg;
+ }
+ }
+ } // Leave m_sStateLock
+
+ // Now release all the resources allocated for the transport now that the
+ // worker thread isn't using them anymore.
+ Release();
+}
+
+// Given a fully initialized debugger event structure, return the size of the structure in bytes (this is not
+// trivial since DebuggerIPCEvent contains a large union member which can cause the portion containing
+// significant data to vary wildy from event to event).
+DWORD DbgTransportSession::GetEventSize(DebuggerIPCEvent *pEvent)
+{
+ DWORD cbBaseSize = offsetof(DebuggerIPCEvent, LeftSideStartupData);
+ DWORD cbAdditionalSize = 0;
+
+ switch (pEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_SYNC_COMPLETE:
+ case DB_IPCE_THREAD_ATTACH:
+ case DB_IPCE_THREAD_DETACH:
+ case DB_IPCE_USER_BREAKPOINT:
+ case DB_IPCE_EXIT_APP_DOMAIN:
+ case DB_IPCE_SET_DEBUG_STATE_RESULT:
+ case DB_IPCE_FUNC_EVAL_ABORT_RESULT:
+ case DB_IPCE_CONTROL_C_EVENT:
+ case DB_IPCE_FUNC_EVAL_CLEANUP_RESULT:
+ case DB_IPCE_SET_METHOD_JMC_STATUS_RESULT:
+ case DB_IPCE_SET_MODULE_JMC_STATUS_RESULT:
+ case DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT:
+ case DB_IPCE_INTERCEPT_EXCEPTION_RESULT:
+ case DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE:
+ case DB_IPCE_CREATE_PROCESS:
+ case DB_IPCE_SET_NGEN_COMPILER_FLAGS_RESULT:
+ case DB_IPCE_LEFTSIDE_STARTUP:
+ case DB_IPCE_ASYNC_BREAK:
+ case DB_IPCE_CONTINUE:
+ case DB_IPCE_ATTACHING:
+ case DB_IPCE_GET_NGEN_COMPILER_FLAGS:
+ case DB_IPCE_DETACH_FROM_PROCESS:
+ case DB_IPCE_CONTROL_C_EVENT_RESULT:
+ cbAdditionalSize = 0;
+ break;
+
+ case DB_IPCE_BREAKPOINT:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_LOAD_MODULE:
+ cbAdditionalSize = sizeof(pEvent->LoadModuleData);
+ break;
+
+ case DB_IPCE_UNLOAD_MODULE:
+ cbAdditionalSize = sizeof(pEvent->UnloadModuleData);
+ break;
+
+ case DB_IPCE_LOAD_CLASS:
+ cbAdditionalSize = sizeof(pEvent->LoadClass);
+ break;
+
+ case DB_IPCE_UNLOAD_CLASS:
+ cbAdditionalSize = sizeof(pEvent->UnloadClass);
+ break;
+
+ case DB_IPCE_EXCEPTION:
+ cbAdditionalSize = sizeof(pEvent->Exception);
+ break;
+
+ case DB_IPCE_BREAKPOINT_ADD_RESULT:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_STEP_RESULT:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ if (pEvent->StepData.rangeCount)
+ cbAdditionalSize += (pEvent->StepData.rangeCount - 1) * sizeof(COR_DEBUG_STEP_RANGE);
+ break;
+
+ case DB_IPCE_STEP_COMPLETE:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ break;
+
+ case DB_IPCE_GET_BUFFER_RESULT:
+ cbAdditionalSize = sizeof(pEvent->GetBufferResult);
+ break;
+
+ case DB_IPCE_RELEASE_BUFFER_RESULT:
+ cbAdditionalSize = sizeof(pEvent->ReleaseBufferResult);
+ break;
+
+ case DB_IPCE_ENC_ADD_FIELD:
+ cbAdditionalSize = sizeof(pEvent->EnCUpdate);
+ break;
+
+ case DB_IPCE_APPLY_CHANGES_RESULT:
+ cbAdditionalSize = sizeof(pEvent->ApplyChangesResult);
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ cbAdditionalSize = sizeof(pEvent->FirstLogMessage);
+ break;
+
+ case DB_IPCE_LOGSWITCH_SET_MESSAGE:
+ cbAdditionalSize = sizeof(pEvent->LogSwitchSettingMessage);
+ break;
+
+ case DB_IPCE_CREATE_APP_DOMAIN:
+ cbAdditionalSize = sizeof(pEvent->AppDomainData);
+ break;
+
+ case DB_IPCE_LOAD_ASSEMBLY:
+ cbAdditionalSize = sizeof(pEvent->AssemblyData);
+ break;
+
+ case DB_IPCE_UNLOAD_ASSEMBLY:
+ cbAdditionalSize = sizeof(pEvent->AssemblyData);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_SETUP_RESULT:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalSetupComplete);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_COMPLETE:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalComplete);
+ break;
+
+ case DB_IPCE_SET_REFERENCE_RESULT:
+ cbAdditionalSize = sizeof(pEvent->SetReference);
+ break;
+
+ case DB_IPCE_NAME_CHANGE:
+ cbAdditionalSize = sizeof(pEvent->NameChange);
+ break;
+
+ case DB_IPCE_UPDATE_MODULE_SYMS:
+ cbAdditionalSize = sizeof(pEvent->UpdateModuleSymsData);
+ break;
+
+ case DB_IPCE_ENC_REMAP:
+ cbAdditionalSize = sizeof(pEvent->EnCRemap);
+ break;
+
+ case DB_IPCE_SET_VALUE_CLASS_RESULT:
+ cbAdditionalSize = sizeof(pEvent->SetValueClass);
+ break;
+
+ case DB_IPCE_BREAKPOINT_SET_ERROR:
+ cbAdditionalSize = sizeof(pEvent->BreakpointSetErrorData);
+ break;
+
+ case DB_IPCE_ENC_UPDATE_FUNCTION:
+ cbAdditionalSize = sizeof(pEvent->EnCUpdate);
+ break;
+
+ case DB_IPCE_GET_METHOD_JMC_STATUS_RESULT:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_GET_THREAD_FOR_TASKID_RESULT:
+ cbAdditionalSize = sizeof(pEvent->GetThreadForTaskIdResult);
+ break;
+
+ case DB_IPCE_CREATE_CONNECTION:
+ cbAdditionalSize = sizeof(pEvent->CreateConnection);
+ break;
+
+ case DB_IPCE_DESTROY_CONNECTION:
+ cbAdditionalSize = sizeof(pEvent->ConnectionChange);
+ break;
+
+ case DB_IPCE_CHANGE_CONNECTION:
+ cbAdditionalSize = sizeof(pEvent->ConnectionChange);
+ break;
+
+ case DB_IPCE_EXCEPTION_CALLBACK2:
+ cbAdditionalSize = sizeof(pEvent->ExceptionCallback2);
+ break;
+
+ case DB_IPCE_EXCEPTION_UNWIND:
+ cbAdditionalSize = sizeof(pEvent->ExceptionUnwind);
+ break;
+
+ case DB_IPCE_CREATE_HANDLE_RESULT:
+ cbAdditionalSize = sizeof(pEvent->CreateHandleResult);
+ break;
+
+ case DB_IPCE_ENC_REMAP_COMPLETE:
+ cbAdditionalSize = sizeof(pEvent->EnCRemapComplete);
+ break;
+
+ case DB_IPCE_ENC_ADD_FUNCTION:
+ cbAdditionalSize = sizeof(pEvent->EnCUpdate);
+ break;
+
+ case DB_IPCE_GET_NGEN_COMPILER_FLAGS_RESULT:
+ cbAdditionalSize = sizeof(pEvent->JitDebugInfo);
+ break;
+
+ case DB_IPCE_MDA_NOTIFICATION:
+ cbAdditionalSize = sizeof(pEvent->MDANotification);
+ break;
+
+ case DB_IPCE_GET_GCHANDLE_INFO_RESULT:
+ cbAdditionalSize = sizeof(pEvent->GetGCHandleInfoResult);
+ break;
+
+ case DB_IPCE_SET_IP:
+ cbAdditionalSize = sizeof(pEvent->SetIP);
+ break;
+
+ case DB_IPCE_BREAKPOINT_ADD:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_BREAKPOINT_REMOVE:
+ cbAdditionalSize = sizeof(pEvent->BreakpointData);
+ break;
+
+ case DB_IPCE_STEP_CANCEL:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ break;
+
+ case DB_IPCE_STEP:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ if (pEvent->StepData.rangeCount)
+ cbAdditionalSize += (pEvent->StepData.rangeCount - 1) * sizeof(COR_DEBUG_STEP_RANGE);
+ break;
+
+ case DB_IPCE_STEP_OUT:
+ cbAdditionalSize = sizeof(pEvent->StepData);
+ break;
+
+ case DB_IPCE_GET_BUFFER:
+ cbAdditionalSize = sizeof(pEvent->GetBuffer);
+ break;
+
+ case DB_IPCE_RELEASE_BUFFER:
+ cbAdditionalSize = sizeof(pEvent->ReleaseBuffer);
+ break;
+
+ case DB_IPCE_SET_CLASS_LOAD_FLAG:
+ cbAdditionalSize = sizeof(pEvent->SetClassLoad);
+ break;
+
+ case DB_IPCE_APPLY_CHANGES:
+ cbAdditionalSize = sizeof(pEvent->ApplyChanges);
+ break;
+
+ case DB_IPCE_SET_NGEN_COMPILER_FLAGS:
+ cbAdditionalSize = sizeof(pEvent->JitDebugInfo);
+ break;
+
+ case DB_IPCE_IS_TRANSITION_STUB:
+ cbAdditionalSize = sizeof(pEvent->IsTransitionStub);
+ break;
+
+ case DB_IPCE_IS_TRANSITION_STUB_RESULT:
+ cbAdditionalSize = sizeof(pEvent->IsTransitionStubResult);
+ break;
+
+ case DB_IPCE_MODIFY_LOGSWITCH:
+ cbAdditionalSize = sizeof(pEvent->LogSwitchSettingMessage);
+ break;
+
+ case DB_IPCE_ENABLE_LOG_MESSAGES:
+ cbAdditionalSize = sizeof(pEvent->LogSwitchSettingMessage);
+ break;
+
+ case DB_IPCE_FUNC_EVAL:
+ cbAdditionalSize = sizeof(pEvent->FuncEval);
+ break;
+
+ case DB_IPCE_SET_REFERENCE:
+ cbAdditionalSize = sizeof(pEvent->SetReference);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_ABORT:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalAbort);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_CLEANUP:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalCleanup);
+ break;
+
+ case DB_IPCE_SET_ALL_DEBUG_STATE:
+ cbAdditionalSize = sizeof(pEvent->SetAllDebugState);
+ break;
+
+ case DB_IPCE_SET_VALUE_CLASS:
+ cbAdditionalSize = sizeof(pEvent->SetValueClass);
+ break;
+
+ case DB_IPCE_SET_METHOD_JMC_STATUS:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_GET_METHOD_JMC_STATUS:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_SET_MODULE_JMC_STATUS:
+ cbAdditionalSize = sizeof(pEvent->SetJMCFunctionStatus);
+ break;
+
+ case DB_IPCE_GET_THREAD_FOR_TASKID:
+ cbAdditionalSize = sizeof(pEvent->GetThreadForTaskId);
+ break;
+
+ case DB_IPCE_FUNC_EVAL_RUDE_ABORT:
+ cbAdditionalSize = sizeof(pEvent->FuncEvalRudeAbort);
+ break;
+
+ case DB_IPCE_CREATE_HANDLE:
+ cbAdditionalSize = sizeof(pEvent->CreateHandle);
+ break;
+
+ case DB_IPCE_DISPOSE_HANDLE:
+ cbAdditionalSize = sizeof(pEvent->DisposeHandle);
+ break;
+
+ case DB_IPCE_INTERCEPT_EXCEPTION:
+ cbAdditionalSize = sizeof(pEvent->InterceptException);
+ break;
+
+ case DB_IPCE_GET_GCHANDLE_INFO:
+ cbAdditionalSize = sizeof(pEvent->GetGCHandleInfo);
+ break;
+
+ case DB_IPCE_CUSTOM_NOTIFICATION:
+ cbAdditionalSize = sizeof(pEvent->CustomNotification);
+ break;
+
+ default:
+ printf("Unknown debugger event type: 0x%x\n", (pEvent->type & DB_IPCE_TYPE_MASK));
+ _ASSERTE(!"Unknown debugger event type");
+ }
+
+ return cbBaseSize + cbAdditionalSize;
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+#ifdef _DEBUG
+// Debug helper which returns the name associated with a MessageType.
+const char *DbgTransportSession::MessageName(MessageType eType)
+{
+ switch (eType)
+ {
+ case MT_SessionRequest:
+ return "SessionRequest";
+ case MT_SessionAccept:
+ return "SessionAccept";
+ case MT_SessionReject:
+ return "SessionReject";
+ case MT_SessionResync:
+ return "SessionResync";
+ case MT_SessionClose:
+ return "SessionClose";
+ case MT_Event:
+ return "Event";
+ case MT_ReadMemory:
+ return "ReadMemory";
+ case MT_WriteMemory:
+ return "WriteMemory";
+ case MT_VirtualUnwind:
+ return "VirtualUnwind";
+ case MT_GetDCB:
+ return "GetDCB";
+ case MT_SetDCB:
+ return "SetDCB";
+ case MT_GetAppDomainCB:
+ return "GetAppDomainCB";
+ default:
+ _ASSERTE(!"Unknown message type");
+ return NULL;
+ }
+}
+
+// Debug logging helper which logs an incoming message of any type (as long as logging for that message
+// class is currently enabled).
+void DbgTransportSession::DbgTransportLogMessageReceived(MessageHeader *pHeader)
+{
+ switch (pHeader->m_eType)
+ {
+ case MT_SessionRequest:
+ DbgTransportLog(LC_Session, "Received 'SessionRequest'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionRequest);
+ return;
+ case MT_SessionAccept:
+ DbgTransportLog(LC_Session, "Received 'SessionAccept'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionAccept);
+ return;
+ case MT_SessionReject:
+ DbgTransportLog(LC_Session, "Received 'SessionReject'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionReject);
+ return;
+ case MT_SessionResync:
+ DbgTransportLog(LC_Session, "Received 'SessionResync'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionResync);
+ return;
+ case MT_SessionClose:
+ DbgTransportLog(LC_Session, "Received 'SessionClose'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSessionClose);
+ return;
+ case MT_Event:
+ DbgTransportLog(LC_Events, "Received '%s'",
+ IPCENames::GetName((DebuggerIPCEventType)(DWORD)pHeader->TypeSpecificData.Event.m_eType));
+ DBG_TRANSPORT_INC_STAT(ReceivedEvent);
+ return;
+#ifdef RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ DbgTransportLog(LC_Requests, "Received 'ReadMemory(0x%08X, %u)' reply",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedReadMemory);
+ return;
+ case MT_WriteMemory:
+ DbgTransportLog(LC_Requests, "Received 'WriteMemory(0x%08X, %u)' reply",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedWriteMemory);
+ return;
+ case MT_VirtualUnwind:
+ DbgTransportLog(LC_Requests, "Received 'VirtualUnwind' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedVirtualUnwind);
+ return;
+ case MT_GetDCB:
+ DbgTransportLog(LC_Requests, "Received 'GetDCB' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetDCB);
+ return;
+ case MT_SetDCB:
+ DbgTransportLog(LC_Requests, "Received 'SetDCB' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedSetDCB);
+ return;
+ case MT_GetAppDomainCB:
+ DbgTransportLog(LC_Requests, "Received 'GetAppDomainCB' reply");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetAppDomainCB);
+ return;
+#else // RIGHT_SIDE_COMPILE
+ case MT_ReadMemory:
+ DbgTransportLog(LC_Requests, "Received 'ReadMemory(0x%08X, %u)'",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedReadMemory);
+ return;
+ case MT_WriteMemory:
+ DbgTransportLog(LC_Requests, "Received 'WriteMemory(0x%08X, %u)'",
+ (PBYTE)pHeader->TypeSpecificData.MemoryAccess.m_pbLeftSideBuffer,
+ (DWORD)pHeader->TypeSpecificData.MemoryAccess.m_cbLeftSideBuffer);
+ DBG_TRANSPORT_INC_STAT(ReceivedWriteMemory);
+ return;
+ case MT_VirtualUnwind:
+ DbgTransportLog(LC_Requests, "Received 'VirtualUnwind'");
+ DBG_TRANSPORT_INC_STAT(ReceivedVirtualUnwind);
+ return;
+ case MT_GetDCB:
+ DbgTransportLog(LC_Requests, "Received 'GetDCB'");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetDCB);
+ return;
+ case MT_SetDCB:
+ DbgTransportLog(LC_Requests, "Received 'SetDCB'");
+ DBG_TRANSPORT_INC_STAT(ReceivedSetDCB);
+ return;
+ case MT_GetAppDomainCB:
+ DbgTransportLog(LC_Requests, "Received 'GetAppDomainCB'");
+ DBG_TRANSPORT_INC_STAT(ReceivedGetAppDomainCB);
+ return;
+#endif // RIGHT_SIDE_COMPILE
+ default:
+ _ASSERTE(!"Unknown message type");
+ return;
+ }
+}
+
+static CLRRandom s_faultInjectionRandom;
+
+// Helper method used by the DBG_TRANSPORT_SHOULD_INJECT_FAULT macro.
+bool DbgTransportSession::DbgTransportShouldInjectFault(DbgTransportFaultOp eOp, const char *szOpName)
+{
+ static DWORD s_dwFaultInjection = 0xffffffff;
+
+ // Init the fault injection system if that hasn't already happened.
+ if (s_dwFaultInjection == 0xffffffff)
+ {
+ s_dwFaultInjection = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTransportFaultInject);
+
+ // Try for repeatable failures here by always initializing the random seed to a fixed value. But use
+ // different seeds for the left and right sides or they'll end up in lock step. The
+ // DBG_TRANSPORT_FAULT_THIS_SIDE macro is a convenient integer value that differs on each side.
+ s_faultInjectionRandom.Init(DBG_TRANSPORT_FAULT_THIS_SIDE);
+
+ // Clamp failure rate to a permissable value.
+ if ((s_dwFaultInjection & DBG_TRANSPORT_FAULT_RATE_MASK) > 99)
+ s_dwFaultInjection = (s_dwFaultInjection & ~DBG_TRANSPORT_FAULT_RATE_MASK) | 99;
+ }
+
+ // Map current session state into the bitmask format used for fault injection control.
+ DWORD dwState = 0;
+ switch (m_eState)
+ {
+ case SS_Opening_NC:
+ case SS_Opening:
+ dwState = FS_Opening;
+ break;
+ case SS_Resync_NC:
+ case SS_Resync:
+ dwState = FS_Resync;
+ break;
+ case SS_Open:
+ dwState = FS_Open;
+ break;
+ case SS_Closed:
+ break;
+ default:
+ _ASSERTE(!"Bad session state");
+ }
+
+ if ((s_dwFaultInjection & DBG_TRANSPORT_FAULT_THIS_SIDE) &&
+ (s_dwFaultInjection & eOp) &&
+ (s_dwFaultInjection & dwState))
+ {
+ // We're faulting this side, op and state. Roll the dice and see if this particular call should fail.
+ DWORD dwChance = s_faultInjectionRandom.Next(100);
+ if (dwChance < (s_dwFaultInjection & DBG_TRANSPORT_FAULT_RATE_MASK))
+ {
+ DbgTransportLog(LC_FaultInject, "Injected fault for %s operation", szOpName);
+#if defined(FEATURE_CORESYSTEM)
+ // not supported
+#else
+ WSASetLastError(WSAEFAULT);
+#endif // defined(FEATURE_CORESYSTEM)
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // _DEBUG
+
+// Lock abstraction code (hides difference in lock implementation between left and right side).
+#ifdef RIGHT_SIDE_COMPILE
+
+// On the right side we use a CRITICAL_SECTION.
+
+void DbgTransportLock::Init()
+{
+ InitializeCriticalSection(&m_sLock);
+}
+
+void DbgTransportLock::Destroy()
+{
+ DeleteCriticalSection(&m_sLock);
+}
+
+void DbgTransportLock::Enter()
+{
+ EnterCriticalSection(&m_sLock);
+}
+
+void DbgTransportLock::Leave()
+{
+ LeaveCriticalSection(&m_sLock);
+}
+#else // RIGHT_SIDE_COMPILE
+
+// On the left side we use a Crst.
+
+void DbgTransportLock::Init()
+{
+ m_sLock.Init(CrstDbgTransport, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_TAKEN_DURING_SHUTDOWN));
+}
+
+void DbgTransportLock::Destroy()
+{
+}
+
+void DbgTransportLock::Enter()
+{
+ m_sLock.Enter();
+}
+
+void DbgTransportLock::Leave()
+{
+ m_sLock.Leave();
+}
+#endif // RIGHT_SIDE_COMPILE
+
+#endif // (!defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_VM)) || (defined(RIGHT_SIDE_COMPILE) && defined(FEATURE_DBGIPC_TRANSPORT_DI))