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
18DWORD GetProcessId(const DEBUG_EVENT * pEvent)
19{
20 return pEvent->dwProcessId;
21}
22DWORD GetThreadId(const DEBUG_EVENT * pEvent)
23{
24 return pEvent->dwThreadId;
25}
26
27// Get exception event
28BOOL 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
55class DbgTransportPipeline :
56 public INativeEventPipeline
57{
58public:
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, const ProcessDescriptor& processDescriptor);
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
121private:
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
166INativeEventPipeline * NewPipelineForThisPlatform()
167{
168 return new (nothrow) DbgTransportPipeline();
169}
170
171// Call to free up the lpProcessInformationpeline.
172void DbgTransportPipeline::Delete()
173{
174 delete this;
175}
176
177// set whether to kill outstanding debuggees when the debugger exits.
178BOOL 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.
187HRESULT 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 ProcessDescriptor processDescriptor = ProcessDescriptor::Create(lpProcessInformation->dwProcessId, NULL);
228
229 // Establish a connection to the actual runtime to be debugged.
230 hr = m_pProxy->GetTransportForProcess(&processDescriptor,
231 &m_pTransport,
232 &m_hProcess);
233 if (SUCCEEDED(hr))
234 {
235 // Wait for the connection to become useable (or time out).
236 if (!m_pTransport->WaitForSessionToOpen(10000))
237 {
238 hr = CORDBG_E_TIMEOUT;
239 }
240 else
241 {
242 if (!m_pTransport->UseAsDebugger(&m_ticket))
243 {
244 hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
245 }
246 }
247 }
248 }
249
250 if (SUCCEEDED(hr))
251 {
252 _ASSERTE((m_hProcess != NULL) && (m_hProcess != INVALID_HANDLE_VALUE));
253
254 m_dwProcessId = lpProcessInformation->dwProcessId;
255
256 // For Mac remote debugging, we don't actually have a process handle to hand back to the debugger.
257 // Instead, we return a handle to an event as the "process handle". The Win32 event thread also waits
258 // on this event handle, and the event will be signaled when the proxy notifies us that the process
259 // on the remote machine is terminated. However, normally the debugger calls CloseHandle() immediately
260 // on the "process handle" after CreateProcess() returns. Doing so causes the Win32 event thread to
261 // continue waiting on a closed event handle, and so it will never wake up.
262 // (In fact, in Whidbey, we also duplicate the process handle in code:CordbProcess::Init.)
263 if (!DuplicateHandle(GetCurrentProcess(),
264 m_hProcess,
265 GetCurrentProcess(),
266 &(lpProcessInformation->hProcess),
267 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
268 FALSE,
269 DUPLICATE_SAME_ACCESS))
270 {
271 hr = HRESULT_FROM_GetLastError();
272 }
273 }
274
275 if (SUCCEEDED(hr))
276 {
277 m_fRunning = TRUE;
278 }
279 else
280 {
281 Dispose();
282 }
283
284 return hr;
285}
286
287// Attach the debugger to this process.
288HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, const ProcessDescriptor& processDescriptor)
289{
290 // INativeEventPipeline has a 1:1 relationship with CordbProcess.
291 _ASSERTE(!IsTransportRunning());
292
293 HRESULT hr = E_FAIL;
294
295 m_pProxy = g_pDbgTransportTarget;
296
297 // Establish a connection to the actual runtime to be debugged.
298 hr = m_pProxy->GetTransportForProcess(&processDescriptor, &m_pTransport, &m_hProcess);
299 if (SUCCEEDED(hr))
300 {
301 // TODO: Pass this timeout as a parameter all the way from debugger
302 // Wait for the connection to become useable (or time out).
303 if (!m_pTransport->WaitForSessionToOpen(10000))
304 {
305 hr = CORDBG_E_TIMEOUT;
306 }
307 else
308 {
309 if (!m_pTransport->UseAsDebugger(&m_ticket))
310 {
311 hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
312 }
313 }
314 }
315
316 if (SUCCEEDED(hr))
317 {
318 m_dwProcessId = processDescriptor.m_Pid;
319 m_fRunning = TRUE;
320 }
321 else
322 {
323 Dispose();
324 }
325
326 return hr;
327}
328
329// Detach
330HRESULT DbgTransportPipeline::DebugActiveProcessStop(DWORD processId)
331{
332 // The only way to tell the transport to detach from a process is by shutting it down.
333 // That will happen when we neuter the CordbProcess object.
334 return E_NOTIMPL;
335}
336
337// Block and wait for the next debug event from the debuggee process.
338BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
339{
340 if (!IsTransportRunning())
341 {
342 return FALSE;
343 }
344
345 // We need to wait for a debug event from the transport and the process termination event.
346 // On Windows, process termination is communicated via a debug event as well, but that's not true for
347 // the Mac debugging transport.
348 DWORD cWaitSet = 2;
349 HANDLE rghWaitSet[2];
350 rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent();
351 rghWaitSet[1] = m_hProcess;
352
353 DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE);
354
355 if (dwRet == WAIT_OBJECT_0)
356 {
357 // The Mac debugging transport actually transmits IPC events and not debug events.
358 // We need to convert the IPC event to a debug event and pass it back to the caller.
359 m_pTransport->GetNextEvent(m_pIPCEvent, CorDBIPC_BUFFER_SIZE);
360
361 pEvent->dwProcessId = m_pIPCEvent->processId;
362 pEvent->dwThreadId = m_pIPCEvent->threadId;
363 _ASSERTE(m_dwProcessId == m_pIPCEvent->processId);
364
365 // The Windows implementation stores the target address of the IPC event in the debug event.
366 // We can do that for Mac debugging, but that would require the caller to do another cross-machine
367 // ReadProcessMemory(). Since we have all the data in-proc already, we just store a local address.
368 //
369 // @dbgtodo Mac - We are using -1 as a dummy base address right now.
370 // Currently Mac remote debugging doesn't really support multi-instance.
371 InitEventForDebuggerNotification(pEvent, PTR_TO_CORDB_ADDRESS(reinterpret_cast<LPVOID>(-1)), m_pIPCEvent);
372
373 return TRUE;
374 }
375 else if (dwRet == (WAIT_OBJECT_0 + 1))
376 {
377 // The process has been terminated.
378
379 // We don't have a lot of information here.
380 pEvent->dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT;
381 pEvent->dwProcessId = m_dwProcessId;
382 pEvent->dwThreadId = 0; // On Windows this is the first thread created in the process.
383 pEvent->u.ExitProcess.dwExitCode = 0; // This is not passed back to us by the transport.
384
385 // Once the process termination event is signaled, we cannot send or receive any events.
386 // So we mark the transport as not running anymore.
387 m_fRunning = FALSE;
388 return TRUE;
389 }
390 else
391 {
392 // We may have timed out, or the actual wait operation may have failed.
393 // Either way, we don't have an event.
394 return FALSE;
395 }
396}
397
398BOOL DbgTransportPipeline::ContinueDebugEvent(
399 DWORD dwProcessId,
400 DWORD dwThreadId,
401 DWORD dwContinueStatus
402)
403{
404 if (!IsTransportRunning())
405 {
406 return FALSE;
407 }
408
409 // See code:INativeEventPipeline::ContinueDebugEvent.
410 return TRUE;
411}
412
413// Return a handle which will be signaled when the debuggee process terminates.
414HANDLE DbgTransportPipeline::GetProcessHandle()
415{
416 HANDLE hProcessTerminated;
417
418 if (!DuplicateHandle(GetCurrentProcess(),
419 m_hProcess,
420 GetCurrentProcess(),
421 &hProcessTerminated,
422 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
423 FALSE,
424 DUPLICATE_SAME_ACCESS))
425 {
426 return NULL;
427 }
428
429 // The handle returned here is only valid for waiting on process termination.
430 // See code:INativeEventPipeline::GetProcessHandle.
431 return hProcessTerminated;
432}
433
434// Terminate the debuggee process.
435BOOL DbgTransportPipeline::TerminateProcess(UINT32 exitCode)
436{
437 _ASSERTE(IsTransportRunning());
438
439 // The transport will still be running until the process termination handle is signaled.
440 m_pProxy->KillProcess(m_dwProcessId);
441 return TRUE;
442}
443