| 1 | // Licensed to the .NET Foundation under one or more agreements. |
| 2 | // The .NET Foundation licenses this file to you under the MIT license. |
| 3 | // See the LICENSE file in the project root for more information. |
| 4 | |
| 5 | |
| 6 | #ifndef __DBG_TRANSPORT_SESSION_INCLUDED |
| 7 | #define __DBG_TRANSPORT_SESSION_INCLUDED |
| 8 | |
| 9 | #ifndef RIGHT_SIDE_COMPILE |
| 10 | #include <utilcode.h> |
| 11 | #include <crst.h> |
| 12 | |
| 13 | #endif // !RIGHT_SIDE_COMPILE |
| 14 | |
| 15 | #if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) |
| 16 | |
| 17 | #include <twowaypipe.h> |
| 18 | |
| 19 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| 20 | DbgTransportSession was originally designed around cross-machine debugging via sockets and it is supposed to |
| 21 | handle network interruptions. Right now we use pipes (see TwoWaypipe) and don't expect to have connection issues. |
| 22 | But there seem to be no good reason to try hard to get rid of existing working protocol even if it's a bit |
| 23 | cautious about connection quality. So please KEEP IN MIND THAT SOME COMMENTS REFERING TO NETWORK AND SOCKETS |
| 24 | CAN BE OUTDATED. |
| 25 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 26 | |
| 27 | // |
| 28 | // Provides a robust and secure transport session between a debugger and a debuggee that are potentially on |
| 29 | // different machines. |
| 30 | // |
| 31 | // The following terminology is used for the wire protocol. The smallest meaningful entity written to or read |
| 32 | // from the connection is a "message". This consists of one or maybe two "blocks" where a block is a |
| 33 | // contiguous region of memory in the host machine. The first block is always a "message header" which is |
| 34 | // fixed size (allowing the receiver to know how many bytes to read off the stream oriented connection) and |
| 35 | // has type codes and other fields which the receiver can use to determine if another block is part of the |
| 36 | // message (and if so, exactly how large that block is). Many management messages consist only of a message |
| 37 | // header block, while operations such as sending a debugger event structure involve a message header followed |
| 38 | // by a block containing the actual event structure. |
| 39 | // |
| 40 | // Message acknowledgement (sometimes abbreviated to ack) refers to a system of marking all messages with an |
| 41 | // ID and noting and reporting which IDs we've seen from our peer. We piggy back the highest seen ID on all |
| 42 | // outgoing messages and this is used by the infrastructure to communicate the fact that a sender can release |
| 43 | // its copy of an outbound message since it successfully made it across the communications channel and won't |
| 44 | // need to be resent in the case of a network failure. |
| 45 | // |
| 46 | // This file uses the debugger conventions for naming the two endpoints of the session: the left side or LS is |
| 47 | // the side with the runtime while the right side (RS) is the side with the debugger. |
| 48 | // |
| 49 | |
| 50 | // The structure of this file necessitates a certain number of forward references (particularly in the |
| 51 | // comments). If you see a term you don't understand please do a search for it further down the file, where |
| 52 | // hopefully you will find a detailed definition (and if not, please add one). |
| 53 | |
| 54 | struct DebuggerIPCEvent; |
| 55 | struct DbgEventBufferEntry; |
| 56 | |
| 57 | // Some simple ad-hoc debug only transport logging. This output is too chatty for an exisitng CLR logging |
| 58 | // channel (and we've run out of bits for an additional channel) and is likely to be of limited use to anyone |
| 59 | // besides the transport developer (and even then only occasionally). |
| 60 | // |
| 61 | // To enable use 'set|export COMPlus_DbgTransportLog=X' where X is 1 for RS logging, 2 for LS logging and 3 |
| 62 | // for both (default is disabled). Use 'set|export COMPlus_DbgTransportLogClass=X' where X is the hex |
| 63 | // representation of one or more DbgTransportLogClass flags defined below (default is all classes enabled). |
| 64 | // For instance, 'set COMPlus_DbgTransportLogClass=f' will enable only message send and receive logging (for |
| 65 | // all message types). |
| 66 | enum DbgTransportLogEnable |
| 67 | { |
| 68 | LE_None = 0x00000000, |
| 69 | LE_LeftSide = 0x00000001, |
| 70 | LE_RightSide = 0x00000002, |
| 71 | LE_Unknown = 0xffffffff, |
| 72 | }; |
| 73 | |
| 74 | enum DbgTransportLogClass |
| 75 | { |
| 76 | LC_None = 0x00000000, |
| 77 | LC_Events = 0x00000001, // Sending and receiving debugger events |
| 78 | LC_Session = 0x00000002, // Sending and receiving session messages |
| 79 | LC_Requests = 0x00000004, // Sending requests such as MT_GetDCB and receiving replies |
| 80 | LC_EventAcks = 0x00000008, // Sending and receiving debugger event acks (DEPRECATED) |
| 81 | LC_NetErrors = 0x00000010, // Network errors |
| 82 | LC_FaultInject = 0x00000020, // Artificially injected network faults |
| 83 | LC_Proxy = 0x00000040, // Proxy interactions |
| 84 | LC_All = 0xffffffff, |
| 85 | LC_Always = 0xffffffff, // Always log, regardless of class setting |
| 86 | }; |
| 87 | |
| 88 | // Status codes that can be returned by various APIs that indicate some conditions of the error that a caller |
| 89 | // might usefully pass on to a user (environmental factors that the user might have some control over). |
| 90 | enum ConnStatus |
| 91 | { |
| 92 | SCS_Success, // The request succeeded |
| 93 | SCS_OutOfMemory, // The request failed due to a low memory situation |
| 94 | SCS_InvalidConfiguration, // Initialize() failed because the debugger settings were not configured or |
| 95 | // have become corrupt |
| 96 | SCS_UnknownTarget, // Connect() failed because the remote machine at the given address could not |
| 97 | // be found |
| 98 | SCS_NoListener, // Connect() failed because the remote machine was not listening for requests |
| 99 | // on the given port (most likely the remote machine is not configured for |
| 100 | // debugging) |
| 101 | SCS_NetworkFailure, // Connect() failed due to miscellaneous network error |
| 102 | SCS_MismatchedCerts, // Connect()/Accept() failed because the remote party was using a different |
| 103 | // cert |
| 104 | }; |
| 105 | |
| 106 | |
| 107 | // Multiple clients can use a single DbgTransportSession, but only one can act as the debugger. |
| 108 | // A valid DebugTicket is given to the client who is acting as the debugger. |
| 109 | struct DebugTicket |
| 110 | { |
| 111 | friend class DbgTransportSession; |
| 112 | |
| 113 | public: |
| 114 | DebugTicket() { m_fValid = false; }; |
| 115 | |
| 116 | bool IsValid() { return m_fValid; }; |
| 117 | |
| 118 | protected: |
| 119 | void SetValid() { m_fValid = true; }; |
| 120 | void SetInvalid() { m_fValid = false; }; |
| 121 | |
| 122 | private: |
| 123 | // Tickets can't be copied around. Hide these definitions so as to enforce that. |
| 124 | // We still need the Copy ctor so that it can be passed in as a parameter. |
| 125 | void operator=(DebugTicket & other); |
| 126 | |
| 127 | bool m_fValid; |
| 128 | }; |
| 129 | |
| 130 | #ifdef RIGHT_SIDE_COMPILE |
| 131 | #define DBG_TRANSPORT_LOG_THIS_SIDE LE_RightSide |
| 132 | #define DBG_TRANSPORT_LOG_PREFIX "RS" |
| 133 | #else // RIGHT_SIDE_COMPILE |
| 134 | #define DBG_TRANSPORT_LOG_THIS_SIDE LE_LeftSide |
| 135 | #define DBG_TRANSPORT_LOG_PREFIX "LS" |
| 136 | #endif // RIGHT_SIDE_COMPILE |
| 137 | |
| 138 | // Method used to log an interesting event (of the given class). The message given will have any additional |
| 139 | // arguments inserted following 'printf' formatiing conventions and will be automatically prepended with a |
| 140 | // LS/RS indicator and suffixed with a newline. |
| 141 | inline void DbgTransportLog(DbgTransportLogClass eClass, const char *szFormat, ...) |
| 142 | { |
| 143 | #ifdef _DEBUG |
| 144 | static DWORD s_dwLoggingEnabled = LE_Unknown; |
| 145 | static DWORD s_dwLoggingClass = LC_All; |
| 146 | |
| 147 | if (s_dwLoggingEnabled == LE_Unknown) |
| 148 | { |
| 149 | s_dwLoggingEnabled = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLog, LE_None); |
| 150 | s_dwLoggingClass = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLogClass, LC_All); |
| 151 | } |
| 152 | |
| 153 | if ((s_dwLoggingEnabled & DBG_TRANSPORT_LOG_THIS_SIDE) && |
| 154 | ((s_dwLoggingClass & eClass) || eClass == LC_Always)) |
| 155 | { |
| 156 | char szOutput[256]; |
| 157 | va_list args; |
| 158 | |
| 159 | va_start(args, szFormat); |
| 160 | vsprintf_s(szOutput, sizeof(szOutput), szFormat, args); |
| 161 | va_end(args); |
| 162 | |
| 163 | printf("%s %04x: %s\n" , DBG_TRANSPORT_LOG_PREFIX, GetCurrentThreadId(), szOutput); |
| 164 | fflush(stdout); |
| 165 | |
| 166 | char szDebugOutput[512]; |
| 167 | sprintf_s(szDebugOutput, sizeof(szDebugOutput), "%s: %s\n" , DBG_TRANSPORT_LOG_PREFIX, szOutput); |
| 168 | OutputDebugStringA(szDebugOutput); |
| 169 | } |
| 170 | #endif // _DEBUG |
| 171 | } |
| 172 | |
| 173 | #ifdef _DEBUG |
| 174 | // |
| 175 | // Debug-only network fault injection (in order to help test the robust session code). Control is via a single |
| 176 | // DWORD read from the environment (COMPlus_DbgTransportFaultInject). This DWORD is treated as a set of bit |
| 177 | // fields as follows: |
| 178 | // |
| 179 | // +-------+-------+-------+----------------+-----------+ |
| 180 | // | Side | Op | State | Reserved | Frequency | |
| 181 | // +-------+-------+-------+----------------+-----------+ |
| 182 | // 31<->28 27<->24 23<->20 19<----------->8 7<------->0 |
| 183 | // |
| 184 | // The 'Side' field indicates whether the left or right side (or both) should have faults injected. See |
| 185 | // DbgTransportFaultSide below for values. |
| 186 | // |
| 187 | // The 'Op' field indicates which connection methods should simulate failures. See DbgTransportFaultOp. |
| 188 | // |
| 189 | // The 'State' field indicates the session states in which faults will be injected. See |
| 190 | // DbgTransportFaultState. Note that introducing too many failures into the Opening and Opening_NC states will |
| 191 | // cause the debugger to timeout and fail. |
| 192 | // |
| 193 | // The 'Reserved' field has no current meaning and should be left as zero. |
| 194 | // |
| 195 | // The 'Frequency' field indicates a percentage failure rate. Valid values are between 0 and 99, values beyond |
| 196 | // this range will be clamped to 99. |
| 197 | // |
| 198 | // For example: |
| 199 | // |
| 200 | // export COMPlus_DbgTransportFaultInject=1ff00001 |
| 201 | // --> Fail all network operations on the left side 1% of the time |
| 202 | // |
| 203 | // export COMPlus_DbgTransportFaultInject=34200063 |
| 204 | // --> Fail Send() calls on both sides while the session is Open 99% of the time |
| 205 | // |
| 206 | |
| 207 | #define DBG_TRANSPORT_FAULT_RATE_MASK 0x000000ff |
| 208 | |
| 209 | // Whether to inject faults to the left, right or both sides. |
| 210 | enum DbgTransportFaultSide |
| 211 | { |
| 212 | FS_Left = 0x10000000, |
| 213 | FS_Right = 0x20000000, |
| 214 | }; |
| 215 | |
| 216 | // Network operations which are candiates for fault injection. |
| 217 | enum DbgTransportFaultOp |
| 218 | { |
| 219 | FO_Connect = 0x01000000, |
| 220 | FO_Accept = 0x02000000, |
| 221 | FO_Send = 0x04000000, |
| 222 | FO_Receive = 0x08000000, |
| 223 | }; |
| 224 | |
| 225 | // Session states into which faults should be injected. |
| 226 | enum DbgTransportFaultState |
| 227 | { |
| 228 | FS_Opening = 0x00100000, // Opening and Opening_NC |
| 229 | FS_Open = 0x00200000, |
| 230 | FS_Resync = 0x00400000, // Resync and Resync_NC |
| 231 | }; |
| 232 | |
| 233 | #ifdef RIGHT_SIDE_COMPILE |
| 234 | #define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Right |
| 235 | #else // RIGHT_SIDE_COMPILE |
| 236 | #define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Left |
| 237 | #endif // RIGHT_SIDE_COMPILE |
| 238 | |
| 239 | // Macro to determine whether a fault should be injected for the given operation. |
| 240 | #define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) DbgTransportShouldInjectFault(FO_##_op, #_op) |
| 241 | |
| 242 | #else // _DEBUG |
| 243 | #define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) false |
| 244 | #endif // _DEBUG |
| 245 | |
| 246 | // The PAL doesn't define htons (host-to-network-short) and friends. So provide our own versions here. |
| 247 | // winsock2.h defines BIGENDIAN to 0x0000 and LITTLEENDIAN to 0x0001, so we need to be careful with the |
| 248 | // #ifdef. |
| 249 | #if BIGENDIAN > 0 |
| 250 | #define DBGIPC_HTONS(x) (x) |
| 251 | #define DBGIPC_NTOHS(x) (x) |
| 252 | #define DBGIPC_HTONL(x) (x) |
| 253 | #define DBGIPC_NTOHL(x) (x) |
| 254 | #else |
| 255 | inline UINT16 DBGIPC_HTONS(UINT16 x) |
| 256 | { |
| 257 | return (x >> 8) | (x << 8); |
| 258 | } |
| 259 | #define DBGIPC_NTOHS(x) DBGIPC_HTONS(x) |
| 260 | inline UINT32 DBGIPC_HTONL(UINT32 x) |
| 261 | { |
| 262 | return (x >> 24) | |
| 263 | ((x >> 8) & 0x0000FF00L) | |
| 264 | ((x & 0x0000FF00L) << 8) | |
| 265 | (x << 24); |
| 266 | } |
| 267 | #define DBGIPC_NTOHL(x) DBGIPC_HTONL(x) |
| 268 | |
| 269 | #endif |
| 270 | |
| 271 | // Lock abstraction (we can't use the same lock implementation on LS and RS since we really want a Crst on the |
| 272 | // LS and this isn't available in the RS environment). |
| 273 | class DbgTransportLock |
| 274 | { |
| 275 | public: |
| 276 | void Init(); |
| 277 | void Destroy(); |
| 278 | void Enter(); |
| 279 | void Leave(); |
| 280 | |
| 281 | private: |
| 282 | #ifdef RIGHT_SIDE_COMPILE |
| 283 | CRITICAL_SECTION m_sLock; |
| 284 | #else // RIGHT_SIDE_COMPILE |
| 285 | CrstExplicitInit m_sLock; |
| 286 | #endif // RIGHT_SIDE_COMPILE |
| 287 | }; |
| 288 | |
| 289 | // The transport has only one queue for IPC events, but each IPC event can be marked as one of two types. |
| 290 | // The transport will signal the handle corresponding to the type of each IPC event. (See |
| 291 | // code:DbgTransportSession::GetIPCEventReadyEvent and code:DbgTransportSession::GetDebugEventReadyEvent.) |
| 292 | // This is effectively a basic multiplexing scheme. The old-style IPC event are for all RS-to-LS IPC events |
| 293 | // and for all LS-to-RS replies. The other type is for LS-to-RS IPC events transported over the native |
| 294 | // pipeline. For more information, see the comments for the interface code:IEventChannel. |
| 295 | enum IPCEventType |
| 296 | { |
| 297 | IPCET_OldStyle, |
| 298 | IPCET_DebugEvent, |
| 299 | IPCET_Max, |
| 300 | }; |
| 301 | |
| 302 | // The class that encapsulates all the state for a single session on either the right or left side. The left |
| 303 | // side supports only one instance of this class for a given runtime. The right side can support several (all |
| 304 | // connected to different LS instances of course). |
| 305 | class DbgTransportSession |
| 306 | { |
| 307 | public: |
| 308 | // No real work done in the constructor. Use Init() instead. |
| 309 | DbgTransportSession(); |
| 310 | |
| 311 | // Cleanup what is allocated/created in Init() |
| 312 | ~DbgTransportSession(); |
| 313 | |
| 314 | // Allocates initial resources (including starting the transport thread). The session will start in the |
| 315 | // SS_Opening state. That is, the RS will immediately start trying to Connect() a connection while the |
| 316 | // LS will perform an Accept() to wait for a connection request. The RS needs an IP address and port |
| 317 | // number to initiate connections. These should be given in host byte order. The LS, on the other hand, |
| 318 | // requires the addresses of a couple of runtime data structures to service certain debugger requests that |
| 319 | // may be delivered once the session is established. |
| 320 | #ifdef RIGHT_SIDE_COMPILE |
| 321 | HRESULT Init(const ProcessDescriptor& pd, HANDLE hProcessExited); |
| 322 | #else |
| 323 | HRESULT Init(DebuggerIPCControlBlock * pDCB, AppDomainEnumerationIPCBlock * pADB); |
| 324 | #endif // RIGHT_SIDE_COMPILE |
| 325 | |
| 326 | // Drive the session to the SS_Closed state, which will deallocate all remaining transport resources |
| 327 | // (including terminating the transport thread). If this is the RS and the session state is SS_Open at the |
| 328 | // time of this call a graceful disconnect will be attempted (which tells the LS to go back to SS_Opening |
| 329 | // to look for a new RS rather than interpreting the disconnection as a temporary error and going into |
| 330 | // SS_Resync). On either side the session will no longer be functional after this call returns (though |
| 331 | // Init() may be called again to start over from the beginning). |
| 332 | void Shutdown(); |
| 333 | |
| 334 | #ifdef RIGHT_SIDE_COMPILE |
| 335 | // Used by debugger side (RS) to cleanup the target (LS) named pipes |
| 336 | // and semaphores when the debugger detects the debuggee process exited. |
| 337 | void CleanupTargetProcess(); |
| 338 | #else |
| 339 | // Cleans up the named pipe connection so no tmp files are left behind. Does only |
| 340 | // the minimum and must be safe to call at any time. Called during PAL ExitProcess, |
| 341 | // TerminateProcess and for unhandled native exceptions and asserts. |
| 342 | void AbortConnection(); |
| 343 | #endif // RIGHT_SIDE_COMPILE |
| 344 | |
| 345 | LONG AddRef() |
| 346 | { |
| 347 | LONG ref = InterlockedIncrement(&m_ref); |
| 348 | return ref; |
| 349 | } |
| 350 | |
| 351 | LONG Release() |
| 352 | { |
| 353 | _ASSERTE(m_ref > 0); |
| 354 | LONG ref = InterlockedDecrement(&m_ref); |
| 355 | if (ref == 0) |
| 356 | { |
| 357 | delete this; |
| 358 | } |
| 359 | return ref; |
| 360 | } |
| 361 | |
| 362 | #ifndef RIGHT_SIDE_COMPILE |
| 363 | // API used only by the LS to drive the transport into a state where it won't accept connections. This is |
| 364 | // used when no proxy is detected at startup but it's too late to shutdown all of the debugging system |
| 365 | // easily. It's mainly paranoia to increase the protection of your system when the proxy isn't started. |
| 366 | void Neuter(); |
| 367 | #endif // !RIGHT_SIDE_COMPILE |
| 368 | |
| 369 | #ifdef RIGHT_SIDE_COMPILE |
| 370 | // On the RS it may be useful to wait and see if the session can reach the SS_Open state. If the target |
| 371 | // runtime has terminated for some reason then we'll never reach the open state. So the method below gives |
| 372 | // the RS a way to try and establish a connection for a reasonable amount of time and to time out |
| 373 | // otherwise. They could then call Shutdown on the session and report an error back to the rest of the |
| 374 | // debugger. The method returns true if the session opened within the time given (in milliseconds) and |
| 375 | // false otherwise. |
| 376 | bool WaitForSessionToOpen(DWORD dwTimeout); |
| 377 | |
| 378 | // A valid ticket is returned if no other client is currently acting as the debugger. |
| 379 | bool UseAsDebugger(DebugTicket * pTicket); |
| 380 | |
| 381 | // A valid ticket is required in order for this function to succeed. After this function succeeds, |
| 382 | // another client can request to be the debugger. |
| 383 | bool StopUsingAsDebugger(DebugTicket * pTicket); |
| 384 | #endif // RIGHT_SIDE_COMPILE |
| 385 | |
| 386 | // Sends a pre-initialized event to the other side. |
| 387 | HRESULT SendEvent(DebuggerIPCEvent * pEvent); |
| 388 | HRESULT SendDebugEvent(DebuggerIPCEvent * pEvent); |
| 389 | |
| 390 | // Retrieves the auto-reset handle which is signalled by the session each time a new event is received |
| 391 | // from the other side. |
| 392 | HANDLE GetIPCEventReadyEvent(); |
| 393 | HANDLE GetDebugEventReadyEvent(); |
| 394 | |
| 395 | // Copies the last event received from the other side into the provided buffer. This should only be called |
| 396 | // (once) after the event returned from GetIPCEventReadyEvent()/GetDebugEventReadyEvent() has been signalled. |
| 397 | void GetNextEvent(DebuggerIPCEvent *pEvent, DWORD cbEvent); |
| 398 | |
| 399 | #ifdef RIGHT_SIDE_COMPILE |
| 400 | // Read and write memory on the LS from the RS. |
| 401 | HRESULT ReadMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer); |
| 402 | HRESULT WriteMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer); |
| 403 | HRESULT VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context); |
| 404 | |
| 405 | // Read and write the debugger control block on the LS from the RS. |
| 406 | HRESULT GetDCB(DebuggerIPCControlBlock *pDCB); |
| 407 | HRESULT SetDCB(DebuggerIPCControlBlock *pDCB); |
| 408 | |
| 409 | // Read the AppDomain control block on the LS from the RS. |
| 410 | HRESULT GetAppDomainCB(AppDomainEnumerationIPCBlock *pADB); |
| 411 | |
| 412 | #endif // RIGHT_SIDE_COMPILE |
| 413 | |
| 414 | private: |
| 415 | |
| 416 | // Highest protocol version supported by this side of the session. See the |
| 417 | // m_dwMajorVersion/m_dwMinorVersion fields for a detailed explanation and the actual version being used |
| 418 | // by the session (if it is formed). |
| 419 | static const DWORD kCurrentMajorVersion = 2; |
| 420 | static const DWORD kCurrentMinorVersion = 0; |
| 421 | |
| 422 | // Session states. These determine which action is taken on a SendMessage (message is sent, queued or an |
| 423 | // error is raised) and which incoming messages are valid. |
| 424 | enum SessionState |
| 425 | { |
| 426 | SS_Closed, // No session and no attempt is being made to form one |
| 427 | SS_Opening_NC, // Session is being formed but no connection is established yet |
| 428 | SS_Opening, // Session is being formed, the low level connection is in place |
| 429 | SS_Open, // Session is fully formed and normal transport messages can be sent and received |
| 430 | SS_Resync_NC, // A low level connection error is occurred and we're attempting to re-form the link |
| 431 | SS_Resync, // We're trying to resynchronize high level state over the new connection |
| 432 | }; |
| 433 | |
| 434 | // Types of messages that can be sent over the transport connection. |
| 435 | enum MessageType |
| 436 | { |
| 437 | // Session management operations. These must come first and MT_SessionClose must be last in the group. |
| 438 | MT_SessionRequest, // RS -> LS : Request a new session be formed (optionally pass encrypted data key) |
| 439 | MT_SessionAccept, // LS -> RS : Accept new session |
| 440 | MT_SessionReject, // LS -> RS : Reject new session, give reason |
| 441 | MT_SessionResync, // RS <-> LS : Resync broken connection by informing other side which messages must be resent |
| 442 | MT_SessionClose, // RS -> LS : Gracefully terminate a session |
| 443 | |
| 444 | // Debugger events. |
| 445 | MT_Event, // RS <-> LS : A debugger event is being sent as the data block of the message |
| 446 | |
| 447 | // Misc management operations. |
| 448 | MT_ReadMemory, // RS <-> LS : RS wants to read LS memory block (or LS is replying to such a request) |
| 449 | MT_WriteMemory, // RS <-> LS : RS wants to write LS memory block (or LS is replying to such a request) |
| 450 | MT_VirtualUnwind, // RS <-> LS : RS wants to LS unwind a stack frame (or LS is replying to such a request) |
| 451 | MT_GetDCB, // RS <-> LS : RS wants to read LS DCB (or LS is replying to such a request) |
| 452 | MT_SetDCB, // RS <-> LS : RS wants to write LS DCB (or LS is replying to such a request) |
| 453 | MT_GetAppDomainCB, // RS <-> LS : RS wants to read LS AppDomainCB (or LS is replying to such a request) |
| 454 | }; |
| 455 | |
| 456 | // Reasons the LS can give for rejecting a session. These codes should *not* be changed other than by |
| 457 | // adding reasons to keep versioning possible. |
| 458 | enum RejectReason |
| 459 | { |
| 460 | RR_IncompatibleVersion, // LS doesn't support the major version asked for in the request. |
| 461 | RR_AlreadyAttached, // LS already has another session open (LS only supports one session at a time) |
| 462 | }; |
| 463 | |
| 464 | // Struct that defines the format of a message header block sent on the connection. Note that the size of |
| 465 | // this structure and the location/size of the m_eType field must *never* change to allow our versioning |
| 466 | // protocol to work properly (in particular any LS must be able to interpret at least the type and version |
| 467 | // number of an MT_SessionRequest and reply with a MT_SessionReject that any RS can interpret the type and |
| 468 | // version of). To help with this there is a padding field at the end for future expansion (this should be |
| 469 | // initialized to zero and not accessed in any other manner). |
| 470 | struct |
| 471 | { |
| 472 | Portable<MessageType> ; // Type of message this is |
| 473 | Portable<DWORD> ; // Size of data block that immediately follows this header (can be zero) |
| 474 | Portable<DWORD> ; // Message ID assigned by the sender of this message |
| 475 | Portable<DWORD> ; // Message ID that this is a reply to (used by messages such as MT_GetDCB) |
| 476 | Portable<DWORD> ; // Message ID last seen by sender (receiver can discard up to here from send queue) |
| 477 | Portable<DWORD> ; // Reserved for future expansion (must be initialized to zero and |
| 478 | // never read) |
| 479 | |
| 480 | // The rest of the header varies depending on the message type (keep the maximum size of this union |
| 481 | // small since all messages will pay the overhead, large message type specific data should go in the |
| 482 | // following data block). |
| 483 | union |
| 484 | { |
| 485 | // Used by MT_SessionRequest / MT_SessionAccept. |
| 486 | struct |
| 487 | { |
| 488 | Portable<DWORD> ; // Protocol version requested/accepted |
| 489 | Portable<DWORD> ; |
| 490 | } ; |
| 491 | |
| 492 | // Used by MT_SessionReject. |
| 493 | struct |
| 494 | { |
| 495 | Portable<RejectReason> ; // Reason for rejection. |
| 496 | Portable<DWORD> ; // Highest protocol version the LS supports |
| 497 | Portable<DWORD> ; |
| 498 | } ; |
| 499 | |
| 500 | // Used by MT_ReadMemory and MT_WriteMemory. |
| 501 | struct |
| 502 | { |
| 503 | Portable<PBYTE> ; // Address of memory to read/write on the LS |
| 504 | Portable<DWORD> ; // Size in bytes of memory to read/write |
| 505 | Portable<HRESULT> ; // Result from LS (access can fail due to unmapped memory etc.) |
| 506 | } ; |
| 507 | |
| 508 | // Used by MT_Event. |
| 509 | struct |
| 510 | { |
| 511 | Portable<IPCEventType> ; // multiplexing type of this IPC event |
| 512 | Portable<DWORD> ; // Event type (useful for debugging) |
| 513 | } ; |
| 514 | |
| 515 | } ; |
| 516 | |
| 517 | BYTE [8]; // Set this to zero when initializing and never read the contents |
| 518 | }; |
| 519 | |
| 520 | // Struct defining the format of the data block sent with a SessionRequest. |
| 521 | struct SessionRequestData |
| 522 | { |
| 523 | GUID m_sSessionID; // Unique session ID. Treated as byte blob so no endian-ness |
| 524 | }; |
| 525 | |
| 526 | // Struct used to track a message that is being (or will soon be) sent but has not yet been acknowledged. |
| 527 | // These are usually found queued on the send queue. |
| 528 | struct Message |
| 529 | { |
| 530 | Message *m_pNext; // Next message in the queue |
| 531 | MessageHeader ; // Inline message header |
| 532 | PBYTE m_pbDataBlock; // Pointer to optional message data block (or NULL) |
| 533 | DWORD m_cbDataBlock; // Count of bytes in above block if it's non-NULL |
| 534 | HANDLE m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL) |
| 535 | PBYTE m_pbReplyBlock; // Optional buffer to place data block from reply into (or NULL) |
| 536 | DWORD m_cbReplyBlock; // Size in bytes of the above buffer if it is non-NULL |
| 537 | Message *m_pOrigMessage; // Used when we need to find the original message from a copy |
| 538 | bool m_fAborted; // True if this send was aborted due to session shutdown |
| 539 | |
| 540 | // Common initialization for messages. |
| 541 | void Init(MessageType eType, |
| 542 | PBYTE pbBufferIn = NULL, |
| 543 | DWORD cbBufferIn = 0, |
| 544 | PBYTE pbBufferOut = NULL, |
| 545 | DWORD cbBufferOut = 0) |
| 546 | { |
| 547 | memset(this, 0, sizeof(*this)); |
| 548 | m_sHeader.m_eType = eType; |
| 549 | m_sHeader.m_cbDataBlock = cbBufferIn; |
| 550 | m_pbDataBlock = pbBufferIn; |
| 551 | m_cbDataBlock = cbBufferIn; |
| 552 | m_pbReplyBlock = pbBufferOut; |
| 553 | m_cbReplyBlock = cbBufferOut; |
| 554 | } |
| 555 | }; |
| 556 | |
| 557 | // Holder class used to take a transport lock in a given scope and automatically release it once that |
| 558 | // scope is exited. |
| 559 | class TransportLockHolder |
| 560 | { |
| 561 | public: |
| 562 | TransportLockHolder(DbgTransportLock *pLock) |
| 563 | { |
| 564 | m_pLock = pLock; |
| 565 | m_pLock->Enter(); |
| 566 | } |
| 567 | |
| 568 | ~TransportLockHolder() |
| 569 | { |
| 570 | m_pLock->Leave(); |
| 571 | } |
| 572 | |
| 573 | private: |
| 574 | DbgTransportLock *m_pLock; |
| 575 | }; |
| 576 | |
| 577 | #ifdef _DEBUG |
| 578 | // Store statistics for various session activities that will be useful for performance analysis and tracking |
| 579 | // down bugs. |
| 580 | struct DbgStats |
| 581 | { |
| 582 | // Message type counts for sends. |
| 583 | LONG m_cSentSessionRequest; |
| 584 | LONG m_cSentSessionAccept; |
| 585 | LONG m_cSentSessionReject; |
| 586 | LONG m_cSentSessionResync; |
| 587 | LONG m_cSentSessionClose; |
| 588 | LONG m_cSentEvent; |
| 589 | LONG m_cSentReadMemory; |
| 590 | LONG m_cSentWriteMemory; |
| 591 | LONG m_cSentVirtualUnwind; |
| 592 | LONG m_cSentGetDCB; |
| 593 | LONG m_cSentSetDCB; |
| 594 | LONG m_cSentGetAppDomainCB; |
| 595 | LONG m_cSentDDMessage; |
| 596 | |
| 597 | // Message type counts for receives. |
| 598 | LONG m_cReceivedSessionRequest; |
| 599 | LONG m_cReceivedSessionAccept; |
| 600 | LONG m_cReceivedSessionReject; |
| 601 | LONG m_cReceivedSessionResync; |
| 602 | LONG m_cReceivedSessionClose; |
| 603 | LONG m_cReceivedEvent; |
| 604 | LONG m_cReceivedReadMemory; |
| 605 | LONG m_cReceivedWriteMemory; |
| 606 | LONG m_cReceivedVirtualUnwind; |
| 607 | LONG m_cReceivedGetDCB; |
| 608 | LONG m_cReceivedSetDCB; |
| 609 | LONG m_cReceivedGetAppDomainCB; |
| 610 | LONG m_cReceivedDDMessage; |
| 611 | |
| 612 | // Low level block counts. |
| 613 | LONG m_cSentBlocks; |
| 614 | LONG m_cReceivedBlocks; |
| 615 | |
| 616 | // Byte count summaries. |
| 617 | LONGLONG m_cbSentBytes; |
| 618 | LONGLONG m_cbReceivedBytes; |
| 619 | |
| 620 | // Errors and recovery |
| 621 | LONG m_cSendErrors; |
| 622 | LONG m_cReceiveErrors; |
| 623 | LONG m_cMiscErrors; |
| 624 | LONG m_cConnections; |
| 625 | LONG m_cResends; |
| 626 | |
| 627 | // Session counts. |
| 628 | LONG m_cSessions; |
| 629 | }; |
| 630 | |
| 631 | DbgStats m_sStats; |
| 632 | |
| 633 | // Macros to update the statistics. The increment version is thread safe, but the add version is assumed to be |
| 634 | // externally serialized since the 64-bit Interlocked operations are not available on all platforms and these |
| 635 | // stats are used for send and receive byte counts which are updated at locations that are serialized anyway. |
| 636 | #define DBG_TRANSPORT_INC_STAT(_name) InterlockedIncrement(&m_sStats.m_c##_name) |
| 637 | #define DBG_TRANSPORT_ADD_STAT(_name, _amount) m_sStats.m_cb##_name += (_amount) |
| 638 | |
| 639 | #else // _DEBUG |
| 640 | |
| 641 | #define DBG_TRANSPORT_INC_STAT(_name) |
| 642 | #define DBG_TRANSPORT_ADD_STAT(_name, _amount) |
| 643 | |
| 644 | #endif // _DEBUG |
| 645 | |
| 646 | // Reference count |
| 647 | LONG m_ref; |
| 648 | |
| 649 | // Some flags used to record how far we got in Init() (used for cleanup in Shutdown()). |
| 650 | bool m_fInitStateLock; |
| 651 | #ifndef RIGHT_SIDE_COMPILE |
| 652 | bool m_fInitWSA; |
| 653 | #endif // !RIGHT_SIDE_COMPILE |
| 654 | |
| 655 | // Protocol version. This consists of two parts. The major version is incremented on incompatible protocol |
| 656 | // updates. That is, a session between left and right sides that cannot use a protocol with the exact same |
| 657 | // major version cannot be formed. The minor version number is incremented on compatible protocol updates. |
| 658 | // These are usually associated with optional extensions to the protocol (e.g. a V1.2 endpoint might set |
| 659 | // previously unused fields in a message header to indicate some optional hint about the message that a |
| 660 | // V1.1 client won't notice at all). |
| 661 | // |
| 662 | // The right side has a hard-coded version number it sends in the SessionRequest message. The left side |
| 663 | // must support the same major version or reply with a SessionReject message containing the highest |
| 664 | // version it does support. For this reason the format of a SessionReject message can never change at all. |
| 665 | // On a SessionAccept the left side sends back the version number and can choose to lower the minor |
| 666 | // version to the highest it knows about. This gives the right side a hint as to the capabilities of the |
| 667 | // left side (though it must be prepared to interact with a left side with any minor version number). |
| 668 | // |
| 669 | // If necessary (and the SessionReject message sent by an incompatible left side indicates a major version |
| 670 | // the right side can also support), the right side can re-attempt a SessionRequest with a lower major |
| 671 | // version. |
| 672 | DWORD m_dwMajorVersion; |
| 673 | DWORD m_dwMinorVersion; |
| 674 | |
| 675 | // Session ID randomly allocated by the right side and sent over in the SessionRequest message. This |
| 676 | // serves to disambiguate a re-send of the SessionRequest due to a network error versus a SessionRequest |
| 677 | // from a different debugger. |
| 678 | GUID m_sSessionID; |
| 679 | |
| 680 | // Lock used to synchronize sending messages and updating the session state. This ensures message bytes |
| 681 | // don't become interleaved on the transport connection, the send queue is updated consistently across |
| 682 | // multiple threads and that we never attempt to use a connection that is being deallocated on another |
| 683 | // thread due to a state change. Receives don't need this since they're performed only on the transport |
| 684 | // thread (which is also the only thread allowed to deallocate the connection). |
| 685 | DbgTransportLock m_sStateLock; |
| 686 | |
| 687 | // Queue of messages that have been sent over the connection but not acknowledged yet or are waiting to be |
| 688 | // sent (because another message is using the connection or we're in a SessionResync state). You must hold |
| 689 | // m_sStateLock in order to access this queue. |
| 690 | Message *m_pSendQueueFirst; |
| 691 | Message *m_pSendQueueLast; |
| 692 | |
| 693 | // Message IDs. These are monotonically increasing numbers starting from 0 that are used to stamp each |
| 694 | // non-session management message sent on this session. If a low-level network error occurs and we must |
| 695 | // abandon and re-form the underlying transport connection the left and right sides send SessionResync |
| 696 | // messages with the ID of the last message they received (and processed). This allows us to determine |
| 697 | // which messages we still have in our send queue must be re-sent over the new transport connection. |
| 698 | // Allocate a new message ID by post incrementing m_dwNextMessageId under the state lock. |
| 699 | DWORD m_dwNextMessageId; // Next ID we'll give to a message we're sending |
| 700 | DWORD m_dwLastMessageIdSeen; // Last ID we saw in an incoming, fully received message |
| 701 | |
| 702 | // The current session state. This is updated atomically under m_sStateLock. |
| 703 | SessionState m_eState; |
| 704 | |
| 705 | #ifdef RIGHT_SIDE_COMPILE |
| 706 | // Manual reset event that is signalled whenever the session state is SS_Open or SS_Closed (after waiting |
| 707 | // on this event the caller should check to see which state it was). |
| 708 | HANDLE m_hSessionOpenEvent; |
| 709 | #endif // RIGHT_SIDE_COMPILE |
| 710 | |
| 711 | // Thread responsible for initial Connect()/Accept() on a low level transport connection and |
| 712 | // subsequently for all message reception on that connection. Any error will cause the thread to reset |
| 713 | // back into the Connect()/Accept() phase (along with the resulting session state change). |
| 714 | HANDLE m_hTransportThread; |
| 715 | |
| 716 | TwoWayPipe m_pipe; |
| 717 | |
| 718 | #ifdef RIGHT_SIDE_COMPILE |
| 719 | // On the RS the transport thread needs to know the IP address and port number to Connect() to. |
| 720 | ProcessDescriptor m_pd; // Descriptor of a process we're talking to. |
| 721 | |
| 722 | HANDLE m_hProcessExited; // event which will be signaled when the debuggee is terminated |
| 723 | |
| 724 | bool m_fDebuggerAttached; |
| 725 | #endif |
| 726 | |
| 727 | // Debugger event handling. To improve performance we allow the debugger to send as many events as it |
| 728 | // likes without acknowledgement from its peer. While not strictly adhering to the semantic provided by |
| 729 | // the shared memory buffer transport (where the buffer could not be written again until the receiver had |
| 730 | // explicitly released it) it turns out that no debugging code relies on this. In particular, the most |
| 731 | // common scenario where this makes sense is the left side sending large scale update events (such as the |
| 732 | // groups of appdomain create, module load etc. events sent during an attach). Here the right hand side |
| 733 | // queues the events for later processing and releases the buffers right away. |
| 734 | // We gain performance since its no longer necessary to send (or wait on) event acknowledgment messages. |
| 735 | // This lowers both network bandwidth and latency (especially when one side is trying to send a continuous |
| 736 | // stream of events). |
| 737 | // From the transport standpoint this design mainly impacts event receipt. We maintain a dynamically sized |
| 738 | // pool of event receipt buffers (the size is determined by the maximum number of unread events we've seen |
| 739 | // at any one time). The buffer is a circular array: clients read from the buffer at head index which is |
| 740 | // followed by some number of valid buffers (wrapping around to the start of the array if necessary). New |
| 741 | // events are added after these (and grow the array if the tail would touch the head otherwise). |
| 742 | DbgEventBufferEntry * m_pEventBuffers; // Pointer to array of incoming debugger events |
| 743 | DWORD m_cEventBuffers; // Size of the array above (in events) |
| 744 | DWORD m_cValidEventBuffers; // Number of events that actually contain data |
| 745 | DWORD m_idxEventBufferHead; // Index of the first valid event |
| 746 | DWORD m_idxEventBufferTail; // Index of the first invalid event |
| 747 | HANDLE m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives |
| 748 | |
| 749 | #ifndef RIGHT_SIDE_COMPILE |
| 750 | // The LS requires the addresses of a couple of runtime data structures in order to service MT_GetDCB etc. |
| 751 | // These are provided by the runtime at intialization time. |
| 752 | DebuggerIPCControlBlock *m_pDCB; |
| 753 | AppDomainEnumerationIPCBlock *m_pADB; |
| 754 | #endif // !RIGHT_SIDE_COMPILE |
| 755 | |
| 756 | HRESULT SendEventWorker(DebuggerIPCEvent * pEvent, IPCEventType type); |
| 757 | |
| 758 | // Sends a pre-formatted message (including the data block, if any). The fWaitsForReply indicates whether |
| 759 | // the caller is going to block until some sort of reply message is received (for instance an event that |
| 760 | // must be ack'd or a request such as MT_GetDCB that needs a reply). SendMessage() uses this to determine |
| 761 | // whether it needs to buffer the message before placing it on the send queue (since it may need to resend |
| 762 | // the message after a transitory network failure). |
| 763 | HRESULT SendMessage(Message *pMessage, bool fWaitsForReply); |
| 764 | |
| 765 | // Helper method for sending messages requiring a reply (such as MT_GetDCB) and waiting on the result. |
| 766 | HRESULT SendRequestMessageAndWait(Message *pMessage); |
| 767 | |
| 768 | // Sends a single contiguous buffer of host memory over the connection. The caller is responsible for |
| 769 | // holding the state lock and ensuring the session state is SS_Open. Returns false if the send failed (the |
| 770 | // error will have already caused the recovery logic to kick in, so handling it is not required, the |
| 771 | // boolean is just returned so that any further blocks in the message are not sent). |
| 772 | bool SendBlock(PBYTE pbBuffer, DWORD cbBuffer); |
| 773 | |
| 774 | // Receives a single contiguous buffer of host memory over the connection. No state lock needs to be |
| 775 | // held (receives are serialized by the fact they're only performed on the transport thread). Returns |
| 776 | // false if a network error is encountered (which will automatically transition the session into the |
| 777 | // correct retry state). |
| 778 | bool ReceiveBlock(PBYTE pbBuffer, DWORD cbBuffer); |
| 779 | |
| 780 | // Called upon encountering a network error (e.g. an error from Send() or Receive()). This handles pushing |
| 781 | // the session state into SS_Resync_NC in order to start the recovery process. |
| 782 | void HandleNetworkError(bool fCallerHoldsStateLock); |
| 783 | |
| 784 | // Scan the send queue and discard any messages which have been processed by the other side according to |
| 785 | // the specified ID). Messages waiting on a reply message (e.g. MT_GetDCB) will be retained until that |
| 786 | // reply is processed. FlushSendQueue will take the state lock. |
| 787 | void FlushSendQueue(DWORD dwLastProcessedId); |
| 788 | |
| 789 | #ifdef RIGHT_SIDE_COMPILE |
| 790 | // Perform processing required to complete a request (such as MT_GetDCB) once a reply comes in. This |
| 791 | // includes reading data from the connection into the output buffer, removing the original message from |
| 792 | // the send queue and signalling the completion event. Returns true if no network error was encountered. |
| 793 | bool ProcessReply(MessageHeader *pHeader); |
| 794 | |
| 795 | // Upon receiving a reply message, signal the event on the message to wake up the thread waiting for |
| 796 | // the reply message and close the handle to the event. |
| 797 | void SignalReplyEvent(Message * pMesssage); |
| 798 | |
| 799 | // Given a message ID, find the matching message in the send queue. If there is no match, return NULL. |
| 800 | // If there is a match, remove the message from the send queue and return it. |
| 801 | Message * RemoveMessageFromSendQueue(DWORD dwMessageId); |
| 802 | #endif |
| 803 | |
| 804 | #ifndef RIGHT_SIDE_COMPILE |
| 805 | // Check read and optionally write memory access to the specified range of bytes. Used to check |
| 806 | // ReadProcessMemory and WriteProcessMemory requests. |
| 807 | HRESULT CheckBufferAccess(PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess); |
| 808 | #endif // !RIGHT_SIDE_COMPILE |
| 809 | |
| 810 | // Initialize all session state to correct starting values. Used during Init() and on the LS when we |
| 811 | // gracefully close one session and prepare for another. |
| 812 | void InitSessionState(); |
| 813 | |
| 814 | // The entry point of the transport worker thread. This one's static, so we immediately dispatch to an |
| 815 | // instance method version defined below for convenience in the implementation. |
| 816 | static DWORD WINAPI TransportWorkerStatic(LPVOID pvContext); |
| 817 | void TransportWorker(); |
| 818 | |
| 819 | // Given a fully initialized debugger event structure, return the size of the structure in bytes (this is |
| 820 | // not trivial since DebuggerIPCEvent contains a large union member which can cause the portion containing |
| 821 | // significant data to vary wildy from event to event). |
| 822 | DWORD GetEventSize(DebuggerIPCEvent *pEvent); |
| 823 | |
| 824 | #ifdef _DEBUG |
| 825 | // Debug helper which returns the name associated with a MessageType. |
| 826 | const char *MessageName(MessageType eType); |
| 827 | |
| 828 | // Debug logging helper which logs an incoming message of any type (as long as logging for that message |
| 829 | // class is currently enabled). |
| 830 | void (MessageHeader *); |
| 831 | |
| 832 | // Helper method used by the DBG_TRANSPORT_SHOULD_INJECT_FAULT macro. |
| 833 | bool DbgTransportShouldInjectFault(DbgTransportFaultOp eOp, const char *szOpName); |
| 834 | #else // _DEBUG |
| 835 | #define DbgTransportLogMessageReceived(x) |
| 836 | #endif // _DEBUG |
| 837 | }; |
| 838 | |
| 839 | #ifndef RIGHT_SIDE_COMPILE |
| 840 | // The one and only transport instance for the left side. Allocated and initialized during EE startup (from |
| 841 | // Debugger::Startup() in debugger.cpp). |
| 842 | extern DbgTransportSession *g_pDbgTransport; |
| 843 | #endif // !RIGHT_SIDE_COMPILE |
| 844 | |
| 845 | #define DBG_GET_LAST_WSA_ERROR() WSAGetLastError() |
| 846 | |
| 847 | #endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) |
| 848 | |
| 849 | #endif // __DBG_TRANSPORT_SESSION_INCLUDED |
| 850 | |