summaryrefslogtreecommitdiff
path: root/src/debug/di/windowspipeline.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/windowspipeline.cpp')
-rw-r--r--src/debug/di/windowspipeline.cpp419
1 files changed, 419 insertions, 0 deletions
diff --git a/src/debug/di/windowspipeline.cpp b/src/debug/di/windowspipeline.cpp
new file mode 100644
index 0000000000..c3050e3290
--- /dev/null
+++ b/src/debug/di/windowspipeline.cpp
@@ -0,0 +1,419 @@
+// 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: WindowsPipeline.cpp
+//
+
+//
+// Implements the native-pipeline on Windows OS.
+//*****************************************************************************
+
+#include "stdafx.h"
+#include "nativepipeline.h"
+
+#include <Tlhelp32.h>
+
+#include "holder.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;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Class serves as a connector to win32 native-debugging API.
+class WindowsNativePipeline :
+ public INativeEventPipeline
+{
+public:
+ WindowsNativePipeline()
+ {
+ // Default value for Win32.
+ m_fKillOnExit = true;
+ m_dwProcessId = 0;
+ }
+
+ // 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);
+
+ virtual BOOL WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess);
+
+ virtual BOOL ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+ );
+
+ // Return a handle for the debuggee process.
+ virtual HANDLE GetProcessHandle();
+
+ // Terminate the debuggee process.
+ virtual BOOL TerminateProcess(UINT32 exitCode);
+
+ // Resume any suspended threads
+ virtual HRESULT EnsureThreadsRunning();
+
+protected:
+ void UpdateDebugSetProcessKillOnExit();
+
+ HRESULT IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent);
+
+ // Cached value from DebugSetProcessKillOnExit.
+ // This is thread-local, and impacts all debuggees on the thread.
+ bool m_fKillOnExit;
+
+ DWORD m_dwProcessId;
+};
+
+// Allocate and return a pipeline object for this platform
+INativeEventPipeline * NewPipelineForThisPlatform()
+{
+ return new (nothrow) WindowsNativePipeline();
+}
+
+// Call to free up the pipeline.
+void WindowsNativePipeline::Delete()
+{
+ delete this;
+}
+
+
+// set whether to kill outstanding debuggees when the debugger exits.
+BOOL WindowsNativePipeline::DebugSetProcessKillOnExit(bool fKillOnExit)
+{
+ // Can't call kernel32!DebugSetProcessKillOnExit until after the event thread
+ // has spawned a debuggee. So cache the value now and call it later.
+ // This bit is enforced in code:WindowsNativePipeline::UpdateDebugSetProcessKillOnExit
+ m_fKillOnExit = fKillOnExit;
+ return TRUE;
+}
+
+// Enforces the bit set in code:WindowsNativePipeline::DebugSetProcessKillOnExit
+void WindowsNativePipeline::UpdateDebugSetProcessKillOnExit()
+{
+#if !defined(FEATURE_CORESYSTEM)
+ // Late bind to DebugSetProcessKillOnExit - WinXP and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ SIMPLIFYING_ASSUMPTION(hKernel32 != NULL);
+ if (hKernel32 == NULL)
+ return;
+
+ typedef BOOL (*DebugSetProcessKillOnExitSig) (BOOL);
+ DebugSetProcessKillOnExitSig pDebugSetProcessKillOnExit =
+ reinterpret_cast<DebugSetProcessKillOnExitSig>(GetProcAddress(hKernel32, "DebugSetProcessKillOnExit"));
+
+ // If the API doesn't exist (eg. Win2k) - there isn't anything we can do, just
+ // silently ignore the request.
+ if (pDebugSetProcessKillOnExit == NULL)
+ return;
+
+ BOOL ret = pDebugSetProcessKillOnExit(m_fKillOnExit);
+
+ // Not a good failure path here.
+ // 1) This shouldn't fail.
+ // 2) Even if it does, this is likely called after the debuggee
+ // has already been created, and if this API fails, most scenarios will
+ // be unaffected, so we don't want to fail the overall debugging session.
+ SIMPLIFYING_ASSUMPTION(ret);
+
+#else
+ // The API doesn't exit on CoreSystem, just return
+ return;
+#endif
+}
+
+// Create an process under the debugger.
+HRESULT WindowsNativePipeline::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)
+{
+ // This is always doing Native-debugging at the OS-level.
+ dwCreationFlags |= (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS);
+
+ BOOL ret = ::WszCreateProcess(
+ lpApplicationName,
+ lpCommandLine,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ bInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ lpCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation);
+ if (!ret)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ m_dwProcessId = lpProcessInformation->dwProcessId;
+ UpdateDebugSetProcessKillOnExit();
+ return S_OK;
+}
+
+// Attach the debugger to this process.
+HRESULT WindowsNativePipeline::DebugActiveProcess(MachineInfo machineInfo, DWORD processId)
+{
+ HRESULT hr = E_FAIL;
+ BOOL ret = ::DebugActiveProcess(processId);
+
+ if (ret)
+ {
+ hr = S_OK;
+ m_dwProcessId = processId;
+ UpdateDebugSetProcessKillOnExit();
+ }
+ else
+ {
+ hr = HRESULT_FROM_GetLastError();
+
+ // There are at least two scenarios in which DebugActiveProcess() returns E_INVALIDARG:
+ // 1) if the specified process doesn't exist, or
+ // 2) if the specified process already has a debugger atttached
+ // We need to distinguish these two cases in order to return the correct HR.
+ if (hr == E_INVALIDARG)
+ {
+ // Check whether a debugger is known to be already attached.
+ // Note that this API won't work on some OSes, in which case we err on the side of returning E_INVALIDARG
+ // even though a debugger may be attached. Another approach could be to assume that if
+ // OpenProcess succeeded, then DebugActiveProcess must only have failed because a debugger is
+ // attached. But I think it's better to only return the specific error code if we know for sure
+ // the case is true.
+ BOOL fIsDebuggerPresent = FALSE;
+ if (SUCCEEDED(IsRemoteDebuggerPresent(processId, &fIsDebuggerPresent)))
+ {
+ if (fIsDebuggerPresent)
+ {
+ hr = CORDBG_E_DEBUGGER_ALREADY_ATTACHED;
+ }
+ }
+ }
+ }
+
+ return hr;
+}
+
+// Determine (if possible) whether a debugger is attached to the target process
+HRESULT WindowsNativePipeline::IsRemoteDebuggerPresent(DWORD processId, BOOL* pfDebuggerPresent)
+{
+#if !defined(FEATURE_CORESYSTEM)
+
+ // Get a process handle for the process ID.
+ HandleHolder hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processId);
+ if (hProc == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // Delay-bind to CheckRemoteDebuggerPresent - WinXP SP1 and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ typedef BOOL (*CheckRemoteDebuggerPresentSig) (HANDLE, PBOOL);
+ CheckRemoteDebuggerPresentSig pCheckRemoteDebuggerPresent =
+ reinterpret_cast<CheckRemoteDebuggerPresentSig>(GetProcAddress(hKernel32, "CheckRemoteDebuggerPresent"));
+ if (pCheckRemoteDebuggerPresent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // API exists - call it
+ if (!pCheckRemoteDebuggerPresent(hProc, pfDebuggerPresent))
+ return HRESULT_FROM_GetLastError();
+
+ return S_OK;
+#else
+
+ //CoreSystem doesn't have this API
+ return E_FAIL;
+#endif
+}
+
+// Detach
+HRESULT WindowsNativePipeline::DebugActiveProcessStop(DWORD processId)
+{
+#if !defined(FEATURE_CORESYSTEM)
+ // Late-bind to DebugActiveProcessStop since it's WinXP and above only
+ HModuleHolder hKernel32;
+ hKernel32 = WszLoadLibrary(W("kernel32"));
+ if (hKernel32 == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ typedef BOOL (*DebugActiveProcessStopSig) (DWORD);
+ DebugActiveProcessStopSig pDebugActiveProcessStop =
+ reinterpret_cast<DebugActiveProcessStopSig>(GetProcAddress(hKernel32, "DebugActiveProcessStop"));
+
+ // Win2K will fail here - can't find DebugActiveProcessStop
+ if (pDebugActiveProcessStop == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ // Ok, the API exists, call it
+ if (!pDebugActiveProcessStop(processId))
+ {
+ // Detach itself failed
+ return HRESULT_FROM_GetLastError();
+ }
+#else
+ // The API exists, call it
+ if (!::DebugActiveProcessStop(processId))
+ {
+ // Detach itself failed
+ return HRESULT_FROM_GetLastError();
+ }
+#endif
+ return S_OK;
+}
+
+BOOL WindowsNativePipeline::WaitForDebugEvent(DEBUG_EVENT * pEvent, DWORD dwTimeout, CordbProcess * pProcess)
+{
+ return ::WaitForDebugEvent(pEvent, dwTimeout);
+}
+
+BOOL WindowsNativePipeline::ContinueDebugEvent(
+ DWORD dwProcessId,
+ DWORD dwThreadId,
+ DWORD dwContinueStatus
+)
+{
+ return ::ContinueDebugEvent(dwProcessId, dwThreadId, dwContinueStatus);
+}
+
+// Return a handle for the debuggee process.
+HANDLE WindowsNativePipeline::GetProcessHandle()
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ return ::OpenProcess(PROCESS_DUP_HANDLE |
+ PROCESS_QUERY_INFORMATION |
+ PROCESS_TERMINATE |
+ PROCESS_VM_OPERATION |
+ PROCESS_VM_READ |
+ PROCESS_VM_WRITE |
+ SYNCHRONIZE,
+ FALSE,
+ m_dwProcessId);
+}
+
+// Terminate the debuggee process.
+BOOL WindowsNativePipeline::TerminateProcess(UINT32 exitCode)
+{
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Get a process handle for the process ID.
+ HandleHolder hProc = OpenProcess(PROCESS_TERMINATE, FALSE, m_dwProcessId);
+
+ if (hProc == NULL)
+ {
+ return FALSE;
+ }
+
+ return ::TerminateProcess(hProc, exitCode);
+}
+
+// Resume any suspended threads (but just once)
+HRESULT WindowsNativePipeline::EnsureThreadsRunning()
+{
+#ifdef FEATURE_CORESYSTEM
+ _ASSERTE("NYI");
+ return E_FAIL;
+#else
+ _ASSERTE(m_dwProcessId != 0);
+
+ // Take a snapshot of all running threads (similar to ShimProcess::QueueFakeThreadAttachEventsNativeOrder)
+ // Alternately we could return thread creation/exit in WaitForDebugEvent. But we expect this to be used
+ // very rarely, so no need to complicate more common codepaths.
+ HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
+ THREADENTRY32 te32;
+
+ hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ return HRESULT_FROM_GetLastError();
+
+ // HandleHolder doesn't deal with INVALID_HANDLE_VALUE, so we only assign if we have a legal value.
+ HandleHolder hSnapshotHolder(hThreadSnap);
+
+ // Fill in the size of the structure before using it.
+ te32.dwSize = sizeof(THREADENTRY32);
+
+ // Retrieve information about the first thread, and exit if unsuccessful
+ if (!Thread32First(hThreadSnap, &te32))
+ return HRESULT_FROM_GetLastError();
+
+ // Now walk the thread list of the system and attempt to resume any that are part of this process
+ // Ignore errors - this is a best effort (but ASSERT in CHK builds since we don't expect errors
+ // in practice - we expect the process to be frozen at a debug event, so no races etc.)
+
+ HRESULT hr = S_FALSE; // no thread was resumed
+ do
+ {
+ if (te32.th32OwnerProcessID == m_dwProcessId)
+ {
+ HandleHolder hThread = ::OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
+ _ASSERTE(hThread != NULL);
+ if (hThread != NULL)
+ {
+ // Resume each thread exactly once (if they were suspended multiple times,
+ // then EnsureThreadsRunning would need to be called multiple times until it
+ // returned S_FALSE.
+ DWORD prevCount = ::ResumeThread(hThread);
+ _ASSERTE(prevCount >= 0);
+ if (prevCount >= 1)
+ hr = S_OK; // some thread was resumed
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+
+ return hr;
+#endif
+}