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 | |