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