summaryrefslogtreecommitdiff
path: root/src/debug/di/dbgtransportpipeline.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/dbgtransportpipeline.cpp')
-rw-r--r--src/debug/di/dbgtransportpipeline.cpp457
1 files changed, 457 insertions, 0 deletions
diff --git a/src/debug/di/dbgtransportpipeline.cpp b/src/debug/di/dbgtransportpipeline.cpp
new file mode 100644
index 0000000000..e3a3a8a54d
--- /dev/null
+++ b/src/debug/di/dbgtransportpipeline.cpp
@@ -0,0 +1,457 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//*****************************************************************************
+// File: DbgTransportPipeline.cpp
+//
+
+//
+// Implements the native pipeline for Mac debugging.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+#include "dbgtransportsession.h"
+#include "dbgtransportmanager.h"
+
+
+DWORD GetProcessId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwProcessId;
+}
+DWORD GetThreadId(const DEBUG_EVENT * pEvent)
+{
+ return pEvent->dwThreadId;
+}
+
+// Get exception event
+BOOL IsExceptionEvent(const DEBUG_EVENT * pEvent, BOOL * pfFirstChance, const EXCEPTION_RECORD ** ppRecord)
+{
+ if (pEvent->dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
+ {
+ *pfFirstChance = FALSE;
+ *ppRecord = NULL;
+ return FALSE;
+ }
+ *pfFirstChance = pEvent->u.Exception.dwFirstChance;
+ *ppRecord = &(pEvent->u.Exception.ExceptionRecord);
+ return TRUE;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// INativeEventPipeline is an abstraction over the Windows native debugging pipeline. This class is an
+// implementation which works over an SSL connection for debugging a target process on a Mac remotely.
+// It builds on top of code:DbgTransportTarget (which is a connection to the debugger proxy on the Mac) and
+// code:DbgTransportSession (which is a connection to the target process on the Mac). See
+// code:IEventChannel for more information.
+//
+// Assumptions:
+// This class is NOT thread-safe. Caller is assumed to have taken the appropriate measures for
+// synchronization.
+//
+
+class DbgTransportPipeline :
+ public INativeEventPipeline
+{
+public:
+ DbgTransportPipeline()
+ {
+ m_fRunning = FALSE;
+ m_hProcess = NULL;
+ m_pIPCEvent = reinterpret_cast<DebuggerIPCEvent * >(m_rgbIPCEventBuffer);
+ m_pProxy = NULL;
+ m_pTransport = NULL;
+ _ASSERTE(!IsTransportRunning());
+ }
+
+ virtual ~DbgTransportPipeline()
+ {
+ Dispose();
+ }
+
+ // Call to free up the pipeline.
+ virtual void Delete();
+
+ virtual BOOL DebugSetProcessKillOnExit(bool fKillOnExit);
+
+ // Create
+ virtual HRESULT CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation);
+
+ // Attach
+ virtual HRESULT DebugActiveProcess(MachineInfo machineInfo, DWORD processId);
+
+ // Detach
+ virtual HRESULT DebugActiveProcessStop(DWORD processId);
+
+ // Block and wait for the next debug event from the debuggee process.
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle which will be signaled when the debuggee process terminates.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+#ifdef FEATURE_PAL
+ virtual void CleanupTargetProcess()
+ {
+ m_pTransport->CleanupTargetProcess();
+ }
+#endif
+
+private:
+ // Return TRUE if the transport is up and runnning
+ BOOL IsTransportRunning()
+ {
+ return m_fRunning;
+ };
+
+ // clean up all resources
+ void Dispose()
+ {
+ if (m_hProcess != NULL)
+ {
+ CloseHandle(m_hProcess);
+ }
+ m_hProcess = NULL;
+
+ if (m_pTransport)
+ {
+ if (m_ticket.IsValid())
+ {
+ m_pTransport->StopUsingAsDebugger(&m_ticket);
+ }
+ m_pProxy->ReleaseTransport(m_pTransport);
+ }
+ m_pTransport = NULL;
+ m_pProxy = NULL;
+ }
+
+ BOOL m_fRunning;
+
+ DWORD m_dwProcessId;
+ // This is actually a handle to an event. This is only valid for waiting on process termination.
+ HANDLE m_hProcess;
+
+ DbgTransportTarget * m_pProxy;
+ DbgTransportSession * m_pTransport;
+
+ // Any buffer for storing a DebuggerIPCEvent must be at least CorDBIPC_BUFFER_SIZE big. For simplicity
+ // sake I have added an extra field member which points to the buffer.
+ DebuggerIPCEvent * m_pIPCEvent;
+ BYTE m_rgbIPCEventBuffer[CorDBIPC_BUFFER_SIZE];
+ DebugTicket m_ticket;
+};
+
+// Allocate and return a pipeline object for this platform
+INativeEventPipeline * NewPipelineForThisPlatform()
+{
+ return new (nothrow) DbgTransportPipeline();
+}
+
+// Call to free up the lpProcessInformationpeline.
+void DbgTransportPipeline::Delete()
+{
+ delete this;
+}
+
+// set whether to kill outstanding debuggees when the debugger exits.
+BOOL DbgTransportPipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // This is not supported or necessary for Mac debugging. The only reason we need this on Windows is to
+ // ask the OS not to terminate the debuggee when the debugger exits. The Mac debugging pipeline doesn't
+ // automatically kill the debuggee when the debugger exits.
+ return TRUE;
+}
+
+// Create an process under the debugger.
+HRESULT DbgTransportPipeline::CreateProcessUnderDebugger(
+ MachineInfo machineInfo,
+ LPCWSTR lpApplicationName,
+ LPCWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation)
+{
+ // INativeEventPipeline has a 1:1 relationship with CordbProcess.
+ _ASSERTE(!IsTransportRunning());
+
+ // We don't support interop-debugging on the Mac.
+ _ASSERTE(!(dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS)));
+
+ // When we're using a transport we can't deal with creating a suspended process (we need the process to
+ // startup in order that it can start up a transport thread and reply to our messages).
+ _ASSERTE(!(dwCreationFlags & CREATE_SUSPENDED));
+
+ // Connect to the debugger proxy on the remote machine and ask it to create a process for us.
+ HRESULT hr = E_FAIL;
+
+ m_pProxy = g_pDbgTransportTarget;
+ hr = m_pProxy->CreateProcess(lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+
+ if (SUCCEEDED(hr))
+ {
+ // Establish a connection to the actual runtime to be debugged.
+ hr = m_pProxy->GetTransportForProcess(lpProcessInformation->dwProcessId,
+ &m_pTransport,
+ &m_hProcess);
+ if (SUCCEEDED(hr))
+ {
+ // Wait for the connection to become useable (or time out).
+ if (!m_pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ }
+ else
+ {
+ if (!m_pTransport->UseAsDebugger(&m_ticket))
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE((m_hProcess != NULL) && (m_hProcess != INVALID_HANDLE_VALUE));
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+
+ // For Mac remote debugging, we don't actually have a process handle to hand back to the debugger.
+ // Instead, we return a handle to an event as the "process handle". The Win32 event thread also waits
+ // on this event handle, and the event will be signaled when the proxy notifies us that the process
+ // on the remote machine is terminated. However, normally the debugger calls CloseHandle() immediately
+ // on the "process handle" after CreateProcess() returns. Doing so causes the Win32 event thread to
+ // continue waiting on a closed event handle, and so it will never wake up.
+ // (In fact, in Whidbey, we also duplicate the process handle in code:CordbProcess::Init.)
+ if (!DuplicateHandle(GetCurrentProcess(),
+ m_hProcess,
+ GetCurrentProcess(),
+ &(lpProcessInformation->hProcess),
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ hr = HRESULT_FROM_GetLastError();
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_fRunning = TRUE;
+ }
+ else
+ {
+ Dispose();
+ }
+
+ return hr;
+}
+
+// Attach the debugger to this process.
+HRESULT DbgTransportPipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ // INativeEventPipeline has a 1:1 relationship with CordbProcess.
+ _ASSERTE(!IsTransportRunning());
+
+ HRESULT hr = E_FAIL;
+
+ m_pProxy = g_pDbgTransportTarget;
+
+ // Establish a connection to the actual runtime to be debugged.
+ hr = m_pProxy->GetTransportForProcess(processId, &m_pTransport, &m_hProcess);
+ if (SUCCEEDED(hr))
+ {
+ // TODO: Pass this timeout as a parameter all the way from debugger
+ // Wait for the connection to become useable (or time out).
+ if (!m_pTransport->WaitForSessionToOpen(10000))
+ {
+ hr = CORDBG_E_TIMEOUT;
+ }
+ else
+ {
+ if (!m_pTransport->UseAsDebugger(&m_ticket))
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_dwProcessId = processId;
+ m_fRunning = TRUE;
+ }
+ else
+ {
+ Dispose();
+ }
+
+ return hr;
+}
+
+// Detach
+HRESULT DbgTransportPipeline::DebugActiveProcessStop(DWORD processId)
+{
+ // The only way to tell the transport to detach from a process is by shutting it down.
+ // That will happen when we neuter the CordbProcess object.
+ return E_NOTIMPL;
+}
+
+// Block and wait for the next debug event from the debuggee process.
+BOOL DbgTransportPipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ if (!IsTransportRunning())
+ {
+ return FALSE;
+ }
+
+ // We need to wait for a debug event from the transport and the process termination event.
+ // On Windows, process termination is communicated via a debug event as well, but that's not true for
+ // the Mac debugging transport.
+ DWORD cWaitSet = 2;
+ HANDLE rghWaitSet[2];
+ rghWaitSet[0] = m_pTransport->GetDebugEventReadyEvent();
+ rghWaitSet[1] = m_hProcess;
+
+ DWORD dwRet = ::WaitForMultipleObjectsEx(cWaitSet, rghWaitSet, FALSE, dwTimeout, FALSE);
+
+ if (dwRet == WAIT_OBJECT_0)
+ {
+ // The Mac debugging transport actually transmits IPC events and not debug events.
+ // We need to convert the IPC event to a debug event and pass it back to the caller.
+ m_pTransport->GetNextEvent(m_pIPCEvent, CorDBIPC_BUFFER_SIZE);
+
+ pEvent->dwProcessId = m_pIPCEvent->processId;
+ _ASSERTE(m_dwProcessId == m_pIPCEvent->processId);
+
+ // We are supposed to return a thread ID in the DEBUG_EVENT back to our caller.
+ // However, we don't actually store the thread ID in the DebuggerIPCEvent anymore. Instead,
+ // we just get a VMPTR_Thread, and so we need to find the thread ID associated with the VMPTR_Thread.
+ pEvent->dwThreadId = 0;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (!m_pIPCEvent->vmThread.IsNull())
+ {
+ pEvent->dwThreadId = pProcess->GetDAC()->TryGetVolatileOSThreadID(m_pIPCEvent->vmThread);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+
+ // The Windows implementation stores the target address of the IPC event in the debug event.
+ // We can do that for Mac debugging, but that would require the caller to do another cross-machine
+ // ReadProcessMemory(). Since we have all the data in-proc already, we just store a local address.
+ //
+ // @dbgtodo Mac - We are using -1 as a dummy base address right now.
+ // Currently Mac remote debugging doesn't really support multi-instance.
+ InitEventForDebuggerNotification(pEvent, PTR_TO_CORDB_ADDRESS(reinterpret_cast<LPVOID>(-1)), m_pIPCEvent);
+
+ return TRUE;
+ }
+ else if (dwRet == (WAIT_OBJECT_0 + 1))
+ {
+ // The process has been terminated.
+
+ // We don't have a lot of information here.
+ pEvent->dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT;
+ pEvent->dwProcessId = m_dwProcessId;
+ pEvent->dwThreadId = 0; // On Windows this is the first thread created in the process.
+ pEvent->u.ExitProcess.dwExitCode = 0; // This is not passed back to us by the transport.
+
+ // Once the process termination event is signaled, we cannot send or receive any events.
+ // So we mark the transport as not running anymore.
+ m_fRunning = FALSE;
+ return TRUE;
+ }
+ else
+ {
+ // We may have timed out, or the actual wait operation may have failed.
+ // Either way, we don't have an event.
+ return FALSE;
+ }
+}
+
+BOOL DbgTransportPipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ if (!IsTransportRunning())
+ {
+ return FALSE;
+ }
+
+ // See code:INativeEventPipeline::ContinueDebugEvent.
+ return TRUE;
+}
+
+// Return a handle which will be signaled when the debuggee process terminates.
+HANDLE DbgTransportPipeline::GetProcessHandle()
+{
+ HANDLE hProcessTerminated;
+
+ if (!DuplicateHandle(GetCurrentProcess(),
+ m_hProcess,
+ GetCurrentProcess(),
+ &hProcessTerminated,
+ 0, // ignored since we are going to pass DUPLICATE_SAME_ACCESS
+ FALSE,
+ DUPLICATE_SAME_ACCESS))
+ {
+ return NULL;
+ }
+
+ // The handle returned here is only valid for waiting on process termination.
+ // See code:INativeEventPipeline::GetProcessHandle.
+ return hProcessTerminated;
+}
+
+// Terminate the debuggee process.
+BOOL DbgTransportPipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(IsTransportRunning());
+
+ // The transport will still be running until the process termination handle is signaled.
+ m_pProxy->KillProcess(m_dwProcessId);
+ return TRUE;
+}