summaryrefslogtreecommitdiff
path: root/src/debug/inc/dbgtransportsession.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/inc/dbgtransportsession.h')
-rw-r--r--src/debug/inc/dbgtransportsession.h849
1 files changed, 849 insertions, 0 deletions
diff --git a/src/debug/inc/dbgtransportsession.h b/src/debug/inc/dbgtransportsession.h
new file mode 100644
index 0000000000..5187202753
--- /dev/null
+++ b/src/debug/inc/dbgtransportsession.h
@@ -0,0 +1,849 @@
+// 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.
+
+
+#ifndef __DBG_TRANSPORT_SESSION_INCLUDED
+#define __DBG_TRANSPORT_SESSION_INCLUDED
+
+#ifndef RIGHT_SIDE_COMPILE
+#include <utilcode.h>
+#include <crst.h>
+
+#endif // !RIGHT_SIDE_COMPILE
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+#include <twowaypipe.h>
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ DbgTransportSession was originally designed around cross-machine debugging via sockets and it is supposed to
+ handle network interruptions. Right now we use pipes (see TwoWaypipe) and don't expect to have connection issues.
+ But there seem to be no good reason to try hard to get rid of existing working protocol even if it's a bit
+ cautious about connection quality. So please KEEP IN MIND THAT SOME COMMENTS REFERING TO NETWORK AND SOCKETS
+ CAN BE OUTDATED.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+//
+// Provides a robust and secure transport session between a debugger and a debuggee that are potentially on
+// different machines.
+//
+// The following terminology is used for the wire protocol. The smallest meaningful entity written to or read
+// from the connection is a "message". This consists of one or maybe two "blocks" where a block is a
+// contiguous region of memory in the host machine. The first block is always a "message header" which is
+// fixed size (allowing the receiver to know how many bytes to read off the stream oriented connection) and
+// has type codes and other fields which the receiver can use to determine if another block is part of the
+// message (and if so, exactly how large that block is). Many management messages consist only of a message
+// header block, while operations such as sending a debugger event structure involve a message header followed
+// by a block containing the actual event structure.
+//
+// Message acknowledgement (sometimes abbreviated to ack) refers to a system of marking all messages with an
+// ID and noting and reporting which IDs we've seen from our peer. We piggy back the highest seen ID on all
+// outgoing messages and this is used by the infrastructure to communicate the fact that a sender can release
+// its copy of an outbound message since it successfully made it across the communications channel and won't
+// need to be resent in the case of a network failure.
+//
+// This file uses the debugger conventions for naming the two endpoints of the session: the left side or LS is
+// the side with the runtime while the right side (RS) is the side with the debugger.
+//
+
+// The structure of this file necessitates a certain number of forward references (particularly in the
+// comments). If you see a term you don't understand please do a search for it further down the file, where
+// hopefully you will find a detailed definition (and if not, please add one).
+
+struct DebuggerIPCEvent;
+struct DbgEventBufferEntry;
+
+// Some simple ad-hoc debug only transport logging. This output is too chatty for an exisitng CLR logging
+// channel (and we've run out of bits for an additional channel) and is likely to be of limited use to anyone
+// besides the transport developer (and even then only occasionally).
+//
+// To enable use 'set|export COMPlus_DbgTransportLog=X' where X is 1 for RS logging, 2 for LS logging and 3
+// for both (default is disabled). Use 'set|export COMPlus_DbgTransportLogClass=X' where X is the hex
+// representation of one or more DbgTransportLogClass flags defined below (default is all classes enabled).
+// For instance, 'set COMPlus_DbgTransportLogClass=f' will enable only message send and receive logging (for
+// all message types).
+enum DbgTransportLogEnable
+{
+ LE_None = 0x00000000,
+ LE_LeftSide = 0x00000001,
+ LE_RightSide = 0x00000002,
+ LE_Unknown = 0xffffffff,
+};
+
+enum DbgTransportLogClass
+{
+ LC_None = 0x00000000,
+ LC_Events = 0x00000001, // Sending and receiving debugger events
+ LC_Session = 0x00000002, // Sending and receiving session messages
+ LC_Requests = 0x00000004, // Sending requests such as MT_GetDCB and receiving replies
+ LC_EventAcks = 0x00000008, // Sending and receiving debugger event acks (DEPRECATED)
+ LC_NetErrors = 0x00000010, // Network errors
+ LC_FaultInject = 0x00000020, // Artificially injected network faults
+ LC_Proxy = 0x00000040, // Proxy interactions
+ LC_All = 0xffffffff,
+ LC_Always = 0xffffffff, // Always log, regardless of class setting
+};
+
+// Status codes that can be returned by various APIs that indicate some conditions of the error that a caller
+// might usefully pass on to a user (environmental factors that the user might have some control over).
+enum ConnStatus
+{
+ SCS_Success, // The request succeeded
+ SCS_OutOfMemory, // The request failed due to a low memory situation
+ SCS_InvalidConfiguration, // Initialize() failed because the debugger settings were not configured or
+ // have become corrupt
+ SCS_UnknownTarget, // Connect() failed because the remote machine at the given address could not
+ // be found
+ SCS_NoListener, // Connect() failed because the remote machine was not listening for requests
+ // on the given port (most likely the remote machine is not configured for
+ // debugging)
+ SCS_NetworkFailure, // Connect() failed due to miscellaneous network error
+ SCS_MismatchedCerts, // Connect()/Accept() failed because the remote party was using a different
+ // cert
+};
+
+
+// Multiple clients can use a single DbgTransportSession, but only one can act as the debugger.
+// A valid DebugTicket is given to the client who is acting as the debugger.
+struct DebugTicket
+{
+friend class DbgTransportSession;
+
+public:
+ DebugTicket() { m_fValid = false; };
+
+ bool IsValid() { return m_fValid; };
+
+protected:
+ void SetValid() { m_fValid = true; };
+ void SetInvalid() { m_fValid = false; };
+
+private:
+ // Tickets can't be copied around. Hide these definitions so as to enforce that.
+ // We still need the Copy ctor so that it can be passed in as a parameter.
+ void operator=(DebugTicket & other);
+
+ bool m_fValid;
+};
+
+#ifdef RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_LOG_THIS_SIDE LE_RightSide
+#define DBG_TRANSPORT_LOG_PREFIX "RS"
+#else // RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_LOG_THIS_SIDE LE_LeftSide
+#define DBG_TRANSPORT_LOG_PREFIX "LS"
+#endif // RIGHT_SIDE_COMPILE
+
+// Method used to log an interesting event (of the given class). The message given will have any additional
+// arguments inserted following 'printf' formatiing conventions and will be automatically prepended with a
+// LS/RS indicator and suffixed with a newline.
+inline void DbgTransportLog(DbgTransportLogClass eClass, const char *szFormat, ...)
+{
+#ifdef _DEBUG
+ static DWORD s_dwLoggingEnabled = LE_Unknown;
+ static DWORD s_dwLoggingClass = LC_All;
+
+ if (s_dwLoggingEnabled == LE_Unknown)
+ {
+ s_dwLoggingEnabled = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLog, LE_None);
+ s_dwLoggingClass = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLogClass, LC_All);
+ }
+
+ if ((s_dwLoggingEnabled & DBG_TRANSPORT_LOG_THIS_SIDE) &&
+ ((s_dwLoggingClass & eClass) || eClass == LC_Always))
+ {
+ char szOutput[256];
+ va_list args;
+
+ va_start(args, szFormat);
+ vsprintf_s(szOutput, sizeof(szOutput), szFormat, args);
+ va_end(args);
+
+ printf("%s %04x: %s\n", DBG_TRANSPORT_LOG_PREFIX, GetCurrentThreadId(), szOutput);
+ fflush(stdout);
+
+ char szDebugOutput[512];
+ sprintf_s(szDebugOutput, sizeof(szDebugOutput), "%s: %s\n", DBG_TRANSPORT_LOG_PREFIX, szOutput);
+ OutputDebugStringA(szDebugOutput);
+ }
+#endif // _DEBUG
+}
+
+#ifdef _DEBUG
+//
+// Debug-only network fault injection (in order to help test the robust session code). Control is via a single
+// DWORD read from the environment (COMPlus_DbgTransportFaultInject). This DWORD is treated as a set of bit
+// fields as follows:
+//
+// +-------+-------+-------+----------------+-----------+
+// | Side | Op | State | Reserved | Frequency |
+// +-------+-------+-------+----------------+-----------+
+// 31<->28 27<->24 23<->20 19<----------->8 7<------->0
+//
+// The 'Side' field indicates whether the left or right side (or both) should have faults injected. See
+// DbgTransportFaultSide below for values.
+//
+// The 'Op' field indicates which connection methods should simulate failures. See DbgTransportFaultOp.
+//
+// The 'State' field indicates the session states in which faults will be injected. See
+// DbgTransportFaultState. Note that introducing too many failures into the Opening and Opening_NC states will
+// cause the debugger to timeout and fail.
+//
+// The 'Reserved' field has no current meaning and should be left as zero.
+//
+// The 'Frequency' field indicates a percentage failure rate. Valid values are between 0 and 99, values beyond
+// this range will be clamped to 99.
+//
+// For example:
+//
+// export COMPlus_DbgTransportFaultInject=1ff00001
+// --> Fail all network operations on the left side 1% of the time
+//
+// export COMPlus_DbgTransportFaultInject=34200063
+// --> Fail Send() calls on both sides while the session is Open 99% of the time
+//
+
+#define DBG_TRANSPORT_FAULT_RATE_MASK 0x000000ff
+
+// Whether to inject faults to the left, right or both sides.
+enum DbgTransportFaultSide
+{
+ FS_Left = 0x10000000,
+ FS_Right = 0x20000000,
+};
+
+// Network operations which are candiates for fault injection.
+enum DbgTransportFaultOp
+{
+ FO_Connect = 0x01000000,
+ FO_Accept = 0x02000000,
+ FO_Send = 0x04000000,
+ FO_Receive = 0x08000000,
+};
+
+// Session states into which faults should be injected.
+enum DbgTransportFaultState
+{
+ FS_Opening = 0x00100000, // Opening and Opening_NC
+ FS_Open = 0x00200000,
+ FS_Resync = 0x00400000, // Resync and Resync_NC
+};
+
+#ifdef RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Right
+#else // RIGHT_SIDE_COMPILE
+#define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Left
+#endif // RIGHT_SIDE_COMPILE
+
+// Macro to determine whether a fault should be injected for the given operation.
+#define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) DbgTransportShouldInjectFault(FO_##_op, #_op)
+
+#else // _DEBUG
+#define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) false
+#endif // _DEBUG
+
+// The PAL doesn't define htons (host-to-network-short) and friends. So provide our own versions here.
+// winsock2.h defines BIGENDIAN to 0x0000 and LITTLEENDIAN to 0x0001, so we need to be careful with the
+// #ifdef.
+#if BIGENDIAN > 0
+#define DBGIPC_HTONS(x) (x)
+#define DBGIPC_NTOHS(x) (x)
+#define DBGIPC_HTONL(x) (x)
+#define DBGIPC_NTOHL(x) (x)
+#else
+inline UINT16 DBGIPC_HTONS(UINT16 x)
+{
+ return (x >> 8) | (x << 8);
+}
+#define DBGIPC_NTOHS(x) DBGIPC_HTONS(x)
+inline UINT32 DBGIPC_HTONL(UINT32 x)
+{
+ return (x >> 24) |
+ ((x >> 8) & 0x0000FF00L) |
+ ((x & 0x0000FF00L) << 8) |
+ (x << 24);
+}
+#define DBGIPC_NTOHL(x) DBGIPC_HTONL(x)
+
+#endif
+
+// Lock abstraction (we can't use the same lock implementation on LS and RS since we really want a Crst on the
+// LS and this isn't available in the RS environment).
+class DbgTransportLock
+{
+public:
+ void Init();
+ void Destroy();
+ void Enter();
+ void Leave();
+
+private:
+#ifdef RIGHT_SIDE_COMPILE
+ CRITICAL_SECTION m_sLock;
+#else // RIGHT_SIDE_COMPILE
+ CrstExplicitInit m_sLock;
+#endif // RIGHT_SIDE_COMPILE
+};
+
+// The transport has only one queue for IPC events, but each IPC event can be marked as one of two types.
+// The transport will signal the handle corresponding to the type of each IPC event. (See
+// code:DbgTransportSession::GetIPCEventReadyEvent and code:DbgTransportSession::GetDebugEventReadyEvent.)
+// This is effectively a basic multiplexing scheme. The old-style IPC event are for all RS-to-LS IPC events
+// and for all LS-to-RS replies. The other type is for LS-to-RS IPC events transported over the native
+// pipeline. For more information, see the comments for the interface code:IEventChannel.
+enum IPCEventType
+{
+ IPCET_OldStyle,
+ IPCET_DebugEvent,
+ IPCET_Max,
+};
+
+// The class that encapsulates all the state for a single session on either the right or left side. The left
+// side supports only one instance of this class for a given runtime. The right side can support several (all
+// connected to different LS instances of course).
+class DbgTransportSession
+{
+public:
+ // No real work done in the constructor. Use Init() instead.
+ DbgTransportSession();
+
+ // Cleanup what is allocated/created in Init()
+ ~DbgTransportSession();
+
+ // 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() 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 Init(DWORD pid, HANDLE hProcessExited);
+#else
+ HRESULT Init(DebuggerIPCControlBlock * pDCB, AppDomainEnumerationIPCBlock * pADB);
+#endif // RIGHT_SIDE_COMPILE
+
+ // 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 Shutdown();
+
+#ifdef 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 CleanupTargetProcess();
+#else
+ // 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 AbortConnection();
+#endif // RIGHT_SIDE_COMPILE
+
+ LONG AddRef()
+ {
+ LONG ref = InterlockedIncrement(&m_ref);
+ return ref;
+ }
+
+ LONG Release()
+ {
+ _ASSERTE(m_ref > 0);
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+ return ref;
+ }
+
+#ifndef RIGHT_SIDE_COMPILE
+ // 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 Neuter();
+#endif // !RIGHT_SIDE_COMPILE
+
+#ifdef RIGHT_SIDE_COMPILE
+ // 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 WaitForSessionToOpen(DWORD dwTimeout);
+
+ // A valid ticket is returned if no other client is currently acting as the debugger.
+ bool UseAsDebugger(DebugTicket * pTicket);
+
+ // A valid ticket is required in order for this function to succeed. After this function succeeds,
+ // another client can request to be the debugger.
+ bool StopUsingAsDebugger(DebugTicket * pTicket);
+#endif // RIGHT_SIDE_COMPILE
+
+ // Sends a pre-initialized event to the other side.
+ HRESULT SendEvent(DebuggerIPCEvent * pEvent);
+ HRESULT SendDebugEvent(DebuggerIPCEvent * pEvent);
+
+ // Retrieves the auto-reset handle which is signalled by the session each time a new event is received
+ // from the other side.
+ HANDLE GetIPCEventReadyEvent();
+ HANDLE GetDebugEventReadyEvent();
+
+ // Copies the last event received from the other side into the provided buffer. This should only be called
+ // (once) after the event returned from GetIPCEventReadyEvent()/GetDebugEventReadyEvent() has been signalled.
+ void GetNextEvent(DebuggerIPCEvent *pEvent, DWORD cbEvent);
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Read and write memory on the LS from the RS.
+ HRESULT ReadMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer);
+ HRESULT WriteMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer);
+ HRESULT VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context);
+
+ // Read and write the debugger control block on the LS from the RS.
+ HRESULT GetDCB(DebuggerIPCControlBlock *pDCB);
+ HRESULT SetDCB(DebuggerIPCControlBlock *pDCB);
+
+ // Read the AppDomain control block on the LS from the RS.
+ HRESULT GetAppDomainCB(AppDomainEnumerationIPCBlock *pADB);
+
+#endif // RIGHT_SIDE_COMPILE
+
+private:
+
+ // Highest protocol version supported by this side of the session. See the
+ // m_dwMajorVersion/m_dwMinorVersion fields for a detailed explanation and the actual version being used
+ // by the session (if it is formed).
+ static const DWORD kCurrentMajorVersion = 2;
+ static const DWORD kCurrentMinorVersion = 0;
+
+ // Session states. These determine which action is taken on a SendMessage (message is sent, queued or an
+ // error is raised) and which incoming messages are valid.
+ enum SessionState
+ {
+ SS_Closed, // No session and no attempt is being made to form one
+ SS_Opening_NC, // Session is being formed but no connection is established yet
+ SS_Opening, // Session is being formed, the low level connection is in place
+ SS_Open, // Session is fully formed and normal transport messages can be sent and received
+ SS_Resync_NC, // A low level connection error is occurred and we're attempting to re-form the link
+ SS_Resync, // We're trying to resynchronize high level state over the new connection
+ };
+
+ // Types of messages that can be sent over the transport connection.
+ enum MessageType
+ {
+ // Session management operations. These must come first and MT_SessionClose must be last in the group.
+ MT_SessionRequest, // RS -> LS : Request a new session be formed (optionally pass encrypted data key)
+ MT_SessionAccept, // LS -> RS : Accept new session
+ MT_SessionReject, // LS -> RS : Reject new session, give reason
+ MT_SessionResync, // RS <-> LS : Resync broken connection by informing other side which messages must be resent
+ MT_SessionClose, // RS -> LS : Gracefully terminate a session
+
+ // Debugger events.
+ MT_Event, // RS <-> LS : A debugger event is being sent as the data block of the message
+
+ // Misc management operations.
+ MT_ReadMemory, // RS <-> LS : RS wants to read LS memory block (or LS is replying to such a request)
+ MT_WriteMemory, // RS <-> LS : RS wants to write LS memory block (or LS is replying to such a request)
+ MT_VirtualUnwind, // RS <-> LS : RS wants to LS unwind a stack frame (or LS is replying to such a request)
+ MT_GetDCB, // RS <-> LS : RS wants to read LS DCB (or LS is replying to such a request)
+ MT_SetDCB, // RS <-> LS : RS wants to write LS DCB (or LS is replying to such a request)
+ MT_GetAppDomainCB, // RS <-> LS : RS wants to read LS AppDomainCB (or LS is replying to such a request)
+ };
+
+ // Reasons the LS can give for rejecting a session. These codes should *not* be changed other than by
+ // adding reasons to keep versioning possible.
+ enum RejectReason
+ {
+ RR_IncompatibleVersion, // LS doesn't support the major version asked for in the request.
+ RR_AlreadyAttached, // LS already has another session open (LS only supports one session at a time)
+ };
+
+ // Struct that defines the format of a message header block sent on the connection. Note that the size of
+ // this structure and the location/size of the m_eType field must *never* change to allow our versioning
+ // protocol to work properly (in particular any LS must be able to interpret at least the type and version
+ // number of an MT_SessionRequest and reply with a MT_SessionReject that any RS can interpret the type and
+ // version of). To help with this there is a padding field at the end for future expansion (this should be
+ // initialized to zero and not accessed in any other manner).
+ struct MessageHeader
+ {
+ Portable<MessageType> m_eType; // Type of message this is
+ Portable<DWORD> m_cbDataBlock; // Size of data block that immediately follows this header (can be zero)
+ Portable<DWORD> m_dwId; // Message ID assigned by the sender of this message
+ Portable<DWORD> m_dwReplyId; // Message ID that this is a reply to (used by messages such as MT_GetDCB)
+ Portable<DWORD> m_dwLastSeenId; // Message ID last seen by sender (receiver can discard up to here from send queue)
+ Portable<DWORD> m_dwReserved; // Reserved for future expansion (must be initialized to zero and
+ // never read)
+
+ // The rest of the header varies depending on the message type (keep the maximum size of this union
+ // small since all messages will pay the overhead, large message type specific data should go in the
+ // following data block).
+ union
+ {
+ // Used by MT_SessionRequest / MT_SessionAccept.
+ struct
+ {
+ Portable<DWORD> m_dwMajorVersion; // Protocol version requested/accepted
+ Portable<DWORD> m_dwMinorVersion;
+ } VersionInfo;
+
+ // Used by MT_SessionReject.
+ struct
+ {
+ Portable<RejectReason> m_eReason; // Reason for rejection.
+ Portable<DWORD> m_dwMajorVersion; // Highest protocol version the LS supports
+ Portable<DWORD> m_dwMinorVersion;
+ } SessionReject;
+
+ // Used by MT_ReadMemory and MT_WriteMemory.
+ struct
+ {
+ Portable<PBYTE> m_pbLeftSideBuffer; // Address of memory to read/write on the LS
+ Portable<DWORD> m_cbLeftSideBuffer; // Size in bytes of memory to read/write
+ Portable<HRESULT> m_hrResult; // Result from LS (access can fail due to unmapped memory etc.)
+ } MemoryAccess;
+
+ // Used by MT_Event.
+ struct
+ {
+ Portable<IPCEventType> m_eIPCEventType; // multiplexing type of this IPC event
+ Portable<DWORD> m_eType; // Event type (useful for debugging)
+ } Event;
+
+ } TypeSpecificData;
+
+ BYTE m_sMustBeZero[8]; // Set this to zero when initializing and never read the contents
+ };
+
+ // Struct defining the format of the data block sent with a SessionRequest.
+ struct SessionRequestData
+ {
+ GUID m_sSessionID; // Unique session ID. Treated as byte blob so no endian-ness
+ };
+
+ // Struct used to track a message that is being (or will soon be) sent but has not yet been acknowledged.
+ // These are usually found queued on the send queue.
+ struct Message
+ {
+ Message *m_pNext; // Next message in the queue
+ MessageHeader m_sHeader; // Inline message header
+ PBYTE m_pbDataBlock; // Pointer to optional message data block (or NULL)
+ DWORD m_cbDataBlock; // Count of bytes in above block if it's non-NULL
+ HANDLE m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL)
+ PBYTE m_pbReplyBlock; // Optional buffer to place data block from reply into (or NULL)
+ DWORD m_cbReplyBlock; // Size in bytes of the above buffer if it is non-NULL
+ Message *m_pOrigMessage; // Used when we need to find the original message from a copy
+ bool m_fAborted; // True if this send was aborted due to session shutdown
+
+ // Common initialization for messages.
+ void Init(MessageType eType,
+ PBYTE pbBufferIn = NULL,
+ DWORD cbBufferIn = 0,
+ PBYTE pbBufferOut = NULL,
+ DWORD cbBufferOut = 0)
+ {
+ memset(this, 0, sizeof(*this));
+ m_sHeader.m_eType = eType;
+ m_sHeader.m_cbDataBlock = cbBufferIn;
+ m_pbDataBlock = pbBufferIn;
+ m_cbDataBlock = cbBufferIn;
+ m_pbReplyBlock = pbBufferOut;
+ m_cbReplyBlock = cbBufferOut;
+ }
+ };
+
+ // Holder class used to take a transport lock in a given scope and automatically release it once that
+ // scope is exited.
+ class TransportLockHolder
+ {
+ public:
+ TransportLockHolder(DbgTransportLock *pLock)
+ {
+ m_pLock = pLock;
+ m_pLock->Enter();
+ }
+
+ ~TransportLockHolder()
+ {
+ m_pLock->Leave();
+ }
+
+ private:
+ DbgTransportLock *m_pLock;
+ };
+
+#ifdef _DEBUG
+ // Store statistics for various session activities that will be useful for performance analysis and tracking
+ // down bugs.
+ struct DbgStats
+ {
+ // Message type counts for sends.
+ LONG m_cSentSessionRequest;
+ LONG m_cSentSessionAccept;
+ LONG m_cSentSessionReject;
+ LONG m_cSentSessionResync;
+ LONG m_cSentSessionClose;
+ LONG m_cSentEvent;
+ LONG m_cSentReadMemory;
+ LONG m_cSentWriteMemory;
+ LONG m_cSentVirtualUnwind;
+ LONG m_cSentGetDCB;
+ LONG m_cSentSetDCB;
+ LONG m_cSentGetAppDomainCB;
+ LONG m_cSentDDMessage;
+
+ // Message type counts for receives.
+ LONG m_cReceivedSessionRequest;
+ LONG m_cReceivedSessionAccept;
+ LONG m_cReceivedSessionReject;
+ LONG m_cReceivedSessionResync;
+ LONG m_cReceivedSessionClose;
+ LONG m_cReceivedEvent;
+ LONG m_cReceivedReadMemory;
+ LONG m_cReceivedWriteMemory;
+ LONG m_cReceivedVirtualUnwind;
+ LONG m_cReceivedGetDCB;
+ LONG m_cReceivedSetDCB;
+ LONG m_cReceivedGetAppDomainCB;
+ LONG m_cReceivedDDMessage;
+
+ // Low level block counts.
+ LONG m_cSentBlocks;
+ LONG m_cReceivedBlocks;
+
+ // Byte count summaries.
+ LONGLONG m_cbSentBytes;
+ LONGLONG m_cbReceivedBytes;
+
+ // Errors and recovery
+ LONG m_cSendErrors;
+ LONG m_cReceiveErrors;
+ LONG m_cMiscErrors;
+ LONG m_cConnections;
+ LONG m_cResends;
+
+ // Session counts.
+ LONG m_cSessions;
+ };
+
+ DbgStats m_sStats;
+
+ // Macros to update the statistics. The increment version is thread safe, but the add version is assumed to be
+ // externally serialized since the 64-bit Interlocked operations are not available on all platforms and these
+ // stats are used for send and receive byte counts which are updated at locations that are serialized anyway.
+#define DBG_TRANSPORT_INC_STAT(_name) InterlockedIncrement(&m_sStats.m_c##_name)
+#define DBG_TRANSPORT_ADD_STAT(_name, _amount) m_sStats.m_cb##_name += (_amount)
+
+#else // _DEBUG
+
+#define DBG_TRANSPORT_INC_STAT(_name)
+#define DBG_TRANSPORT_ADD_STAT(_name, _amount)
+
+#endif // _DEBUG
+
+ // Reference count
+ LONG m_ref;
+
+ // Some flags used to record how far we got in Init() (used for cleanup in Shutdown()).
+ bool m_fInitStateLock;
+#ifndef RIGHT_SIDE_COMPILE
+ bool m_fInitWSA;
+#endif // !RIGHT_SIDE_COMPILE
+
+ // Protocol version. This consists of two parts. The major version is incremented on incompatible protocol
+ // updates. That is, a session between left and right sides that cannot use a protocol with the exact same
+ // major version cannot be formed. The minor version number is incremented on compatible protocol updates.
+ // These are usually associated with optional extensions to the protocol (e.g. a V1.2 endpoint might set
+ // previously unused fields in a message header to indicate some optional hint about the message that a
+ // V1.1 client won't notice at all).
+ //
+ // The right side has a hard-coded version number it sends in the SessionRequest message. The left side
+ // must support the same major version or reply with a SessionReject message containing the highest
+ // version it does support. For this reason the format of a SessionReject message can never change at all.
+ // On a SessionAccept the left side sends back the version number and can choose to lower the minor
+ // version to the highest it knows about. This gives the right side a hint as to the capabilities of the
+ // left side (though it must be prepared to interact with a left side with any minor version number).
+ //
+ // If necessary (and the SessionReject message sent by an incompatible left side indicates a major version
+ // the right side can also support), the right side can re-attempt a SessionRequest with a lower major
+ // version.
+ DWORD m_dwMajorVersion;
+ DWORD m_dwMinorVersion;
+
+ // Session ID randomly allocated by the right side and sent over in the SessionRequest message. This
+ // serves to disambiguate a re-send of the SessionRequest due to a network error versus a SessionRequest
+ // from a different debugger.
+ GUID m_sSessionID;
+
+ // Lock used to synchronize sending messages and updating the session state. This ensures message bytes
+ // don't become interleaved on the transport connection, the send queue is updated consistently across
+ // multiple threads and that we never attempt to use a connection that is being deallocated on another
+ // thread due to a state change. Receives don't need this since they're performed only on the transport
+ // thread (which is also the only thread allowed to deallocate the connection).
+ DbgTransportLock m_sStateLock;
+
+ // Queue of messages that have been sent over the connection but not acknowledged yet or are waiting to be
+ // sent (because another message is using the connection or we're in a SessionResync state). You must hold
+ // m_sStateLock in order to access this queue.
+ Message *m_pSendQueueFirst;
+ Message *m_pSendQueueLast;
+
+ // Message IDs. These are monotonically increasing numbers starting from 0 that are used to stamp each
+ // non-session management message sent on this session. If a low-level network error occurs and we must
+ // abandon and re-form the underlying transport connection the left and right sides send SessionResync
+ // messages with the ID of the last message they received (and processed). This allows us to determine
+ // which messages we still have in our send queue must be re-sent over the new transport connection.
+ // Allocate a new message ID by post incrementing m_dwNextMessageId under the state lock.
+ DWORD m_dwNextMessageId; // Next ID we'll give to a message we're sending
+ DWORD m_dwLastMessageIdSeen; // Last ID we saw in an incoming, fully received message
+
+ // The current session state. This is updated atomically under m_sStateLock.
+ SessionState m_eState;
+
+#ifdef RIGHT_SIDE_COMPILE
+ // Manual reset event that is signalled whenever the session state is SS_Open or SS_Closed (after waiting
+ // on this event the caller should check to see which state it was).
+ HANDLE m_hSessionOpenEvent;
+#endif // RIGHT_SIDE_COMPILE
+
+ // Thread responsible for initial Connect()/Accept() on a low level transport connection and
+ // subsequently for all message reception on that connection. Any error will cause the thread to reset
+ // back into the Connect()/Accept() phase (along with the resulting session state change).
+ HANDLE m_hTransportThread;
+
+ TwoWayPipe m_pipe;
+
+#ifdef RIGHT_SIDE_COMPILE
+ // On the RS the transport thread needs to know the IP address and port number to Connect() to.
+ DWORD m_pid; // Id of a process we're talking to.
+
+ HANDLE m_hProcessExited; // event which will be signaled when the debuggee is terminated
+
+ bool m_fDebuggerAttached;
+#endif
+
+ // Debugger event handling. To improve performance we allow the debugger to send as many events as it
+ // likes without acknowledgement from its peer. While not strictly adhering to the semantic provided by
+ // the shared memory buffer transport (where the buffer could not be written again until the receiver had
+ // explicitly released it) it turns out that no debugging code relies on this. In particular, the most
+ // common scenario where this makes sense is the left side sending large scale update events (such as the
+ // groups of appdomain create, module load etc. events sent during an attach). Here the right hand side
+ // queues the events for later processing and releases the buffers right away.
+ // We gain performance since its no longer necessary to send (or wait on) event acknowledgment messages.
+ // This lowers both network bandwidth and latency (especially when one side is trying to send a continuous
+ // stream of events).
+ // From the transport standpoint this design mainly impacts event receipt. We maintain a dynamically sized
+ // pool of event receipt buffers (the size is determined by the maximum number of unread events we've seen
+ // at any one time). The buffer is a circular array: clients read from the buffer at head index which is
+ // followed by some number of valid buffers (wrapping around to the start of the array if necessary). New
+ // events are added after these (and grow the array if the tail would touch the head otherwise).
+ DbgEventBufferEntry * m_pEventBuffers; // Pointer to array of incoming debugger events
+ DWORD m_cEventBuffers; // Size of the array above (in events)
+ DWORD m_cValidEventBuffers; // Number of events that actually contain data
+ DWORD m_idxEventBufferHead; // Index of the first valid event
+ DWORD m_idxEventBufferTail; // Index of the first invalid event
+ HANDLE m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives
+
+#ifndef RIGHT_SIDE_COMPILE
+ // The LS requires the addresses of a couple of runtime data structures in order to service MT_GetDCB etc.
+ // These are provided by the runtime at intialization time.
+ DebuggerIPCControlBlock *m_pDCB;
+ AppDomainEnumerationIPCBlock *m_pADB;
+#endif // !RIGHT_SIDE_COMPILE
+
+ HRESULT SendEventWorker(DebuggerIPCEvent * pEvent, IPCEventType type);
+
+ // 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 SendMessage(Message *pMessage, bool fWaitsForReply);
+
+ // Helper method for sending messages requiring a reply (such as MT_GetDCB) and waiting on the result.
+ HRESULT SendRequestMessageAndWait(Message *pMessage);
+
+ // 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 SendBlock(PBYTE pbBuffer, DWORD cbBuffer);
+
+ // 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 ReceiveBlock(PBYTE pbBuffer, DWORD cbBuffer);
+
+ // Called upon encountering a network error (e.g. an error from Send() or Receive()). This handles pushing
+ // the session state into SS_Resync_NC in order to start the recovery process.
+ void HandleNetworkError(bool fCallerHoldsStateLock);
+
+ // 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 FlushSendQueue(DWORD dwLastProcessedId);
+
+#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 ProcessReply(MessageHeader *pHeader);
+
+ // 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.
+ void SignalReplyEvent(Message * pMesssage);
+
+ // 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.
+ Message * RemoveMessageFromSendQueue(DWORD dwMessageId);
+#endif
+
+#ifndef RIGHT_SIDE_COMPILE
+ // Check read and optionally write memory access to the specified range of bytes. Used to check
+ // ReadProcessMemory and WriteProcessMemory requests.
+ HRESULT CheckBufferAccess(PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess);
+#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 InitSessionState();
+
+ // 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.
+ static DWORD WINAPI TransportWorkerStatic(LPVOID pvContext);
+ void TransportWorker();
+
+ // 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 GetEventSize(DebuggerIPCEvent *pEvent);
+
+#ifdef _DEBUG
+ // Debug helper which returns the name associated with a MessageType.
+ const char *MessageName(MessageType eType);
+
+ // Debug logging helper which logs an incoming message of any type (as long as logging for that message
+ // class is currently enabled).
+ void DbgTransportLogMessageReceived(MessageHeader *pHeader);
+
+ // Helper method used by the DBG_TRANSPORT_SHOULD_INJECT_FAULT macro.
+ bool DbgTransportShouldInjectFault(DbgTransportFaultOp eOp, const char *szOpName);
+#else // _DEBUG
+#define DbgTransportLogMessageReceived(x)
+#endif // _DEBUG
+};
+
+#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).
+extern DbgTransportSession *g_pDbgTransport;
+#endif // !RIGHT_SIDE_COMPILE
+
+#define DBG_GET_LAST_WSA_ERROR() WSAGetLastError()
+
+#endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI)
+
+#endif // __DBG_TRANSPORT_SESSION_INCLUDED