Merge pull request #2178 from justinvp/keyedcollection_nullchecks
[platform/upstream/coreclr.git] / src / debug / di / dbgtransportpipeline.cpp
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 // File: DbgTransportPipeline.cpp
6 // 
7
8 //
9 // Implements the native pipeline for Mac debugging.
10 //*****************************************************************************
11
12 #include "stdafx.h"
13 #include "nativepipeline.h"
14 #include "dbgtransportsession.h"
15 #include "dbgtransportmanager.h"
16
17
18 DWORD GetProcessId(const DEBUG_EVENT * pEvent)
19 {
20     return pEvent->dwProcessId;
21 }
22 DWORD GetThreadId(const DEBUG_EVENT * pEvent)
23 {
24     return pEvent->dwThreadId;
25 }
26
27 // Get exception event
28 BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
29 {
30     if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
31     {
32         *pfFirstChance = FALSE;
33         *ppRecord = NULL;
34         return FALSE;
35     }
36     *pfFirstChance = pEvent->u.Exception.dwFirstChance;
37     *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
38     return TRUE;
39 }
40
41
42 //---------------------------------------------------------------------------------------
43 //
44 // INativeEventPipeline is an abstraction over the Windows native debugging pipeline.  This class is an
45 // implementation which works over an SSL connection for debugging a target process on a Mac remotely.
46 // It builds on top of code:DbgTransportTarget (which is a connection to the debugger proxy on the Mac) and 
47 // code:DbgTransportSession (which is a connection to the target process on the Mac).  See
48 // code:IEventChannel for more information.
49 //
50 // Assumptions:
51 //    This class is NOT thread-safe.  Caller is assumed to have taken the appropriate measures for 
52 //    synchronization.
53 //
54
55 class DbgTransportPipeline : 
56     public INativeEventPipeline
57 {
58 public:
59     DbgTransportPipeline()
60     {
61         m_fRunning   = FALSE;
62         m_hProcess   = NULL;
63         m_pIPCEvent  = reinterpret_cast<DebuggerIPCEvent * >(m_rgbIPCEventBuffer);
64         m_pProxy     = NULL;
65         m_pTransport = NULL;
66         _ASSERTE(!IsTransportRunning());
67     }
68
69     virtual ~DbgTransportPipeline()
70     {
71         Dispose();
72     }
73
74     // Call to free up the pipeline.
75     virtual void Delete();
76
77     virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
78
79     // Create
80     virtual HRESULT CreateProcessUnderDebugger(
81         MachineInfo machineInfo,
82         LPCWSTR lpApplicationName,
83         LPCWSTR lpCommandLine,
84         LPSECURITY_ATTRIBUTES lpProcessAttributes,
85         LPSECURITY_ATTRIBUTES lpThreadAttributes,
86         BOOL bInheritHandles,
87         DWORD dwCreationFlags,
88         LPVOID lpEnvironment,
89         LPCWSTR lpCurrentDirectory,
90         LPSTARTUPINFOW lpStartupInfo,
91         LPPROCESS_INFORMATION lpProcessInformation);
92
93     // Attach
94     virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
95
96     // Detach
97     virtual HRESULT DebugActiveProcessStop(DWORD processId);
98
99     // Block and wait for the next debug event from the debuggee process.
100     virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
101
102     virtual BOOL ContinueDebugEvent(
103       DWORD dwProcessId,
104       DWORD dwThreadId,
105       DWORD dwContinueStatus
106     );
107
108     // Return a handle which will be signaled when the debuggee process terminates.
109     virtual HANDLE GetProcessHandle();
110
111     // Terminate the debuggee process.
112     virtual BOOL TerminateProcess(UINT32 exitCode);
113
114 private:
115     // Return TRUE if the transport is up and runnning
116     BOOL IsTransportRunning()
117     {
118         return m_fRunning;
119     };
120
121     // clean up all resources
122     void Dispose()
123     {
124         if (m_hProcess != NULL)
125         {
126             CloseHandle(m_hProcess);
127         }
128         m_hProcess = NULL;
129
130         if (m_pTransport)
131         {
132             if (m_ticket.IsValid())
133             {
134                 m_pTransport->StopUsingAsDebugger(&m_ticket);
135             }
136             m_pProxy->ReleaseTransport(m_pTransport);
137         }
138         m_pTransport = NULL;
139         m_pProxy = NULL;
140     }
141
142     BOOL                  m_fRunning;
143
144     DWORD                 m_dwProcessId;
145     // This is actually a handle to an event.  This is only valid for waiting on process termination.
146     HANDLE                m_hProcess;
147
148     DbgTransportTarget *  m_pProxy;
149     DbgTransportSession * m_pTransport;
150
151     // Any buffer for storing a DebuggerIPCEvent must be at least CorDBIPC_BUFFER_SIZE big.  For simplicity
152     // sake I have added an extra field member which points to the buffer.
153     DebuggerIPCEvent *    m_pIPCEvent;
154     BYTE                  m_rgbIPCEventBuffer[CorDBIPC_BUFFER_SIZE];
155     DebugTicket           m_ticket;
156 };
157
158 // Allocate and return a pipeline object for this platform
159 INativeEventPipeline * NewPipelineForThisPlatform()
160 {
161     return new (nothrow) DbgTransportPipeline();
162 }
163
164 // Call to free up the lpProcessInformationpeline.
165 void DbgTransportPipeline::Delete()
166 {
167     delete this;
168 }
169
170 // set whether to kill outstanding debuggees when the debugger exits.
171 BOOL DbgTransportPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
172 {
173     // This is not supported or necessary for Mac debugging.  The only reason we need this on Windows is to
174     // ask the OS not to terminate the debuggee when the debugger exits.  The Mac debugging pipeline doesn't 
175     // automatically kill the debuggee when the debugger exits.
176     return TRUE;
177 }
178
179 // Create an process under the debugger.
180 HRESULT DbgTransportPipeline::CreateProcessUnderDebugger(
181     MachineInfo machineInfo,
182     LPCWSTR lpApplicationName,
183     LPCWSTR lpCommandLine,
184     LPSECURITY_ATTRIBUTES lpProcessAttributes,
185     LPSECURITY_ATTRIBUTES lpThreadAttributes,
186     BOOL bInheritHandles,
187     DWORD dwCreationFlags,
188     LPVOID lpEnvironment,
189     LPCWSTR lpCurrentDirectory,
190     LPSTARTUPINFOW lpStartupInfo,
191     LPPROCESS_INFORMATION lpProcessInformation)
192 {
193     // INativeEventPipeline has a 1:1 relationship with CordbProcess.
194     _ASSERTE(!IsTransportRunning());
195
196     // We don't support interop-debugging on the Mac.
197     _ASSERTE(!(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)));
198
199     // When we're using a transport we can't deal with creating a suspended process (we need the process to
200     // startup in order that it can start up a transport thread and reply to our messages).
201     _ASSERTE(!(dwCreationFlags & CREATE_SUSPENDED));
202
203     // Connect to the debugger proxy on the remote machine and ask it to create a process for us.
204     HRESULT hr  = E_FAIL;
205
206     m_pProxy = g_pDbgTransportTarget;
207     hr = m_pProxy->CreateProcess(lpApplicationName,
208                                  lpCommandLine,
209                                  lpProcessAttributes,
210                                  lpThreadAttributes,
211                                  bInheritHandles,
212                                  dwCreationFlags,
213                                  lpEnvironment,
214                                  lpCurrentDirectory,
215                                  lpStartupInfo,
216                                  lpProcessInformation);
217
218     if (SUCCEEDED(hr))
219     {
220         // Establish a connection to the actual runtime to be debugged.
221         hr = m_pProxy->GetTransportForProcess(lpProcessInformation->dwProcessId, 
222                                               &m_pTransport, 
223                                               &m_hProcess);
224         if (SUCCEEDED(hr))
225         {
226             // Wait for the connection to become useable (or time out).
227             if (!m_pTransport->WaitForSessionToOpen(10000))
228             {
229                 hr = CORDBG_E_TIMEOUT;
230             }
231             else
232             {
233                 if (!m_pTransport->UseAsDebugger(&m_ticket))
234                 {
235                     hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
236                 }
237             }
238         }
239     }
240
241     if (SUCCEEDED(hr))
242     {
243         _ASSERTE((m_hProcess != NULL) && (m_hProcess != INVALID_HANDLE_VALUE));
244
245         m_dwProcessId = lpProcessInformation->dwProcessId;
246
247         // For Mac remote debugging, we don't actually have a process handle to hand back to the debugger.
248         // Instead, we return a handle to an event as the "process handle".  The Win32 event thread also waits
249         // on this event handle, and the event will be signaled when the proxy notifies us that the process
250         // on the remote machine is terminated.  However, normally the debugger calls CloseHandle() immediately 
251         // on the "process handle" after CreateProcess() returns.  Doing so causes the Win32 event thread to
252         // continue waiting on a closed event handle, and so it will never wake up.  
253         // (In fact, in Whidbey, we also duplicate the process handle in code:CordbProcess::Init.)
254         if (!DuplicateHandle(GetCurrentProcess(), 
255                              m_hProcess,
256                              GetCurrentProcess(), 
257                              &(lpProcessInformation->hProcess), 
258                              0,      // ignored since we are going to pass DUPLICATE_SAME_ACCESS
259                              FALSE, 
260                              DUPLICATE_SAME_ACCESS))
261         {
262             hr = HRESULT_FROM_GetLastError();
263         }
264     }
265
266     if (SUCCEEDED(hr))
267     {
268         m_fRunning = TRUE;
269     }
270     else
271     {
272         Dispose();
273     }
274
275     return hr;
276 }
277
278 // Attach the debugger to this process.
279 HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
280 {
281     // INativeEventPipeline has a 1:1 relationship with CordbProcess.
282     _ASSERTE(!IsTransportRunning());
283
284     HRESULT hr = E_FAIL;
285
286     m_pProxy = g_pDbgTransportTarget;
287
288     // Establish a connection to the actual runtime to be debugged.
289     hr = m_pProxy->GetTransportForProcess(processId, &m_pTransport, &m_hProcess);
290     if (SUCCEEDED(hr))
291     {
292         // TODO: Pass this timeout as a parameter all the way from debugger
293         // Wait for the connection to become useable (or time out).
294         if (!m_pTransport->WaitForSessionToOpen(10000))
295         {
296             hr = CORDBG_E_TIMEOUT;
297         }
298         else
299         {
300             if (!m_pTransport->UseAsDebugger(&m_ticket))
301             {
302                 hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
303             }
304         }
305     }
306
307     if (SUCCEEDED(hr))
308     {
309         m_dwProcessId = processId;
310         m_fRunning = TRUE;
311     }
312     else
313     {
314         Dispose();
315     }
316
317     return hr;
318 }
319
320 // Detach
321 HRESULT DbgTransportPipeline::DebugActiveProcessStop(DWORD processId)
322 {
323     // The only way to tell the transport to detach from a process is by shutting it down.
324     // That will happen when we neuter the CordbProcess object.
325     return E_NOTIMPL;
326 }
327
328 // Block and wait for the next debug event from the debuggee process.
329 BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
330 {
331     if (!IsTransportRunning())
332     {
333         return FALSE;
334     }
335
336     // We need to wait for a debug event from the transport and the process termination event.
337     // On Windows, process termination is communicated via a debug event as well, but that's not true for
338     // the Mac debugging transport.
339     DWORD cWaitSet = 2;
340     HANDLE rghWaitSet[2];
341     rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent();
342     rghWaitSet[1] = m_hProcess;
343
344     DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE);
345
346     if (dwRet == WAIT_OBJECT_0)
347     {
348         // The Mac debugging transport actually transmits IPC events and not debug events.
349         // We need to convert the IPC event to a debug event and pass it back to the caller.
350         m_pTransport->GetNextEvent(m_pIPCEvent, CorDBIPC_BUFFER_SIZE);
351
352         pEvent->dwProcessId = m_pIPCEvent->processId;
353         _ASSERTE(m_dwProcessId == m_pIPCEvent->processId);
354
355         // We are supposed to return a thread ID in the DEBUG_EVENT back to our caller.  
356         // However, we don't actually store the thread ID in the DebuggerIPCEvent anymore.  Instead, 
357         // we just get a VMPTR_Thread, and so we need to find the thread ID associated with the VMPTR_Thread.
358         pEvent->dwThreadId = 0;
359         HRESULT hr = S_OK;
360         EX_TRY
361         {
362             if (!m_pIPCEvent->vmThread.IsNull())
363             {
364                 pEvent->dwThreadId = pProcess->GetDAC()->TryGetVolatileOSThreadID(m_pIPCEvent->vmThread);
365             }
366         }
367         EX_CATCH_HRESULT(hr);
368         if (FAILED(hr))
369         {
370             return FALSE;
371         }
372
373         // The Windows implementation stores the target address of the IPC event in the debug event.
374         // We can do that for Mac debugging, but that would require the caller to do another cross-machine
375         // ReadProcessMemory().  Since we have all the data in-proc already, we just store a local address.
376         // 
377         // @dbgtodo  Mac - We are using -1 as a dummy base address right now.  
378         // Currently Mac remote debugging doesn't really support multi-instance.
379         InitEventForDebuggerNotification(pEvent, PTR_TO_CORDB_ADDRESS(reinterpret_cast<LPVOID>(-1)), m_pIPCEvent);
380
381         return TRUE;
382     }
383     else if (dwRet == (WAIT_OBJECT_0 + 1))
384     {
385         // The process has been terminated.
386
387         // We don't have a lot of information here.
388         pEvent->dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT;
389         pEvent->dwProcessId = m_dwProcessId;
390         pEvent->dwThreadId = 0;                 // On Windows this is the first thread created in the process.
391         pEvent->u.ExitProcess.dwExitCode = 0;   // This is not passed back to us by the transport.
392
393         // Once the process termination event is signaled, we cannot send or receive any events.
394         // So we mark the transport as not running anymore.
395         m_fRunning = FALSE;
396         return TRUE;
397     }
398     else
399     {
400         // We may have timed out, or the actual wait operation may have failed.
401         // Either way, we don't have an event.
402         return FALSE;
403     }
404 }
405
406 BOOL DbgTransportPipeline::ContinueDebugEvent(
407   DWORD dwProcessId,
408   DWORD dwThreadId,
409   DWORD dwContinueStatus
410 )
411 {
412     if (!IsTransportRunning())
413     {
414         return FALSE;
415     }
416
417     // See code:INativeEventPipeline::ContinueDebugEvent.
418     return TRUE;
419 }
420
421 // Return a handle which will be signaled when the debuggee process terminates.
422 HANDLE DbgTransportPipeline::GetProcessHandle()
423 {
424     HANDLE hProcessTerminated;
425
426     if (!DuplicateHandle(GetCurrentProcess(), 
427                          m_hProcess,
428                          GetCurrentProcess(), 
429                          &hProcessTerminated,
430                          0,      // ignored since we are going to pass DUPLICATE_SAME_ACCESS
431                          FALSE, 
432                          DUPLICATE_SAME_ACCESS))
433     {
434         return NULL;
435     }
436
437     // The handle returned here is only valid for waiting on process termination.
438     // See code:INativeEventPipeline::GetProcessHandle.
439     return hProcessTerminated;
440 }
441
442 // Terminate the debuggee process.
443 BOOL DbgTransportPipeline::TerminateProcess(UINT32 exitCode)
444 {
445     _ASSERTE(IsTransportRunning());
446
447     // The transport will still be running until the process termination handle is signaled.
448     m_pProxy->KillProcess(m_dwProcessId);
449     return TRUE;
450 }