summaryrefslogtreecommitdiff
path: root/src/debug/di/shimprocess.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/shimprocess.cpp')
-rw-r--r--src/debug/di/shimprocess.cpp1904
1 files changed, 1904 insertions, 0 deletions
diff --git a/src/debug/di/shimprocess.cpp b/src/debug/di/shimprocess.cpp
new file mode 100644
index 0000000000..a6fc15407e
--- /dev/null
+++ b/src/debug/di/shimprocess.cpp
@@ -0,0 +1,1904 @@
+// 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: ShimProcess.cpp
+//
+
+//
+// The V3 ICD debugging APIs have a lower abstraction level than V2.
+// This provides V2 ICD debugging functionality on top of the V3 debugger object.
+//*****************************************************************************
+
+#include "stdafx.h"
+
+#include "safewrap.h"
+#include "check.h"
+
+#include <limits.h>
+#include "shimpriv.h"
+
+#if !defined(FEATURE_CORESYSTEM)
+#include <tlhelp32.h>
+#endif
+
+//---------------------------------------------------------------------------------------
+//
+// Ctor for a ShimProcess
+//
+// Notes:
+// See InitializeDataTarget in header for details of how to instantiate a ShimProcess and hook it up.
+// Initial ref count is 0. This is the convention used int the RS, and it plays well with semantics
+// like immediately assigning to a smart pointer (which will bump the count up to 1).
+
+ShimProcess::ShimProcess() :
+ m_ref(0),
+ m_fFirstManagedEvent(false),
+ m_fInCreateProcess(false),
+ m_fInLoadModule(false),
+ m_fIsInteropDebugging(false),
+ m_fIsDisposed(false),
+ m_loaderBPReceived(false)
+{
+ m_ShimLock.Init("ShimLock", RSLock::cLockReentrant, RSLock::LL_SHIM_LOCK);
+ m_ShimProcessDisposeLock.Init(
+ "ShimProcessDisposeLock",
+ RSLock::cLockReentrant | RSLock::cLockNonDbgApi,
+ RSLock::LL_SHIM_PROCESS_DISPOSE_LOCK);
+ m_eventQueue.Init(&m_ShimLock);
+ m_pShimCallback.Assign(new ShimProxyCallback(this)); // Throws
+
+ m_fNeedFakeAttachEvents = false;
+ m_ContinueStatusChangedData.Clear();
+
+ m_pShimStackWalkHashTable = new ShimStackWalkHashTable();
+
+ m_pDupeEventsHashTable = new DuplicateCreationEventsHashTable();
+
+ m_machineInfo.Clear();
+
+ m_markAttachPendingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_markAttachPendingEvent == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_terminatingEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_terminatingEvent == NULL)
+ {
+ ThrowLastError();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// ShimProcess dtor. Invoked when reference count goes to 0.
+//
+// Assumptions:
+// Dtors should not do any interesting work. If this object has been initialized,
+// then call Dispose() first.
+//
+//
+ShimProcess::~ShimProcess()
+{
+ // Expected that this was either already disposed first, or not initialized.
+ _ASSERTE(m_pWin32EventThread == NULL);
+
+ _ASSERTE(m_ShimProcessDisposeLock.IsInit());
+ m_ShimProcessDisposeLock.Destroy();
+
+ if (m_markAttachPendingEvent != NULL)
+ {
+ CloseHandle(m_markAttachPendingEvent);
+ m_markAttachPendingEvent = NULL;
+ }
+
+ if (m_terminatingEvent != NULL)
+ {
+ CloseHandle(m_terminatingEvent);
+ m_terminatingEvent = NULL;
+ }
+
+ // Dtor will release m_pLiveDataTarget
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Part of initialization to hook up to process.
+//
+// Arguments:
+// pProcess - debuggee object to connect to. Maybe null if part of shutdown.
+//
+// Notes:
+// This will take a strong reference to the process object.
+// This is part of the initialization phase.
+// This should only be called once.
+//
+//
+void ShimProcess::SetProcess(ICorDebugProcess * pProcess)
+{
+ PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(NULL);
+
+ // Data-target should already be setup before we try to connect to a process.
+ _ASSERTE(m_pLiveDataTarget != NULL);
+
+ // Reference is kept by m_pProcess;
+ m_pIProcess.Assign(pProcess);
+
+ // Get the private shim hooks. This just exists to access private functionality that has not
+ // yet been promoted to the ICorDebug interfaces.
+ m_pProcess = static_cast<CordbProcess *>(pProcess);
+
+ if (pProcess != NULL)
+ {
+ // Verify that DataTarget + new process have the same pid?
+ _ASSERTE(m_pProcess->GetPid() == m_pLiveDataTarget->GetPid());
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Create a Data-Target around the live process.
+//
+// Arguments:
+// processId - OS process ID to connect to. Must be a local, same platform, process.
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// This is part of the initialization dance.
+//
+// Notes:
+// Only call this once, during the initialization dance.
+//
+HRESULT ShimProcess::InitializeDataTarget(DWORD processId)
+{
+ _ASSERTE(m_pLiveDataTarget == NULL);
+
+
+ HRESULT hr = BuildPlatformSpecificDataTarget(GetMachineInfo(), processId, &m_pLiveDataTarget);
+ if (FAILED(hr))
+ {
+ _ASSERTE(m_pLiveDataTarget == NULL);
+ return hr;
+ }
+ m_pLiveDataTarget->HookContinueStatusChanged(ShimProcess::ContinueStatusChanged, this);
+
+ // Ref on pDataTarget is now 1.
+ _ASSERTE(m_pLiveDataTarget != NULL);
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Determines if current thread is the Win32 Event Thread
+//
+// Return Value:
+// True iff current thread is win32 event thread, else false.
+//
+// Notes:
+// The win32 event thread is created by code:ShimProcess::CreateAndStartWin32ET
+//
+bool ShimProcess::IsWin32EventThread()
+{
+ return (m_pWin32EventThread != NULL) && m_pWin32EventThread->IsWin32EventThread();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add a reference
+//
+void ShimProcess::AddRef()
+{
+ InterlockedIncrement(&m_ref);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Release a reference.
+//
+// Notes:
+// When ref goes to 0, object is deleted.
+//
+void ShimProcess::Release()
+{
+ LONG ref = InterlockedDecrement(&m_ref);
+ if (ref == 0)
+ {
+ delete this;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Dispose (Neuter) the object.
+//
+//
+// Assumptions:
+// This is called to gracefully shutdown the ShimProcess object.
+// This must be called before destruction if the object was initialized.
+//
+// Notes:
+// This will release all external resources, including getting the win32 event thread to exit.
+// This can safely be called multiple times.
+//
+void ShimProcess::Dispose()
+{
+ // Serialize Dispose with any other locked access to the shim. This helps
+ // protect against the debugger detaching while we're in the middle of
+ // doing stuff on the ShimProcess
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+
+ m_fIsDisposed = true;
+
+ // Can't shut down the W32ET if we're on it.
+ _ASSERTE(!IsWin32EventThread());
+
+ m_eventQueue.DeleteAll();
+
+ if (m_pWin32EventThread != NULL)
+ {
+ // This will block waiting for the thread to exit gracefully.
+ m_pWin32EventThread->Stop();
+
+ delete m_pWin32EventThread;
+ m_pWin32EventThread = NULL;
+ }
+
+ if (m_pLiveDataTarget != NULL)
+ {
+ m_pLiveDataTarget->Dispose();
+ m_pLiveDataTarget.Clear();
+ }
+
+ m_pIProcess.Clear();
+ m_pProcess = NULL;
+
+ _ASSERTE(m_ShimLock.IsInit());
+ m_ShimLock.Destroy();
+
+ if (m_pShimStackWalkHashTable != NULL)
+ {
+ // The hash table should be empty by now. ClearAllShimStackWalk() should have been called.
+ _ASSERTE(m_pShimStackWalkHashTable->GetCount() == 0);
+
+ delete m_pShimStackWalkHashTable;
+ m_pShimStackWalkHashTable = NULL;
+ }
+
+ if (m_pDupeEventsHashTable != NULL)
+ {
+ if (m_pDupeEventsHashTable->GetCount() > 0)
+ {
+ // loop through all the entries in the hash table, remove them, and delete them
+ for (DuplicateCreationEventsHashTable::Iterator pCurElem = m_pDupeEventsHashTable->Begin(),
+ pEndElem = m_pDupeEventsHashTable->End();
+ pCurElem != pEndElem;
+ pCurElem++)
+ {
+ DuplicateCreationEventEntry * pEntry = *pCurElem;
+ delete pEntry;
+ }
+ m_pDupeEventsHashTable->RemoveAll();
+ }
+
+ delete m_pDupeEventsHashTable;
+ m_pDupeEventsHashTable = NULL;
+ }
+}
+
+
+
+//---------------------------------------------------------------------------------------
+// Track (and close) file handles from debug events.
+//
+// Arguments:
+// pEvent - debug event
+//
+// Notes:
+// Some debug events introduce file handles that the debugger needs to track and
+// close on other debug events. For example, the LoadDll,CreateProcess debug
+// events both give back a file handle that the debugger must close. This is generally
+// done on the corresponding UnloadDll/ExitProcess debug events.
+//
+// Since we won't use the file handles, we'll just close them as soon as we get them.
+// That way, we don't need to remember any state.
+void ShimProcess::TrackFileHandleForDebugEvent(const DEBUG_EVENT * pEvent)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HANDLE hFile = NULL;
+
+ switch(pEvent->dwDebugEventCode)
+ {
+ //
+ // Events that add a file handle
+ //
+ case CREATE_PROCESS_DEBUG_EVENT:
+ hFile = pEvent->u.CreateProcessInfo.hFile;
+ CloseHandle(hFile);
+ break;
+
+ case LOAD_DLL_DEBUG_EVENT:
+ hFile = pEvent->u.LoadDll.hFile;
+ CloseHandle(hFile);
+ break;
+
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// ThreadProc helper to drain event queue.
+//
+// Arguments:
+// parameter - thread proc parameter, an ICorDebugProcess*
+//
+// Returns
+// 0.
+//
+// Notes:
+// This is useful when the shim queued a fake managed event (such as Control+C)
+// and needs to get the debuggee to synchronize in order to start dispatching events.
+// @dbgtodo sync: this will likely change as we iron out the Synchronization feature crew.
+//
+// We do this in a new thread proc to avoid thread restrictions:
+// Can't call this on win32 event thread because that can't send the IPC event to
+// make the aysnc-break request.
+// Can't call this on the RCET because that can't send an async-break (see SendIPCEvent for details)
+// So we just spin up a new thread to do the work.
+//---------------------------------------------------------------------------------------
+DWORD WINAPI CallStopGoThreadProc(LPVOID parameter)
+{
+ ICorDebugProcess* pProc = reinterpret_cast<ICorDebugProcess *>(parameter);
+
+ // We expect these operations to succeed; but if they do fail, there's nothing we can really do about it.
+ // If it fails on process exit/neuter/detach, then it would be ignorable.
+ HRESULT hr;
+
+
+ // Calling Stop + Continue will synchronize the process and force any queued events to be called.
+ // Stop is synchronous and will block until debuggee is synchronized.
+ hr = pProc->Stop(INFINITE);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // Continue will resume the debuggee. If there are queued events (which we expect in this case)
+ // then continue will drain the event queue instead of actually resuming the process.
+ hr = pProc->Continue(FALSE);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ // This thread just needs to trigger an event dispatch. Now that it's done that, it can exit.
+ return 0;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Does default event handling for native debug events.
+//
+// Arguments:
+// pEvent - IN event ot handle
+// pdwContinueStatus - IN /OUT - continuation status for event.
+//
+// Assumptions:
+// Called when target is stopped. Caller still needs to Continue the debug event.
+// This is called on the win32 event thread.
+//
+// Notes:
+// Some native events require extra work before continuing. Eg, skip loader
+// breakpoint, close certain handles, etc.
+// This is only called in the manage-only case. In the interop-case, the
+// debugger will get and handle these native debug events.
+void ShimProcess::DefaultEventHandler(
+ const DEBUG_EVENT * pEvent,
+ DWORD * pdwContinueStatus)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+
+ //
+ // Loader breakpoint
+ //
+
+ BOOL fFirstChance;
+ const EXCEPTION_RECORD * pRecord = NULL;
+
+ if (IsExceptionEvent(pEvent, &fFirstChance, &pRecord))
+ {
+ DWORD dwThreadId = GetThreadId(pEvent);
+
+ switch(pRecord->ExceptionCode)
+ {
+ case STATUS_BREAKPOINT:
+ {
+ if (!m_loaderBPReceived)
+ {
+ m_loaderBPReceived = true;
+
+ // Clear the loader breakpoint
+ *pdwContinueStatus = DBG_CONTINUE;
+
+ // After loader-breakpoint, notify that managed attach can begin.
+ // This is done to trigger a synchronization. The shim
+ // can then send the fake attach events once the target
+ // is synced.
+ // @dbgtodo sync: not needed once shim can
+ // work on sync APIs.
+ m_pProcess->QueueManagedAttachIfNeeded(); // throws
+ }
+ }
+ break;
+
+ /*
+ // If we handle the Ctlr-C event here and send the notification to the debugger, then we may break pre-V4
+ // behaviour because the debugger may handle the event and intercept the handlers registered in the debuggee
+ // process. So don't handle the event here and let the debuggee process handle it instead. See Dev10 issue
+ // 846455 for more info.
+ //
+ // However, when the re-arch is completed, we will need to work with VS to define what the right behaviour
+ // should be. We don't want to rely on in-process code to handle the Ctrl-C event.
+ case DBG_CONTROL_C:
+ {
+ // Queue a fake managed Ctrl+C event.
+ m_pShimCallback->ControlCTrap(GetProcess());
+
+ // Request an Async Break
+ // This is on Win32 Event Thread, so we can't call Stop / Continue.
+ // Instead, spawn a new threead, and have that call Stop/Continue, which
+ // will get the RCET to drain the event queue and dispatch the ControlCTrap we just queued.
+ {
+ DWORD dwDummyId;
+ CreateThread(NULL,
+ 0,
+ CallStopGoThreadProc,
+ (LPVOID) GetProcess(),
+ 0,
+ &dwDummyId);
+ }
+
+ // We don't worry about suspending the Control-C thread right now. The event is
+ // coming asynchronously, and so it's ok if the debuggee slips forward while
+ // we try to do a managed async break.
+
+
+ // Clear the control-C event.
+ *pdwContinueStatus = DBG_CONTINUE;
+ }
+ break;
+
+*/
+ }
+
+
+ }
+
+
+ // Native debugging APIs have an undocumented expectation that you clear for OutputDebugString.
+ if (pEvent->dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
+ {
+ *pdwContinueStatus = DBG_CONTINUE;
+ }
+
+ //
+ // File handles.
+ //
+ TrackFileHandleForDebugEvent(pEvent);
+}
+
+//---------------------------------------------------------------------------------------
+// Determine if we need to change the continue status
+//
+// Returns:
+// True if the continue status was changed. Else false.
+//
+// Assumptions:
+// This is single-threaded, which is enforced by it only be called on the win32et.
+// The shim guarnatees only 1 outstanding debug-event at a time.
+//
+// Notes:
+// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
+// Continue status is changed from a data-target callback which invokes
+// code:ShimProcess::ContinueStatusChangedWorker.
+// Call code:ShimProcess::ContinueStatusChangedData::Clear to clear the 'IsSet' bit.
+//
+bool ShimProcess::ContinueStatusChangedData::IsSet()
+{
+
+ return m_dwThreadId != 0;
+}
+
+//---------------------------------------------------------------------------------------
+// Clears the bit marking
+//
+// Assumptions:
+// This is single-threaded, which is enforced by it only be called on the win32et.
+// The shim guarantees only 1 outstanding debug-event at a time.
+//
+// Notes:
+// See code:ShimProcess::ContinueStatusChangedWorker for big picture.
+// This makes code:ShimProcess::ContinueStatusChangedData::IsSet return false.
+// This can safely be called multiple times in a row.
+//
+void ShimProcess::ContinueStatusChangedData::Clear()
+{
+ m_dwThreadId = 0;
+}
+
+//---------------------------------------------------------------------------------------
+// Callback invoked from data-target when continue status is changed.
+//
+// Arguments:
+// pUserData - data we supplied to the callback. a 'this' pointer.
+// dwThreadId - the tid whose continue status is changing
+// dwContinueStatus - the new continue status.
+//
+// Notes:
+//
+
+// Static
+HRESULT ShimProcess::ContinueStatusChanged(void * pUserData, DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ ShimProcess * pThis = reinterpret_cast<ShimProcess *>(pUserData);
+ return pThis->ContinueStatusChangedWorker(dwThreadId, dwContinueStatus);
+}
+
+//---------------------------------------------------------------------------------------
+// Real worker callback invoked from data-target when continue status is changed.
+//
+// Arguments:
+// dwThreadId - the tid whose continue status is changing
+// dwContinueStatus - the new continue status.
+//
+// Notes:
+// ICorDebugProcess4::Filter returns an initial continue status (heavily biased to 'gn').
+// Some ICorDebug operations may need to change the continue status that filter returned.
+// For example, on windows, hijacking a thread at an unhandled exception would need to
+// change the status to 'gh' (since continuing 2nd chance exception 'gn' will tear down the
+// process and the hijack would never execute).
+//
+// Such operations will invoke into the data-target (code:ICorDebugMutableDataTarget::ContinueStatusChanged)
+// to notify the debugger that the continue status was changed.
+//
+// The shim only executes such operations on the win32-event thread in a small window between
+// WaitForDebugEvent and Continue. Therefore, we know:
+// * the callback must come on the Win32EventThread (which means our handling the callback is
+// single-threaded.
+// * We only have 1 outstanding debug event to worry about at a time. This simplifies our tracking.
+//
+// The shim tracks the outstanding change request in m_ContinueStatusChangedData.
+
+HRESULT ShimProcess::ContinueStatusChangedWorker(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ // Should only be set once. This is only called on the win32 event thread, which protects against races.
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(!m_ContinueStatusChangedData.IsSet());
+
+ m_ContinueStatusChangedData.m_dwThreadId = dwThreadId;
+ m_ContinueStatusChangedData.m_status = dwContinueStatus;
+
+ // Setting dwThreadId to non-zero should now mark this as set.
+ _ASSERTE(m_ContinueStatusChangedData.IsSet());
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Add a duplicate creation event entry for the specified key.
+//
+// Arguments:
+// pKey - the key of the entry to be added; this is expected to be an
+// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
+//
+// Assumptions:
+// pKey is really an interface pointer of one of the types mentioned above
+//
+// Notes:
+// We have to keep track of which creation events we have sent already because some runtime data structures
+// are discoverable through enumeration before they send their creation events. As a result, we may have
+// faked up a creation event for a data structure during attach, and then later on get another creation
+// event for the same data structure. VS is not resilient in the face of multiple creation events for
+// the same data structure.
+//
+// Needless to say this is a problem in attach scenarios only. However, keep in mind that for CoreCLR,
+// launch really is early attach. For early attach, we get three creation events up front: a create
+// process, a create appdomain, and a create thread.
+//
+
+void ShimProcess::AddDuplicateCreationEvent(void * pKey)
+{
+ NewHolder<DuplicateCreationEventEntry> pEntry(new DuplicateCreationEventEntry(pKey));
+ m_pDupeEventsHashTable->Add(pEntry);
+ pEntry.SuppressRelease();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Check whether the specified key exists in the hash table. If so, remove it.
+//
+// Arguments:
+// pKey - the key of the entry to check; this is expected to be an
+// ICDProcess/ICDAppDomain/ICDThread/ICDAssembly/ICDModule
+//
+// Return Value:
+// Returns true if the entry exists. The entry will have been removed because we can't have more than two
+// duplicates for any given event.
+//
+// Assumptions:
+// pKey is really an interface pointer of one of the types mentioned above
+//
+// Notes:
+// See code:ShimProcess::AddDuplicateCreationEvent.
+//
+
+bool ShimProcess::RemoveDuplicateCreationEventIfPresent(void * pKey)
+{
+ // We only worry about duplicate events in attach scenarios.
+ if (GetAttached())
+ {
+ // Only do the check if the hash table actually contains entries.
+ if (m_pDupeEventsHashTable->GetCount() > 0)
+ {
+ // Check if this is a dupe.
+ DuplicateCreationEventEntry * pResult = m_pDupeEventsHashTable->Lookup(pKey);
+ if (pResult != NULL)
+ {
+ // This is a dupe. We can't have a dupe twice, so remove it.
+ // This will help as a bit of optimization, since we will no longer check the hash table if
+ // its count reaches 0.
+ m_pDupeEventsHashTable->Remove(pKey);
+ delete pResult;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Gets the exception record format of the host
+//
+// Returns:
+// The CorDebugRecordFormat for the host architecture.
+//
+// Notes:
+// This corresponds to the definition EXCEPTION_RECORD on the host-architecture.
+// It can be passed into ICorDebugProcess4::Filter.
+CorDebugRecordFormat GetHostExceptionRecordFormat()
+{
+#if defined(_WIN64)
+ return FORMAT_WINDOWS_EXCEPTIONRECORD64;
+#else
+ return FORMAT_WINDOWS_EXCEPTIONRECORD32;
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+// Main event handler for native debug events. Must also ensure Continue is called.
+//
+// Arguments:
+// pEvent - debug event to handle
+//
+// Assumptions:
+// Caller did a Flush() if needed.
+//
+// Notes:
+// The main Handle native debug events.
+// This must call back into ICD to let ICD filter the debug event (in case it's a managed notification).
+//
+// If we're interop-debugging (V2), then the debugger is expecting the debug events. In that case,
+// we go through the V2 interop-debugging logic to queue / dispatch the events.
+// If we're managed-only debugging, then the shim provides a default handler for the native debug.
+// This includes some basic work (skipping the loader breakpoint, close certain handles, etc).
+//---------------------------------------------------------------------------------------
+HRESULT ShimProcess::HandleWin32DebugEvent(const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(IsWin32EventThread());
+
+ //
+ // If this is an exception event, then we need to feed it into the CLR.
+ //
+ BOOL dwFirstChance = FALSE;
+ const EXCEPTION_RECORD * pRecord = NULL;
+ const DWORD dwThreadId = GetThreadId(pEvent);
+
+ bool fContinueNow = true;
+
+ // If true, we're continuing (unhandled) a 2nd-chance exception
+ bool fExceptionGoingUnhandled = false;
+
+ //
+ const DWORD kDONTCARE = 0;
+ DWORD dwContinueStatus = kDONTCARE;
+
+ if (IsExceptionEvent(pEvent, &dwFirstChance, &pRecord))
+ {
+ // As a diagnostic aid we can configure the debugger to assert when the debuggee does DebugBreak()
+#ifdef DEBUG
+ static ConfigDWORD config;
+ DWORD fAssert = config.val(CLRConfig::INTERNAL_DbgAssertOnDebuggeeDebugBreak);
+ if (fAssert)
+ {
+ // If we got a 2nd-chance breakpoint, then it's extremely likely that it's from an
+ // _ASSERTE in the target and we really want to know about it now before we kill the
+ // target. The debuggee will exit once we continue (unless we are mixed-mode debugging), so alert now.
+ // This assert could be our only warning of various catastrophic failures in the left-side.
+ if (!dwFirstChance && (pRecord->ExceptionCode == STATUS_BREAKPOINT) && !m_fIsInteropDebugging)
+ {
+ DWORD pid = (m_pLiveDataTarget == NULL) ? 0 : m_pLiveDataTarget->GetPid();
+
+ CONSISTENCY_CHECK_MSGF(false,
+ ("Unhandled breakpoint exception in debuggee (pid=%d (0x%x)) on thread %d(0x%x)\n"
+ "This may mean there was an assert in the debuggee on that thread.\n"
+ "\n"
+ "You should attach to that process (non-invasively) and get a callstack of that thread.\n"
+ "(This assert only occurs when CLRConfig::INTERNAL_DebuggerAssertOnDebuggeeDebugBreak is set)\n",
+ pid, pid, dwThreadId,dwThreadId));
+ }
+ }
+#endif
+
+ // We pass the Shim's proxy callback object, which will just take the callbacks and queue them
+ // to an event-queue in the shim. When we get the sync-complete event, the shim
+ // will then drain the event queue and dispatch the events to the user's callback object.
+ const DWORD dwFlags = dwFirstChance ? 1 : 0;
+
+ m_ContinueStatusChangedData.Clear();
+
+ // If ICorDebug doesn't care about this exception, it will leave dwContinueStatus unchanged.
+ RSExtSmartPtr<ICorDebugProcess4> pProcess4;
+ GetProcess()->QueryInterface(IID_ICorDebugProcess4, (void**) &pProcess4);
+
+ HRESULT hrFilter = pProcess4->Filter(
+ (const BYTE*) pRecord,
+ sizeof(EXCEPTION_RECORD),
+ GetHostExceptionRecordFormat(),
+ dwFlags,
+ dwThreadId,
+ m_pShimCallback,
+ &dwContinueStatus);
+ if (FAILED(hrFilter))
+ {
+ // Filter failed (eg. DAC couldn't be loaded), return the
+ // error so it can become an unrecoverable error.
+ return hrFilter;
+ }
+
+ // For unhandled exceptions, hijacking if needed.
+ if (!dwFirstChance)
+ {
+ // May invoke data-target callback (which may call code:ShimProcess::ContinueStatusChanged) to change continue status.
+ if (!m_pProcess->HijackThreadForUnhandledExceptionIfNeeded(dwThreadId))
+ {
+ // We decided not to hijack, so this exception is going to go unhandled
+ fExceptionGoingUnhandled = true;
+ }
+
+ if (m_ContinueStatusChangedData.IsSet())
+ {
+ _ASSERTE(m_ContinueStatusChangedData.m_dwThreadId == dwThreadId);
+
+ // Claiming this now means we won't do any other processing on the exception event.
+ // This means the interop-debugging logic will never see 2nd-chance managed exceptions.
+ dwContinueStatus = m_ContinueStatusChangedData.m_status;
+ }
+ }
+ }
+
+ // Do standard event handling, including Handling loader-breakpoint,
+ // and callback into CordbProcess for Attach if needed.
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ // For NonClr notifications, allow extra processing.
+ // This includes both non-exception events, and exception events that aren't
+ // specific CLR debugging services notifications.
+ if (dwContinueStatus == kDONTCARE)
+ {
+ if (m_fIsInteropDebugging)
+ {
+ // Interop-debugging logic will handle the continue.
+ fContinueNow = false;
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ // @dbgtodo interop: All the interop-debugging logic is still in CordbProcess.
+ // Call back into that. This will handle Continuing the debug event.
+ m_pProcess->HandleDebugEventForInteropDebugging(pEvent);
+#else
+ _ASSERTE(!"Interop debugging not supported on Rotor");
+#endif
+ }
+ else
+ {
+ dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
+
+ // For managed-only debugging, there's no user handler for native debug events,
+ // and so we still need to do some basic work on certain debug events.
+ DefaultEventHandler(pEvent, &dwContinueStatus);
+
+ // This is the managed-only case. No reason to keep the target win32 frozen, so continue it immediately.
+ _ASSERTE(fContinueNow);
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ // Dont' expect errors here (but could probably return it up to become an
+ // unrecoverable error if necessary). We still want to call Continue thought.
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+
+ //
+ // Continue the debuggee if needed.
+ //
+ if (fContinueNow)
+ {
+ BOOL fContinueOk = GetNativePipeline()->ContinueDebugEvent(
+ GetProcessId(pEvent),
+ dwThreadId,
+ dwContinueStatus);
+ (void)fContinueOk; //prevent "unused variable" error from GCC
+ SIMPLIFYING_ASSUMPTION(fContinueOk);
+
+ if (fExceptionGoingUnhandled)
+ {
+ _ASSERTE(dwContinueStatus == DBG_EXCEPTION_NOT_HANDLED);
+ // We just passed a 2nd-chance exception back to the OS which may have now invoked
+ // Windows error-reporting logic which suspended all threads in the target. Since we're
+ // still debugging and may want to break, inspect state and even detach (eg. to attach
+ // a different sort of debugger that can handle the exception) we need to let our threads run.
+ // Note that when WER auto-invokes a debugger it doesn't suspend threads, so it doesn't really
+ // make sense for them to be suspended now when a debugger is already attached.
+ // A better solution may be to suspend this faulting thread before continuing the event, do an
+ // async-break and give the debugger a notification of an unhandled exception. But this will require
+ // an ICorDebug API change, and also makes it harder to reliably get the WER dialog box once we're
+ // ready for it.
+ // Unfortunately we have to wait for WerFault.exe to start and actually suspend the threads, and
+ // there doesn't appear to be any better way than to just sleep for a little here. In practice 200ms
+ // seems like more than enough, but this is so uncommon of a scenario that a half-second delay
+ // (just to be safe) isn't a problem.
+ // Provide an undocumented knob to turn this behavior off in the very rare case it's not what we want
+ // (eg. we're trying to debug something that races with crashing / terminating the process on multiple
+ // threads)
+ static ConfigDWORD config;
+ DWORD fSkipResume = config.val(CLRConfig::UNSUPPORTED_DbgDontResumeThreadsOnUnhandledException);
+ if (!fSkipResume)
+ {
+ ::Sleep(500);
+ hrIgnore = GetNativePipeline()->EnsureThreadsRunning();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+// Trivial accessor to get the event queue.
+ManagedEventQueue * ShimProcess::GetManagedEventQueue()
+{
+ return &m_eventQueue;
+}
+
+// Combines GetManagedEventQueue() and Dequeue() into a single function
+// that holds m_ShimProcessDisposeLock for the duration
+ManagedEvent * ShimProcess::DequeueManagedEvent()
+{
+ // Serialize this function with Dispoe()
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+ if (m_fIsDisposed)
+ return NULL;
+
+ return m_eventQueue.Dequeue();
+}
+
+// Trivial accessor to get Shim's proxy callback object.
+ShimProxyCallback * ShimProcess::GetShimCallback()
+{
+ return m_pShimCallback;
+}
+
+// Trivial accessor to get the ICDProcess for the debuggee.
+// A ShimProcess object can then provide V2 functionality by building it on V3 functionality
+// exposed by the ICDProcess object.
+ICorDebugProcess * ShimProcess::GetProcess()
+{
+ return m_pIProcess;
+}
+
+// Trivial accessor to get the data-target for the debuggee.
+// The data-target lets us access the debuggee, especially reading debuggee memory.
+ICorDebugMutableDataTarget * ShimProcess::GetDataTarget()
+{
+ return m_pLiveDataTarget;
+};
+
+
+// Trivial accessor to get the raw native event pipeline.
+// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
+INativeEventPipeline * ShimProcess::GetNativePipeline()
+{
+ return m_pWin32EventThread->GetNativePipeline();
+}
+
+// Trivial accessor to expose the W32ET thread to the CordbProcess so that it can emulate V2 behavior.
+// In V3, ICorDebug no longer owns the event thread and it does not own the event pipeline either.
+// The Win32 Event Thread is the only thread that can use the native pipeline
+// see code:ShimProcess::GetNativePipeline.
+CordbWin32EventThread * ShimProcess::GetWin32EventThread()
+{
+ return m_pWin32EventThread;
+}
+
+
+// Trivial accessor to mark whether we're interop-debugging.
+// Retreived via code:ShimProcess::IsInteropDebugging
+void ShimProcess::SetIsInteropDebugging(bool fIsInteropDebugging)
+{
+ m_fIsInteropDebugging = fIsInteropDebugging;
+}
+
+// Trivial accessor to check if we're interop-debugging.
+// This affects how we handle native debug events.
+// The significant usage of this is in code:ShimProcess::HandleWin32DebugEvent
+bool ShimProcess::IsInteropDebugging()
+{
+ return m_fIsInteropDebugging;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Begin queueing the fake attach events.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents for more about "fake attach events".
+//
+// This marks that we need to send fake attach events, and queus a CreateProcess.
+// Caller calls code:ShimProcess::QueueFakeAttachEventsIfNeeded to finish queuing
+// the rest of the fake attach events.
+void ShimProcess::BeginQueueFakeAttachEvents()
+{
+ m_fNeedFakeAttachEvents = true;
+
+ // Put a fake CreateProcess event in the queue.
+ // This will not be drained until we get a Sync-Complete from the Left-side.
+ GetShimCallback()->QueueCreateProcess(GetProcess());
+ AddDuplicateCreationEvent(GetProcess());
+}
+
+//---------------------------------------------------------------------------------------
+// potentially Queue fake attach events like we did in V2.
+//
+// Arguments:
+// fRealCreateProcessEvent - true if the shim is about to dispatch a real create process event (as opposed
+// to one faked up by the shim itself)
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents for details.
+void ShimProcess::QueueFakeAttachEventsIfNeeded(bool fRealCreateProcessEvent)
+{
+ // This was set high in code:ShimProcess::BeginQueueFakeAttachEvents
+ if (!m_fNeedFakeAttachEvents)
+ {
+ return;
+ }
+ m_fNeedFakeAttachEvents = false;
+
+ // If the first event we get after attaching is a create process event, then this is an early attach
+ // scenario and we don't need to queue any fake attach events.
+ if (!fRealCreateProcessEvent)
+ {
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ QueueFakeAttachEvents();
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Send fake Thread-create events for attach, using an arbitrary order.
+//
+// Returns:
+// S_OK on success, else error.
+//
+// Notes:
+// This sends fake thread-create events, ala V2 attach.
+// See code:ShimProcess::QueueFakeAttachEvents for details
+//
+// The order of thread creates is random and at the mercy of ICorDebugProcess::EnumerateThreads.
+// Whidbey would send thread creates in the order of the OS's native thread
+// list. Since Arrowhead no longer sends fake attach events, the shim simulates
+// the fake attach events. But ICorDebug doesn't provide a way to get the
+// same order that V2 used. So without using platform-specific thread-enumeration,
+// we can't get the V2 ordering.
+//
+// Compare to code:ShimProcess::QueueFakeThreadAttachEventsNativeOrder,
+// which sends threads in the OS native thread create order.
+//
+HRESULT ShimProcess::QueueFakeThreadAttachEventsNoOrder()
+{
+ ICorDebugProcess * pProcess = GetProcess();
+
+ RSExtSmartPtr<ICorDebugThreadEnum> pThreadEnum;
+ RSExtSmartPtr<ICorDebugThread> pThread;
+
+ // V2 would only send create threads after a thread had run managed code.
+ // V3 has a discovery model where Enumeration can find threads before they've run managed code.
+ // So the emulation here may send some additional create-thread events that v2 didn't send.
+ HRESULT hr = pProcess->EnumerateThreads(&pThreadEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG cDummy;
+
+ while(SUCCEEDED(pThreadEnum->Next(1, &pThread, &cDummy)) && (pThread != NULL))
+ {
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ hr = pThread->GetAppDomain(&pAppDomain);
+
+ // Getting the appdomain shouldn't fail. If it does, we can't dispatch
+ // this callback, but we can still dispatch the other thread creates.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (pAppDomain != NULL)
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+ }
+ pThread.Clear();
+ }
+
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// Send fake Thread-create events for attach, using the order of the OS native
+// thread list.
+//
+// Returns:
+// S_OK on success, else error.
+//
+// Notes:
+// This sends fake thread-create events, ala V2 attach.
+// See code:ShimProcess::QueueFakeAttachEvents for details
+// The order of the thread creates matches the OS's native thread list.
+// This is important because the debugger can use the order of thread-create
+// callbacks to associate logical thread-ids (0,1,2...) with threads. Users
+// may rely on thread 0 always being the main thread.
+// In contrast, the order from ICorDebugProcess::EnumerateThreads is random.
+//
+// Compare to code:ShimProcess::QueueFakeThreadAttachEventsNoOrder, which
+// sends the threads in an arbitrary order.
+HRESULT ShimProcess::QueueFakeThreadAttachEventsNativeOrder()
+{
+#ifdef FEATURE_CORESYSTEM
+ _ASSERTE("NYI");
+ return E_FAIL;
+#else
+ ICorDebugProcess * pProcess = GetProcess();
+
+ DWORD dwProcessId;
+ HRESULT hr = pProcess->GetID(&dwProcessId);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
+ THREADENTRY32 te32;
+
+ // Take a snapshot of all running threads
+ hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ {
+ hr = HRESULT_FROM_GetLastError();
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ return hr;
+ }
+ // 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))
+ {
+ hr = HRESULT_FROM_GetLastError();
+ return hr;
+ }
+
+ // Now walk the thread list of the system,
+ // and display information about each thread
+ // associated with the specified process
+ do
+ {
+ if (te32.th32OwnerProcessID == dwProcessId)
+ {
+ RSExtSmartPtr<ICorDebugThread> pThread;
+ pProcess->GetThread(te32.th32ThreadID, &pThread);
+ if (pThread != NULL)
+ {
+ // If we fail to get the appdomain for some reason, then then
+ // we can't dispatch this thread callback. But we can still
+ // finish enumerating.
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ HRESULT hrGetAppDomain = pThread->GetAppDomain(&pAppDomain);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hrGetAppDomain);
+ if (pAppDomain != NULL)
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+
+ //fix for issue DevDiv2\DevDiv 77523 - threads are switched out in SQL don't get thread create notifications
+ // mark that this thread has queued a create event
+ CordbThread* pThreadInternal = static_cast<CordbThread*>(pThread.GetValue());
+ pThreadInternal->SetCreateEventQueued();
+ }
+ }
+ }
+ } while(Thread32Next(hThreadSnap, &te32));
+
+
+ //fix for issue DevDiv2\DevDiv 77523 - threads are switched out in SQL don't get thread create notifications
+ //
+
+
+ // Threads which were switched out won't be present in the native thread order enumeration above.
+ // In order to not miss them we will enumerate all the managed thread objects and for any that we haven't
+ // already queued a notification for, we will queue a notification now.
+ RSExtSmartPtr<ICorDebugThreadEnum> pThreadEnum;
+ RSExtSmartPtr<ICorDebugThread> pThread;
+ hr = pProcess->EnumerateThreads(&pThreadEnum);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+ ULONG cDummy;
+
+ while(SUCCEEDED(pThreadEnum->Next(1, &pThread, &cDummy)) && (pThread != NULL))
+ {
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+ hr = pThread->GetAppDomain(&pAppDomain);
+ CordbThread* pThreadInternal = static_cast<CordbThread*>(pThread.GetValue());
+
+ // Getting the appdomain shouldn't fail. If it does, we can't dispatch
+ // this callback, but we can still dispatch the other thread creates.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ if (pAppDomain != NULL && !pThreadInternal->CreateEventWasQueued())
+ {
+ GetShimCallback()->CreateThread(pAppDomain, pThread);
+ AddDuplicateCreationEvent(pThread);
+ pThreadInternal->SetCreateEventQueued();
+ }
+ pThread.Clear();
+ }
+
+
+ return S_OK;
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+// Queues the fake Assembly and Module load events
+//
+// Arguments:
+// pAssembly - non-null, the assembly to queue.
+//
+// Notes:
+// Helper for code:ShimProcess::QueueFakeAttachEvents
+// Queues create events for the assembly and for all modules within the
+// assembly. Most assemblies only have 1 module.
+void ShimProcess::QueueFakeAssemblyAndModuleEvent(ICorDebugAssembly * pAssembly)
+{
+ RSExtSmartPtr<ICorDebugAppDomain> pAppDomain;
+
+ HRESULT hr = pAssembly->GetAppDomain(&pAppDomain);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ //
+ // Send the fake Load Assembly event.
+ //
+ GetShimCallback()->LoadAssembly(pAppDomain, pAssembly);
+ AddDuplicateCreationEvent(pAssembly);
+
+ //
+ // Send Modules - must be in load order
+ //
+ RSExtSmartPtr<ICorDebugModuleEnum> pModuleEnum;
+ hr = pAssembly->EnumerateModules(&pModuleEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ ULONG countModules;
+ hr = pModuleEnum->GetCount(&countModules);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ // ISSUE WORKAROUND 835869
+ // The CordbEnumFilter used as the implementation of CordbAssembly::EnumerateModules has
+ // a ref counting bug in it. It adds one ref to each item when it is constructed and never
+ // removes that ref. Expected behavior would be that it adds a ref at construction, another on
+ // every call to next, and releases the construction ref when the enumerator is destroyed. The
+ // user is expected to release the reference they receive from Next. Thus enumerating exactly
+ // one time and calling Release() does the correct thing regardless of whether this bug is present
+ // or not. Note that with the bug the enumerator holds 0 references at the end of this loop,
+ // however the assembly also holds references so the modules will not be prematurely released.
+ for(ULONG i = 0; i < countModules; i++)
+ {
+ ICorDebugModule* pModule = NULL;
+ ULONG countFetched = 0;
+ pModuleEnum->Next(1, &pModule, &countFetched);
+ _ASSERTE(pModule != NULL);
+ if(pModule != NULL)
+ {
+ pModule->Release();
+ }
+ }
+
+ RSExtSmartPtr<ICorDebugModule> * pModules = new RSExtSmartPtr<ICorDebugModule> [countModules];
+ m_pProcess->GetModulesInLoadOrder(pAssembly, pModules, countModules);
+ for(ULONG iModule = 0; iModule < countModules; iModule++)
+ {
+ ICorDebugModule * pModule = pModules[iModule];
+
+ GetShimCallback()->FakeLoadModule(pAppDomain, pModule);
+ AddDuplicateCreationEvent(pModule);
+
+ // V2 may send UpdatePdbStreams for certain modules (like dynamic or in-memory modules).
+ // We don't yet have this support for out-of-proc.
+ // When the LoadModule event that we just queued is actually dispatched, it will
+ // send an IPC event in-process that will collect the information and queue the event
+ // at that time.
+ // @dbgtodo : I don't think the above is true anymore - clean it up?
+
+ RSExtSmartPtr<IStream> pSymbolStream;
+
+ // ICorDebug has no public way to request raw symbols. This is by-design because we
+ // don't want people taking a dependency on a specific format (to give us the ability
+ // to innovate for the RefEmit case). So we must use a private hook here to get the
+ // symbol data.
+ CordbModule * pCordbModule = static_cast<CordbModule *>(pModule);
+ IDacDbiInterface::SymbolFormat symFormat = IDacDbiInterface::kSymbolFormatNone;
+ EX_TRY
+ {
+ symFormat = pCordbModule->GetInMemorySymbolStream(&pSymbolStream);
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr); // Shouldn't be any errors trying to read symbols
+
+ // Only pass the raw symbols onto the debugger if they're in PDB format (all that was supported
+ // in V2). Note that we could have avoided creating a stream for the non-PDB case, but we'd have
+ // to refactor GetInMemorySymbolStream and the perf impact should be negligable.
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+ _ASSERTE(pSymbolStream != NULL); // symFormat should have been kSymbolFormatNone if null stream
+ GetShimCallback()->UpdateModuleSymbols(pAppDomain, pModule, pSymbolStream);
+ }
+
+ }
+ delete [] pModules;
+}
+
+//---------------------------------------------------------------------------------------
+// Get an array of appdomains, sorted by increasing AppDomain ID
+//
+// Arguments:
+// pProcess - process containing the appdomains
+// ppAppDomains - array that this function will allocate to hold appdomains
+// pCount - size of ppAppDomains array
+//
+// Assumptions:
+// Caller must delete [] ppAppDomains
+//
+// Notes
+// This is used as part of code:ShimProcess::QueueFakeAttachEvents.
+// The fake attach events want appdomains in creation order. ICorDebug doesn't provide
+// this ordering in the enumerators.
+//
+// This returns the appdomains sorted in order of increasing AppDomain ID, since that's the best
+// approximation of creation order that we have.
+// @dbgtodo - determine if ICD will provide
+// ordered enumerators
+//
+HRESULT GetSortedAppDomains(ICorDebugProcess * pProcess, RSExtSmartPtr<ICorDebugAppDomain> **ppAppDomains, ULONG * pCount)
+{
+ _ASSERTE(ppAppDomains != NULL);
+
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<ICorDebugAppDomainEnum> pAppEnum;
+
+ //
+ // Find the size of the array to hold all the appdomains
+ //
+ hr = pProcess->EnumerateAppDomains(&pAppEnum);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ ULONG countAppDomains = 0;
+
+ hr = pAppEnum->GetCount(&countAppDomains);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ //
+ // Allocate the array
+ //
+ RSExtSmartPtr<ICorDebugAppDomain> * pAppDomains = new RSExtSmartPtr<ICorDebugAppDomain>[countAppDomains];
+ *ppAppDomains = pAppDomains;
+ *pCount = countAppDomains;
+
+ //
+ // Load all the appdomains into the array
+ //
+ ULONG countDummy;
+ hr = pAppEnum->Next(countAppDomains, (ICorDebugAppDomain**) pAppDomains, &countDummy);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ SIMPLIFYING_ASSUMPTION(countDummy == countAppDomains);
+
+ //
+ // Now sort them based on appdomain ID.
+ // We generally expect a very low number of appdomains (usually 1). So a n^2 sort shouldn't be a perf
+ // problem here.
+ //
+ for(ULONG i = 0; i < countAppDomains; i++)
+ {
+ ULONG32 id1;
+ hr = pAppDomains[i]->GetID(&id1);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ for(ULONG j = i + 1; j < countAppDomains; j++)
+ {
+ ULONG32 id2;
+ hr = pAppDomains[j]->GetID(&id2);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ if (id1 > id2)
+ {
+ // swap values
+ ICorDebugAppDomain * pTemp = pAppDomains[i];
+ pAppDomains[i].Assign(pAppDomains[j]);
+ pAppDomains[j].Assign(pTemp);
+
+ // update id1 key since it's in the outer-loop.
+ id1 = id2;
+ }
+ }
+ }
+
+
+
+ return S_OK;
+
+}
+
+//---------------------------------------------------------------------------------------
+// To emulate the V2 attach-handshake, give the shim a chance to inject fake attach events.
+//
+// Notes:
+// Do this before the queue is empty so that HasQueuedCallbacks() doesn't toggle from false to true.
+// This is called once the process is synchronized, which emulates V2 semantics on attach.
+// This may be called on the Win32Event Thread from inside of Filter, or on another thread.
+void ShimProcess::QueueFakeAttachEvents()
+{
+ // Serialize this function with Dispose()
+ RSLockHolder lockHolder(&m_ShimProcessDisposeLock);
+ if (m_fIsDisposed)
+ return;
+
+ // The fake CreateProcess is already queued. Start queuing the rest of the events.
+ // The target is stopped (synchronized) this whole time.
+ // This will use the inspection API to look at the process and queue up the fake
+ // events that V2 would have sent in a similar situation. All of the callbacks to GetShimCallback()
+ // just queue up the events. The event queue is then drained as the V2 debugger calls continue.
+
+ HRESULT hr = S_OK;
+ ICorDebugProcess * pProcess = GetProcess();
+
+ //
+ // First, Queue all the Fake AppDomains
+ //
+ RSExtSmartPtr<ICorDebugAppDomain> * pAppDomains = NULL;
+ ULONG countAppDomains = 0;
+
+ hr = GetSortedAppDomains(pProcess, &pAppDomains, &countAppDomains);
+ if (FAILED(hr))
+ return;
+
+ for(ULONG i = 0; i < countAppDomains; i++)
+ {
+ // V2 expects that the debugger then attaches to each AppDomain during the Create-appdomain callback.
+ // This was done to allow for potential per-appdomain debugging. However, only-process
+ // wide debugging support was allowed in V2. The caller had to attach to all Appdomains.
+
+ GetShimCallback()->CreateAppDomain(pProcess, pAppDomains[i]);
+ AddDuplicateCreationEvent(pAppDomains[i]);
+ }
+
+ // V2 had a break in the callback queue at this point.
+
+ //
+ // Second, queue all Assembly and Modules events.
+ //
+
+ for(ULONG iAppDomain = 0; iAppDomain < countAppDomains; iAppDomain++)
+ {
+ ICorDebugAppDomain * pAppDomain = pAppDomains[iAppDomain];
+ //
+ // Send Assemblies. Must be in load order.
+ //
+
+ RSExtSmartPtr<ICorDebugAssemblyEnum> pAssemblyEnum;
+ hr = pAppDomain->EnumerateAssemblies(&pAssemblyEnum);
+ if (FAILED(hr))
+ break;
+
+ ULONG countAssemblies;
+ hr = pAssemblyEnum->GetCount(&countAssemblies);
+ if (FAILED(hr))
+ break;
+
+ RSExtSmartPtr<ICorDebugAssembly> * pAssemblies = new RSExtSmartPtr<ICorDebugAssembly> [countAssemblies];
+ m_pProcess->GetAssembliesInLoadOrder(pAppDomain, pAssemblies, countAssemblies);
+ for(ULONG iAssembly = 0; iAssembly < countAssemblies; iAssembly++)
+ {
+ QueueFakeAssemblyAndModuleEvent(pAssemblies[iAssembly]);
+ }
+ delete [] pAssemblies;
+
+ }
+
+ delete [] pAppDomains;
+
+
+ // V2 would have a break in the callback queue at this point.
+
+ // V2 would send all relevant ClassLoad events now.
+ //
+ // That includes class loads for all modules that:
+ // - are dynamic
+ // - subscribed to class load events via ICorDebugModule::EnableClassLoadCallbacks.
+ // We don't provide Class-loads in our emulation because:
+ // 1. "ClassLoad" doesn't actually mean anything here.
+ // 2. We have no way of enumerating "loaded" classes in the CLR. We could use the metadata to enumerate
+ // all classes, but that's offers no value.
+ // 3. ClassLoad is useful for dynamic modules to notify a debugger that the module changed and
+ // to update symbols; but the LoadModule/UpdateModule syms already do that.
+
+
+ //
+ // Third, Queue all Threads
+ //
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI) && !defined(FEATURE_CORESYSTEM)
+ // Use OS thread enumeration facilities to ensure that the managed thread
+ // thread order is the same as the corresponding native thread order.
+ QueueFakeThreadAttachEventsNativeOrder();
+#else
+ // Use ICorDebug to enumerate threads. The order of managed threads may
+ // not match the order the threads were created in.
+ QueueFakeThreadAttachEventsNoOrder();
+#endif
+
+ // Forth, Queue all Connections.
+ // Enumerate connections is not exposed through ICorDebug, so we need to go use a private hook on CordbProcess.
+ m_pProcess->QueueFakeConnectionEvents();
+
+ // For V2 jit-attach, the callback queue would also include the jit-attach event (Exception, UserBreak, MDA, etc).
+ // This was explicitly in the same callback queue so that a debugger would drain it as part of draining the attach
+ // events.
+
+ // In V3, on normal attach, the VM just sends a Sync-complete event.
+ // On jit-attach, the VM sends the jit-attach event and then the sync-complete.
+ // The shim just queues the fake attach events at the first event it gets from the left-side.
+ // In jit-attach, the shim will queue the fake events right before it queues the jit-attach event,
+ // thus keeping them in the same callback queue as V2 did.
+
+}
+
+// Accessor for m_attached.
+bool ShimProcess::GetAttached()
+{
+ return m_attached;
+}
+// We need to know whether we are in the CreateProcess callback to be able to
+// return the v2.0 hresults from code:CordbProcess::SetDesiredNGENCompilerFlags
+// when we are using the shim.
+//
+// Expose m_fInCreateProcess
+bool ShimProcess::GetInCreateProcess()
+{
+ return m_fInCreateProcess;
+}
+
+void ShimProcess::SetInCreateProcess(bool value)
+{
+ m_fInCreateProcess = value;
+}
+
+// We need to know whether we are in the FakeLoadModule callback to be able to
+// return the v2.0 hresults from code:CordbModule::SetJITCompilerFlags when
+// we are using the shim.
+//
+// Expose m_fInLoadModule
+bool ShimProcess::GetInLoadModule()
+{
+ return m_fInLoadModule;
+
+}
+
+void ShimProcess::SetInLoadModule(bool value)
+{
+ m_fInLoadModule = value;
+}
+
+// When we get a continue, we need to clear the flags indicating we're still in a callback
+void ShimProcess::NotifyOnContinue ()
+{
+ m_fInCreateProcess = false;
+ m_fInLoadModule = false;
+}
+
+// The RS calls this function when the stack is about to be changed in any way, e.g. continue, SetIP, etc.
+void ShimProcess::NotifyOnStackInvalidate()
+{
+ ClearAllShimStackWalk();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Filter HResults for ICorDebugProcess2::SetDesiredNGENCompilerFlags to emualte V2 error semantics.
+// Arguments:
+// hr - V3 hresult
+//
+// Returns:
+// hresult V2 would have returned in same situation.
+HRESULT ShimProcess::FilterSetNgenHresult(HRESULT hr)
+{
+ if ((hr == CORDBG_E_MUST_BE_IN_CREATE_PROCESS) && !m_fInCreateProcess)
+ {
+ return hr;
+ }
+ if (m_attached)
+ {
+ return CORDBG_E_CANNOT_BE_ON_ATTACH;
+ }
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Filter HRs for ICorDebugModule::EnableJITDebugging, ICorDebugModule2::SetJITCompilerFlags
+// to emulate V2 error semantics
+//
+// Arguments:
+// hr - V3 hresult
+//
+// Returns:
+// hresult V2 would have returned in same situation.
+HRESULT ShimProcess::FilterSetJitFlagsHresult(HRESULT hr)
+{
+ if ((hr == CORDBG_E_MUST_BE_IN_LOAD_MODULE) && !m_fInLoadModule)
+ {
+ return hr;
+ }
+ if (m_attached && (hr == CORDBG_E_MUST_BE_IN_LOAD_MODULE))
+ {
+ return CORDBG_E_CANNOT_BE_ON_ATTACH;
+ }
+ return hr;
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::LookupOrCreateShimStackWalk
+//
+// Description:
+// Find the ShimStackWalk associated with the specified ICDThread. Create one if it's not found.
+//
+// Arguments:
+// * pThread - the specified thread
+//
+// Return Value:
+// Return the ShimStackWalk associated with the specified thread.
+//
+// Notes:
+// The ShimStackWalks handed back by this function is only valid until the next time the stack is changed
+// in any way. In other words, the ShimStackWalks are valid until the next time
+// code:CordbThread::CleanupStack or code:CordbThread::MarkStackFramesDirty is called.
+//
+// ShimStackWalk and ICDThread have a 1:1 relationship. Only one ShimStackWalk will be created for any
+// given ICDThread. So if two threads in the debugger are walking the same thread in the debuggee, they
+// operate on the same ShimStackWalk. This is ok because ShimStackWalks walk the stack at creation time,
+// cache all the frames, and become read-only after creation.
+//
+// Refer to code:ShimProcess::ClearAllShimStackWalk to see how ShimStackWalks are cleared.
+//
+
+ShimStackWalk * ShimProcess::LookupOrCreateShimStackWalk(ICorDebugThread * pThread)
+{
+ ShimStackWalk * pSW = NULL;
+
+ {
+ // do the lookup under the Shim lock
+ RSLockHolder lockHolder(&m_ShimLock);
+ pSW = m_pShimStackWalkHashTable->Lookup(pThread);
+ }
+
+ if (pSW == NULL)
+ {
+ // create one if it's not found and add it to the hash table
+ NewHolder<ShimStackWalk> pNewSW(new ShimStackWalk(this, pThread));
+
+ {
+ // Do the lookup again under the Shim lock, and only add the new ShimStackWalk if no other thread
+ // has beaten us to it.
+ RSLockHolder lockHolder(&m_ShimLock);
+ pSW = m_pShimStackWalkHashTable->Lookup(pThread);
+ if (pSW == NULL)
+ {
+ m_pShimStackWalkHashTable->Add(pNewSW);
+ pSW = pNewSW;
+
+ // don't release the memory if all goes well
+ pNewSW.SuppressRelease();
+ }
+ else
+ {
+ // The NewHolder will automatically delete the ShimStackWalk when it goes out of scope.
+ }
+ }
+ }
+
+ return pSW;
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::ClearAllShimStackWalk
+//
+// Description:
+// Remove and delete all the entries in the hash table of ShimStackWalks.
+//
+// Notes:
+// Refer to code:ShimProcess::LookupOrCreateShimStackWalk to see how ShimStackWalks are created.
+//
+
+void ShimProcess::ClearAllShimStackWalk()
+{
+ RSLockHolder lockHolder(&m_ShimLock);
+
+ // loop through all the entries in the hash table, remove them, and delete them
+ for (ShimStackWalkHashTable::Iterator pCurElem = m_pShimStackWalkHashTable->Begin(),
+ pEndElem = m_pShimStackWalkHashTable->End();
+ pCurElem != pEndElem;
+ pCurElem++)
+ {
+ ShimStackWalk * pSW = *pCurElem;
+ m_pShimStackWalkHashTable->Remove(pSW->GetThread());
+ delete pSW;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Called before shim dispatches an event.
+//
+// Arguments:
+// fRealCreateProcessEvent - true if the shim is about to dispatch a real create process event (as opposed
+// to one faked up by the shim itself)
+// Notes:
+// This may be called from within Filter, which means we may be on the win32-event-thread.
+// This is called on all callbacks from the VM.
+// This gives us a chance to queue fake-attach events. So call it before the Jit-attach
+// event has been queued.
+void ShimProcess::PreDispatchEvent(bool fRealCreateProcessEvent /*= false*/)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // For emulating the V2 case, we need to do additional initialization before dispatching the callback to the user.
+ if (!m_fFirstManagedEvent)
+ {
+ // Remember that we're processing the first managed event so that we only call HandleFirstRCEvent() once
+ m_fFirstManagedEvent = true;
+
+ // This can fail with the incompatable version HR. The process has already been terminated if this
+ // is the case. This will dispatch an Error callback
+ // If this fails, the process is in an undefined state.
+ // @dbgtodo ipc-block: this will go away once we get rid
+ // of the IPC block.
+ m_pProcess->FinishInitializeIPCChannel(); // throws on error
+ }
+
+ {
+ // In jit-attach cases, the first event the shim gets is the event that triggered the jit-attach.
+ // Queue up the fake events now, and then once we return, our caller will queue the jit-attach event.
+ // In the jit-attach case, this is before a sync-complete has been sent (since the sync doesn't get sent
+ // until after the jit-attach event is sent).
+ QueueFakeAttachEventsIfNeeded(fRealCreateProcessEvent);
+ }
+
+ // Always request an sync (emulates V2 behavior). If LS is not sync-ready, it will ignore the request.
+ m_pProcess->RequestSyncAtEvent();
+
+
+}
+
+// ----------------------------------------------------------------------------
+// ShimProcess::GetCLRInstanceBaseAddress
+// Finds the base address of [core]clr.dll
+// Arguments: none
+// Return value: returns the base address of [core]clr.dll if possible or NULL otherwise
+//
+CORDB_ADDRESS ShimProcess::GetCLRInstanceBaseAddress()
+{
+ CORDB_ADDRESS baseAddress = CORDB_ADDRESS(NULL);
+ DWORD dwPid = m_pLiveDataTarget->GetPid();
+
+#if defined(FEATURE_CORESYSTEM)
+ // Debugger attaching to CoreCLR via CoreCLRCreateCordbObject should have already specified CLR module address.
+ // Code that help to find it now lives in dbgshim.
+#else
+ // get a "snapshot" of all modules in the target
+ HandleHolder hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
+ MODULEENTRY32 moduleEntry = { 0 };
+
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ // we haven't got a loaded CLR yet
+ baseAddress = CORDB_ADDRESS(NULL);
+ }
+ else
+ {
+ // we need to loop through the modules until we find mscorwks.dll
+ moduleEntry.dwSize = sizeof(MODULEENTRY32);
+
+ if (!Module32First(hSnapshot, &moduleEntry))
+ {
+ baseAddress = CORDB_ADDRESS(NULL);
+ }
+ else
+ {
+
+ do
+ {
+ if (!_wcsicmp(moduleEntry.szModule, MAKEDLLNAME_W(MAIN_CLR_MODULE_NAME_W)))
+ {
+ // we found it, so save the base address
+ baseAddress = PTR_TO_CORDB_ADDRESS(moduleEntry.modBaseAddr);
+ }
+ } while (Module32Next(hSnapshot, &moduleEntry));
+ }
+ }
+#endif
+ return baseAddress;
+} // ShimProcess::GetCLRInstanceBaseAddress
+
+// ----------------------------------------------------------------------------
+// ShimProcess::FindLoadedCLR
+//
+// Description:
+// Look for any CLR loaded into the process. If found, return the instance ID for it.
+//
+// Arguments:
+// * pClrInstanceId - out parameter for the instance ID of the CLR
+//
+// Return Value:
+// Returns S_OK if a CLR was found, and stores its instance ID in pClrInstanceId.
+// Otherwise returns an error code.
+//
+// Notes:
+// If there are multiple CLRs loaded in the process, the one chosen for the returned
+// instance ID is unspecified.
+//
+HRESULT ShimProcess::FindLoadedCLR(CORDB_ADDRESS * pClrInstanceId)
+{
+ *pClrInstanceId = GetCLRInstanceBaseAddress();
+
+ if (*pClrInstanceId == 0)
+ {
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Locates DAC by finding mscordac{wks|core} next to DBI
+//
+// Return Value:
+// Returns the module handle for DAC
+// Throws on errors.
+//
+
+HMODULE ShimProcess::GetDacModule()
+{
+ HModuleHolder hDacDll;
+ PathString wszAccessDllPath;
+
+#ifdef FEATURE_PAL
+ if (!PAL_GetPALDirectoryWrapper(wszAccessDllPath))
+ {
+ ThrowLastError();
+ }
+ PCWSTR eeFlavor = MAKEDLLNAME_W(W("mscordaccore"));
+#else
+ //
+ // Load the access DLL from the same directory as the the current CLR Debugging Services DLL.
+ //
+
+ if (!WszGetModuleFileName(GetModuleInst(), wszAccessDllPath))
+ {
+ ThrowLastError();
+ }
+
+ if (!SUCCEEDED(CopySystemDirectory(wszAccessDllPath, wszAccessDllPath)))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // Dac Dll is named:
+ // mscordaccore.dll <-- coreclr
+ // mscordacwks.dll <-- desktop
+ PCWSTR eeFlavor =
+#if defined(FEATURE_MAIN_CLR_MODULE_USES_CORE_NAME)
+ W("mscordaccore.dll");
+#else
+ W("mscordacwks.dll");
+#endif
+
+#endif // FEATURE_PAL
+ wszAccessDllPath.Append(eeFlavor);
+
+ hDacDll.Assign(WszLoadLibrary(wszAccessDllPath));
+ if (!hDacDll)
+ {
+ DWORD dwLastError = GetLastError();
+ if (dwLastError == ERROR_MOD_NOT_FOUND)
+ {
+ // Give a more specific error in the case where we can't find the DAC dll.
+ ThrowHR(CORDBG_E_DEBUG_COMPONENT_MISSING);
+ }
+ else
+ {
+ ThrowWin32(dwLastError);
+ }
+ }
+ hDacDll.SuppressRelease();
+ return (HMODULE) hDacDll;
+}
+
+MachineInfo ShimProcess::GetMachineInfo()
+{
+ return m_machineInfo;
+}
+
+void ShimProcess::SetMarkAttachPendingEvent()
+{
+ SetEvent(m_markAttachPendingEvent);
+}
+
+void ShimProcess::SetTerminatingEvent()
+{
+ SetEvent(m_terminatingEvent);
+}
+
+RSLock * ShimProcess::GetShimLock()
+{
+ return &m_ShimLock;
+}
+
+
+bool ShimProcess::IsThreadSuspendedOrHijacked(ICorDebugThread * pThread)
+{
+ return m_pProcess->IsThreadSuspendedOrHijacked(pThread);
+}