2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
5 //*****************************************************************************
6 // File: DbgTransportPipeline.cpp
10 // Implements the native pipeline for Mac debugging.
11 //*****************************************************************************
14 #include "nativepipeline.h"
15 #include "dbgtransportsession.h"
16 #include "dbgtransportmanager.h"
19 DWORD GetProcessId(const DEBUG_EVENT * pEvent)
21 return pEvent->dwProcessId;
23 DWORD GetThreadId(const DEBUG_EVENT * pEvent)
25 return pEvent->dwThreadId;
28 // Get exception event
29 BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
31 if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
33 *pfFirstChance = FALSE;
37 *pfFirstChance = pEvent->u.Exception.dwFirstChance;
38 *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
43 //---------------------------------------------------------------------------------------
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.
52 // This class is NOT thread-safe. Caller is assumed to have taken the appropriate measures for
56 class DbgTransportPipeline :
57 public INativeEventPipeline
60 DbgTransportPipeline()
64 m_pIPCEvent = reinterpret_cast<DebuggerIPCEvent * >(m_rgbIPCEventBuffer);
67 _ASSERTE(!IsTransportRunning());
70 ~DbgTransportPipeline()
75 // Call to free up the pipeline.
76 virtual void Delete();
78 virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
81 virtual HRESULT CreateProcessUnderDebugger(
82 MachineInfo machineInfo,
83 LPCWSTR lpApplicationName,
84 LPCWSTR lpCommandLine,
85 LPSECURITY_ATTRIBUTES lpProcessAttributes,
86 LPSECURITY_ATTRIBUTES lpThreadAttributes,
88 DWORD dwCreationFlags,
90 LPCWSTR lpCurrentDirectory,
91 LPSTARTUPINFOW lpStartupInfo,
92 LPPROCESS_INFORMATION lpProcessInformation);
95 virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
98 virtual HRESULT DebugActiveProcessStop(DWORD processId);
100 // Block and wait for the next debug event from the debuggee process.
101 virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
103 virtual BOOL ContinueDebugEvent(
106 DWORD dwContinueStatus
109 // Return a handle which will be signaled when the debuggee process terminates.
110 virtual HANDLE GetProcessHandle();
112 // Terminate the debuggee process.
113 virtual BOOL TerminateProcess(UINT32 exitCode);
116 // Return TRUE if the transport is up and runnning
117 BOOL IsTransportRunning()
122 // clean up all resources
125 if (m_hProcess != NULL)
127 CloseHandle(m_hProcess);
133 if (m_ticket.IsValid())
135 m_pTransport->StopUsingAsDebugger(&m_ticket);
137 m_pProxy->ReleaseTransport(m_pTransport);
146 // This is actually a handle to an event. This is only valid for waiting on process termination.
149 DbgTransportTarget * m_pProxy;
150 DbgTransportSession * m_pTransport;
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;
159 // Allocate and return a pipeline object for this platform
160 INativeEventPipeline * NewPipelineForThisPlatform()
162 return new (nothrow) DbgTransportPipeline();
165 // Call to free up the lpProcessInformationpeline.
166 void DbgTransportPipeline::Delete()
171 // set whether to kill outstanding debuggees when the debugger exits.
172 BOOL DbgTransportPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
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.
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)
194 // INativeEventPipeline has a 1:1 relationship with CordbProcess.
195 _ASSERTE(!IsTransportRunning());
197 // We don't support interop-debugging on the Mac.
198 _ASSERTE(!(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)));
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));
204 // Connect to the debugger proxy on the remote machine and ask it to create a process for us.
207 m_pProxy = g_pDbgTransportTarget;
208 hr = m_pProxy->CreateProcess(lpApplicationName,
217 lpProcessInformation);
221 // Establish a connection to the actual runtime to be debugged.
222 hr = m_pProxy->GetTransportForProcess(lpProcessInformation->dwProcessId,
227 // Wait for the connection to become useable (or time out).
228 if (!m_pTransport->WaitForSessionToOpen(10000))
230 hr = CORDBG_E_TIMEOUT;
234 if (!m_pTransport->UseAsDebugger(&m_ticket))
236 hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
244 _ASSERTE((m_hProcess != NULL) && (m_hProcess != INVALID_HANDLE_VALUE));
246 m_dwProcessId = lpProcessInformation->dwProcessId;
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(),
258 &(lpProcessInformation->hProcess),
259 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
261 DUPLICATE_SAME_ACCESS))
263 hr = HRESULT_FROM_GetLastError();
279 // Attach the debugger to this process.
280 HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
282 // INativeEventPipeline has a 1:1 relationship with CordbProcess.
283 _ASSERTE(!IsTransportRunning());
287 m_pProxy = g_pDbgTransportTarget;
289 // Establish a connection to the actual runtime to be debugged.
290 hr = m_pProxy->GetTransportForProcess(processId, &m_pTransport, &m_hProcess);
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))
297 hr = CORDBG_E_TIMEOUT;
301 if (!m_pTransport->UseAsDebugger(&m_ticket))
303 hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
310 m_dwProcessId = processId;
322 HRESULT DbgTransportPipeline::DebugActiveProcessStop(DWORD processId)
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.
329 // Block and wait for the next debug event from the debuggee process.
330 BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
332 if (!IsTransportRunning())
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.
341 HANDLE rghWaitSet[2];
342 rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent();
343 rghWaitSet[1] = m_hProcess;
345 DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE);
347 if (dwRet == WAIT_OBJECT_0)
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);
353 pEvent->dwProcessId = m_pIPCEvent->processId;
354 _ASSERTE(m_dwProcessId == m_pIPCEvent->processId);
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;
363 if (!m_pIPCEvent->vmThread.IsNull())
365 pEvent->dwThreadId = pProcess->GetDAC()->TryGetVolatileOSThreadID(m_pIPCEvent->vmThread);
368 EX_CATCH_HRESULT(hr);
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.
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);
384 else if (dwRet == (WAIT_OBJECT_0 + 1))
386 // The process has been terminated.
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.
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.
401 // We may have timed out, or the actual wait operation may have failed.
402 // Either way, we don't have an event.
407 BOOL DbgTransportPipeline::ContinueDebugEvent(
410 DWORD dwContinueStatus
413 if (!IsTransportRunning())
418 // See code:INativeEventPipeline::ContinueDebugEvent.
422 // Return a handle which will be signaled when the debuggee process terminates.
423 HANDLE DbgTransportPipeline::GetProcessHandle()
425 HANDLE hProcessTerminated;
427 if (!DuplicateHandle(GetCurrentProcess(),
431 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
433 DUPLICATE_SAME_ACCESS))
438 // The handle returned here is only valid for waiting on process termination.
439 // See code:INativeEventPipeline::GetProcessHandle.
440 return hProcessTerminated;
443 // Terminate the debuggee process.
444 BOOL DbgTransportPipeline::TerminateProcess(UINT32 exitCode)
446 _ASSERTE(IsTransportRunning());
448 // The transport will still be running until the process termination handle is signaled.
449 m_pProxy->KillProcess(m_dwProcessId);