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
9 // Implements the native pipeline for Mac debugging.
10 //*****************************************************************************
13 #include "nativepipeline.h"
14 #include "dbgtransportsession.h"
15 #include "dbgtransportmanager.h"
18 DWORD GetProcessId(const DEBUG_EVENT * pEvent)
20 return pEvent->dwProcessId;
22 DWORD GetThreadId(const DEBUG_EVENT * pEvent)
24 return pEvent->dwThreadId;
27 // Get exception event
28 BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
30 if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
32 *pfFirstChance = FALSE;
36 *pfFirstChance = pEvent->u.Exception.dwFirstChance;
37 *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
42 //---------------------------------------------------------------------------------------
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.
51 // This class is NOT thread-safe. Caller is assumed to have taken the appropriate measures for
55 class DbgTransportPipeline :
56 public INativeEventPipeline
59 DbgTransportPipeline()
63 m_pIPCEvent = reinterpret_cast<DebuggerIPCEvent * >(m_rgbIPCEventBuffer);
66 _ASSERTE(!IsTransportRunning());
69 virtual ~DbgTransportPipeline()
74 // Call to free up the pipeline.
75 virtual void Delete();
77 virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
80 virtual HRESULT CreateProcessUnderDebugger(
81 MachineInfo machineInfo,
82 LPCWSTR lpApplicationName,
83 LPCWSTR lpCommandLine,
84 LPSECURITY_ATTRIBUTES lpProcessAttributes,
85 LPSECURITY_ATTRIBUTES lpThreadAttributes,
87 DWORD dwCreationFlags,
89 LPCWSTR lpCurrentDirectory,
90 LPSTARTUPINFOW lpStartupInfo,
91 LPPROCESS_INFORMATION lpProcessInformation);
94 virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
97 virtual HRESULT DebugActiveProcessStop(DWORD processId);
99 // Block and wait for the next debug event from the debuggee process.
100 virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
102 virtual BOOL ContinueDebugEvent(
105 DWORD dwContinueStatus
108 // Return a handle which will be signaled when the debuggee process terminates.
109 virtual HANDLE GetProcessHandle();
111 // Terminate the debuggee process.
112 virtual BOOL TerminateProcess(UINT32 exitCode);
115 virtual void CleanupTargetProcess()
117 m_pTransport->CleanupTargetProcess();
122 // Return TRUE if the transport is up and runnning
123 BOOL IsTransportRunning()
128 // clean up all resources
131 if (m_hProcess != NULL)
133 CloseHandle(m_hProcess);
139 if (m_ticket.IsValid())
141 m_pTransport->StopUsingAsDebugger(&m_ticket);
143 m_pProxy->ReleaseTransport(m_pTransport);
152 // This is actually a handle to an event. This is only valid for waiting on process termination.
155 DbgTransportTarget * m_pProxy;
156 DbgTransportSession * m_pTransport;
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;
165 // Allocate and return a pipeline object for this platform
166 INativeEventPipeline * NewPipelineForThisPlatform()
168 return new (nothrow) DbgTransportPipeline();
171 // Call to free up the lpProcessInformationpeline.
172 void DbgTransportPipeline::Delete()
177 // set whether to kill outstanding debuggees when the debugger exits.
178 BOOL DbgTransportPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
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.
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)
200 // INativeEventPipeline has a 1:1 relationship with CordbProcess.
201 _ASSERTE(!IsTransportRunning());
203 // We don't support interop-debugging on the Mac.
204 _ASSERTE(!(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)));
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));
210 // Connect to the debugger proxy on the remote machine and ask it to create a process for us.
213 m_pProxy = g_pDbgTransportTarget;
214 hr = m_pProxy->CreateProcess(lpApplicationName,
223 lpProcessInformation);
227 // Establish a connection to the actual runtime to be debugged.
228 hr = m_pProxy->GetTransportForProcess(lpProcessInformation->dwProcessId,
233 // Wait for the connection to become useable (or time out).
234 if (!m_pTransport->WaitForSessionToOpen(10000))
236 hr = CORDBG_E_TIMEOUT;
240 if (!m_pTransport->UseAsDebugger(&m_ticket))
242 hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
250 _ASSERTE((m_hProcess != NULL) && (m_hProcess != INVALID_HANDLE_VALUE));
252 m_dwProcessId = lpProcessInformation->dwProcessId;
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(),
264 &(lpProcessInformation->hProcess),
265 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
267 DUPLICATE_SAME_ACCESS))
269 hr = HRESULT_FROM_GetLastError();
285 // Attach the debugger to this process.
286 HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
288 // INativeEventPipeline has a 1:1 relationship with CordbProcess.
289 _ASSERTE(!IsTransportRunning());
293 m_pProxy = g_pDbgTransportTarget;
295 // Establish a connection to the actual runtime to be debugged.
296 hr = m_pProxy->GetTransportForProcess(processId, &m_pTransport, &m_hProcess);
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))
303 hr = CORDBG_E_TIMEOUT;
307 if (!m_pTransport->UseAsDebugger(&m_ticket))
309 hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
316 m_dwProcessId = processId;
328 HRESULT DbgTransportPipeline::DebugActiveProcessStop(DWORD processId)
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.
335 // Block and wait for the next debug event from the debuggee process.
336 BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
338 if (!IsTransportRunning())
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.
347 HANDLE rghWaitSet[2];
348 rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent();
349 rghWaitSet[1] = m_hProcess;
351 DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE);
353 if (dwRet == WAIT_OBJECT_0)
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);
359 pEvent->dwProcessId = m_pIPCEvent->processId;
360 _ASSERTE(m_dwProcessId == m_pIPCEvent->processId);
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;
369 if (!m_pIPCEvent->vmThread.IsNull())
371 pEvent->dwThreadId = pProcess->GetDAC()->TryGetVolatileOSThreadID(m_pIPCEvent->vmThread);
374 EX_CATCH_HRESULT(hr);
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.
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);
390 else if (dwRet == (WAIT_OBJECT_0 + 1))
392 // The process has been terminated.
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.
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.
407 // We may have timed out, or the actual wait operation may have failed.
408 // Either way, we don't have an event.
413 BOOL DbgTransportPipeline::ContinueDebugEvent(
416 DWORD dwContinueStatus
419 if (!IsTransportRunning())
424 // See code:INativeEventPipeline::ContinueDebugEvent.
428 // Return a handle which will be signaled when the debuggee process terminates.
429 HANDLE DbgTransportPipeline::GetProcessHandle()
431 HANDLE hProcessTerminated;
433 if (!DuplicateHandle(GetCurrentProcess(),
437 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
439 DUPLICATE_SAME_ACCESS))
444 // The handle returned here is only valid for waiting on process termination.
445 // See code:INativeEventPipeline::GetProcessHandle.
446 return hProcessTerminated;
449 // Terminate the debuggee process.
450 BOOL DbgTransportPipeline::TerminateProcess(UINT32 exitCode)
452 _ASSERTE(IsTransportRunning());
454 // The transport will still be running until the process termination handle is signaled.
455 m_pProxy->KillProcess(m_dwProcessId);