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