summaryrefslogtreecommitdiff
path: root/src/debug/di/process.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/di/process.cpp')
-rw-r--r--src/debug/di/process.cpp15235
1 files changed, 15235 insertions, 0 deletions
diff --git a/src/debug/di/process.cpp b/src/debug/di/process.cpp
new file mode 100644
index 0000000000..44e4a0c667
--- /dev/null
+++ b/src/debug/di/process.cpp
@@ -0,0 +1,15235 @@
+// 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: process.cpp
+//
+
+//
+//*****************************************************************************
+#include "stdafx.h"
+#include "primitives.h"
+#include "safewrap.h"
+
+#include "check.h"
+
+#ifndef SM_REMOTESESSION
+#define SM_REMOTESESSION 0x1000
+#endif
+
+#include "corpriv.h"
+#include "corexcep.h"
+#include "../../dlls/mscorrc/resource.h"
+#include <limits.h>
+
+#include <sstring.h>
+
+// @dbgtodo shim: process has some private hooks into the shim.
+#include "shimpriv.h"
+
+#include "metadataexports.h"
+#include "readonlydatatargetfacade.h"
+#include "metahost.h"
+
+// Keep this around for retail debugging. It's very very useful because
+// it's global state that we can always find, regardless of how many locals the compiler
+// optimizes away ;)
+struct RSDebuggingInfo;
+extern RSDebuggingInfo * g_pRSDebuggingInfo;
+
+//---------------------------------------------------------------------------------------
+//
+// OpenVirtualProcessImpl method called by the shim to get an ICorDebugProcess4 instance
+//
+// Arguments:
+// clrInstanceId - target pointer identifying which CLR in the Target to debug.
+// pDataTarget - data target abstraction.
+// hDacModule - the handle of the appropriate DAC dll for this runtime
+// riid - interface ID to query for.
+// ppProcessOut - new object for target, interface ID matches riid.
+// ppFlagsOut - currently only has 1 bit to indicate whether or not this runtime
+// instance will send a managed event after attach
+//
+// Return Value:
+// S_OK on success. Else failure
+//
+// Assumptions:
+//
+// Notes:
+// The outgoing process object can be cleaned up by calling Detach (which
+// will reset the Attach bit.)
+// @dbgtodo attach-bit: need to determine fate of attach bit.
+//
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcessImpl(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ CLR_DEBUGGING_VERSION * pMaxDebuggerSupportedVersion,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS* pFlagsOut)
+{
+ HRESULT hr = S_OK;
+ RSExtSmartPtr<CordbProcess> pProcess;
+ PUBLIC_API_ENTRY(NULL);
+ EX_TRY
+ {
+
+ if ( (pDataTarget == NULL) || (clrInstanceId == 0) || (pMaxDebuggerSupportedVersion == NULL) ||
+ ((pFlagsOut == NULL) && (ppInstance == NULL))
+ )
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // We consider the top 8 bits of the struct version to be the only part that represents
+ // a breaking change. This gives us some freedom in the future to have the debugger
+ // opt into getting more data.
+ const WORD kMajorMask = 0xff00;
+ const WORD kMaxStructMajor = 0;
+ if ((pMaxDebuggerSupportedVersion->wStructVersion & kMajorMask) > kMaxStructMajor)
+ {
+ // Don't know how to interpret the version structure
+ ThrowHR(CORDBG_E_UNSUPPORTED_VERSION_STRUCT);
+ }
+
+ // This process object is intended to be used for the V3 pipeline, and so
+ // much of the process from V2 is not being used. For example,
+ // - there is no ShimProcess object
+ // - there is no w32et thread (all threads are effectively an event thread)
+ // - the stop state is 'live', which corresponds to CordbProcess not knowing what
+ // its stop state really is (because that is now controlled by the shim).
+ IfFailThrow(CordbProcess::OpenVirtualProcess(
+ clrInstanceId,
+ pDataTarget, // takes a reference
+ hDacModule,
+ NULL, // Cordb
+ (DWORD) 0, // 0 for V3 cases (pShim == NULL).
+ NULL, // no Shim in V3 cases
+ &pProcess));
+
+ // CordbProcess::OpenVirtualProcess already did the external addref to pProcess.
+ // Since pProcess is a smart ptr, it will external release in this function.
+ // Living reference will be the one from the QI.
+
+ // get the managed debug event pending flag
+ if(pFlagsOut != NULL)
+ {
+ hr = pProcess->GetAttachStateFlags(pFlagsOut);
+ if(FAILED(hr))
+ {
+ ThrowHR(hr);
+ }
+ }
+
+ //
+ // Check to make sure the debugger supports debugging this version
+ // Note that it's important that we still store the flags (above) in this case
+ //
+ if (!CordbProcess::IsCompatibleWith(pMaxDebuggerSupportedVersion->wMajor))
+ {
+ // Not compatible - don't keep the process instance, and return this specific error-code
+ ThrowHR(CORDBG_E_UNSUPPORTED_FORWARD_COMPAT);
+ }
+
+ //
+ // Now Query for the requested interface
+ //
+ if(ppInstance != NULL)
+ {
+ IfFailThrow(pProcess->QueryInterface(riid, reinterpret_cast<void**> (ppInstance)));
+ }
+
+ // if you have to add code here that could fail make sure ppInstance gets released and NULL'ed at exit
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if((FAILED(hr) || ppInstance == NULL) && pProcess != NULL)
+ {
+ // The process has a strong reference to itself which is only released by neutering it.
+ // Since we aren't handing out the ref then we need to clean it up
+ _ASSERTE(ppInstance == NULL || *ppInstance == NULL);
+ pProcess->Neuter();
+ }
+ return hr;
+};
+
+//---------------------------------------------------------------------------------------
+// DEPRECATED - use OpenVirtualProcessImpl
+// OpenVirtualProcess method used by the shim in CLR v4 Beta1
+// We'd like a beta1 shim/VS to still be able to open dumps using a CLR v4 Beta2+ mscordbi.dll,
+// so we'll leave this in place (at least until after Beta2 is in wide use).
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcess2(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ REFIID riid,
+ IUnknown ** ppInstance,
+ CLR_DEBUGGING_PROCESS_FLAGS* pFlagsOut)
+{
+ CLR_DEBUGGING_VERSION maxVersion = {0};
+ maxVersion.wMajor = 4;
+ return OpenVirtualProcessImpl(clrInstanceId, pDataTarget, hDacModule, &maxVersion, riid, ppInstance, pFlagsOut);
+}
+
+//---------------------------------------------------------------------------------------
+// DEPRECATED - use OpenVirtualProcessImpl
+// Public OpenVirtualProcess method to get an ICorDebugProcess4 instance
+// Used directly in CLR v4 pre Beta1 - can probably be safely removed now
+//---------------------------------------------------------------------------------------
+STDAPI OpenVirtualProcess(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ REFIID riid,
+ IUnknown ** ppInstance)
+{
+ return OpenVirtualProcess2(clrInstanceId, pDataTarget, NULL, riid, ppInstance, NULL);
+};
+
+//-----------------------------------------------------------------------------
+// Most Hresults to Unrecoverable error indicate an internal error
+// in the Right-Side.
+// However, a few are legal (eg, "could actually happen in a retail scenario and
+// not indicate an issue in mscorbi"). Track that here.
+//-----------------------------------------------------------------------------
+
+bool IsLegalFatalError(HRESULT hr)
+{
+ return
+ (hr == CORDBG_E_INCOMPATIBLE_PROTOCOL) ||
+ (hr == CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS) ||
+ (hr == CORDBG_E_UNCOMPATIBLE_PLATFORMS) ||
+ (hr == CORDBG_E_MISMATCHED_CORWKS_AND_DACWKS_DLLS) ||
+ // This should only happen in the case of a security attack on us.
+ (hr == E_ACCESSDENIED) ||
+ (hr == E_FAIL);
+}
+
+//-----------------------------------------------------------------------------
+// Safe wait. Use this anytime we're waiting on:
+// - an event signaled by the helper thread.
+// - something signaled by a thread that holds the process lock.
+// Note that we must preserve GetLastError() semantics.
+//-----------------------------------------------------------------------------
+inline DWORD SafeWaitForSingleObject(CordbProcess * p, HANDLE h, DWORD dwTimeout)
+{
+ // Can't hold process lock while blocking
+ _ASSERTE(!p->ThreadHoldsProcessLock());
+
+ return ::WaitForSingleObject(h, dwTimeout);
+}
+
+#define CORDB_WAIT_TIMEOUT 360000 // milliseconds
+
+//---------------------------------------------------------------------------------------
+//
+// Get the timeout value used in waits.
+//
+// Return Value:
+// Number of milliseconds to waite or possible INFINITE (-1).
+//
+//
+// Notes:
+// Uses registry values for fine tuning.
+//
+
+// static
+static inline DWORD CordbGetWaitTimeout()
+{
+#ifdef _DEBUG
+ // 0 = Wait forever
+ // 1 = Wait for CORDB_WAIT_TIMEOUT
+ // n = Wait for n milliseconds
+ static ConfigDWORD cordbWaitTimeout;
+ DWORD dwTimeoutVal = cordbWaitTimeout.val(CLRConfig::INTERNAL_DbgWaitTimeout);
+ if (dwTimeoutVal == 0)
+ return DWORD(-1);
+ else if (dwTimeoutVal != 1)
+ return dwTimeoutVal;
+ else
+#endif
+ {
+ return CORDB_WAIT_TIMEOUT;
+ }
+}
+
+//----------------------------------------------------------------------------
+// Implementation of IDacDbiInterface::IMetaDataLookup.
+// lookup Internal Metadata Importer keyed by PEFile
+// isILMetaDataForNGENImage is true iff the IMDInternalImport returned represents a pointer to
+// metadata from an IL image when the module was an ngen'ed image.
+IMDInternalImport * CordbProcess::LookupMetaData(VMPTR_PEFile vmPEFile, bool &isILMetaDataForNGENImage)
+{
+ INTERNAL_DAC_CALLBACK(this);
+
+ HASHFIND hashFindAppDomain;
+ HASHFIND hashFindModule;
+ IMDInternalImport * pMDII = NULL;
+ isILMetaDataForNGENImage = false;
+
+ // Check to see if one of the cached modules has the metadata we need
+ // If not we will do a more exhaustive search below
+ for (CordbAppDomain * pAppDomain = m_appDomains.FindFirst(&hashFindAppDomain);
+ pAppDomain != NULL;
+ pAppDomain = m_appDomains.FindNext(&hashFindAppDomain))
+ {
+ for (CordbModule * pModule = pAppDomain->m_modules.FindFirst(&hashFindModule);
+ pModule != NULL;
+ pModule = pAppDomain->m_modules.FindNext(&hashFindModule))
+ {
+ if (pModule->GetPEFile() == vmPEFile)
+ {
+ pMDII = NULL;
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = pModule->GetInternalMD();
+ );
+ if(pMDII != NULL)
+ return pMDII;
+ }
+ }
+ }
+
+ // Cache didn't have it... time to search harder
+ PrepopulateAppDomainsOrThrow();
+
+ // There may be perf issues here. The DAC may make a lot of metadata requests, and so
+ // this may be an area for potential perf optimizations if we find things running slow.
+
+ // enumerate through all Modules
+ for (CordbAppDomain * pAppDomain = m_appDomains.FindFirst(&hashFindAppDomain);
+ pAppDomain != NULL;
+ pAppDomain = m_appDomains.FindNext(&hashFindAppDomain))
+ {
+ pAppDomain->PrepopulateModules();
+
+ for (CordbModule * pModule = pAppDomain->m_modules.FindFirst(&hashFindModule);
+ pModule != NULL;
+ pModule = pAppDomain->m_modules.FindNext(&hashFindModule))
+ {
+ if (pModule->GetPEFile() == vmPEFile)
+ {
+ pMDII = NULL;
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = pModule->GetInternalMD();
+ );
+
+ if ( pMDII == NULL)
+ {
+ // If we couldn't get metadata from the CordbModule, then we need to ask the
+ // debugger if it can find the metadata elsewhere.
+ // If this was live debugging, we should have just gotten the memory contents.
+ // Thus this code is for dump debugging, when you don't have the metadata in the dump.
+ pMDII = LookupMetaDataFromDebugger(vmPEFile, isILMetaDataForNGENImage, pModule);
+ }
+ return pMDII;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+IMDInternalImport * CordbProcess::LookupMetaDataFromDebugger(
+ VMPTR_PEFile vmPEFile,
+ bool &isILMetaDataForNGENImage,
+ CordbModule * pModule)
+{
+ DWORD dwImageTimeStamp = 0;
+ DWORD dwImageSize = 0;
+ bool isNGEN = false;
+ StringCopyHolder filePath;
+ IMDInternalImport * pMDII = NULL;
+
+ // First, see if the debugger can locate the exact metadata we want.
+ if (this->GetDAC()->GetMetaDataFileInfoFromPEFile(vmPEFile, dwImageTimeStamp, dwImageSize, isNGEN, &filePath))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ // Since we track modules by their IL images, that presents a little bit of oddness here. The correct
+ // thing to do is preferentially load the NI content.
+ // We don't discriminate between timestamps & sizes becuase CLRv4 deterministic NGEN guarantees that the
+ // IL image and NGEN image have the same timestamp and size. Should that guarantee change, this code
+ // will be horribly broken.
+
+ // If we happen to have an NI file path, use it instead.
+ const WCHAR * pwszFilePath = pModule->GetNGenImagePath();
+ if (pwszFilePath)
+ {
+ // Force the issue, regardless of the older codepath's opinion.
+ isNGEN = true;
+ }
+ else
+ {
+ pwszFilePath = (WCHAR *)filePath;
+ }
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = LookupMetaDataFromDebuggerForSingleFile(pModule, pwszFilePath, dwImageTimeStamp, dwImageSize);
+ );
+
+ // If it's an ngen'ed image and the debugger couldn't find it, we can use the metadata from
+ // the corresponding IL image if the debugger can locate it.
+ filePath.Clear();
+ if ((pMDII == NULL) &&
+ (isNGEN) &&
+ (this->GetDAC()->GetILImageInfoFromNgenPEFile(vmPEFile, dwImageTimeStamp, dwImageSize, &filePath)))
+ {
+ _ASSERTE(filePath.IsSet());
+
+ WCHAR *mutableFilePath = (WCHAR *)filePath;
+
+#if defined(FEATURE_CORESYSTEM)
+ size_t pathLen = wcslen(mutableFilePath);
+
+ const wchar_t *nidll = W(".ni.dll");
+ const wchar_t *niexe = W(".ni.exe");
+ const size_t dllLen = wcslen(nidll); // used for ni.exe as well
+
+ const wchar_t *niwinmd = W(".ni.winmd");
+ const size_t winmdLen = wcslen(niwinmd);
+
+ if (pathLen > dllLen && _wcsicmp(mutableFilePath+pathLen-dllLen, nidll) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-dllLen, dllLen, W(".dll"));
+ }
+ else if (pathLen > dllLen && _wcsicmp(mutableFilePath+pathLen-dllLen, niexe) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-dllLen, dllLen, W(".exe"));
+ }
+ else if (pathLen > winmdLen && _wcsicmp(mutableFilePath+pathLen-winmdLen, niwinmd) == 0)
+ {
+ wcscpy_s(mutableFilePath+pathLen-winmdLen, winmdLen, W(".winmd"));
+ }
+#endif//FEATURE_CORESYSTEM
+
+ ALLOW_DATATARGET_MISSING_MEMORY(
+ pMDII = LookupMetaDataFromDebuggerForSingleFile(pModule, mutableFilePath, dwImageTimeStamp, dwImageSize);
+ );
+
+ if (pMDII != NULL)
+ {
+ isILMetaDataForNGENImage = true;
+ }
+ }
+ }
+ return pMDII;
+}
+
+// We do not know if the image being sent to us is an IL image or ngen image.
+// CordbProcess::LookupMetaDataFromDebugger() has this knowledge when it looks up the file to hand off
+// to this function.
+// DacDbiInterfaceImpl::GetMDImport() has this knowledge in the isNGEN flag.
+// The CLR v2 code that windbg used made a distinction whether the metadata came from
+// the exact binary or not (i.e. were we getting metadata from the IL image and using
+// it against the ngen image?) but that information was never used and so not brought forward.
+// It would probably be more interesting generally to track whether the debugger gives us back
+// a file that bears some relationship to the file we asked for, which would catch the NI/IL case
+// as well.
+IMDInternalImport * CordbProcess::LookupMetaDataFromDebuggerForSingleFile(
+ CordbModule * pModule,
+ LPCWSTR pwszFilePath,
+ DWORD dwTimeStamp,
+ DWORD dwSize)
+{
+ INTERNAL_DAC_CALLBACK(this);
+
+ ULONG32 cchLocalImagePath = MAX_LONGPATH;
+ ULONG32 cchLocalImagePathRequired;
+ NewArrayHolder<WCHAR> pwszLocalFilePath = NULL;
+ IMDInternalImport * pMDII = NULL;
+
+ const HRESULT E_NSF_BUFFER = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ HRESULT hr = E_NSF_BUFFER;
+ for(unsigned i=0; i<2 && hr == E_NSF_BUFFER; i++)
+ {
+ if (pwszLocalFilePath != NULL)
+ pwszLocalFilePath.Release();
+
+ if (NULL == (pwszLocalFilePath = new (nothrow) WCHAR[cchLocalImagePath+1]))
+ ThrowHR(E_OUTOFMEMORY);
+
+ cchLocalImagePathRequired = 0;
+
+ hr = m_pMetaDataLocator->GetMetaData(pwszFilePath,
+ dwTimeStamp,
+ dwSize,
+ cchLocalImagePath,
+ &cchLocalImagePathRequired,
+ pwszLocalFilePath);
+
+ pwszLocalFilePath[cchLocalImagePath] = W('\0');
+ cchLocalImagePath = cchLocalImagePathRequired;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pModule->InitPublicMetaDataFromFile(pwszLocalFilePath, ofReadOnly, false);
+ if (SUCCEEDED(hr))
+ {
+ // While we're successfully returning a metadata reader, remember that there's
+ // absolutely no guarantee this metadata is an exact match for the vmPEFile.
+ // The debugger could literally send us back a path to any managed file with
+ // metadata content that is readable and we'll 'succeed'.
+ // For now, this is by-design. A debugger should be allowed to decide if it wants
+ // to take a risk by returning 'mostly matching' metadata to see if debugging is
+ // possible in the absense of a true match.
+ pMDII = pModule->GetInternalMD();
+ }
+ }
+
+ return pMDII;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Implement IDacDbiInterface::IAllocator::Alloc
+// Expected to throws on error.
+//
+// Arguments:
+// lenBytes - size of the byte array to allocate
+//
+// Return Value:
+// Return the newly allocated byte array, or throw on OOM
+//
+// Notes:
+// Since this function is a callback from DAC, it must not take the process lock.
+// If it does, we may deadlock between the DD lock and the process lock.
+// If we really need to take the process lock for whatever reason, we must take it in the DBI functions
+// which call the DAC API that ends up calling this function.
+// See code:InternalDacCallbackHolder for more information.
+//
+
+void * CordbProcess::Alloc(SIZE_T lenBytes)
+{
+ return new BYTE[lenBytes]; // throws
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Implements IDacDbiInterface::IAllocator::Free
+//
+// Arguments:
+// p - pointer to the memory to be released
+//
+// Notes:
+// Since this function is a callback from DAC, it must not take the process lock.
+// If it does, we may deadlock between the DD lock and the process lock.
+// If we really need to take the process lock for whatever reason, we must take it in the DBI functions
+// which call the DAC API that ends up calling this function.
+// See code:InternalDacCallbackHolder for more information.
+//
+
+void CordbProcess::Free(void * p)
+{
+ // This shouldn't throw.
+ delete [] ((BYTE *) p);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// #DBIVersionChecking
+//
+// There are a few checks we need to do to make sure we are using the matching DBI and DAC for a particular
+// version of the runtime.
+//
+// 1. Runtime vs. DBI
+// - Desktop
+// This is done by making sure that the CorDebugInterfaceVersion passed to code:CreateCordbObject is
+// compatible with the version of the DBI.
+//
+// - Windows CoreCLR
+// This is done by dbgshim.dll. It checks whether the runtime DLL and the DBI DLL have the same
+// product version. See CreateDebuggingInterfaceForVersion() in dbgshim.cpp.
+//
+// - Remote transport (Mac CoreCLR + CoreSystem CoreCLR)
+// Since there is no dbgshim.dll for a remote CoreCLR, we have to do this check in some other place.
+// We do this in code:CordbProcess::CreateDacDbiInterface, by calling
+// code:DacDbiInterfaceImpl::CheckDbiVersion right after we have created the DDMarshal.
+// The IDacDbiInterface implementation on remote device checks the product version of the device
+// coreclr by:
+// mac - looking at the Info.plist file in the CoreCLR bundle.
+// CoreSystem - this check is skipped at the moment, but should be implemented if we release it
+//
+// The one twist here is that the DBI needs to communicate with the IDacDbiInterface
+// implementation on the device BEFORE it can verify the product versions. This means that we need to
+// have one IDacDbiInterface API which is consistent across all versions of the IDacDbiInterface.
+// This puts two constraints on CheckDbiVersion():
+//
+// 1. It has to be the first API on the IDacDbiInterface.
+// - Otherwise, a wrong version of the DBI may end up calling a different API on the
+// IDacDbiInterface and getting random results. (Really what matters is that it is
+// protocol message id 0, at present the source code position implies the message id)
+//
+// 2. Its parameters cannot change.
+// - Otherwise, we may run into random errors when we marshal/unmarshal the arguments for the
+// call to CheckDbiVersion(). Debugging will still fail, but we won't get the
+// version mismatch error. (Again, the protocol is what ultimately matters)
+// - To mitigate the impact of this constraint, we use the code:DbiVersion structure.
+// In addition to the DBI version, it also contains a format number (in case we decide to
+// check something else in the future), a breaking change number so that we can force
+// breaking changes between a DBI and a DAC, and space reserved for future use.
+//
+// 2. DBI vs. DAC
+// - Desktop and Windows CoreCLR (old architecture)
+// No verification is done. There is a transitive implication that if DBI matches runtime and DAC matches
+// runtime then DBI matches DAC. Technically because the DBI only matches runtime on major version number
+// runtime and DAC could be from different builds. However because we service all three binaries together
+// and DBI always loads the DAC that is sitting in the same directory DAC and DBI generally get tight
+// version coupling. A user with admin privleges could put different builds together and no version check
+// would ever fail though.
+//
+// - Desktop and Windows CoreCLR (new architecture)
+// No verification is done. Similar to above its implied that if DBI matches runtime and runtime matches
+// DAC then DBI matches DAC. The only difference is that here both the DBI and DAC are provided by the
+// debugger. We provide timestamp and filesize for both binaries which are relatively strongly bound hints,
+// but there is no enforcement on the returned binaries beyond the runtime compat checking.
+//
+// - Remote transport (Mac CoreCLR and CoreSystem CoreCLR)
+// Because the transport exists between DBI and DAC it becomes much more important to do a versioning check
+//
+// Mac - currently does a tightly bound version check between DBI and the runtime (CheckDbiVersion() above),
+// which transitively gives a tightly bound check to DAC. In same function there is also a check that is
+// logically a DAC DBI protocol check, verifying that the m_dwProtocolBreakingChangeCounter of DbiVersion
+// matches. However this check should be weaker than the build version check and doesn't add anything here.
+//
+// CoreSystem - currently skips the tightly bound version check to make internal deployment and usage easier.
+// We want to use old desktop side debugger components to target newer CoreCLR builds, only forcing a desktop
+// upgrade when the protocol actually does change. To do this we use two checks:
+// 1. The breaking change counter in CheckDbiVersion() whenever a dev knows they are breaking back
+// compat and wants to be explicit about it. This is the same as mac above.
+// 2. During the auto-generation of the DDMarshal classes we take an MD5 hash of IDacDbiInterface source
+// code and embed it in two DDMarshal functions, one which runs locally and one that runs remotely.
+// If both DBI and DAC were built from the same source then the local and remote hashes will match. If the
+// hashes don't match then we assume there has been a been a breaking change in the protocol. Note
+// this hash could have both false-positives and false-negatives. False positives could occur when
+// IDacDbiInterface is changed in a trivial way, such as changing a comment. False negatives could
+// occur when the semantics of the protocol are changed even though the interface is not. Another
+// case would be changing the DDMarshal proxy generation code. In addition to the hashes we also
+// embed timestamps when the auto-generated code was produced. However this isn't used for version
+// matching, only as a hint to indicate which of two mismatched versions is newer.
+//
+//
+// 3. Runtime vs. DAC
+// - Desktop, Windows CoreCLR, CoreSystem CoreCLR
+// In both cases we check this by matching the timestamp in the debug directory of the runtime image
+// and the timestamp we store in the DAC table when we generate the DAC dll. This is done in
+// code:ClrDataAccess::VerifyDlls.
+//
+// - Mac CoreCLR
+// On Mac, we don't have a timestamp in the runtime image. Instead, we rely on checking the 16-byte
+// UUID in the image. This UUID is used to check whether a symbol file matches the image, so
+// conceptually it's the same as the timestamp we use on Windows. This is also done in
+// code:ClrDataAccess::VerifyDlls.
+//
+//---------------------------------------------------------------------------------------
+//
+// Instantiates a DacDbi Interface object in a live-debugging scenario that matches
+// the current instance of mscorwks in this process.
+//
+// Return Value:
+// Returns on success. Else throws.
+//
+// Assumptions:
+// Client will code:CordbProcess::FreeDac when its done with the DacDbi interface.
+// Caller has initialized clrInstanceId.
+//
+// Notes:
+// This looks for the DAC next to this current DBI. This assumes that Dac and Dbi are both on
+// the local file system. That assumption will break in zero-copy deployment scenarios.
+//
+//---------------------------------------------------------------------------------------
+void
+CordbProcess::CreateDacDbiInterface()
+{
+ _ASSERTE(m_pDACDataTarget != NULL);
+ _ASSERTE(m_pDacPrimitives == NULL); // don't double-init
+
+ // Caller has already determined which CLR in the target is being debugged.
+ _ASSERTE(m_clrInstanceId != 0);
+
+ m_pDacPrimitives = NULL;
+
+ HRESULT hrStatus = S_OK;
+
+ // Non-marshalling path for live local dac.
+ // in the new arch we can get the module from OpenVirtualProcess2 but in the shim case
+ // and the deprecated OpenVirtualProcess case we must assume it comes from DAC in the
+ // same directory as DBI
+ if(m_hDacModule == NULL)
+ {
+ m_hDacModule.Assign(ShimProcess::GetDacModule());
+ }
+
+ //
+ // Get the access interface, passing our callback interfaces (data target, allocator and metadata lookup)
+ //
+
+ IDacDbiInterface::IAllocator * pAllocator = this;
+ IDacDbiInterface::IMetaDataLookup * pMetaDataLookup = this;
+
+
+ typedef HRESULT (STDAPICALLTYPE * PFN_DacDbiInterfaceInstance)(
+ ICorDebugDataTarget *,
+ CORDB_ADDRESS,
+ IDacDbiInterface::IAllocator *,
+ IDacDbiInterface::IMetaDataLookup *,
+ IDacDbiInterface **);
+
+ IDacDbiInterface* pInterfacePtr = NULL;
+ PFN_DacDbiInterfaceInstance pfnEntry = (PFN_DacDbiInterfaceInstance)GetProcAddress(m_hDacModule, "DacDbiInterfaceInstance");
+ if (!pfnEntry)
+ {
+ ThrowLastError();
+ }
+
+ hrStatus = pfnEntry(m_pDACDataTarget, m_clrInstanceId, pAllocator, pMetaDataLookup, &pInterfacePtr);
+ IfFailThrow(hrStatus);
+
+ // We now have a resource, pInterfacePtr, that needs to be freed.
+ m_pDacPrimitives = pInterfacePtr;
+
+ // Setup DAC target consistency checking based on what we're using for DBI
+ m_pDacPrimitives->DacSetTargetConsistencyChecks( m_fAssertOnTargetInconsistency );
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Is the DAC/DBI interface initialized?
+//
+// Return Value:
+// TRUE iff init.
+//
+// Notes:
+// The RS will try to initialize DD as soon as it detects the runtime as loaded.
+// If the DD interface has not initialized, then it very likely the runtime has not
+// been loaded into the target.
+//
+BOOL CordbProcess::IsDacInitialized()
+{
+ return m_pDacPrimitives != NULL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Get the DAC interface.
+//
+// Return Value:
+// the Dac/Dbi interface pointer to the process.
+// Never returns NULL.
+//
+// Assumptions:
+// Caller is responsible for ensuring Data-Target is safe to access (eg, not
+// currently running).
+// Caller is responsible for ensuring DAC-cache is flushed. Call code:CordbProcess::ForceDacFlush
+// as needed.
+//
+//---------------------------------------------------------------------------------------
+IDacDbiInterface * CordbProcess::GetDAC()
+{
+ // Since the DD primitives may throw, easiest way to model that is to make this throw.
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // We should always have the DAC/DBI interface.
+ _ASSERTE(m_pDacPrimitives != NULL);
+ return m_pDacPrimitives;
+}
+
+//---------------------------------------------------------------------------------------
+// Get the Data-Target
+//
+// Returns:
+// pointer to the data-target. Should be non-null.
+// Lifetime of the pointer is until this process object is neutered.
+//
+ICorDebugDataTarget * CordbProcess::GetDataTarget()
+{
+ return m_pDACDataTarget;
+}
+
+//---------------------------------------------------------------------------------------
+// Create a CordbProcess object around an existing OS process.
+//
+// Arguments:
+// pDataTarget - abstracts access to the debuggee.
+// clrInstanceId - identifies the CLR instance within the debuggee. (This is the
+// base address of mscorwks)
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// This should go away - we can get the functionality from the pShim.
+// If this is null, then pShim must be null too.
+// processID - OS process ID of target process. 0 if pShim == NULL.
+// pShim - shim counter part object. This allows hooks back for v2 compat. This will
+// go away once we no longer support V2 backwards compat.
+// This must be non-null for any V2 paths (including non-DAC-ized code).
+// If this is null, then we're in a V3 path.
+// ppProcess - out parameter for new process object. This gets addreffed.
+//
+// Return Value:
+// S_OK on success, and *ppProcess set to newly created debuggee object. Else error.
+//
+// Notes:
+// @dbgtodo - , shim: Cordb, and pShim will all eventually go away.
+//
+//---------------------------------------------------------------------------------------
+
+// static
+HRESULT CordbProcess::OpenVirtualProcess(
+ ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb* pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim,
+ CordbProcess ** ppProcess)
+{
+ _ASSERTE(pDataTarget != NULL);
+
+ // In DEBUG builds, verify that we do actually have an ICorDebugDataTarget (i.e. that
+ // someone hasn't messed up the COM interop marshalling, etc.).
+#ifdef _DEBUG
+ {
+ IUnknown * pTempDt;
+ HRESULT hrQi = pDataTarget->QueryInterface(IID_ICorDebugDataTarget, (void**)&pTempDt);
+ _ASSERTE_MSG(SUCCEEDED(hrQi), "OpenVirtualProcess was passed something that isn't actually an ICorDebugDataTarget");
+ pTempDt->Release();
+ }
+#endif
+
+ // If we're emulating V2, then both pCordb and pShim are non-NULL.
+ // If we're doing a real V3 path, then they're both NULL.
+ // Either way, they should have the same null-status.
+ _ASSERTE((pCordb == NULL) == (pShim == NULL));
+
+ // If we're doing real V3, then we must have a real instance ID
+ _ASSERTE(!((pShim == NULL) && (clrInstanceId == 0)));
+
+ *ppProcess = NULL;
+
+ HRESULT hr = S_OK;
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+ pProcess.Assign(new (nothrow) CordbProcess(clrInstanceId, pDataTarget, hDacModule, pCordb, dwProcessID, pShim));
+
+ if (pProcess == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ ICorDebugProcess * pThis = pProcess;
+ (void)pThis; //prevent "unused variable" error from GCC
+
+ // CordbProcess::Init may need shim hooks, so connect Shim now.
+ // This will bump reference count.
+ if (pShim != NULL)
+ {
+ pShim->SetProcess(pProcess);
+
+ _ASSERTE(pShim->GetProcess() == pThis);
+ _ASSERTE(pShim->GetWin32EventThread() != NULL);
+ }
+
+ hr = pProcess->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ *ppProcess = pProcess;
+ pProcess->ExternalAddRef();
+ }
+ else
+ {
+ // handle failure path
+ pProcess->CleanupHalfBakedLeftSide();
+
+ if (pShim != NULL)
+ {
+ // Shim still needs to be disposed to clean up other resources.
+ pShim->SetProcess(NULL);
+ }
+
+ // In failure case, pProcess's dtor will do the final release.
+ }
+
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// CordbProcess constructor
+//
+// Arguments:
+// pDataTarget - Pointer to an implementation of ICorDebugDataTarget
+// (or ICorDebugMutableDataTarget), which virtualizes access to the process.
+// clrInstanceId - representation of the CLR to debug in the process. Must be specified
+// (non-zero) if pShim is NULL. If 0, use the first CLR that we see.
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// pW32 - Pointer to the Win32 event thread to use when processing events for this
+// process.
+// dwProcessID - For V3, 0.
+// Else for shim codepaths, the processID of the process this object will represent.
+// pShim - Pointer to the shim for handling V2 debuggers on the V3 architecture.
+//
+//---------------------------------------------------------------------------------------
+
+CordbProcess::CordbProcess(ULONG64 clrInstanceId,
+ IUnknown * pDataTarget,
+ HMODULE hDacModule,
+ Cordb * pCordb,
+ DWORD dwProcessID,
+ ShimProcess * pShim)
+ : CordbBase(NULL, dwProcessID, enumCordbProcess),
+ m_fDoDelayedManagedAttached(false),
+ m_cordb(pCordb),
+ m_handle(NULL),
+ m_detached(false),
+ m_uninitializedStop(false),
+ m_exiting(false),
+ m_terminated(false),
+ m_unrecoverableError(false),
+ m_specialDeferment(false),
+ m_helperThreadDead(false),
+ m_loaderBPReceived(false),
+ m_cOutstandingEvals(0),
+ m_cOutstandingHandles(0),
+ m_clrInstanceId(clrInstanceId),
+ m_stopCount(0),
+ m_synchronized(false),
+ m_syncCompleteReceived(false),
+ m_pShim(pShim),
+ m_userThreads(11),
+ m_oddSync(false),
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads(11),
+#endif
+ m_appDomains(11),
+ m_sharedAppDomain(0),
+ m_steppers(11),
+ m_continueCounter(1),
+ m_flushCounter(0),
+ m_leftSideEventAvailable(NULL),
+ m_leftSideEventRead(NULL),
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ m_leftSideUnmanagedWaitEvent(NULL),
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_initialized(false),
+ m_stopRequested(false),
+ m_stopWaitEvent(NULL),
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_cFirstChanceHijackedThreads(0),
+ m_unmanagedEventQueue(NULL),
+ m_lastQueuedUnmanagedEvent(NULL),
+ m_lastQueuedOOBEvent(NULL),
+ m_outOfBandEventQueue(NULL),
+ m_lastDispatchedIBEvent(NULL),
+ m_dispatchingUnmanagedEvent(false),
+ m_dispatchingOOBEvent(false),
+ m_doRealContinueAfterOOBBlock(false),
+ m_state(0),
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_helperThreadId(0),
+ m_pPatchTable(NULL),
+ m_cPatch(0),
+ m_rgData(NULL),
+ m_rgNextPatch(NULL),
+ m_rgUncommitedOpcode(NULL),
+ m_minPatchAddr(MAX_ADDRESS),
+ m_maxPatchAddr(MIN_ADDRESS),
+ m_iFirstPatch(0),
+ m_hHelperThread(NULL),
+ m_dispatchedEvent(DB_IPCE_DEBUGGER_INVALID),
+ m_pDefaultAppDomain(NULL),
+ m_hDacModule(hDacModule),
+ m_pDacPrimitives(NULL),
+ m_pEventChannel(NULL),
+ m_fAssertOnTargetInconsistency(false),
+ m_runtimeOffsetsInitialized(false),
+ m_writableMetadataUpdateMode(LegacyCompatPolicy)
+{
+ _ASSERTE((m_id == 0) == (pShim == NULL));
+
+ HRESULT hr = pDataTarget->QueryInterface(IID_ICorDebugDataTarget, reinterpret_cast<void **>(&m_pDACDataTarget));
+ IfFailThrow(hr);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_DbgSupport.m_DebugEventQueueIdx = 0;
+ m_DbgSupport.m_TotalNativeEvents = 0;
+ m_DbgSupport.m_TotalIB = 0;
+ m_DbgSupport.m_TotalOOB = 0;
+ m_DbgSupport.m_TotalCLR = 0;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ g_pRSDebuggingInfo->m_MRUprocess = this;
+
+ // This is a strong reference to ourselves.
+ // This is cleared in code:CordbProcess::Neuter
+ m_pProcess.Assign(this);
+
+#ifdef _DEBUG
+ // On Debug builds, we'll ASSERT by default whenever the target appears to be corrupt or
+ // otherwise inconsistent (both in DAC and DBI). But we also need the ability to
+ // explicitly test corrupt targets.
+ // Tests should set COMPlus_DbgIgnoreInconsistentTarget=1 to suppress these asserts
+ // Note that this controls two things:
+ // 1) DAC behavior - see code:IDacDbiInterface::DacSetTargetConsistencyChecks
+ // 2) RS-only consistency asserts - see code:CordbProcess::TargetConsistencyCheck
+ if( !CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgDisableTargetConsistencyAsserts) )
+ {
+ m_fAssertOnTargetInconsistency = true;
+ }
+#endif
+}
+
+/*
+ A list of which resources owned by this object are accounted for.
+
+ UNKNOWN
+ Cordb* m_cordb;
+ CordbHashTable m_unmanagedThreads; // Released in CordbProcess but not removed from hash
+ DebuggerIPCEvent* m_lastQueuedEvent;
+
+ // CordbUnmannagedEvent is a struct which is not derrived from CordbBase.
+ // It contains a CordbUnmannagedThread which may need to be released.
+ CordbUnmanagedEvent *m_unmanagedEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedUnmanagedEvent;
+ CordbUnmanagedEvent *m_outOfBandEventQueue;
+ CordbUnmanagedEvent *m_lastQueuedOOBEvent;
+
+ BYTE* m_pPatchTable;
+ BYTE *m_rgData;
+ void *m_pbRemoteBuf;
+
+ RESOLVED
+ // Nutered
+ CordbHashTable m_userThreads;
+ CordbHashTable m_appDomains;
+
+ // Cleaned up in ExitProcess
+ DebuggerIPCEvent* m_queuedEventList;
+
+ CordbHashTable m_steppers; // Closed in ~CordbProcess
+
+ // Closed in CloseIPCEventHandles called from ~CordbProcess
+ HANDLE m_leftSideEventAvailable;
+ HANDLE m_leftSideEventRead;
+
+ // Closed in ~CordbProcess
+ HANDLE m_handle;
+ HANDLE m_leftSideUnmanagedWaitEvent;
+ HANDLE m_stopWaitEvent;
+
+ // Deleted in ~CordbProcess
+ CRITICAL_SECTION m_processMutex;
+
+*/
+
+
+CordbProcess::~CordbProcess()
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::~CP: deleting process 0x%08x\n", this));
+
+ DTOR_ENTRY(this);
+
+ _ASSERTE(IsNeutered());
+
+ _ASSERTE(m_cordb == NULL);
+
+ // We shouldn't still be in Cordb's list of processes. Unfortunately, our root Cordb object
+ // may have already been deleted b/c we're at the mercy of ref-counting, so we can't check.
+
+ _ASSERTE(m_sharedAppDomain == NULL);
+
+ m_processMutex.Destroy();
+ m_StopGoLock.Destroy();
+
+ // These handles were cleared in neuter
+ _ASSERTE(m_handle == NULL);
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ _ASSERTE(m_leftSideUnmanagedWaitEvent == NULL);
+#endif // FEATURE_INTEROP_DEBUGGING
+ _ASSERTE(m_stopWaitEvent == NULL);
+
+ // Set this to mark that we really did cleanup.
+}
+
+//-----------------------------------------------------------------------------
+// Static build helper.
+// This will create a process under the pCordb root, and add it to the list.
+// We don't return the process - caller gets the pid and looks it up under
+// the Cordb object.
+//
+// Arguments:
+// pCordb - Pointer to the implementation of the owning Cordb object implementing the
+// owning ICD interface.
+// szProgramName - Name of the program to execute.
+// szProgramArgs - Command line arguments for the process.
+// lpProcessAttributes - OS-specific attributes for process creation.
+// lpThreadAttributes - OS-specific attributes for thread creation.
+// fInheritFlags - OS-specific flag for child process inheritance.
+// dwCreationFlags - OS-specific creation flags.
+// lpEnvironment - OS-specific environmental strings.
+// szCurrentDirectory - OS-specific string for directory to run in.
+// lpStartupInfo - OS-specific info on startup.
+// lpProcessInformation - OS-specific process information buffer.
+// corDebugFlags - What type of process to create, currently always managed.
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::CreateProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ LPCWSTR szProgramName,
+ __in_z LPWSTR szProgramArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL fInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR szCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags
+)
+{
+ _ASSERTE(pCordb != NULL);
+
+#if defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // The transport cannot deal with creating a suspended process (it needs the debugger to start up and
+ // listen for connections).
+ _ASSERTE((dwCreationFlags & CREATE_SUSPENDED) == 0);
+#endif // FEATURE_DBGIPC_TRANSPORT_DI
+
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<ShimProcess> pShim;
+ EX_TRY
+ {
+ pShim.Assign(new ShimProcess());
+
+ // Indicate that this process was started under the debugger as opposed to attaching later.
+ pShim->m_attached = false;
+
+ hr = pShim->CreateAndStartWin32ET(pCordb);
+ IfFailThrow(hr);
+
+ // Call out to newly created Win32-event Thread to create the process.
+ // If this succeeds, new CordbProcess will add a ref to the ShimProcess
+ hr = pShim->GetWin32EventThread()->SendCreateProcessEvent(pShim->GetMachineInfo(),
+ szProgramName,
+ szProgramArgs,
+ lpProcessAttributes,
+ lpThreadAttributes,
+ fInheritHandles,
+ dwCreationFlags,
+ lpEnvironment,
+ szCurrentDirectory,
+ lpStartupInfo,
+ lpProcessInformation,
+ corDebugFlags);
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // If this succeeds, then process takes ownership of thread. Else we need to kill it.
+ if (FAILED(hr))
+ {
+ if (pShim != NULL)
+ {
+ pShim->Dispose();
+ }
+ }
+ // Always release our ref to ShimProcess. If the Process was created, then it takes a reference.
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Static build helper for the attach case.
+// On success, this will add the process to the pCordb list, and then
+// callers can look it up there by pid.
+//
+// Arguments:
+// pCordb - root under which this all lives
+// dwProcessID - OS process ID to attach to
+// fWin32Attach - are we interop debugging?
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::DebugActiveProcess(
+ Cordb * pCordb,
+ ICorDebugRemoteTarget * pRemoteTarget,
+ DWORD dwProcessID,
+ BOOL fWin32Attach
+)
+{
+ _ASSERTE(pCordb != NULL);
+
+ HRESULT hr = S_OK;
+
+ RSExtSmartPtr<ShimProcess> pShim;
+
+ EX_TRY
+ {
+ pShim.Assign(new ShimProcess());
+
+ // Indicate that this process was attached to, asopposed to being started under the debugger.
+ pShim->m_attached = true;
+
+ hr = pShim->CreateAndStartWin32ET(pCordb);
+ IfFailThrow(hr);
+
+ // If this succeeds, new CordbProcess will add a ref to the ShimProcess
+ hr = pShim->GetWin32EventThread()->SendDebugActiveProcessEvent(pShim->GetMachineInfo(),
+ dwProcessID,
+ fWin32Attach == TRUE,
+ NULL);
+ IfFailThrow(hr);
+
+ _ASSERTE(SUCCEEDED(hr));
+
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // Don't do this when we are remote debugging since we won't be getting the loader breakpoint.
+ // We don't support JIT attach in remote debugging scenarios anyway.
+ //
+ // When doing jit attach for pure managed debugging we allow the native attach event to be signaled
+ // after DebugActiveProcess completes which means we must wait here long enough to have set the debuggee
+ // bit indicating managed attach is coming.
+ // However in interop debugging we can't do that because there are debug events which come before the
+ // loader breakpoint (which is how far we need to get to set the debuggee bit). If we blocked
+ // DebugActiveProcess there then the debug events would be refering to an ICorDebugProcess that hasn't
+ // yet been returned to the caller of DebugActiveProcess. Instead, for interop debugging we force the
+ // native debugger to wait until it gets the loader breakpoint to set the event. Note we can't converge
+ // on that solution for the pure managed case because there is no loader breakpoint event. Hence pure
+ // managed and interop debugging each require their own solution
+ //
+ // See bugs Dev10 600873 and 595322 for examples of what happens if we wait in interop or don't wait
+ // in pure managed respectively
+ //
+ // Long term this should all go away because we won't need to set a managed attach pending bit because
+ // there shouldn't be any IPC events involved in managed attach. There might not even be a notion of
+ // being 'managed attached'
+ if(!pShim->m_fIsInteropDebugging)
+ {
+ DWORD dwHandles = 2;
+ HANDLE arrHandles[2];
+
+ arrHandles[0] = pShim->m_terminatingEvent;
+ arrHandles[1] = pShim->m_markAttachPendingEvent;
+
+ // Wait for the completion of marking pending attach bit or debugger detaching
+ WaitForMultipleObjectsEx(dwHandles, arrHandles, FALSE, INFINITE, FALSE);
+ }
+#endif //!FEATURE_DBGIPC_TRANSPORT_DI
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // If this succeeds, then process takes ownership of thread. Else we need to kill it.
+ if (FAILED(hr))
+ {
+ if (pShim!= NULL)
+ {
+ pShim->Dispose();
+ }
+ }
+
+ // Always release our ref to ShimProcess. If the Process was created, then it takes a reference.
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Neuter all of all children, but not the actual process object.
+//
+// Assumptions:
+// This clears Right-side state. Assumptions about left-side state are either:
+// 1. We're in a shutdown scenario, where all left-side state is already
+// freed.
+// 2. Caller already verified there are no left-side resources (eg, by calling
+// code:CordbProcess::IsReadyForDetach)
+// 3. Caller did code:CordbProcess::NeuterLeftSideResources first
+// to clean up left-side resources.
+//
+// Notes:
+// This could be called multiple times (code:CordbProcess::FlushAll), so
+// be sure to null out any potential dangling pointers. State may be rebuilt
+// up after each time.
+void CordbProcess::NeuterChildren()
+{
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ // Frees left-side resources. See assumptions above.
+ m_LeftSideResourceCleanupList.NeuterAndClear(this);
+
+
+ m_EvalTable.Clear();
+
+
+ // Sweep neuter lists.
+ m_ExitNeuterList.NeuterAndClear(this);
+ m_ContinueNeuterList.NeuterAndClear(this);
+
+ m_userThreads.NeuterAndClear(GetProcessLock());
+
+ m_pDefaultAppDomain = NULL;
+
+ // Frees per-appdomain left-side resources. See assumptions above.
+ m_appDomains.NeuterAndClear(GetProcessLock());
+ if (m_sharedAppDomain != NULL)
+ {
+ m_sharedAppDomain->Neuter();
+ m_sharedAppDomain->InternalRelease();
+ m_sharedAppDomain = NULL;
+ }
+
+ m_steppers.NeuterAndClear(GetProcessLock());
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads.NeuterAndClear(GetProcessLock());
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Explicitly keep the Win32EventThread alive so that we can use it in the window
+ // between NeuterChildren + Neuter.
+}
+
+//-----------------------------------------------------------------------------
+// Neuter
+//
+// When the process dies, remove all the resources associated with this object.
+//
+// Notes:
+// Once we neuter ourself, we can no longer send IPC events. So this is useful
+// on detach. This will be called on FlushAll (which has Whidbey detach
+// semantics)
+//-----------------------------------------------------------------------------
+void CordbProcess::Neuter()
+{
+ // Process's Neuter is at the top of the neuter tree. So we take the process-lock
+ // here and then all child items (appdomains, modules, etc) will assert
+ // that they hold the lock.
+ _ASSERTE(!this->ThreadHoldsProcessLock());
+
+ // Take the process lock.
+ RSLockHolder lockHolder(GetProcessLock());
+
+
+ NeuterChildren();
+
+ // Release the metadata interfaces
+ m_pMetaDispenser.Clear();
+
+
+ if (m_hHelperThread != NULL)
+ {
+ CloseHandle(m_hHelperThread);
+ m_hHelperThread = NULL;
+ }
+
+ {
+ lockHolder.Release();
+ {
+ // We may still hold the Stop-Go lock.
+ // @dbgtodo - left-side resources / shutdown, shim: Currently
+ // the shim shutdown is too interwoven with CordbProcess to split
+ // it out from the locks. Must fully hoist the W32ET and make
+ // it safely outside the RS, and outside the protection of RS
+ // locks.
+ PUBLIC_API_UNSAFE_ENTRY_FOR_SHIM(this);
+
+ // Now that all of our children are neutered, it should be safe to kill the W32ET.
+ // Shutdown the shim, and this will also shutdown the W32ET.
+ // Do this outside of the process-lock so that we can shutdown the
+ // W23ET.
+ if (m_pShim != NULL)
+ {
+ m_pShim->Dispose();
+ m_pShim.Clear();
+ }
+ }
+
+ lockHolder.Acquire();
+ }
+
+ // Unload DAC, and then release our final data target references
+ FreeDac();
+ m_pDACDataTarget.Clear();
+ m_pMutableDataTarget.Clear();
+ m_pMetaDataLocator.Clear();
+
+ if (m_pEventChannel != NULL)
+ {
+ m_pEventChannel->Delete();
+ m_pEventChannel = NULL;
+ }
+
+ // Need process lock to clear the patch table
+ ClearPatchTable();
+
+ CordbProcess::CloseIPCHandles();
+
+ CordbBase::Neuter();
+
+ m_cordb.Clear();
+
+ // Need to release this reference to ourselves. Other leaf objects may still hold
+ // strong references back to this CordbProcess object.
+ _ASSERTE(m_pProcess == this);
+ m_pProcess.Clear();
+}
+
+// Wrapper to return metadata dispenser.
+//
+// Notes:
+// Does not adjust reference count of dispenser.
+// Dispenser is destroyed in code:CordbProcess::Neuter
+// Dispenser is non-null.
+IMetaDataDispenserEx * CordbProcess::GetDispenser()
+{
+ _ASSERTE(m_pMetaDispenser != NULL);
+ return m_pMetaDispenser;
+}
+
+
+void CordbProcess::CloseIPCHandles()
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Close off Right Side's handles.
+ if (m_leftSideEventAvailable != NULL)
+ {
+ CloseHandle(m_leftSideEventAvailable);
+ m_leftSideEventAvailable = NULL;
+ }
+
+ if (m_leftSideEventRead != NULL)
+ {
+ CloseHandle(m_leftSideEventRead);
+ m_leftSideEventRead = NULL;
+ }
+
+ if (m_handle != NULL)
+ {
+ // @dbgtodo - We should probably add asserts to all calls to CloseHandles(), but this has been
+ // a particularly problematic spot in the past for Mac debugging.
+ BOOL fSuccess = CloseHandle(m_handle);
+ (void)fSuccess; //prevent "unused variable" error from GCC
+ _ASSERTE(fSuccess);
+
+ m_handle = NULL;
+ }
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ if (m_leftSideUnmanagedWaitEvent != NULL)
+ {
+ CloseHandle(m_leftSideUnmanagedWaitEvent);
+ m_leftSideUnmanagedWaitEvent = NULL;
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ if (m_stopWaitEvent != NULL)
+ {
+ CloseHandle(m_stopWaitEvent);
+ m_stopWaitEvent = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Create new OS Thread for the Win32 Event Thread (the thread used in interop-debugging to sniff
+// native debug events). This is 1:1 w/ a CordbProcess object.
+// This will then be used to actuall create the CordbProcess object.
+// The process object will then take ownership of the thread.
+//
+// Arguments:
+// pCordb - the root object that the process lives under
+//
+// Return values:
+// S_OK on success.
+//-----------------------------------------------------------------------------
+HRESULT ShimProcess::CreateAndStartWin32ET(Cordb * pCordb)
+{
+
+ //
+ // Create the win32 event listening thread
+ //
+ CordbWin32EventThread * pWin32EventThread = new (nothrow) CordbWin32EventThread(pCordb, this);
+
+ HRESULT hr = S_OK;
+
+ if (pWin32EventThread != NULL)
+ {
+ hr = pWin32EventThread->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pWin32EventThread->Start();
+ }
+
+ if (FAILED(hr))
+ {
+ delete pWin32EventThread;
+ pWin32EventThread = NULL;
+ }
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ m_pWin32EventThread = pWin32EventThread;
+ return ErrWrapper(hr);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Try to initialize the DAC. Called in scenarios where it may fail.
+//
+// Return Value:
+// TRUE - DAC is initialized.
+// FALSE - Not initialized, but can try again later. Common case if
+// target has not yet loaded the runtime.
+// Throws exception - fatal.
+//
+// Assumptions:
+// Target is stopped by OS, so we can safely inspect it without it moving on us.
+//
+// Notes:
+// This can be called eagerly to sniff if the LS is initialized.
+//
+//---------------------------------------------------------------------------------------
+BOOL CordbProcess::TryInitializeDac()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ // Target is stopped by OS, so we can safely inspect it without it moving on us.
+
+ // We want to avoid exceptions in the normal case, so we do some pre-checks
+ // to detect failure without relying on exceptions.
+ // Can't initialize DAC until mscorwks is loaded. So that's a sanity test.
+ HRESULT hr = EnsureClrInstanceIdSet();
+ if (FAILED(hr))
+ {
+ return FALSE;
+ }
+
+ // By this point, we know which CLR in the target to debug. That means there is a CLR
+ // in the target, and it's safe to initialize DAC.
+ _ASSERTE(m_clrInstanceId != 0);
+
+ // Now expect it to succeed
+ InitializeDac();
+ return TRUE;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Load & Init DAC, expecting to succeed.
+//
+// Return Value:
+// Throws on failure.
+//
+// Assumptions:
+// Caller invokes this at a point where they can expect it to succeed.
+// This is called early in the startup path because DAC is needed for accessing
+// data in the target.
+//
+// Notes:
+// This needs to succeed, and should always succeed (baring a bad installation)
+// so we assert on failure paths.
+// This may be called mutliple times.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::InitializeDac()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+ INTERNAL_API_ENTRY(this);
+
+ // For Mac debugginger, m_hDacModule is not used, and it will always be NULL. To check whether DAC has
+ // been initialized, we need to check something else, namely m_pDacPrimitives.
+ if (m_pDacPrimitives == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "About to load DAC\n"));
+ CreateDacDbiInterface(); // throws
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Dac already loaded, 0x%p\n", (HMODULE)m_hDacModule));
+ }
+
+ // Always flush dac.
+ ForceDacFlush();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Free DAC resources
+//
+// Notes:
+// This should clean up state such that code:CordbProcess::InitializeDac could be called again.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::FreeDac()
+{
+ CONTRACTL
+ {
+ NOTHROW; // backout code.
+ }
+ CONTRACTL_END;
+
+ if (m_pDacPrimitives != NULL)
+ {
+ m_pDacPrimitives->Destroy();
+ m_pDacPrimitives = NULL;
+ }
+
+ if (m_hDacModule != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Unloading DAC\n"));
+ m_hDacModule.Clear();
+ }
+}
+
+IEventChannel * CordbProcess::GetEventChannel()
+{
+ _ASSERTE(m_pEventChannel != NULL);
+ return m_pEventChannel;
+}
+
+//---------------------------------------------------------------------------------------
+// Mark that the process is being interop-debugged.
+//
+// Notes:
+// @dbgtodo shim: this should eventually move into the shim or go away.
+// It's only to support V2 legacy interop-debugging.
+// Called after code:CordbProcess::Init if we want to enable interop debugging.
+// This allows us to separate out Interop-debugging flags from the core initialization,
+// and paves the way for us to eventually remove it.
+//
+// Since we're always on the naitve-pipeline, the Enabling interop debugging just changes
+// how the native debug events are being handled. So this must be called after Init, but
+// before any events are actually handled.
+// This mus be calle on the win32 event thread to gaurantee that it's called before WFDE.
+void CordbProcess::EnableInteropDebugging()
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(m_pShim != NULL);
+ }
+ CONTRACTL_END;
+
+ // Must be on W32ET to gaurantee that we're called after Init yet before WFDE (which
+ // are both called on the W32et).
+ _ASSERTE(IsWin32EventThread());
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+ m_state |= PS_WIN32_ATTACHED;
+ if (GetDCB() != NULL)
+ {
+ GetDCB()->m_rightSideIsWin32Debugger = true;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+ }
+
+ // Tell the Shim we're interop-debugging.
+ m_pShim->SetIsInteropDebugging(true);
+#else
+ ThrowHR(CORDBG_E_INTEROP_NOT_SUPPORTED);
+#endif
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Init -- create any objects that the process object needs to operate.
+//
+// Arguments:
+//
+// Return Value:
+// S_OK on success
+//
+// Assumptions:
+// Called on Win32 Event Thread, after OS debugging pipeline is established but
+// before WaitForDebugEvent / ContinueDebugEvent. This means the target is stopped.
+//
+// Notes:
+// To enable interop-debugging, call code:CordbProcess::EnableInteropDebugging
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::Init()
+{
+ INTERNAL_API_ENTRY(this);
+
+ HRESULT hr = S_OK;
+ BOOL fIsLSStarted = FALSE; // see meaning below.
+
+ FAIL_IF_NEUTERED(this);
+
+
+ EX_TRY
+ {
+ m_processMutex.Init("Process Lock", RSLock::cLockReentrant, RSLock::LL_PROCESS_LOCK);
+ m_StopGoLock.Init("Stop-Go Lock", RSLock::cLockReentrant, RSLock::LL_STOP_GO_LOCK);
+
+#ifdef _DEBUG
+ m_appDomains.DebugSetRSLock(GetProcessLock());
+ m_userThreads.DebugSetRSLock(GetProcessLock());
+#ifdef FEATURE_INTEROP_DEBUGGING
+ m_unmanagedThreads.DebugSetRSLock(GetProcessLock());
+#endif
+ m_steppers.DebugSetRSLock(GetProcessLock());
+#endif
+
+ // See if the data target is mutable, and cache the mutable interface if it is
+ // We must initialize this before we try to use the data target to access the memory in the target process.
+ m_pMutableDataTarget.Clear(); // if we were called already, release
+ hr = m_pDACDataTarget->QueryInterface(IID_ICorDebugMutableDataTarget, (void**)&m_pMutableDataTarget);
+ if (!SUCCEEDED(hr))
+ {
+ // The data target doesn't support mutation. We'll fail any requests that require mutation.
+ m_pMutableDataTarget.Assign(new ReadOnlyDataTargetFacade());
+ }
+
+ m_pMetaDataLocator.Clear();
+ hr = m_pDACDataTarget->QueryInterface(IID_ICorDebugMetaDataLocator, reinterpret_cast<void **>(&m_pMetaDataLocator));
+
+ // Get the metadata dispenser.
+ hr = InternalCreateMetaDataDispenser(IID_IMetaDataDispenserEx, (void **)&m_pMetaDispenser);
+
+ // We statically link in the dispenser. We expect it to succeed, except for OOM, which
+ // debugger doesn't yet handle.
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ IfFailThrow(hr);
+
+ _ASSERTE(m_pMetaDispenser != NULL);
+
+ // In order to allow users to call the metadata reader from multiple threads we need to set
+ // a flag on the dispenser to create threadsafe readers. This is done best-effort but
+ // really shouldn't ever fail. See issue 696511.
+ VARIANT optionValue;
+ VariantInit(&optionValue);
+ V_VT(&optionValue) = VT_UI4;
+ V_UI4(&optionValue) = MDThreadSafetyOn;
+ m_pMetaDispenser->SetOption(MetaDataThreadSafetyOptions, &optionValue);
+
+ //
+ // Setup internal events.
+ // @dbgtodo shim: these events should eventually be in the shim.
+ //
+
+
+ // Managed debugging is built on the native-pipeline, and that will detect against double-attaches.
+
+ // @dbgtodo shim: In V2, LSEA + LSER were used by the LS's helper thread. Now with the V3 pipeline,
+ // that helper-thread uses native-debug events. The W32ET gets those events and then uses LSEA, LSER to
+ // signal existing RS infrastructure. Eventually get rid of LSEA, LSER completely.
+ //
+
+ m_leftSideEventAvailable = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_leftSideEventAvailable == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_leftSideEventRead = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_leftSideEventRead == NULL)
+ {
+ ThrowLastError();
+ }
+
+ m_stopWaitEvent = WszCreateEvent(NULL, TRUE, FALSE, NULL);
+ if (m_stopWaitEvent == NULL)
+ {
+ ThrowLastError();
+ }
+
+ if (m_pShim != NULL)
+ {
+ // Get a handle to the debuggee.
+ // This is not needed in the V3 pipeline because we don't assume we have a live, local, process.
+ m_handle = GetShim()->GetNativePipeline()->GetProcessHandle();
+
+ if (m_handle == NULL)
+ {
+ ThrowLastError();
+ }
+ }
+
+ // The LS startup goes through the following phases:
+ // 1) mscorwks not yet loaded (eg, any unmanaged app)
+ // 2) mscorwks loaded (DAC can now be used)
+ // 3) IPC Block created at OS level
+ // 4) IPC block data initialized (so we can read meainingful data from it)
+ // 5) LS marks that it's initialized (queryable by a DAC primitive) (may not be atomic)
+ // 6) LS fires a "Startup" exception (sniffed by WFDE).
+ //
+ // LS is currently stopped by OS debugging, so it's doesn't shift phases.
+ // From the RS's perspective:
+ // - after phase 5 is an attach
+ // - before phase 6 is a launch.
+ // This means there's an overlap: if we catch it at phase 5, we'll just get
+ // an extra Startup exception from phase 6, which is safe. This overlap is good
+ // because it means there's no bad window to do an attach in.
+
+ // fIsLSStarted means before phase 6 (eg, RS should expect a startup exception)
+
+ // Determines if the LS is started.
+
+ {
+ BOOL fReady = TryInitializeDac();
+
+ if (fReady)
+ {
+ // Invoke DAC primitive.
+ _ASSERTE(m_pDacPrimitives != NULL);
+ fIsLSStarted = m_pDacPrimitives->IsLeftSideInitialized();
+ }
+ else
+ {
+ _ASSERTE(m_pDacPrimitives == NULL);
+
+ // DAC is not yet loaded, so we're at least before phase 2, which is before phase 6.
+ // So leave fIsLSStarted = false. We'll get a startup exception later.
+ _ASSERTE(!fIsLSStarted);
+ }
+ }
+
+
+ if (fIsLSStarted)
+ {
+ // Left-side has started up. This is common for Attach cases when managed-code is already running.
+
+ if (m_pShim != NULL)
+ {
+ FinishInitializeIPCChannelWorker(); // throws
+
+ // At this point, the control block is complete and all four
+ // events are available and valid for the remote process.
+
+ // Request that the process object send an Attach IPC event.
+ // This is only used in an attach case.
+ // @dbgtodo sync: this flag can go away once the
+ // shim can use real sync APIs.
+ m_fDoDelayedManagedAttached = true;
+ }
+ else
+ {
+ // In the V3 pipeline case, if we have the DD-interface, then the runtime is loaded
+ // and we consider it initialized.
+ if (IsDacInitialized())
+ {
+ m_initialized = true;
+ }
+ }
+ }
+ else
+ {
+ // LS is not started yet. This would be common for "Launch" cases.
+ // We will get a Startup Exception notification when it does start.
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ CleanupHalfBakedLeftSide();
+ }
+
+ return hr;
+}
+
+
+COM_METHOD CordbProcess::CanCommitChanges(ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+COM_METHOD CordbProcess::CommitChanges(ULONG cSnapshots,
+ ICorDebugEditAndContinueSnapshot *pSnapshots[],
+ ICorDebugErrorInfoEnum **pError)
+{
+ return E_NOTIMPL;
+}
+
+
+//
+// Terminating -- places the process into the terminated state. This should
+// also get any blocking process functions unblocked so they'll return
+// a failure code.
+//
+void CordbProcess::Terminating(BOOL fDetach)
+{
+ INTERNAL_API_ENTRY(this);
+
+ LOG((LF_CORDB, LL_INFO1000,"CP::T: Terminating process 0x%x detach=%d\n", m_id, fDetach));
+ m_terminated = true;
+
+ m_cordb->ProcessStateChanged();
+
+ // Set events that may be blocking stuff.
+ // But don't set RSER unless we actually read the event. We don't block on RSER
+ // since that wait also checks the leftside's process handle.
+ SetEvent(m_leftSideEventRead);
+ SetEvent(m_leftSideEventAvailable);
+ SetEvent(m_stopWaitEvent);
+
+ if (m_pShim != NULL)
+ m_pShim->SetTerminatingEvent();
+
+ if (fDetach && (m_pEventChannel != NULL))
+ {
+ m_pEventChannel->Detach();
+ }
+}
+
+
+// Wrapper to give shim access to code:CordbProcess::QueueManagedAttachIfNeededWorker
+void CordbProcess::QueueManagedAttachIfNeeded()
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ QueueManagedAttachIfNeededWorker();
+}
+
+//---------------------------------------------------------------------------------------
+// Hook from Shim to request a managed attach IPC event
+//
+// Notes:
+// Called by shim after the loader-breakpoint is handled.
+// @dbgtodo sync: ths should go away once the shim can initiate
+// a sync
+void CordbProcess::QueueManagedAttachIfNeededWorker()
+{
+ HRESULT hrQueue = S_OK;
+
+ // m_fDoDelayedManagedAttached ensures that we only send an Attach event if the LS is actually present.
+ if (m_fDoDelayedManagedAttached && GetShim()->GetAttached())
+ {
+ RSLockHolder lockHolder(&this->m_processMutex);
+ GetDAC()->MarkDebuggerAttachPending();
+
+ hrQueue = this->QueueManagedAttach();
+ }
+
+ if (m_pShim != NULL)
+ m_pShim->SetMarkAttachPendingEvent();
+
+ IfFailThrow(hrQueue);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// QueueManagedAttach
+//
+// Send a managed attach. This is asynchronous and will return immediately.
+//
+// Return Value:
+// S_OK on success
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::QueueManagedAttach()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ _ASSERTE(m_fDoDelayedManagedAttached);
+ m_fDoDelayedManagedAttached = false;
+
+ _ASSERTE(IsDacInitialized());
+
+ // We don't know what Queue it.
+ SendAttachProcessWorkItem * pItem = new (nothrow) SendAttachProcessWorkItem(this);
+
+ if (pItem == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ this->m_cordb->m_rcEventThread->QueueAsyncWorkItem(pItem);
+
+ return S_OK;
+}
+
+// However, we still want to synchronize.
+// @dbgtodo sync: when we hoist attaching, we can send an DB_IPCE_ASYNC_BREAK event instead or Attach
+// (for V2 semantics, we still need to synchronize the process)?
+void SendAttachProcessWorkItem::Do()
+{
+ HRESULT hr;
+
+ // This is being processed on the RCET, where it's safe to take the Stop-Go lock.
+ RSLockHolder ch(this->GetProcess()->GetStopGoLock());
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ // This just acts like an async-break, which will kick off things.
+ // This will not induce any faked attach events from the VM (like it did in V2).
+ // The Left-side will still slip foward allowing the async-break to happen, so
+ // we may get normal debug events in addition to the sync-complete.
+ //
+ // 1. In the common attach case, we should just get a sync-complete.
+ // 2. In Jit-attach cases, the LS is sending an event, and so we'll get that event and then the sync-complete.
+ GetProcess()->InitAsyncIPCEvent(event, DB_IPCE_ATTACHING, VMPTR_AppDomain::NullPtr());
+
+ // This should result in a sync-complete from the Left-side, which will be raised as an exception
+ // that the debugger passes into Filter and then internally goes through code:CordbProcess::TriageSyncComplete
+ // and that triggers code:CordbRCEventThread::FlushQueuedEvents to be called on the RCET.
+ // We already pre-queued a fake CreateProcess event.
+
+ // The left-side will also mark itself as attached in response to this event.
+ // We explicitly don't mark it as attached from the right-side because we want to let the left-side
+ // synchronize first (to stop all running threads) before marking the debugger as attached.
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::S: sending attach.\n", GetCurrentThreadId()));
+
+ hr = GetProcess()->SendIPCEvent(event, CorDBIPC_BUFFER_SIZE);
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::S: sent attach.\n", GetCurrentThreadId()));
+}
+
+//---------------------------------------------------------------------------------------
+// Try to lookup a cached thread object
+//
+// Arguments:
+// vmThread - vm identifier for thread.
+//
+// Returns:
+// Thread object if cached; null if not yet cached.
+//
+// Notes:
+// This does not create the thread object if it's not cached. Caching is unpredictable,
+// and so this may appear to randomly return NULL.
+// Callers should prefer code:CordbProcess::LookupOrCreateThread unless they expicitly
+// want to check RS state.
+CordbThread * CordbProcess::TryLookupThread(VMPTR_Thread vmThread)
+{
+ return m_userThreads.GetBase(VmPtrToCookie(vmThread));
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup (or create) a CordbThread object by the given volatile OS id. Returns null if not a manged thread
+//
+// Arguments:
+// dwThreadId - os thread id that a managed thread may be using.
+//
+// Returns:
+// Thread instance if there is currently a managed thread scheduled to run on dwThreadId.
+// NULL if this tid is not a valid Managed thread. (This is considered a common case)
+// Throws on error.
+//
+// Notes:
+// OS Thread ID is not fiber-safe, so this is a dangerous function to call.
+// Avoid this as much as possible. Prefer using VMPTR_Thread and
+// code:CordbProcess::LookupOrCreateThread instead of OS thread IDs.
+// See code:CordbThread::GetID for details.
+CordbThread * CordbProcess::TryLookupOrCreateThreadByVolatileOSId(DWORD dwThreadId)
+{
+ PrepopulateThreadsOrThrow();
+ return TryLookupThreadByVolatileOSId(dwThreadId);
+}
+
+//---------------------------------------------------------------------------------------
+// Lookup a cached CordbThread object by the tid. Returns null if not in the cache (which
+// includes unmanged thread)
+//
+// Arguments:
+// dwThreadId - os thread id that a managed thread may be using.
+//
+// Returns:
+// Thread instance if there is currently a managed thread scheduled to run on dwThreadId.
+// NULL if this tid is not a valid Managed thread. (This is considered a common case)
+// Throws on error.
+//
+// Notes:
+// Avoids this method:
+// * OS Thread ID is not fiber-safe, so this is a dangerous function to call.
+// * This is juts a Lookup, not LookupOrCreate, so it should only be used by methods
+// that care about the RS state (instead of just LS state).
+// Prefer using VMPTR_Thread and code:CordbProcess::LookupOrCreateThread
+//
+CordbThread * CordbProcess::TryLookupThreadByVolatileOSId(DWORD dwThreadId)
+{
+ HASHFIND find;
+ for (CordbThread * pThread = m_userThreads.FindFirst(&find);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&find))
+ {
+ _ASSERTE(pThread != NULL);
+
+ // Get the OS tid. This returns 0 if the thread is switched out.
+ DWORD dwThreadId2 = GetDAC()->TryGetVolatileOSThreadID(pThread->m_vmThreadToken);
+ if (dwThreadId2 == dwThreadId)
+ {
+ return pThread;
+ }
+ }
+
+ // This OS thread ID does not match any managed thread id.
+ return NULL;
+}
+
+//---------------------------------------------------------------------------------------
+// Preferred CordbThread lookup routine.
+//
+// Arguments:
+// vmThread - LS thread to lookup. Must be non-null.
+//
+// Returns:
+// CordbThread instance for given vmThread. May return a previously cached
+// instance or create a new instance. Never returns NULL.
+// Throw on error.
+CordbThread * CordbProcess::LookupOrCreateThread(VMPTR_Thread vmThread)
+{
+ _ASSERTE(!vmThread.IsNull());
+
+ // Return if we have an existing instance.
+ CordbThread * pReturn = TryLookupThread(vmThread);
+ if (pReturn != NULL)
+ {
+ return pReturn;
+ }
+
+ RSInitHolder<CordbThread> pThread(new CordbThread(this, vmThread)); // throws
+ pReturn = pThread.TransferOwnershipToHash(&m_userThreads);
+
+ return pReturn;
+}
+
+
+
+
+HRESULT CordbProcess::QueryInterface(REFIID id, void **pInterface)
+{
+ if (id == IID_ICorDebugProcess)
+ {
+ *pInterface = static_cast<ICorDebugProcess*>(this);
+ }
+ else if (id == IID_ICorDebugController)
+ {
+ *pInterface = static_cast<ICorDebugController*>(static_cast<ICorDebugProcess*>(this));
+ }
+ else if (id == IID_ICorDebugProcess2)
+
+ {
+ *pInterface = static_cast<ICorDebugProcess2*>(this);
+ }
+ else if (id == IID_ICorDebugProcess3)
+ {
+ *pInterface = static_cast<ICorDebugProcess3*>(this);
+ }
+ else if (id == IID_ICorDebugProcess4)
+ {
+ *pInterface = static_cast<ICorDebugProcess4*>(this);
+ }
+ else if (id == IID_ICorDebugProcess5)
+ {
+ *pInterface = static_cast<ICorDebugProcess5*>(this);
+ }
+ else if (id == IID_ICorDebugProcess7)
+ {
+ *pInterface = static_cast<ICorDebugProcess7*>(this);
+ }
+ else if (id == IID_ICorDebugProcess8)
+ {
+ *pInterface = static_cast<ICorDebugProcess8*>(this);
+ }
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+ else if (id == IID_ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly)
+ {
+ *pInterface = static_cast<ICorDebugLegacyNetCFHostCallbackInvoker_PrivateWindowsPhoneOnly*>(this);
+ }
+#endif
+ else if (id == IID_IUnknown)
+ {
+ *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugProcess*>(this));
+ }
+
+ else
+ {
+ *pInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+
+
+
+// Public implementation of ICorDebugProcess4::ProcessStateChanged
+HRESULT CordbProcess::ProcessStateChanged(CorDebugStateChange eChange)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this)
+ {
+ switch(eChange)
+ {
+ case PROCESS_RUNNING:
+ FlushProcessRunning();
+ break;
+
+ case FLUSH_ALL:
+ FlushAll();
+ break;
+
+ default:
+ ThrowHR(E_INVALIDARG);
+
+ }
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateHeap(ICorDebugHeapEnum **ppObjects)
+{
+ if (!ppObjects)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ if (m_pDacPrimitives->AreGCStructuresValid())
+ {
+ CordbHeapEnum *pHeapEnum = new CordbHeapEnum(this);
+ GetContinueNeuterList()->Add(this, pHeapEnum);
+ hr = pHeapEnum->QueryInterface(__uuidof(ICorDebugHeapEnum), (void**)ppObjects);
+ }
+ else
+ {
+ hr = CORDBG_E_GC_STRUCTURES_INVALID;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetGCHeapInformation(COR_HEAPINFO *pHeapInfo)
+{
+ if (!pHeapInfo)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ GetDAC()->GetGCHeapInformation(pHeapInfo);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::EnumerateHeapRegions(ICorDebugHeapSegmentEnum **ppRegions)
+{
+ if (!ppRegions)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ DacDbiArrayList<COR_SEGMENT> segments;
+ hr = GetDAC()->GetHeapSegments(&segments);
+
+ if (SUCCEEDED(hr))
+ {
+ if (!segments.IsEmpty())
+ {
+ CordbHeapSegmentEnumerator *segEnum = new CordbHeapSegmentEnumerator(this, &segments[0], (DWORD)segments.Count());
+ GetContinueNeuterList()->Add(this, segEnum);
+ hr = segEnum->QueryInterface(__uuidof(ICorDebugHeapSegmentEnum), (void**)ppRegions);
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetObject(CORDB_ADDRESS addr, ICorDebugObjectValue **pObject)
+{
+ HRESULT hr = S_OK;
+
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ if (!m_pDacPrimitives->IsValidObject(addr))
+ {
+ hr = CORDBG_E_CORRUPT_OBJECT;
+ }
+ else if (pObject == NULL)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+
+ CordbAppDomain *cdbAppDomain = NULL;
+ CordbType *pType = NULL;
+ hr = GetTypeForObject(addr, &pType, &cdbAppDomain);
+
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(pType != NULL);
+ _ASSERTE(cdbAppDomain != NULL);
+
+ DebuggerIPCE_ObjectData objData;
+ m_pDacPrimitives->GetBasicObjectInfo(addr, ELEMENT_TYPE_CLASS, cdbAppDomain->GetADToken(), &objData);
+
+ NewHolder<CordbObjectValue> pNewObjectValue(new CordbObjectValue(cdbAppDomain, pType, TargetBuffer(addr, (ULONG)objData.objSize), &objData));
+ hr = pNewObjectValue->Init();
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pNewObjectValue->QueryInterface(__uuidof(ICorDebugObjectValue), (void**)pObject);
+ if (SUCCEEDED(hr))
+ pNewObjectValue.SuppressRelease();
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateGCReferences(BOOL enumerateWeakReferences, ICorDebugGCReferenceEnum **ppEnum)
+{
+ if (!ppEnum)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ CordbRefEnum *pRefEnum = new CordbRefEnum(this, enumerateWeakReferences);
+ GetContinueNeuterList()->Add(this, pRefEnum);
+ hr = pRefEnum->QueryInterface(IID_ICorDebugGCReferenceEnum, (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+HRESULT CordbProcess::EnumerateHandles(CorGCReferenceType types, ICorDebugGCReferenceEnum **ppEnum)
+{
+ if (!ppEnum)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ CordbRefEnum *pRefEnum = new CordbRefEnum(this, types);
+ GetContinueNeuterList()->Add(this, pRefEnum);
+ hr = pRefEnum->QueryInterface(IID_ICorDebugGCReferenceEnum, (void**)ppEnum);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::EnableNGENPolicy(CorDebugNGENPolicy ePolicy)
+{
+#ifdef FEATURE_CORECLR
+ return E_NOTIMPL;
+#else
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ hr = pDAC->EnableNGENPolicy(ePolicy);
+
+ PUBLIC_API_END(hr);
+ return hr;
+#endif
+}
+
+
+HRESULT CordbProcess::GetTypeID(CORDB_ADDRESS obj, COR_TYPEID *pId)
+{
+ if (pId == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ EX_TRY
+ {
+ hr = GetProcess()->GetDAC()->GetTypeID(obj, pId);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbProcess::GetTypeForTypeID(COR_TYPEID id, ICorDebugType **ppType)
+{
+ if (ppType == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ RSLockHolder stopGoLock(this->GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+
+ EX_TRY
+ {
+ DebuggerIPCE_ExpandedTypeData data;
+ GetDAC()->GetObjectExpandedTypeInfoFromID(AllBoxed, VMPTR_AppDomain::NullPtr(), id, &data);
+
+ CordbType *type = 0;
+ hr = CordbType::TypeDataToType(GetSharedAppDomain(), &data, &type);
+
+ if (SUCCEEDED(hr))
+ hr = type->QueryInterface(IID_ICorDebugType, (void**)ppType);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+COM_METHOD CordbProcess::GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetArrayLayout(id, pLayout);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT *pLayout)
+{
+ if (pLayout == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetTypeLayout(id, pLayout);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::GetTypeFields(COR_TYPEID id, ULONG32 celt, COR_FIELD fields[], ULONG32 *pceltNeeded)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->GetObjectFields(id, celt, fields, pceltNeeded);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::SetWriteableMetadataUpdateMode(WriteableMetadataUpdateMode flags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ if(flags != LegacyCompatPolicy &&
+ flags != AlwaysShowUpdates)
+ {
+ hr = E_INVALIDARG;
+ }
+ else if(m_pShim != NULL)
+ {
+ if(flags != LegacyCompatPolicy)
+ {
+ hr = CORDBG_E_UNSUPPORTED;
+ }
+ }
+
+ if(SUCCEEDED(hr))
+ {
+ m_writableMetadataUpdateMode = flags;
+ }
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+COM_METHOD CordbProcess::EnableExceptionCallbacksOutsideOfMyCode(BOOL enableExceptionsOutsideOfJMC)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+
+ hr = GetProcess()->GetDAC()->SetSendExceptionsOutsideOfJMC(enableExceptionsOutsideOfJMC);
+
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+#ifdef FEATURE_LEGACYNETCF_DBG_HOST_CONTROL
+
+COM_METHOD CordbProcess::InvokePauseCallback()
+{
+ return S_OK;
+}
+
+COM_METHOD CordbProcess::InvokeResumeCallback()
+{
+ return S_OK;
+}
+
+#endif
+
+HRESULT CordbProcess::GetTypeForObject(CORDB_ADDRESS addr, CordbType **ppType, CordbAppDomain **pAppDomain)
+{
+ VMPTR_AppDomain appDomain;
+ VMPTR_Module mod;
+ VMPTR_DomainFile domainFile;
+
+ HRESULT hr = E_FAIL;
+ if (GetDAC()->GetAppDomainForObject(addr, &appDomain, &mod, &domainFile))
+ {
+ CordbAppDomain *cdbAppDomain = appDomain.IsNull() ? GetSharedAppDomain() : LookupOrCreateAppDomain(appDomain);
+
+ _ASSERTE(cdbAppDomain);
+
+ DebuggerIPCE_ExpandedTypeData data;
+ GetDAC()->GetObjectExpandedTypeInfo(AllBoxed, appDomain, addr, &data);
+
+ CordbType *type = 0;
+ hr = CordbType::TypeDataToType(cdbAppDomain, &data, &type);
+
+ if (SUCCEEDED(hr))
+ {
+ *ppType = type;
+ if (pAppDomain)
+ *pAppDomain = cdbAppDomain;
+ }
+ }
+
+ return hr;
+}
+
+
+// ******************************************
+// CordbRefEnum
+// ******************************************
+CordbRefEnum::CordbRefEnum(CordbProcess *proc, BOOL walkWeakRefs)
+ : CordbBase(proc, 0, enumCordbHeap), mRefHandle(0), mEnumStacksFQ(TRUE),
+ mHandleMask((UINT32)(walkWeakRefs ? CorHandleAll : CorHandleStrongOnly))
+{
+}
+
+CordbRefEnum::CordbRefEnum(CordbProcess *proc, CorGCReferenceType types)
+ : CordbBase(proc, 0, enumCordbHeap), mRefHandle(0), mEnumStacksFQ(FALSE),
+ mHandleMask((UINT32)types)
+{
+}
+
+void CordbRefEnum::Neuter()
+{
+ EX_TRY
+ {
+ if (mRefHandle)
+ {
+ GetProcess()->GetDAC()->DeleteRefWalk(mRefHandle);
+ mRefHandle = 0;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Hit an error freeing a ref walk.");
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+
+ CordbBase::Neuter();
+}
+
+HRESULT CordbRefEnum::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ICorDebugGCReferenceEnum)
+ {
+ *ppInterface = static_cast<ICorDebugGCReferenceEnum*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugGCReferenceEnum*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbRefEnum::Skip(ULONG celt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbRefEnum::Reset()
+{
+ PUBLIC_API_ENTRY(this);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ if (mRefHandle)
+ {
+ GetProcess()->GetDAC()->DeleteRefWalk(mRefHandle);
+ mRefHandle = 0;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+HRESULT CordbRefEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbRefEnum::GetCount(ULONG *pcelt)
+{
+ return E_NOTIMPL;
+}
+
+
+//
+
+HRESULT CordbRefEnum::Next(ULONG celt, COR_GC_REFERENCE refs[], ULONG *pceltFetched)
+{
+ if (refs == NULL || pceltFetched == NULL)
+ return E_POINTER;
+
+ CordbProcess *process = GetProcess();
+ HRESULT hr = S_OK;
+
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(process);
+
+ RSLockHolder procLockHolder(process->GetProcessLock());
+
+ EX_TRY
+ {
+ if (!mRefHandle)
+ hr = process->GetDAC()->CreateRefWalk(&mRefHandle, mEnumStacksFQ, mEnumStacksFQ, mHandleMask);
+
+ if (SUCCEEDED(hr))
+ {
+ DacGcReference dacRefs[32];
+ ULONG toFetch = _countof(dacRefs);
+ ULONG total = 0;
+
+ for (ULONG c = 0; SUCCEEDED(hr) && c < (celt/_countof(dacRefs) + 1); ++c)
+ {
+ // Fetch 32 references at a time, the last time, only fetch the remainder (that is, if
+ // the user didn't fetch a multiple of 32).
+ if (c == celt/_countof(dacRefs))
+ toFetch = celt % _countof(dacRefs);
+
+ ULONG fetched = 0;
+ hr = process->GetDAC()->WalkRefs(mRefHandle, toFetch, dacRefs, &fetched);
+
+ if (SUCCEEDED(hr))
+ {
+ for (ULONG i = 0; i < fetched; ++i)
+ {
+ CordbAppDomain *pDomain = process->LookupOrCreateAppDomain(dacRefs[i].vmDomain);
+
+ ICorDebugAppDomain *pAppDomain;
+ ICorDebugValue *pOutObject = NULL;
+ if (dacRefs[i].pObject & 1)
+ {
+ dacRefs[i].pObject &= ~1;
+ ICorDebugObjectValue *pObjValue = NULL;
+
+ hr = process->GetObject(dacRefs[i].pObject, &pObjValue);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pObjValue->QueryInterface(IID_ICorDebugValue, (void**)&pOutObject);
+ pObjValue->Release();
+ }
+ }
+ else
+ {
+ ICorDebugReferenceValue *tmpValue = NULL;
+ IfFailThrow(CordbReferenceValue::BuildFromGCHandle(pDomain,
+ dacRefs[i].objHnd,
+ &tmpValue));
+
+ if (SUCCEEDED(hr))
+ {
+ hr = tmpValue->QueryInterface(IID_ICorDebugValue, (void**)&pOutObject);
+ tmpValue->Release();
+ }
+ }
+
+ if (SUCCEEDED(hr) && pDomain)
+ {
+ hr = pDomain->QueryInterface(IID_ICorDebugAppDomain, (void**)&pAppDomain);
+ }
+
+ if (FAILED(hr))
+ break;
+
+ refs[total].Domain = pAppDomain;
+ refs[total].Location = pOutObject;
+ refs[total].Type = (CorGCReferenceType)dacRefs[i].dwType;
+ refs[total].ExtraData = dacRefs[i].i64ExtraData;
+
+ total++;
+ }
+ }
+ }
+
+ *pceltFetched = total;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+// ******************************************
+// CordbHeapEnum
+// ******************************************
+CordbHeapEnum::CordbHeapEnum(CordbProcess *proc)
+ : CordbBase(proc, 0, enumCordbHeap), mHeapHandle(0)
+{
+}
+
+HRESULT CordbHeapEnum::QueryInterface(REFIID riid, void **ppInterface)
+{
+ if (ppInterface == NULL)
+ return E_INVALIDARG;
+
+ if (riid == IID_ICorDebugHeapEnum)
+ {
+ *ppInterface = static_cast<ICorDebugHeapEnum*>(this);
+ }
+ else if (riid == IID_IUnknown)
+ {
+ *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugHeapEnum*>(this));
+ }
+ else
+ {
+ *ppInterface = NULL;
+ return E_NOINTERFACE;
+ }
+
+ ExternalAddRef();
+ return S_OK;
+}
+
+HRESULT CordbHeapEnum::Skip(ULONG celt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::Reset()
+{
+ Clear();
+ return S_OK;
+}
+
+void CordbHeapEnum::Clear()
+{
+ EX_TRY
+ {
+ if (mHeapHandle)
+ {
+ GetProcess()->GetDAC()->DeleteHeapWalk(mHeapHandle);
+ mHeapHandle = 0;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Hit an error freeing the heap walk.");
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+HRESULT CordbHeapEnum::Clone(ICorDebugEnum **ppEnum)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::GetCount(ULONG *pcelt)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbHeapEnum::Next(ULONG celt, COR_HEAPOBJECT objects[], ULONG *pceltFetched)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_ENTRY(this);
+ RSLockHolder stopGoLock(this->GetProcess()->GetStopGoLock());
+ RSLockHolder procLock(this->GetProcess()->GetProcessLock());
+ ULONG fetched = 0;
+
+ EX_TRY
+ {
+ if (mHeapHandle == 0)
+ {
+ hr = GetProcess()->GetDAC()->CreateHeapWalk(&mHeapHandle);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = GetProcess()->GetDAC()->WalkHeap(mHeapHandle, celt, objects, &fetched);
+ _ASSERTE(fetched <= celt);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Return S_FALSE if we've reached the end of the enum.
+ if (fetched < celt)
+ hr = S_FALSE;
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // Set the fetched parameter to reflect the number of elements (if any)
+ // that were successfully saved to "objects"
+ if (pceltFetched)
+ *pceltFetched = fetched;
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Flush state for when the process starts running.
+//
+// Notes:
+// Helper for code:CordbProcess::ProcessStateChanged.
+// Since ICD Arrowhead does not own the eventing pipeline, it needs the debugger to
+// notifying it of when the process is running again. This is like the counterpart
+// to code:CordbProcess::Filter
+void CordbProcess::FlushProcessRunning()
+{
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ // Update the continue counter.
+ m_continueCounter++;
+
+ // Safely dispose anything that should be neutered on continue.
+ MarkAllThreadsDirty();
+ ForceDacFlush();
+}
+
+//---------------------------------------------------------------------------------------
+// Flush all cached state and bring us back to "cold startup"
+//
+// Notes:
+// Helper for code:CordbProcess::ProcessStateChanged.
+// This is used if the data-target changes underneath us in a way that is
+// not consistent with the process running forward. For example, if for
+// a time-travel debugger, the data-target may flow "backwards" in time.
+//
+void CordbProcess::FlushAll()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr;
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ //
+ // First, determine if it's safe to Flush
+ //
+
+ hr = IsReadyForDetach();
+ IfFailThrow(hr);
+
+ // Check for outstanding CordbHandle values.
+ if (OutstandingHandles())
+ {
+ ThrowHR(CORDBG_E_DETACH_FAILED_OUTSTANDING_TARGET_RESOURCES);
+ }
+
+ // FlushAll is a superset of FlushProcessRunning.
+ // This will also ensure we clear the DAC cache.
+ FlushProcessRunning();
+
+ // If we detach before the CLR is loaded into the debuggee, then we can no-op a lot of work.
+ // We sure can't be sending IPC events to the LS before it exists.
+ NeuterChildren();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Detach the Debugger from the LS process.
+//
+//
+// Return Value:
+// S_OK on successful detach. Else errror.
+//
+// Assumptions:
+// Target is stopped.
+//
+// Notes:
+// Once we're detached, the LS can resume running and exit.
+// So it's possible to get an ExitProcess callback in the middle of the Detach phase. If that happens,
+// we must return CORDBG_E_PROCESS_TERMINATED and pretend that the exit happened before we tried to detach.
+// Else if we detach successfully, return S_OK.
+//
+// @dbgtodo attach-bit: need to figure out semantics of Detach
+// in V3, especially w.r.t to an attach bit.
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::Detach()
+{
+ PUBLIC_API_ENTRY(this);
+
+ FAIL_IF_NEUTERED(this);
+
+ if (IsInteropDebugging())
+ {
+ return CORDBG_E_INTEROP_NOT_SUPPORTED;
+ }
+
+
+ HRESULT hr = S_OK;
+ // A very important note: we require that the process is synchronized before doing a detach. This ensures
+ // that no events are on their way from the Left Side. We also require that the user has drained the
+ // managed event queue, but there is currently no way to really enforce that here.
+ // @todo- why can't we enforce that the managed event Q is drained?
+ ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(this);
+
+
+ hr = IsReadyForDetach();
+ if (FAILED(hr))
+ {
+ // Avoid neutering. Gives client a chance to fix detach issue and retry.
+ return hr;
+ }
+
+ // Since the detach may resume the LS and allow it to exit, which may invoke the EP callback
+ // which may destroy this process object, be sure to protect us w/ an extra AddRef/Release
+ RSSmartPtr<CordbProcess> pRef(this);
+
+
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::Detach - beginning\n"));
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+
+ // This is still invasive.
+ // Ignore failures. This will fail for a non-invasive target.
+ if (IsDacInitialized())
+ {
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ GetDAC()->MarkDebuggerAttached(FALSE);
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ }
+ }
+ else
+ {
+ EX_TRY
+ {
+ DetachShim();
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ // Either way, neuter everything.
+ this->Neuter();
+
+ // Implicit release on pRef
+ LOG((LF_CORDB, LL_INFO1000, "CP::Detach - returning w/ hr=0x%x\n", hr));
+ return hr;
+}
+
+// Free up key left-side resources
+//
+// Called on detach
+// This does key neutering of objects that hold left-side resources and require
+// preemptively freeing the resources.
+// After this, code:CordbProcess::Neuter should only affect right-side state.
+void CordbProcess::NeuterChildrenLeftSideResources()
+{
+ _ASSERTE(GetStopGoLock()->HasLock());
+
+ _ASSERTE(!GetProcessLock()->HasLock());
+ RSLockHolder lockHolder(GetProcessLock());
+
+
+ // Need process-lock to operate on hashtable, but can't yet Neuter under process-lock,
+ // so we have to copy the contents to an auxilary list which we can then traverse outside the lock.
+ RSPtrArray<CordbAppDomain> listAppDomains;
+ m_appDomains.CopyToArray(&listAppDomains);
+
+
+
+ // Must not hold process lock so that we can be safe to send IPC events
+ // to cleanup left-side resources.
+ lockHolder.Release();
+ _ASSERTE(!GetProcessLock()->HasLock());
+
+ // Frees left-side resources. This may send IPC events.
+ // This will make normal neutering a nop.
+ m_LeftSideResourceCleanupList.NeuterLeftSideResourcesAndClear(this);
+
+ for(unsigned int idx = 0; idx < listAppDomains.Length(); idx++)
+ {
+ CordbAppDomain * pAppDomain = listAppDomains[idx];
+
+ // CordbHandleValue is in the appdomain exit list, and that needs
+ // to send an IPC event to cleanup and release the handle from
+ // the GCs handle table.
+ pAppDomain->GetSweepableExitNeuterList()->NeuterLeftSideResourcesAndClear(this);
+ }
+ listAppDomains.Clear();
+
+}
+
+//---------------------------------------------------------------------------------------
+// Detach the Debugger from the LS process for the V2 case
+//
+// Assumptions:
+// This will NeuterChildren(), caller will do the real Neuter()
+// Caller has already ensured that detach is safe.
+//
+// @dbgtodo attach-bit: this should be moved into the shim; need
+// to figure out semantics for freeing left-side resources (especially GC
+// handles) on detach.
+void CordbProcess::DetachShim()
+{
+
+ HASHFIND hashFind;
+ HRESULT hr = S_OK;
+
+ // If we detach before the CLR is loaded into the debuggee, then we can no-op a lot of work.
+ // We sure can't be sending IPC events to the LS before it exists.
+ if (m_initialized)
+ {
+ // The managed event queue is not necessarily drained. Cordbg could call detach between any callback.
+ // While the process is still stopped, neuter all of our children.
+ // This will make our Neuter() a nop and saves the W32ET from having to do dangerous work.
+ this->NeuterChildrenLeftSideResources();
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ this->NeuterChildren();
+ }
+
+ // Go ahead and detach from the entire process now. This is like sending a "Continue".
+ DebuggerIPCEvent * pIPCEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(pIPCEvent, DB_IPCE_DETACH_FROM_PROCESS, true, VMPTR_AppDomain::NullPtr());
+
+ hr = m_cordb->SendIPCEvent(this, pIPCEvent, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, pIPCEvent->hr);
+ IfFailThrow(hr);
+ }
+ else
+ {
+ // @dbgtodo attach-bit: push this up, once detach IPC event is hoisted.
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // Shouldn't have any appdomains.
+ (void)hashFind; //prevent "unused variable" error from GCC
+ _ASSERTE(m_appDomains.FindFirst(&hashFind) == NULL);
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::Detach - got reply from LS\n"));
+
+ // It's possible that the LS may exit after they reply to our detach_from_process, but
+ // before we update our internal state that they're detached. So still have to check
+ // failure codes here.
+ hr = this->m_pShim->GetWin32EventThread()->SendDetachProcessEvent(this);
+
+
+ // Since we're auto-continuing when we detach, we should set the stop count back to zero.
+ // This (along w/ m_detached) prevents anyone from calling Continue on this process
+ // after this call returns.
+ m_stopCount = 0;
+
+ if (hr != CORDBG_E_PROCESS_TERMINATED)
+ {
+ // Remember that we've detached from this process object. This will prevent any further operations on
+ // this process, just in case... :)
+ // If LS exited, then don't set this flag because it overrides m_terminated when reporting errors;
+ // and we want to provide a consistent story about whether we detached or whether the LS exited.
+ m_detached = true;
+ }
+ IfFailThrow(hr);
+
+
+ // Now that all complicated cleanup is done, caller can do a final neuter.
+ // This will implicitly stop our Win32 event thread as well.
+}
+
+// Delete all events from the queue without dispatching. This is useful in shutdown.
+// An event that is currently dispatching is not on the queue.
+void CordbProcess::DeleteQueuedEvents()
+{
+ INTERNAL_API_ENTRY(this);
+ // We must have the process lock to ensure that no one is trying to add an event
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ if (m_pShim != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+
+ // DeleteAll() is part of the shim, and it will change external ref counts, so must really
+ // be marked as outside the RS.
+ m_pShim->GetManagedEventQueue()->DeleteAll();
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Track that we're about to dispatch a managed event.
+//
+// Arguments:
+// event - event being dispatched
+//
+// Assumptions:
+// This is used to support code:CordbProcess::AreDispatchingEvent
+// This is always called on the same thread as code:CordbProcess::FinishEventDispatch
+void CordbProcess::StartEventDispatch(DebuggerIPCEventType event)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(m_dispatchedEvent == DB_IPCE_DEBUGGER_INVALID);
+ _ASSERTE(event != DB_IPCE_DEBUGGER_INVALID);
+ m_dispatchedEvent = event;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Track that we're done dispatching a managed event.
+//
+//
+// Assumptions:
+// This is always called on the same thread as code:CordbProcess::StartEventDispatch
+//
+// Notes:
+// @dbgtodo shim: eventually this goes into the shim when we hoist Continue
+void CordbProcess::FinishEventDispatch()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(m_dispatchedEvent != DB_IPCE_DEBUGGER_INVALID);
+ m_dispatchedEvent = DB_IPCE_DEBUGGER_INVALID;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Are we in the middle of dispatching an event?
+//
+// Notes:
+// This is used by code::CordbProcess::ContinueInternal. Continue logic takes
+// a shortcut if the continue is called on the dispatch thread.
+// It doesn't matter which event is being dispatch; only that we're on the dispatch thread.
+// @dbgtodo shim: eventually this goes into the shim when we hoist Continue
+bool CordbProcess::AreDispatchingEvent()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_dispatchedEvent != DB_IPCE_DEBUGGER_INVALID;
+}
+
+
+
+
+
+// Terminate the app. We'll still dispatch an ExitProcess callback, so the app
+// must wait for that before calling Cordb::Terminate.
+// If this fails, the client can always call the OS's TerminateProcess command
+// to rudely kill the debuggee.
+HRESULT CordbProcess::Terminate(unsigned int exitCode)
+{
+ PUBLIC_API_ENTRY(this);
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::Terminate: with exitcode %u\n", exitCode));
+ FAIL_IF_NEUTERED(this);
+
+
+ // @dbgtodo shutdown: eventually, all of Terminate() will be in the Shim.
+ // Free all the remaining events. Since this will call into the shim, do this outside of any locks.
+ // (ATT_ takes locks).
+ DeleteQueuedEvents();
+
+ ATT_REQUIRE_SYNCED_OR_NONINIT_MAY_FAIL(this);
+
+ // When we terminate the process, it's handle will become signaled and
+ // Win32 Event Thread will leap into action and call CordbWin32EventThread::ExitProcess
+ // Unfortunately, that may destroy this object if the ExitProcess callback
+ // decides to call Release() on the process.
+
+
+ // Indicate that the process is exiting so that (among other things) we don't try and
+ // send messages to the left side while it's being deleted.
+ Lock();
+
+ // In case we're continuing from the loader bp, we don't want to try and kick off an attach. :)
+ m_fDoDelayedManagedAttached = false;
+ m_exiting = true;
+
+
+
+ // We'd like to just take a lock around everything here, but that may deadlock us
+ // since W32ET will wait on the lock, and Continue may wait on W32ET.
+ // So we just do an extra AddRef/Release to make sure we're still around.
+ // @todo - could we move this smartptr up so that it's well-nested w/ the lock?
+ RSSmartPtr<CordbProcess> pRef(this);
+
+ Unlock();
+
+
+ // At any point after this call, the w32 ET may run the ExitProcess code which will race w/ the continue call.
+ // This call only posts a request that the process terminate and does not guarantee the process actually
+ // terminates. In particular, the process can not exit until any outstanding IO requests are done (on cancelled).
+ // It also can not exit if we have an outstanding not-continued native-debug event.
+ // Fortunately, the interesting work in terminate is done in ExitProcessWorkItem::Do, which can take the Stop-Go lock.
+ // Since we're currently holding the stop-go lock, that means we at least get some serialization.
+ //
+ // Note that on Windows, the process isn't really terminated until we receive the EXIT_PROCESS_DEBUG_EVENT.
+ // Before then, we can still still access the debuggee's address space. On the other, for Mac debugging,
+ // the process can die any time after this call, and so we can no longer call into the DAC.
+ GetShim()->GetNativePipeline()->TerminateProcess(exitCode);
+
+ // We just call Continue() so that the debugger doesn't have to. (It's arguably odd
+ // to call Continue() after Terminate).
+ // We're stopped & Synced.
+ // For interop-debugging this is very important because the Terminate may not really kill the process
+ // until after we continue from the current native debug event.
+ ContinueInternal(FALSE);
+
+ // Implicit release on pRef here (since it's going out of scope)...
+ // After this release, this object may be destroyed. So don't use any member functions
+ // (including Locks) after here.
+
+
+ return S_OK;
+}
+
+// This can be called at any time, even if we're in an unrecoverable error state.
+HRESULT CordbProcess::GetID(DWORD *pdwProcessId)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ OK_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwProcessId, DWORD *);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // This shouldn't be used in V3 paths. Normally, we can enforce that by checking against
+ // m_pShim. However, this API can be called after being neutered, in which case m_pShim is cleared.
+ // So check against 0 instead.
+ if (m_id == 0)
+ {
+ *pdwProcessId = 0;
+ ThrowHR(E_NOTIMPL);
+ }
+ *pdwProcessId = GetPid();
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+// Helper to get PID internally. We know we'll always succeed.
+// This is more convient for internal callers since they can just use it as an expression
+// without having to check HRESULTS.
+DWORD CordbProcess::GetPid()
+{
+ // This shouldn't be used in V3 paths, in which case it's set to 0. Only the shim should be
+ // calling this. Assert to catch anybody else.
+ _ASSERTE(m_id != 0);
+
+ return (DWORD) m_id;
+}
+
+
+HRESULT CordbProcess::GetHandle(HANDLE *phProcessHandle)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this); // Once we neuter the process, we close our OS handle to it.
+ VALIDATE_POINTER_TO_OBJECT(phProcessHandle, HANDLE *);
+
+ if (m_pShim == NULL)
+ {
+ _ASSERTE(!"CordbProcess::GetHandle() should be not be called on the new architecture");
+ *phProcessHandle = NULL;
+ return E_NOTIMPL;
+ }
+ else
+ {
+ *phProcessHandle = m_handle;
+ return S_OK;
+ }
+}
+
+HRESULT CordbProcess::IsRunning(BOOL *pbRunning)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pbRunning, BOOL*);
+
+ *pbRunning = !GetSynchronized();
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::EnableSynchronization(BOOL bEnableSynchronization)
+{
+ /* !!! */
+ PUBLIC_API_ENTRY(this);
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::Stop(DWORD dwTimeout)
+{
+ PUBLIC_API_ENTRY(this);
+ CORDBRequireProcessStateOK(this);
+
+ HRESULT hr = StopInternal(dwTimeout, VMPTR_AppDomain::NullPtr());
+
+ return ErrWrapper(hr);
+}
+
+HRESULT CordbProcess::StopInternal(DWORD dwTimeout, VMPTR_AppDomain pAppDomainToken)
+{
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: stopping process 0x%x(%d) with timeout %d\n", m_id, m_id, dwTimeout));
+
+ INTERNAL_API_ENTRY(this);
+
+ // Stop + Continue are executed under the Stop-Go lock. This makes them atomic.
+ // We'll toggle the process-lock (b/c we communicate w/ the W32et, so just the process-lock is
+ // not sufficient to make this atomic).
+ // It's ok to take this lock before checking if the CordbProcess has been neutered because
+ // the lock is destroyed in the dtor after neutering.
+ RSLockHolder ch(&m_StopGoLock);
+
+ // Check if this CordbProcess has been neutered under the SG lock.
+ // Otherwise it's possible to race with Detach() and Terminate().
+ FAIL_IF_NEUTERED(this);
+ CORDBFailIfOnWin32EventThread(this);
+
+ if (m_pShim == NULL) // Stop/Go is moved off to the shim
+ {
+ return E_NOTIMPL;
+ }
+
+
+ DebuggerIPCEvent* event;
+ HRESULT hr = S_OK;
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::SI, timeout=%d, this=%p\n", dwTimeout, this);
+
+ // Stop() is a syncronous (blocking) operation. Furthermore, we have no way to cancel the async-break request.
+ // Thus if we returned early on a timeout, then we'll be in a random state b/c the LS may get stopped at any
+ // later spot.
+ // One solution just require the param is INFINITE until we fix this and E_INVALIDARG if it's not.
+ // But that could be a breaking change (what if a debugger passes in a really large value that's effectively
+ // INFINITE).
+ // So we'll just ignore it and always treat it as infinite.
+ dwTimeout = INFINITE;
+
+ // Do the checks on the process state under the SG lock. This ensures that another thread cannot come in
+ // after we do the checks and take the lock before we do. For example, Detach() can race with Stop() such
+ // that:
+ // 1. Thread A calls CordbProcess::Detach() and takes the stop-go lock
+ // 2. Thread B calls CordbProcess::Stop(), passes all the checks, and then blocks on the stop-go lock
+ // 3. Thread A finishes the detach, invalides the process state, cleans all the resources, and then
+ // releases the stop-go lock
+ // 4. Thread B gets the lock, but everything has changed
+ CORDBRequireProcessStateOK(this);
+
+ Lock();
+
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+
+ // Don't need to stop if the process hasn't even executed any managed code yet.
+ if (!m_initialized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process isn't initialized yet.\n"));
+
+ // Mark the process as synchronized so no events will be dispatched until the thing is continued.
+ SetSynchronized(true);
+
+ // Remember uninitialized stop...
+ m_uninitializedStop = true;
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // If we're Win32 attached, then suspend all the unmanaged threads in the process.
+ // We may or may not be stopped at a native debug event.
+ if (IsInteropDebugging())
+ {
+ SuspendUnmanagedThreads();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Get the RC Event Thread to stop listening to the process.
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ goto Exit;
+ }
+
+ // Don't need to stop if the process is already synchronized.
+ // @todo - Issue 129917. It's possible that we'll get a call to Stop when the LS is already stopped.
+ // Sending an AsyncBreak would deadlock here (b/c the LS will ignore the frivilous request,
+ // and thus never send a SyncComplete, and thus our Waiting on the SyncComplete will deadlock).
+ // We avoid this case by checking m_syncCompleteReceived (which should roughly correspond to
+ // the LS's m_stopped variable).
+ // One window this can happen is after a Continue() pings the RCET but before the RCET actually sweeps + flushes.
+
+ if (GetSynchronized() || GetSyncCompleteRecv())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process was already synchronized. m_syncCompleteReceived=%d\n", GetSyncCompleteRecv()));
+
+ if (GetSyncCompleteRecv())
+ {
+ // We must be in that window alluded to above (while the RCET is sweeping). Re-ping the RCET.
+ SetSynchronized(true);
+ m_cordb->ProcessStateChanged();
+ }
+ hr = S_OK;
+ goto Exit;
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::S: process not sync'd, requesting stop.\n");
+
+ m_stopRequested = true;
+
+ // We don't want to dispatch any Win32 debug events while we're trying to stop.
+ // Setting m_specialDeferment=true means that any debug event we get will be queued and not dispatched.
+ // We do this to avoid a nested call to Continue.
+ // These defered events will get dispatched when somebody calls continue (and since they're calling
+ // stop now, they must call continue eventually).
+ // Note that if we got a Win32 debug event between when we took the Stop-Go lock above and now,
+ // that even may have been dispatched. We're ok because SSFW32Stop will hijack that event and continue it,
+ // and then all future events will be queued.
+ m_specialDeferment = true;
+ Unlock();
+
+ BOOL asyncBreakSent;
+
+ // We need to ensure that the helper thread is alive.
+ hr = this->StartSyncFromWin32Stop(&asyncBreakSent);
+ if (FAILED(hr))
+ {
+ return hr;
+ }
+
+
+ if (asyncBreakSent)
+ {
+ hr = S_OK;
+ Lock();
+
+ m_stopRequested = false;
+
+ goto Exit;
+ }
+
+ // Send the async break event to the RC.
+ event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_ASYNC_BREAK, false, pAppDomainToken);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::S: sending async stop to appd 0x%x.\n", VmPtrToCookie(pAppDomainToken));
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+ if (FAILED(hr))
+ {
+ // We don't hold the lock so just return immediately. Don't adjust stop-count.
+ _ASSERTE(!ThreadHoldsProcessLock());
+ return hr;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: sent async stop to appd 0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+
+ // Wait for the sync complete message to come in. Note: when the sync complete message arrives to the RCEventThread,
+ // it will mark the process as synchronized and _not_ dispatch any events. Instead, it will set m_stopWaitEvent
+ // which will let this function return. If the user wants to process any queued events, they will need to call
+ // Continue.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::S: waiting for event.\n");
+
+ DWORD ret;
+ ret = SafeWaitForSingleObject(this, m_stopWaitEvent, dwTimeout);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::S: got event, %d.\n", ret);
+
+ if (m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ if (ret == WAIT_OBJECT_0)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process stopped.\n"));
+
+ m_stopRequested = false;
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ Lock();
+ goto Exit;
+ }
+ else if (ret == WAIT_TIMEOUT)
+ {
+ hr = ErrWrapper(CORDBG_E_TIMEOUT);
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ // We came out of the wait, but we weren't signaled because a sync complete event came in. Re-check the process and
+ // remove the stop requested flag.
+ Lock();
+ m_stopRequested = false;
+
+ if (GetSynchronized())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::S: process stopped.\n"));
+
+ m_cordb->ProcessStateChanged();
+
+ hr = S_OK;
+ }
+
+Exit:
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Stop queuing any Win32 Debug events. We should be synchronized now.
+ m_specialDeferment = false;
+
+ if (SUCCEEDED(hr))
+ {
+ IncStopCount();
+ }
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::S: returning from Stop, hr=0x%08x, m_stopCount=%d.\n", hr, GetStopCount());
+
+ Unlock();
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Clear all RS state on all CordbThread objects.
+//
+// Notes:
+// This clears all the thread-related state that the RS may have cached,
+// such as locals, frames, etc.
+// This would be called if the debugger is resuming execution.
+void CordbProcess::MarkAllThreadsDirty()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ CordbThread * pThread;
+ HASHFIND find;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (pThread = m_userThreads.FindFirst(&find);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&find))
+ {
+ _ASSERTE(pThread != NULL);
+ pThread->MarkStackFramesDirty();
+ }
+
+ ClearPatchTable();
+}
+
+HRESULT CordbProcess::Continue(BOOL fIsOutOfBand)
+{
+ PUBLIC_API_ENTRY(this);
+
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+ // bias towards failing with CORDBG_E_NUETERED.
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr;
+
+ if (fIsOutOfBand)
+ {
+#ifdef FEATURE_INTEROP_DEBUGGING
+ hr = ContinueOOB();
+#else
+ hr = E_INVALIDARG;
+#endif // FEATURE_INTEROP_DEBUGGING
+ }
+ else
+ {
+ hr = ContinueInternal(fIsOutOfBand);
+ }
+
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//---------------------------------------------------------------------------------------
+//
+// ContinueOOB
+//
+// Continue the Win32 event as an out-of-band event.
+//
+// Return Value:
+// S_OK on successful continue. Else error.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::ContinueOOB()
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+
+ // If we're continuing from an out-of-band unmanaged event, then just go
+ // ahead and get the Win32 event thread to continue the process. No other
+ // work needs to be done (i.e., don't need to send a managed continue message
+ // or dispatch any events) because any processing done due to the out-of-band
+ // message can't alter the synchronized state of the process.
+
+ Lock();
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+
+ // Are we calling this from the unmanaged callback?
+ if (m_dispatchingOOBEvent)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue while dispatching unmanaged out-of-band event.\n");
+ // We don't know what thread we're on here.
+
+ // Tell the Win32 event thread to continue when it returns from handling its unmanaged callback.
+ m_dispatchingOOBEvent = false;
+
+ Unlock();
+ }
+ else
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue outside of dispatching.\n");
+
+ // If we're not dispatching this, then they shouldn't be on the win32 event thread.
+ _ASSERTE(!this->IsWin32EventThread());
+
+ Unlock();
+
+ // Send an event to the Win32 event thread to do the continue. This is an out-of-band continue.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cOobUMContinue);
+ }
+
+ return hr;
+
+
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//---------------------------------------------------------------------------------------
+//
+// ContinueInternal
+//
+// Continue the Win32 event.
+//
+// Return Value:
+// S_OK on success. Else error.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::ContinueInternal(BOOL fIsOutOfBand)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // Continue has an ATT similar to ATT_REQUIRE_STOPPED_MAY_FAIL, but w/ some subtle differences.
+ // - if we're stopped at a native DE, but not synchronized, we don't want to sync.
+ // - We may get Debug events (especially native ones) at weird times, and thus we have to continue
+ // at weird times.
+
+ // External APIs should not have the process lock.
+ _ASSERTE(!ThreadHoldsProcessLock());
+ _ASSERTE(m_pShim != NULL);
+
+ // OutOfBand should use ContinueOOB
+ _ASSERTE(!fIsOutOfBand);
+
+ // Since Continue is process-wide, just use a null appdomain pointer.
+ VMPTR_AppDomain pAppDomainToken = VMPTR_AppDomain::NullPtr();
+
+ HRESULT hr = S_OK;
+
+ if (m_unrecoverableError)
+ {
+ return CORDBHRFromProcessState(this, NULL);
+ }
+
+
+ // We can't call ContinueInternal for an inband event on the win32 event thread.
+ // This is an issue in the CLR (or an API design decision, depending on your perspective).
+ // Continue() may send an IPC event and we can't do that on the win32 event thread.
+
+ CORDBFailIfOnWin32EventThread(this);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: continuing IB, this=0x%X\n", this);
+
+ // Stop + Continue are executed under the Stop-Go lock. This makes them atomic.
+ // We'll toggle the process-lock (b/c we communicate w/ the W32et, so that's not sufficient).
+ RSLockHolder rsLockHolder(&m_StopGoLock);
+
+ // Check for other failures (do these after we have the SG lock).
+ if (m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ if (m_detached)
+ {
+ return CORDBG_E_PROCESS_DETACHED;
+ }
+
+ Lock();
+
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+ _ASSERTE(fIsOutOfBand == FALSE);
+
+ // If we've got multiple Stop calls, we need a Continue for each one. So, if the stop count > 1, just go ahead and
+ // return without doing anything. Note: this is only for in-band or managed events. OOB events are still handled as
+ // normal above.
+ _ASSERTE(GetStopCount() > 0);
+
+ if (GetStopCount() == 0)
+ {
+ Unlock();
+ _ASSERTE(!"Superflous Continue. ICorDebugProcess.Continue() called too many times");
+ return CORDBG_E_SUPERFLOUS_CONTINUE;
+ }
+
+ DecStopCount();
+
+ // We give managed events priority over unmanaged events. That way, the entire queued managed state can drain before
+ // we let any other unmanaged events through.
+
+ // Every stop or event must be matched by a corresponding Continue. m_stopCount counts outstanding stopping events
+ // along with calls to Stop. If the count is high at this point, we simply return. This ensures that even if someone
+ // calls Stop just as they're receiving an event that they can call Continue for that Stop and for that event
+ // without problems.
+ if (GetStopCount() > 0)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: m_stopCount=%d, Continue just returning S_OK...\n", GetStopCount());
+
+ Unlock();
+ return S_OK;
+ }
+
+ // We're no longer stopped, so reset the m_stopWaitEvent.
+ ResetEvent(m_stopWaitEvent);
+
+ // If we're continuing from an uninitialized stop, then we don't need to do much at all. No event need be sent to
+ // the Left Side (duh, it isn't even there yet.) We just need to get the RC Event Thread to start listening to the
+ // process again, and resume any unmanaged threads if necessary.
+ if (m_uninitializedStop)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continuing from uninitialized stop.\n");
+
+ // No longer synchronized (it was a partial sync in the first place.)
+ SetSynchronized(false);
+ MarkAllThreadsDirty();
+
+ // No longer in an uninitialized stop.
+ m_uninitializedStop = false;
+
+ // Notify the RC Event Thread.
+ m_cordb->ProcessStateChanged();
+
+ Unlock();
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // We may or may not have a native debug event queued here.
+ // If Cordbg called Stop() from a native debug event (to get the process Synchronized), then
+ // we'll have a native debug event, and we need to continue it.
+ // If Cordbg called Stop() to do an AsyncBreak, then there's no native-debug event.
+
+ // If we're Win32 attached, resume all the unmanaged threads.
+ if (IsInteropDebugging())
+ {
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->SetState(CUES_UserContinued);
+ }
+
+ // Send to the Win32 event thread to do the unmanaged continue for us.
+ // If we're at a debug event, this will continue it.
+ // Else it will degenerate into ResumeUnmanagedThreads();
+ this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ return S_OK;
+ }
+
+ // If there are more managed events, get them dispatched now.
+ if (!m_pShim->GetManagedEventQueue()->IsEmpty() && GetSynchronized())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: managed event queued.\n");
+
+ // Mark that we're not synchronized anymore.
+ SetSynchronized(false);
+
+ // If the callback queue is not empty, then the LS is not actually continuing, and so our cached
+ // state is still valid.
+
+ // If we're in the middle of dispatching a managed event, then simply return. This indicates to HandleRCEvent
+ // that the user called Continue and HandleRCEvent will dispatch the next queued event. But if Continue was
+ // called outside the managed callback, all we have to do is tell the RC event thread that something about the
+ // process has changed and it will dispatch the next managed event.
+ if (!AreDispatchingEvent())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continuing while not dispatching managed event.\n");
+
+ m_cordb->ProcessStateChanged();
+ }
+
+ Unlock();
+ return S_OK;
+ }
+
+ // Neuter if we have an outstanding object.
+ // Only do this if we're really continuining the debuggee. So don't do this if our stop-count is high b/c we
+ // shouldn't neuter until we're done w/ the current event. And don't do this until we drain the current callback queue.
+ // Note that we can't hold the process lock while we do this b/c Neutering may send IPC events.
+ // However, we're still under the StopGo lock b/c that may help us serialize things.
+
+ // Sweep neuter list. This will catch anything that's marked as 'safe to neuter'. This includes
+ // all objects added to the 'neuter-on-Continue'.
+ // Only do this if we're synced- we don't want to do this if we're continuing from a Native Debug event.
+ if (GetSynchronized())
+ {
+ // Need process-lock to operate on hashtable, but can't yet Neuter under process-lock,
+ // so we have to copy the contents to an auxilary list which we can then traverse outside the lock.
+ RSPtrArray<CordbAppDomain> listAppDomains;
+ HRESULT hrCopy = S_OK;
+ EX_TRY // @dbgtodo cleanup: push this up
+ {
+ m_appDomains.CopyToArray(&listAppDomains);
+ }
+ EX_CATCH_HRESULT(hrCopy);
+ SetUnrecoverableIfFailed(GetProcess(), hrCopy);
+
+ m_ContinueNeuterList.NeuterAndClear(this);
+
+ // @dbgtodo left-side resources: eventually (once
+ // NeuterLeftSideResources is process-lock safe), do this all under the
+ // lock. Can't hold process lock b/c neutering left-side resources
+ // may send events.
+ Unlock();
+
+ // This may send IPC events.
+ // This will make normal neutering a nop.
+ // This will toggle the process lock.
+ m_LeftSideResourceCleanupList.SweepNeuterLeftSideResources(this);
+
+
+ // Many objects (especially CordbValue, FuncEval) don't have clear lifetime semantics and
+ // so they must be put into an exit-neuter list (Process/AppDomain) for worst-case scenarios.
+ // These objects are likely released early, and so we sweep them aggressively on each Continue (kind of like a mini-GC).
+ //
+ // One drawback is that there may be a lot of useless sweeping if the debugger creates a lot of
+ // objects that it holds onto. Consider instead of sweeping, have the object explicitly post itself
+ // to a list that's guaranteed to be cleared. This would let us avoid sweeping not-yet-ready objects.
+ // This will toggle the process lock
+ m_ExitNeuterList.SweepAllNeuterAtWillObjects(this);
+
+
+ for(unsigned int idx = 0; idx < listAppDomains.Length(); idx++)
+ {
+ CordbAppDomain * pAppDomain = listAppDomains[idx];
+
+ // CordbHandleValue is in the appdomain exit list, and that needs
+ // to send an IPC event to cleanup and release the handle from
+ // the GCs handle table.
+ // This will toggle the process lock.
+ pAppDomain->GetSweepableExitNeuterList()->SweepNeuterLeftSideResources(this);
+ }
+ listAppDomains.Clear();
+
+ Lock();
+ }
+
+
+ // At this point, if the managed event queue is empty, m_synchronized may still be true if we had previously
+ // synchronized.
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Next, check for unmanaged events that may be queued. If there are some queued, then we need to get the Win32
+ // event thread to go ahead and dispatch the next one. If there aren't any queued, then we can just fall through and
+ // send the continue message to the left side. This works even if we have an outstanding ownership request, because
+ // until that answer is received, its just like the event hasn't happened yet.
+ //
+ // If we're terminated, then we've already continued from the last win32 event and so don't continue.
+ // @todo - or we could ensure the PS_SOME_THREADS_SUSPENDED | PS_HIJACKS_IN_PLACE are removed.
+ // Either way, we're just protecting against exit-process at strange times.
+ bool fDoWin32Continue = !m_terminated && ((m_state & (PS_WIN32_STOPPED | PS_SOME_THREADS_SUSPENDED | PS_HIJACKS_IN_PLACE)) != 0);
+
+ // We need to store this before marking the event user continued below
+ BOOL fHasUserUncontinuedEvents = HasUserUncontinuedNativeEvents();
+
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->SetState(CUES_UserContinued);
+ }
+
+ if (fHasUserUncontinuedEvents)
+ {
+ // ExitProcess is the last debug event we'll get. The Process Handle is not signaled until
+ // after we continue from ExitProcess. m_terminated is only set once we know the process is signaled.
+ // (This isn't 100% true for the detach case, but since you can't do interop detach, we don't care)
+ //_ASSERTE(!m_terminated);
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP::CI: there are queued uncontinued events. m_dispatchingUnmanagedEvent = %d\n", m_dispatchingUnmanagedEvent);
+
+ // Are we being called while in the unmanaged event callback?
+ if (m_dispatchingUnmanagedEvent)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continue while dispatching.\n"));
+ // The Win32ET could have made a cross-thread call to Continue while dispatching,
+ // so we don't know if this is the win32 ET.
+
+ // Tell the Win32 thread to continue when it returns from handling its unmanaged callback.
+ m_dispatchingUnmanagedEvent = false;
+
+ // If there are no more unmanaged events, then we fall through and continue the process for real. Otherwise,
+ // we can simply return.
+ if (HasUndispatchedNativeEvents())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: more unmanaged events need dispatching.\n");
+
+ // Note: if we tried to access the Left Side while stopped but couldn't, then m_oddSync will be true. We
+ // need to reset it to false since we're continuing now.
+ m_oddSync = false;
+
+ Unlock();
+ return S_OK;
+ }
+ else
+ {
+ // Also, if there are no more unmanaged events, then when DispatchUnmanagedInBandEvent sees that
+ // m_dispatchingUnmanagedEvent is false, it will continue the process. So we set doWin32Continue to
+ // false here so that we don't try to double continue the process below.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: no more unmanaged events to dispatch.\n");
+
+ fDoWin32Continue = false;
+ }
+ }
+ else
+ {
+ // after the DebugEvent callback returned the continue still had no been issued. Then later
+ // on another thread the user called back to continue the event, which gets us to right here
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continue outside of dispatching.\n"));
+
+ // This should be the common place to Dispatch an IB event that was hijacked for sync.
+
+ // If we're not dispatching, this better not be the win32 event thread.
+ _ASSERTE(!IsWin32EventThread());
+
+ // If the event at the head of the queue is really the last event, or if the event at the head of the queue
+ // hasn't been dispatched yet, then we simply fall through and continue the process for real. However, if
+ // its not the last event, we send to the Win32 event thread and get it to continue, then we return.
+ if (HasUndispatchedNativeEvents())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: more unmanaged events need dispatching.\n");
+
+ // Note: if we tried to access the Left Side while stopped but couldn't, then m_oddSync will be true. We
+ // need to reset it to false since we're continuing now.
+ m_oddSync = false;
+
+ Unlock();
+
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+
+ return hr;
+ }
+ }
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Both the managed and unmanaged event queues are now empty. Go
+ // ahead and continue the process for real.
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: headed for true continue.\n"));
+
+ // We need to check these while under the lock, but action must be
+ // taked outside of the lock.
+ bool fIsExiting = m_exiting;
+ bool fWasSynchronized = GetSynchronized();
+
+ // Mark that we're no longer synchronized.
+ if (fWasSynchronized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: process was synchronized.\n"));
+
+ SetSynchronized(false);
+ SetSyncCompleteRecv(false);
+
+ // we're no longer in a callback, so set flags to indicate that we've finished.
+ GetShim()->NotifyOnContinue();
+
+ // Flush will update state, including continue counter and marking
+ // frames dirty.
+ this->FlushProcessRunning();
+
+
+ // Tell the RC event thread that something about this process has changed.
+ m_cordb->ProcessStateChanged();
+ }
+
+ m_continueCounter++;
+
+ // If m_oddSync is set, then out last synchronization was due to us syncing the process because we were Win32
+ // stopped. Therefore, while we do need to do most of the work to continue the process below, we don't actually have
+ // to send the managed continue event. Setting wasSynchronized to false here helps us do that.
+ if (m_oddSync)
+ {
+ fWasSynchronized = false;
+ m_oddSync = false;
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // We must ensure that all managed threads are suspended here. We're about to let all managed threads run free via
+ // the managed continue message to the Left Side. If we don't suspend the managed threads, then they may start
+ // slipping forward even if we receive an in-band unmanaged event. We have to hijack in-band unmanaged events while
+ // getting the managed continue message over to the Left Side to keep the process running free. Otherwise, the
+ // SendIPCEvent will hang below. But in doing so, we could let managed threads slip to far. So we ensure they're all
+ // suspended here.
+ //
+ // Note: we only do this suspension if the helper thread hasn't died yet. If the helper thread has died, then we
+ // know that we're loosing the Runtime. No more managed code is going to run, so we don't bother trying to prevent
+ // managed threads from slipping via the call below.
+ //
+ // Note: we just remember here, under the lock, so we can unlock then wait for the syncing thread to free the
+ // debugger lock. Otherwise, we may block here and prevent someone from continuing from an OOB event, which also
+ // prevents the syncing thread from releasing the debugger lock like we want it to.
+ bool fNeedSuspend = fWasSynchronized && fDoWin32Continue && !m_helperThreadDead;
+
+ // If we receive a new in-band event once we unlock, we need to know to hijack it and keep going while we're still
+ // trying to send the managed continue event to the process.
+ if (fWasSynchronized && fDoWin32Continue && !fIsExiting)
+ {
+ m_specialDeferment = true;
+ }
+
+ if (fNeedSuspend)
+ {
+ // @todo - what does this actually accomplish? We already suspended everything when we first synced.
+
+ // Any thread that may hold a lock blocking the helper is
+ // inside of a can't stop region, and thus we won't suspend it.
+ SuspendUnmanagedThreads();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ Unlock();
+
+ // Although we've released the Process-lock, we still have the Stop-Go lock.
+ _ASSERTE(m_StopGoLock.HasLock());
+
+ // If we're processing an ExitProcess managed event, then we don't want to really continue the process, so just fall
+ // thru. Note: we did let the unmanaged continue go through above for this case.
+ if (fIsExiting)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: continuing from exit case.\n"));
+ }
+ else if (fWasSynchronized)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: Sending continue to AppD:0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+#ifdef FEATURE_INTEROP_DEBUGGING
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Continue flags:special=%d, dowin32=%d\n", m_specialDeferment, fDoWin32Continue);
+#endif
+ // Send to the RC to continue the process.
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+
+ InitIPCEvent(pEvent, DB_IPCE_CONTINUE, false, pAppDomainToken);
+
+ hr = m_cordb->SendIPCEvent(this, pEvent, CorDBIPC_BUFFER_SIZE);
+
+ // It is possible that we continue and then the process immediately exits before the helper
+ // thread is finished continuing and can report success back to us. That's arguably a success
+ // case sinceu the process did indeed continue, but since we didn't get the acknowledgement,
+ // we can't be sure it's success. So we call it S_FALSE instead of S_OK.
+ // @todo - how do we handle other failure here?
+ if (hr == CORDBG_E_PROCESS_TERMINATED)
+ {
+ hr = S_FALSE;
+ }
+ _ASSERTE(SUCCEEDED(pEvent->hr));
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::CI: Continue sent to AppD:0x%x.\n", VmPtrToCookie(pAppDomainToken)));
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // If we're win32 attached to the Left side, then we need to win32 continue the process too (unless, of course, it's
+ // already been done above.)
+ //
+ // Note: we do this here because we want to get the Left Side to receive and ack our continue message above if we
+ // were sync'd. If we were sync'd, then by definition the process (and the helper thread) is running anyway, so all
+ // this continue is going to do is to let the threads that have been suspended go.
+ if (fDoWin32Continue)
+ {
+#ifdef _DEBUG
+ {
+ // A little pause here extends the special deferment region and thus causes native-debug
+ // events to get hijacked. This test some wildly different corner case paths.
+ // See VSWhidbey bugs 131905, 168971
+ static DWORD dwRace = -1;
+ if (dwRace == -1)
+ dwRace = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgRace);
+
+ if ((dwRace & 1) == 1)
+ {
+ Sleep(30);
+ }
+ }
+#endif
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: sending unmanaged continue.\n");
+
+ // Send to the Win32 event thread to do the unmanaged continue for us.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cRealUMContinue);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::CI: continue done, returning.\n");
+
+ return hr;
+}
+
+HRESULT CordbProcess::HasQueuedCallbacks(ICorDebugThread *pThread,
+ BOOL *pbQueued)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pThread,ICorDebugThread *);
+ VALIDATE_POINTER_TO_OBJECT(pbQueued,BOOL *);
+
+ // Shim owns the event queue
+ if (m_pShim != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this); // Calling to shim, leaving RS.
+ *pbQueued = m_pShim->GetManagedEventQueue()->HasQueuedCallbacks(pThread);
+ return S_OK;
+ }
+ return E_NOTIMPL; // Not implemented in V3.
+}
+
+//
+// A small helper function to convert a CordbBreakpoint to an ICorDebugBreakpoint based on its type.
+//
+static ICorDebugBreakpoint *CordbBreakpointToInterface(CordbBreakpoint * pBreakpoint)
+{
+ _ASSERTE(pBreakpoint != NULL);
+
+ //
+ // I really dislike this. We've got three subclasses of CordbBreakpoint, but we store them all into the same hash
+ // (m_breakpoints), so when we get one out of the hash, we don't really know what type it is. But we need to know
+ // what type it is because we need to cast it to the proper interface before passing it out. I.e., when we create a
+ // function breakpoint, we return the breakpoint casted to an ICorDebugFunctionBreakpoint. But if we grab that same
+ // breakpoint out of the hash as a CordbBreakpoint and pass it out as an ICorDebugBreakpoint, then that's a
+ // different pointer, and its wrong. So I've added the type to the breakpoint so we can cast properly here. I'd love
+ // to do this a different way, though...
+ //
+ // -- Mon Dec 14 21:06:46 1998
+ //
+ switch(pBreakpoint->GetBPType())
+ {
+ case CBT_FUNCTION:
+ return static_cast<ICorDebugFunctionBreakpoint *>(static_cast<CordbFunctionBreakpoint *> (pBreakpoint));
+ break;
+
+ case CBT_MODULE:
+ return static_cast<ICorDebugModuleBreakpoint*>(static_cast<CordbModuleBreakpoint *> (pBreakpoint));
+ break;
+
+ case CBT_VALUE:
+ return static_cast<ICorDebugValueBreakpoint *>(static_cast<CordbValueBreakpoint *> (pBreakpoint));
+ break;
+
+ default:
+ _ASSERTE(!"Invalid breakpoint type!");
+ }
+
+ return NULL;
+}
+
+
+// Callback data for code:CordbProcess::GetAssembliesInLoadOrder
+class ShimAssemblyCallbackData
+{
+public:
+ // Ctor to intialize callback data
+ //
+ // Arguments:
+ // pAppDomain - appdomain that the assemblies are in.
+ // pAssemblies - preallocated array of smart pointers to hold assemblies
+ // countAssemblies - size of pAssemblies in elements.
+ ShimAssemblyCallbackData(
+ CordbAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies)
+ {
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pAssemblies != NULL);
+
+ m_pProcess = pAppDomain->GetProcess();
+ m_pAppDomain = pAppDomain;
+ m_pAssemblies = pAssemblies;
+ m_countElements = countAssemblies;
+ m_index = 0;
+
+ // Just to be safe, clear them all out
+ for(ULONG i = 0; i < countAssemblies; i++)
+ {
+ pAssemblies[i].Clear();
+ }
+ }
+
+ // Dtor
+ //
+ // Notes:
+ // This can assert end-of-enumeration invariants.
+ ~ShimAssemblyCallbackData()
+ {
+ // Ensure that we went through all assemblies.
+ _ASSERTE(m_index == m_countElements);
+ }
+
+ // Callback invoked from DAC enumeration.
+ //
+ // arguments:
+ // vmDomainAssembly - VMPTR for assembly
+ // pData - a 'this' pointer
+ //
+ static void Callback(VMPTR_DomainAssembly vmDomainAssembly, void * pData)
+ {
+ ShimAssemblyCallbackData * pThis = static_cast<ShimAssemblyCallbackData *> (pData);
+ INTERNAL_DAC_CALLBACK(pThis->m_pProcess);
+
+ CordbAssembly * pAssembly = pThis->m_pAppDomain->LookupOrCreateAssembly(vmDomainAssembly);
+
+ pThis->SetAndMoveNext(pAssembly);
+ }
+
+ // Set the current index in the table and increment the cursor.
+ //
+ // Arguments:
+ // pAssembly - assembly from DAC enumerator
+ void SetAndMoveNext(CordbAssembly * pAssembly)
+ {
+ _ASSERTE(pAssembly != NULL);
+
+ if (m_index >= m_countElements)
+ {
+ // Enumerating the assemblies in the target should be fixed since
+ // the target is not running.
+ // We should never get here unless the target is unstable.
+ // The caller (the shim) pre-allocated the table of assemblies.
+ m_pProcess->TargetConsistencyCheck(!"Target changed assembly count");
+ return;
+ }
+
+ m_pAssemblies[m_index].Assign(pAssembly);
+ m_index++;
+ }
+
+protected:
+ CordbProcess * m_pProcess;
+ CordbAppDomain * m_pAppDomain;
+ RSExtSmartPtr<ICorDebugAssembly>* m_pAssemblies;
+ ULONG m_countElements;
+ ULONG m_index;
+};
+
+//---------------------------------------------------------------------------------------
+// Shim Helper to enumerate the assemblies in the load-order
+//
+// Arguments:
+// pAppdomain - non-null appdmomain to enumerate assemblies.
+// pAssemblies - caller pre-allocated array to hold assemblies
+// countAssemblies - size of the array.
+//
+// Notes:
+// Caller preallocated array (likely from ICorDebugAssemblyEnum::GetCount),
+// and now this function fills in the assemblies in the order they were
+// loaded.
+//
+// The target should be stable, such that the number of assemblies in the
+// target is stable, and therefore countAssemblies as determined by the
+// shim via ICorDebugAssemblyEnum::GetCount should match the number of
+// assemblies enumerated here.
+//
+// Called by code:ShimProcess::QueueFakeAttachEvents.
+// This provides the assemblies in load-order. In contrast,
+// ICorDebugAppDomain::EnumerateAssemblies is a random order. The shim needs
+// load-order to match Whidbey semantics for dispatching fake load-assembly
+// callbacks on attach. The debugger then uses the order
+// in its module display window.
+//
+void CordbProcess::GetAssembliesInLoadOrder(
+ ICorDebugAppDomain * pAppDomain,
+ RSExtSmartPtr<ICorDebugAssembly>* pAssemblies,
+ ULONG countAssemblies)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ RSLockHolder lockHolder(GetProcessLock());
+
+ _ASSERTE(GetShim() != NULL);
+
+ CordbAppDomain * pAppDomainInternal = static_cast<CordbAppDomain *> (pAppDomain);
+
+ ShimAssemblyCallbackData data(pAppDomainInternal, pAssemblies, countAssemblies);
+
+ // Enumerate through and fill out pAssemblies table.
+ GetDAC()->EnumerateAssembliesInAppDomain(
+ pAppDomainInternal->GetADToken(),
+ ShimAssemblyCallbackData::Callback,
+ &data); // user data
+
+ // pAssemblies array has now been updated.
+}
+
+// Callback data for code:CordbProcess::GetModulesInLoadOrder
+class ShimModuleCallbackData
+{
+public:
+ // Ctor to intialize callback data
+ //
+ // Arguments:
+ // pAssembly - assembly that the Modules are in.
+ // pModules - preallocated array of smart pointers to hold Modules
+ // countModules - size of pModules in elements.
+ ShimModuleCallbackData(
+ CordbAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules)
+ {
+ _ASSERTE(pAssembly != NULL);
+ _ASSERTE(pModules != NULL);
+
+ m_pProcess = pAssembly->GetAppDomain()->GetProcess();
+ m_pAssembly = pAssembly;
+ m_pModules = pModules;
+ m_countElements = countModules;
+ m_index = 0;
+
+ // Just to be safe, clear them all out
+ for(ULONG i = 0; i < countModules; i++)
+ {
+ pModules[i].Clear();
+ }
+ }
+
+ // Dtor
+ //
+ // Notes:
+ // This can assert end-of-enumeration invariants.
+ ~ShimModuleCallbackData()
+ {
+ // Ensure that we went through all Modules.
+ _ASSERTE(m_index == m_countElements);
+ }
+
+ // Callback invoked from DAC enumeration.
+ //
+ // arguments:
+ // vmDomainFile - VMPTR for Module
+ // pData - a 'this' pointer
+ //
+ static void Callback(VMPTR_DomainFile vmDomainFile, void * pData)
+ {
+ ShimModuleCallbackData * pThis = static_cast<ShimModuleCallbackData *> (pData);
+ INTERNAL_DAC_CALLBACK(pThis->m_pProcess);
+
+ CordbModule * pModule = pThis->m_pAssembly->GetAppDomain()->LookupOrCreateModule(vmDomainFile);
+
+ pThis->SetAndMoveNext(pModule);
+ }
+
+ // Set the current index in the table and increment the cursor.
+ //
+ // Arguments:
+ // pModule - Module from DAC enumerator
+ void SetAndMoveNext(CordbModule * pModule)
+ {
+ _ASSERTE(pModule != NULL);
+
+ if (m_index >= m_countElements)
+ {
+ // Enumerating the Modules in the target should be fixed since
+ // the target is not running.
+ // We should never get here unless the target is unstable.
+ // The caller (the shim) pre-allocated the table of Modules.
+ m_pProcess->TargetConsistencyCheck(!"Target changed Module count");
+ return;
+ }
+
+ m_pModules[m_index].Assign(pModule);
+ m_index++;
+ }
+
+protected:
+ CordbProcess * m_pProcess;
+ CordbAssembly * m_pAssembly;
+ RSExtSmartPtr<ICorDebugModule>* m_pModules;
+ ULONG m_countElements;
+ ULONG m_index;
+};
+
+//---------------------------------------------------------------------------------------
+// Shim Helper to enumerate the Modules in the load-order
+//
+// Arguments:
+// pAppdomain - non-null appdmomain to enumerate Modules.
+// pModules - caller pre-allocated array to hold Modules
+// countModules - size of the array.
+//
+// Notes:
+// Caller preallocated array (likely from ICorDebugModuleEnum::GetCount),
+// and now this function fills in the Modules in the order they were
+// loaded.
+//
+// The target should be stable, such that the number of Modules in the
+// target is stable, and therefore countModules as determined by the
+// shim via ICorDebugModuleEnum::GetCount should match the number of
+// Modules enumerated here.
+//
+// Called by code:ShimProcess::QueueFakeAssemblyAndModuleEvent.
+// This provides the Modules in load-order. In contrast,
+// ICorDebugAssembly::EnumerateModules is a random order. The shim needs
+// load-order to match Whidbey semantics for dispatching fake load-Module
+// callbacks on attach. The most important thing is that the manifest module
+// gets a LodModule callback before any secondary modules. For dynamic
+// modules, this is necessary for operations on the secondary module
+// that rely on manifest metadata (eg. GetSimpleName).
+//
+// @dbgtodo : This is almost identical to GetAssembliesInLoadOrder, and
+// (together wih the CallbackData classes) seems a HUGE amount of code and
+// complexity for such a simple thing. We also have extra code to order
+// AppDomains and Threads. We should try and rip all of this extra complexity
+// out, and replace it with better data structures for storing these items.
+// Eg., if we used std::map, we could have efficient lookups and ordered
+// enumerations. However, we do need to be careful about exposing new invariants
+// through ICorDebug that customers may depend on, which could place a long-term
+// compatibility burden on us. We could have a simple generic data structure
+// (eg. built on std::hash_map and std::list) which provided efficient look-up
+// and both in-order and random enumeration.
+//
+void CordbProcess::GetModulesInLoadOrder(
+ ICorDebugAssembly * pAssembly,
+ RSExtSmartPtr<ICorDebugModule>* pModules,
+ ULONG countModules)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ RSLockHolder lockHolder(GetProcessLock());
+
+ _ASSERTE(GetShim() != NULL);
+
+ CordbAssembly * pAssemblyInternal = static_cast<CordbAssembly *> (pAssembly);
+
+ ShimModuleCallbackData data(pAssemblyInternal, pModules, countModules);
+
+ // Enumerate through and fill out pModules table.
+ GetDAC()->EnumerateModulesInAssembly(
+ pAssemblyInternal->GetDomainAssemblyPtr(),
+ ShimModuleCallbackData::Callback,
+ &data); // user data
+
+ // pModules array has now been updated.
+}
+
+
+//---------------------------------------------------------------------------------------
+// Callback to count the number of enumerations in a process.
+//
+// Arguments:
+// id - the connection id.
+// pName - name of the connection
+// pUserData - an EnumerateConnectionsData
+//
+// Notes:
+// Helper function for code:CordbProcess::QueueFakeConnectionEvents
+//
+// static
+void CordbProcess::CountConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData)
+{
+#if defined(FEATURE_INCLUDE_ALL_INTERFACES)
+ EnumerateConnectionsData * pCallbackData = reinterpret_cast<EnumerateConnectionsData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ pCallbackData->m_uIndex += 1;
+#endif // FEATURE_INCLUDE_ALL_INTERFACES
+}
+
+//---------------------------------------------------------------------------------------
+// Callback to enumerate all the connections in a process.
+//
+// Arguments:
+// id - the connection id.
+// pName - name of the connection
+// pUserData - an EnumerateConnectionsData
+//
+// Notes:
+// Helper function for code:CordbProcess::QueueFakeConnectionEvents
+//
+// static
+void CordbProcess::EnumerateConnectionsCallback(DWORD id, LPCWSTR pName, void * pUserData)
+{
+#if defined(FEATURE_INCLUDE_ALL_INTERFACES)
+ EnumerateConnectionsData * pCallbackData = reinterpret_cast<EnumerateConnectionsData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ // get the next entry in the array to be filled in
+ EnumerateConnectionsEntry * pEntry = &(pCallbackData->m_pEntryArray[pCallbackData->m_uIndex]);
+
+ // initialize the StringCopyHolder in the entry and copy over the name of the connection
+ new (&(pEntry->m_pName)) StringCopyHolder;
+ pEntry->m_pName.AssignCopy(pName);
+ pEntry->m_dwID = id;
+
+ pCallbackData->m_uIndex += 1;
+#endif // FEATURE_INCLUDE_ALL_INTERFACES
+}
+
+//---------------------------------------------------------------------------------------
+// Callback from Shim to queue fake Connection events on attach.
+//
+// Notes:
+// See code:ShimProcess::QueueFakeAttachEvents
+void CordbProcess::QueueFakeConnectionEvents()
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+
+#ifdef FEATURE_INCLUDE_ALL_INTERFACES
+ EnumerateConnectionsData callbackData;
+ callbackData.m_pThis = this;
+ callbackData.m_uIndex = 0;
+ callbackData.m_pEntryArray = NULL;
+
+ UINT32 uSize = 0;
+
+ // We must take the process lock before calling DAC primitives which will call back into DBI.
+ // On the other hand, we must NOT be holding the lock when we call out to the shim.
+ // So introduce a new scope here.
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ GetDAC()->EnumerateConnections(CountConnectionsCallback, &callbackData);
+
+ // save the size for later
+ uSize = callbackData.m_uIndex;
+
+ // Allocate the array to store the connections. This array will be released when the dtor runs.
+ callbackData.m_uIndex = 0;
+ callbackData.m_pEntryArray = new EnumerateConnectionsEntry[uSize];
+ GetDAC()->EnumerateConnections(EnumerateConnectionsCallback, &callbackData);
+ _ASSERTE(uSize == callbackData.m_uIndex);
+ }
+
+ {
+ // V2 would send CreateConnection for all connections, and then ChangeConnection
+ // for all connections.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+ for (UINT32 i = 0; i < uSize; i++)
+ {
+ EnumerateConnectionsEntry * pEntry = &(callbackData.m_pEntryArray[i]);
+ GetShim()->GetShimCallback()->CreateConnection(
+ this,
+ (CONNID)pEntry->m_dwID,
+ const_cast<WCHAR *>((const WCHAR *)(pEntry->m_pName)));
+ }
+
+ for (UINT32 i = 0; i < uSize; i++)
+ {
+ EnumerateConnectionsEntry * pEntry = &(callbackData.m_pEntryArray[i]);
+ GetShim()->GetShimCallback()->ChangeConnection(this, (CONNID)pEntry->m_dwID);
+ }
+ }
+#endif
+}
+
+//
+// DispatchRCEvent -- dispatches a previously queued IPC event received
+// from the runtime controller. This represents the last amount of processing
+// the DI gets to do on an event before giving it to the user.
+//
+#ifdef _PREFAST_
+#pragma warning(push)
+#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
+#endif
+void CordbProcess::DispatchRCEvent()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CONTRACTL
+ {
+ // This is happening on the RCET thread, so there's no place to propogate an error back up.
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_pShim != NULL); // V2 case
+
+ //
+ // Note: the current thread should have the process locked when it
+ // enters this method.
+ //
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Create/Launch paths already ensured that we had a callback.
+ _ASSERTE(m_cordb != NULL);
+ _ASSERTE(m_cordb->m_managedCallback != NULL);
+ _ASSERTE(m_cordb->m_managedCallback2 != NULL);
+ _ASSERTE(m_cordb->m_managedCallback3 != NULL);
+
+
+ // Bump up the stop count. Either we'll dispatch a managed event,
+ // or the logic below will decide not to dispatch one and call
+ // Continue itself. Either way, the stop count needs to go up by
+ // one...
+ _ASSERTE(this->GetSyncCompleteRecv());
+ SetSynchronized(true);
+ IncStopCount();
+
+ // As soon as we call Unlock(), we might get neutered and lose our reference to
+ // the shim. Grab it now for use later.
+ RSExtSmartPtr<ShimProcess> pShim(m_pShim);
+
+ Unlock();
+
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+
+ // We want to stay synced until after the callbacks return. This is b/c we're on the RCET,
+ // and we may deadlock if we send IPC events on the RCET if we're not synced (see SendIPCEvent for details).
+ // So here, stopcount=1. The StopContinueHolder bumps it up to 2.
+ // - If Cordbg calls continue in the callback, that bumps it back down to 1, but doesn't actually continue.
+ // The holder dtor then bumps it down to 0, doing the real continue.
+ // - If Cordbg doesn't call continue in the callback, then stopcount stays at 2, holder dtor drops it down to 1,
+ // and then the holder was just a nop.
+ // This gives us delayed continues w/ no extra state flags.
+
+
+ // The debugger may call Detach() immediately after it returns from the callback, but before this thread returns
+ // from this function. Thus after we execute the callbacks, it's possible the CordbProcess object has been neutered.
+
+ // Since we're already sycned, the Stop from the holder here is practically a nop that just bumps up a count.
+ // Create an extra scope for the StopContinueHolder.
+ {
+ StopContinueHolder h;
+ HRESULT hr = h.Init(this);
+ if (FAILED(hr))
+ {
+ CORDBSetUnrecoverableError(this, hr, 0);
+ }
+
+ HRESULT hrCallback = S_OK;
+ // It's possible a ICorDebugProcess::Detach() may have occurred by now.
+ {
+ // @dbgtodo shim: eventually the entire RCET should be considered outside the RS.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(this);
+
+
+ // Snag the first event off the queue.
+ // Holder will call Delete, which will invoke virtual Dtor that will release ICD objects.
+ // Since these are external refs, we want to do it while "outside" the RS.
+ NewHolder<ManagedEvent> pEvent(pShim->DequeueManagedEvent());
+
+ // Normally pEvent shouldn't be NULL, since this method is called when the queue is not empty.
+ // But due to a race between CordbProcess::Terminate(), CordbWin32EventThread::ExitProcess() and this method
+ // it is totally possible that the queue has already been cleaned up and we can't expect that event is always available.
+ if (pEvent != NULL)
+ {
+ // Since we need to access a member (m_cordb), protect this block with a
+ // lock and a check for Neutering (in case process detach has just
+ // occurred). We'll release the lock around the dispatch later on.
+ RSLockHolder lockHolder(GetProcessLock());
+ if (!IsNeutered())
+ {
+#ifdef _DEBUG
+ // On a debug build, keep track of the last IPC event we dispatched.
+ m_pDBGLastIPCEventType = pEvent->GetDebugCookie();
+#endif
+
+ ManagedEvent::DispatchArgs args(m_cordb->m_managedCallback, m_cordb->m_managedCallback2, m_cordb->m_managedCallback3);
+
+ {
+ // Release lock around the dispatch of the event
+ RSInverseLockHolder inverseLockHolder(GetProcessLock());
+
+ EX_TRY
+ {
+ // This dispatches almost directly into the user's callbacks.
+ // It does not update any RS state.
+ hrCallback = pEvent->Dispatch(args);
+ }
+ EX_CATCH_HRESULT(hrCallback);
+ }
+ }
+ }
+
+ } // we're now back inside the RS
+
+ if (hrCallback == E_NOTIMPL)
+ {
+ ContinueInternal(FALSE);
+ }
+
+
+ } // forces Continue to be called
+
+ Lock();
+
+};
+
+#ifdef _DEBUG
+//---------------------------------------------------------------------------------------
+// Debug-only callback to ensure that an appdomain is not available after the ExitAppDomain event.
+//
+// Arguments:
+// vmAppDomain - appdomain from enumeration
+// pUserData - pointer to a DbgAssertAppDomainDeletedData which contains the VMAppDomain that was just deleted.
+// notes:
+// see code:CordbProcess::DbgAssertAppDomainDeleted for details.
+void CordbProcess::DbgAssertAppDomainDeletedCallback(VMPTR_AppDomain vmAppDomain, void * pUserData)
+{
+ DbgAssertAppDomainDeletedData * pCallbackData = reinterpret_cast<DbgAssertAppDomainDeletedData *>(pUserData);
+ INTERNAL_DAC_CALLBACK(pCallbackData->m_pThis);
+
+ VMPTR_AppDomain vmAppDomainDeleted = pCallbackData->m_vmAppDomainDeleted;
+ CONSISTENCY_CHECK_MSGF((vmAppDomain != vmAppDomainDeleted),
+ ("An ExitAppDomain event was sent for appdomain, but it still shows up in the enumeration.\n vmAppDomain=%p\n",
+ VmPtrToCookie(vmAppDomainDeleted)));
+}
+
+//---------------------------------------------------------------------------------------
+// Debug-only helper to Assert that VMPTR is actually removed.
+//
+// Arguments:
+// vmAppDomainDeleted - vmptr of appdomain that we just got exit event for.
+// This should not be discoverable from the RS.
+//
+// Notes:
+// See code:IDacDbiInterface#Enumeration for rules that we're asserting.
+// Once the exit appdomain event is dispatched, the appdomain should not be discoverable by the RS.
+// Else the RS may use the AppDomain* after it's deleted.
+// This asserts that the AppDomain* is not discoverable.
+//
+// Since this is a debug-only function, it should have no side-effects.
+void CordbProcess::DbgAssertAppDomainDeleted(VMPTR_AppDomain vmAppDomainDeleted)
+{
+ DbgAssertAppDomainDeletedData callbackData;
+ callbackData.m_pThis = this;
+ callbackData.m_vmAppDomainDeleted = vmAppDomainDeleted;
+
+ GetDAC()->EnumerateAppDomains(
+ CordbProcess::DbgAssertAppDomainDeletedCallback,
+ &callbackData);
+}
+
+#endif // _DEBUG
+
+//---------------------------------------------------------------------------------------
+// Update state and potentially Dispatch a single event.
+//
+// Arguments:
+// pEvent - non-null pointer to debug event.
+// pCallback1 - callback object to dispatch on (for V1 callbacks)
+// pCallback2 - 2nd callback object to dispatch on (for new V2 callbacks)
+// pCallback3 - 3rd callback object to dispatch on (for new V4 callbacks)
+//
+//
+// Returns:
+// Nothing. Throws on error.
+//
+// Notes:
+// Generally, this will dispatch exactly 1 callback. It may dispatch 0 callbacks if there is an error
+// or in other corner cases (documented within the dispatch code below).
+// Errors could occur because:
+// - the event is corrupted (exceptional case)
+// - the RS is corrupted / OOM (exceptional case)
+// Exception errors here will propogate back to the Filter() call, and there's not really anything
+// a debugger can do about an error here (perhaps report it to the user).
+// Errors must leave IcorDebug in a consistent state.
+//
+// This is dispatched directly on the Win32Event Thread in response to calling Filter.
+// Therefore, this can't send any IPC events (Not an issue once everything is DAC-ized).
+// A V2 shim can provide a proxy calllack that takes these events and queues them and
+// does the real dispatch to the user to emulate V2 semantics.
+//
+void CordbProcess::RawDispatchEvent(
+ DebuggerIPCEvent * pEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback1,
+ ICorDebugManagedCallback2 * pCallback2,
+ ICorDebugManagedCallback3 * pCallback3)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ // We start off with the lock, and we'll toggle it.
+ _ASSERTE(ThreadHoldsProcessLock());
+
+
+ //
+ // Call StartEventDispatch to true to guard against calls to Continue()
+ // from within the user's callback. We need Continue() to behave a little
+ // bit differently in such a case.
+ //
+ // Also note that Win32EventThread::ExitProcess will take the lock and free all
+ // events in the queue. (the current event is already off the queue, so
+ // it will be ok). But we can't do the EP callback in the middle of this dispatch
+ // so if this flag is set, EP will wait on the miscWaitEvent (which will
+ // get set in FlushQueuedEvents when we return from here) and let us finish here.
+ //
+ StartEventDispatch(pEvent->type);
+
+ // Keep strong references to these objects in case a callback deletes them from underneath us.
+ RSSmartPtr<CordbAppDomain> pAppDomain;
+ CordbThread * pThread = NULL;
+
+
+ // Get thread that this event is on. In attach scenarios, this may be the first time ICorDebug has seen this thread.
+ if (!pEvent->vmThread.IsNull())
+ {
+ pThread = LookupOrCreateThread(pEvent->vmThread);
+ }
+
+ if (!pEvent->vmAppDomain.IsNull())
+ {
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->vmAppDomain));
+ }
+
+ DWORD dwVolatileThreadId = 0;
+ if (pThread != NULL)
+ {
+ dwVolatileThreadId = pThread->GetUniqueId();
+ }
+
+
+ //
+ // Update the app domain that this thread lives in.
+ //
+ if ((pThread != NULL) && (pAppDomain != NULL))
+ {
+ // It shouldn't be possible for us to see an exited AppDomain here
+ _ASSERTE( !pAppDomain->IsNeutered() );
+
+ pThread->m_pAppDomain = pAppDomain;
+ }
+
+ _ASSERTE(pEvent != NULL);
+ _ASSERTE(pCallback1 != NULL);
+ _ASSERTE(pCallback2 != NULL);
+ _ASSERTE(pCallback3 != NULL);
+
+
+ STRESS_LOG1(LF_CORDB, LL_EVERYTHING, "Pre-Dispatch IPC event: %s\n", IPCENames::GetName(pEvent->type));
+
+ switch (pEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_CREATE_PROCESS:
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->CreateProcess(static_cast<ICorDebugProcess*> (this));
+ }
+ break;
+
+ case DB_IPCE_BREAKPOINT:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ // Find the breakpoint object on this side.
+ CordbBreakpoint *pBreakpoint = NULL;
+
+ // We've found cases out in the wild where we get this event on a thread we don't recognize.
+ // We're not sure how this happens. Add a runtime check to protect ourselves to avoid the
+ // an AV. We still assert because this should not be happening.
+ // It likely means theres some issue where we failed to send a CreateThread notification.
+ TargetConsistencyCheck(pThread != NULL);
+ pBreakpoint = pAppDomain->m_breakpoints.GetBase(LsPtrToCookie(pEvent->BreakpointData.breakpointToken));
+
+ if (pBreakpoint != NULL)
+ {
+ ICorDebugBreakpoint * pIBreakpoint = CordbBreakpointToInterface(pBreakpoint);
+ _ASSERTE(pIBreakpoint != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, bp=0x%p", pThread, pBreakpoint);
+ pCallback1->Breakpoint(pAppDomain, pThread, pIBreakpoint);
+ }
+ }
+ }
+ break;
+
+ case DB_IPCE_USER_BREAKPOINT:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: user breakpoint.\n",
+ GetCurrentThreadId());
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->Break(pThread->m_pAppDomain, pThread);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_STEP_COMPLETE:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: step complete.\n",
+ GetCurrentThreadId());
+
+ PREFIX_ASSUME(pThread != NULL);
+
+ CordbStepper * pStepper = m_steppers.GetBase(LsPtrToCookie(pEvent->StepData.stepperToken));
+
+ // It's possible the stepper is NULL if:
+ // - event X & step-complete are both in the queue
+ // - during dispatch for event X, Cordbg cancels the stepper (thus removing it from m_steppers)
+ // - the Step-Complete still stays in the queue, and so we're here, but out stepper's been removed.
+ // (This could happen for breakpoints too)
+ // Don't dispatch a callback if the stepper is NULL.
+ if (pStepper != NULL)
+ {
+ RSSmartPtr<CordbStepper> pRef(pStepper);
+ pStepper->m_active = false;
+ m_steppers.RemoveBase((ULONG_PTR)pStepper->m_id);
+
+ {
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thrad=0x%p, stepper=0x%p", pThread, pStepper);
+ pCallback1->StepComplete(pThread->m_pAppDomain, pThread, pStepper, pEvent->StepData.reason);
+ }
+
+ // implicit Release on pRef
+ }
+ }
+ break;
+
+ case DB_IPCE_EXCEPTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: exception.\n",
+ GetCurrentThreadId());
+
+ _ASSERTE(pAppDomain != NULL);
+
+ // For some exceptions very early in startup (eg, TypeLoad), this may have occurred before we
+ // even executed jitted code on the thread. We may have not received a CreateThread yet.
+ // In V2, we detected this and sent a LogMessage on a random thread.
+ // In V3, we lazily create the CordbThread objects (possibly before the CreateThread event),
+ // and so we know we should have one.
+ _ASSERTE(pThread != NULL);
+
+ pThread->SetExInfo(pEvent->Exception.vmExceptionHandle);
+
+ _ASSERTE(pThread->m_pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->Exception(pThread->m_pAppDomain, pThread, !pEvent->Exception.firstChance);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_SYNC_COMPLETE:
+ _ASSERTE(!"Should have never queued a sync complete pEvent.");
+ break;
+
+ case DB_IPCE_THREAD_ATTACH:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "RCET::DRCE: thread attach : ID=%x.\n", dwVolatileThreadId);
+
+ TargetConsistencyCheck(pThread != NULL);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "thread=0x%p", pThread);
+ pCallback1->CreateThread(pAppDomain, pThread);
+ }
+ }
+ break;
+
+ case DB_IPCE_THREAD_DETACH:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "[%x] RCET::HRCE: thread detach : ID=%x \n",
+ GetCurrentThreadId(), dwVolatileThreadId);
+
+ // If the runtime thread never entered managed code, there
+ // won't be a CordbThread, and CreateThread was never
+ // called, so don't bother calling ExitThread.
+ if (pThread != NULL)
+ {
+ AddToNeuterOnContinueList(pThread);
+
+ RSSmartPtr<CordbThread> pRefThread(pThread);
+
+ _ASSERTE(pAppDomain != NULL);
+
+ // A thread is reported as dead before we get the exit event.
+ // See code:IDacDbiInterface#IsThreadMarkedDead for the invariant being asserted here.
+ TargetConsistencyCheck(pThread->IsThreadDead());
+
+ // Enforce the enumeration invariants (see code:IDacDbiInterface#Enumeration)that the thread is not discoverable.
+ INDEBUG(pThread->DbgAssertThreadDeleted());
+
+ // Remove the thread from the hash. If we've removed it from the hash, we really should
+ // neuter it ... but that causes test failures.
+ // We'll neuter it in continue.
+ m_userThreads.RemoveBase(VmPtrToCookie(pThread->m_vmThreadToken));
+
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HRCE: sending thread detach.\n", GetCurrentThreadId()));
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->ExitThread(pAppDomain, pThread);
+ }
+
+ // Implicit release on thread & pAppDomain
+ }
+ }
+ break;
+
+ case DB_IPCE_METADATA_UPDATE:
+ {
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->MetadataUpdateData.vmDomainFile);
+ pModule->RefreshMetaData();
+ }
+ break;
+
+ case DB_IPCE_LOAD_MODULE:
+ {
+ _ASSERTE (pAppDomain != NULL);
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->LoadModuleData.vmDomainFile);
+
+ {
+ pModule->SetLoadEventContinueMarker();
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LoadModule(pAppDomain, pModule);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_CREATE_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection change %d \n",
+ pEvent->CreateConnection.connectionId);
+
+ // pass back the connection id and the connection name.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->CreateConnection(
+ this,
+ pEvent->CreateConnection.connectionId,
+ const_cast<WCHAR*> (pEvent->CreateConnection.wzConnectionName.GetString()));
+ }
+ break;
+
+ case DB_IPCE_DESTROY_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection destroyed %d \n",
+ pEvent->ConnectionChange.connectionId);
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->DestroyConnection(this, pEvent->ConnectionChange.connectionId);
+ }
+ break;
+
+ case DB_IPCE_CHANGE_CONNECTION:
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: Connection changed %d \n",
+ pEvent->ConnectionChange.connectionId);
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->ChangeConnection(this, pEvent->ConnectionChange.connectionId);
+ }
+ break;
+
+ case DB_IPCE_UNLOAD_MODULE:
+ {
+ STRESS_LOG3(LF_CORDB, LL_INFO100, "RCET::HRCE: unload module on thread %#x Mod:0x%x AD:0x%08x\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->UnloadModuleData.vmDomainFile),
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+ PREFIX_ASSUME (pAppDomain != NULL);
+
+ CordbModule *module = pAppDomain->LookupOrCreateModule(pEvent->UnloadModuleData.vmDomainFile);
+
+ if (module == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Already unloaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(module != NULL);
+ INDEBUG(module->DbgAssertModuleDeleted());
+
+ // The appdomain we're unloading in must be the appdomain we were loaded in. Otherwise, we've got mismatched
+ // module and appdomain pointers. Bugs 65943 & 81728.
+ _ASSERTE(pAppDomain == module->GetAppDomain());
+
+ // Ensure the module gets neutered once we call continue.
+ AddToNeuterOnContinueList(module); // throws
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->UnloadModule(pAppDomain, module);
+ }
+
+ pAppDomain->m_modules.RemoveBase(VmPtrToCookie(pEvent->UnloadModuleData.vmDomainFile));
+ }
+ break;
+
+ case DB_IPCE_LOAD_CLASS:
+ {
+ CordbClass *pClass = NULL;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "RCET::HRCE: load class on thread %#x Tok:0x%08x Mod:0x%08x Asm:0x%08x AD:0x%08x\n",
+ dwVolatileThreadId,
+ pEvent->LoadClass.classMetadataToken,
+ VmPtrToCookie(pEvent->LoadClass.vmDomainFile),
+ LsPtrToCookie(pEvent->LoadClass.classDebuggerAssemblyToken),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(pEvent->LoadClass.vmDomainFile);
+ if (pModule == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Load Class on not-loaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(pModule != NULL);
+
+ BOOL fDynamic = pModule->IsDynamic();
+
+ // If this is a class load in a dynamic module, the metadata has become invalid.
+ if (fDynamic)
+ {
+ pModule->RefreshMetaData();
+ }
+
+ hr = pModule->LookupOrCreateClass(pEvent->LoadClass.classMetadataToken, &pClass);
+ _ASSERTE(SUCCEEDED(hr) == (pClass != NULL));
+ IfFailThrow(hr);
+
+ // Prevent class load from being sent twice.
+ // @dbgtodo - Microsoft, cordbclass: this is legacy. Can this really happen? Investigate as we dac-ize CordbClass.
+ if (pClass->LoadEventSent())
+ {
+ // Dynamic modules are dynamic at the module level -
+ // you can't add a new version of a class once the module
+ // is baked.
+ // EnC adds completely new classes.
+ // There shouldn't be any other way to send multiple
+ // ClassLoad events.
+ // Except that there are race conditions between loading
+ // an appdomain, and loading a class, so if we get the extra
+ // class load, we should ignore it.
+ break; //out of the switch statement
+ }
+ pClass->SetLoadEventSent(TRUE);
+
+
+ if (pClass != NULL)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LoadClass(pAppDomain, pClass);
+ }
+ }
+ break;
+
+ case DB_IPCE_UNLOAD_CLASS:
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "RCET::HRCE: unload class on thread %#x Tok:0x%08x Mod:0x%08x AD:0x%08x\n",
+ dwVolatileThreadId,
+ pEvent->UnloadClass.classMetadataToken,
+ VmPtrToCookie(pEvent->UnloadClass.vmDomainFile),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ // get the appdomain object
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbModule *pModule = pAppDomain->LookupOrCreateModule(pEvent->UnloadClass.vmDomainFile);
+ if (pModule == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100, "Unload Class on not-loaded Module - continue()ing!" ));
+ break;
+ }
+ _ASSERTE(pModule != NULL);
+
+ CordbClass *pClass = pModule->LookupClass(pEvent->UnloadClass.classMetadataToken);
+
+ if (pClass != NULL && !pClass->HasBeenUnloaded())
+ {
+ pClass->SetHasBeenUnloaded(true);
+
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->UnloadClass(pAppDomain, pClass);
+ }
+ }
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ const WCHAR * pszContent = pEvent->FirstLogMessage.szContent.GetString();
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LogMessage(
+ pAppDomain,
+ pThread,
+ pEvent->FirstLogMessage.iLevel,
+ const_cast<WCHAR*> (pEvent->FirstLogMessage.szCategory.GetString()),
+ const_cast<WCHAR*> (pszContent));
+ }
+ }
+ break;
+
+ case DB_IPCE_LOGSWITCH_SET_MESSAGE:
+ {
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "[%x] RCET::DRCE: Log Switch Setting Message.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(pThread != NULL);
+
+ const WCHAR *pstrLogSwitchName = pEvent->LogSwitchSettingMessage.szSwitchName.GetString();
+ const WCHAR *pstrParentName = pEvent->LogSwitchSettingMessage.szParentSwitchName.GetString();
+
+ // from the thread object get the appdomain object
+ _ASSERTE(pAppDomain == pThread->m_pAppDomain);
+ _ASSERTE (pAppDomain != NULL);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->LogSwitch(
+ pAppDomain,
+ pThread,
+ pEvent->LogSwitchSettingMessage.iLevel,
+ pEvent->LogSwitchSettingMessage.iReason,
+ const_cast<WCHAR*> (pstrLogSwitchName),
+ const_cast<WCHAR*> (pstrParentName));
+
+ }
+ }
+
+ break;
+ case DB_IPCE_CUSTOM_NOTIFICATION:
+ {
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+
+ // determine first whether custom notifications for this type are enabled -- if not
+ // we just return without doing anything.
+ CordbClass * pNotificationClass = LookupClass(pAppDomain,
+ pEvent->CustomNotification.vmDomainFile,
+ pEvent->CustomNotification.classToken);
+
+ // if the class is NULL, that means the debugger never enabled notifications for it. Otherwise,
+ // the CordbClass instance would already have been created when the notifications were
+ // enabled.
+ if ((pNotificationClass != NULL) && pNotificationClass->CustomNotificationsEnabled())
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback3->CustomNotification(pThread, pAppDomain);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_CREATE_APP_DOMAIN:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100,
+ "RCET::HRCE: create appdomain on thread %#x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+
+ // Enumerate may have prepopulated the appdomain, so check if it already exists.
+ // Either way, still send the CreateEvent. (We don't want to skip the Create event
+ // just because the debugger did an enumerate)
+ // We remove AppDomains from the hash as soon as they are exited.
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->AppDomainData.vmAppDomain));
+ _ASSERTE(pAppDomain != NULL); // throws on failure
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->CreateAppDomain(this, pAppDomain);
+ }
+ }
+
+
+ break;
+
+ case DB_IPCE_EXIT_APP_DOMAIN:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100, "RCET::HRCE: exit appdomain on thread %#x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->vmAppDomain));
+
+ // In debug-only builds, assert that the appdomain is indeed deleted and not discoverable.
+ INDEBUG(DbgAssertAppDomainDeleted(pEvent->vmAppDomain));
+
+ // If we get an ExitAD message for which we have no AppDomain, then ignore it.
+ // This can happen if an AD gets torn down very early (before the LS AD is to the
+ // point that it can be published).
+ // This could also happen if we attach a debugger right before the Exit event is sent.
+ // In this case, the debuggee is no longer publishing the appdomain.
+ if (pAppDomain == NULL)
+ {
+ break;
+ }
+ _ASSERTE (pAppDomain != NULL);
+
+ // See if this is the default AppDomain exiting. This should only happen very late in
+ // the shutdown cycle, and so we shouldn't do anything significant with m_pDefaultDomain==NULL.
+ // We should try and remove m_pDefaultDomain entirely since we can't count on it always existing.
+ if (pAppDomain == m_pDefaultAppDomain)
+ {
+ m_pDefaultAppDomain = NULL;
+ }
+
+ // Update any threads which were last seen in this AppDomain. We don't
+ // get any notification when a thread leaves an AppDomain, so our idea
+ // of what AppDomain the thread is in may be out of date.
+ UpdateThreadsForAdUnload( pAppDomain );
+
+ // This will still maintain weak references so we could call Continue.
+ AddToNeuterOnContinueList(pAppDomain);
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->ExitAppDomain(this, pAppDomain);
+ }
+
+ // @dbgtodo appdomain: This should occur before the callback.
+ // Even after ExitAppDomain, the outside world will want to continue calling
+ // Continue (and thus they may need to call CordbAppDomain::GetProcess(), which Neutering
+ // would clear). Thus we can't neuter yet.
+
+ // Remove this app domain. This means any attempt to lookup the AppDomain
+ // will fail (which we do at the top of this method). Since any threads (incorrectly) referring
+ // to this AppDomain have been moved to the default AppDomain, no one should be
+ // interested in looking this AppDomain up anymore.
+ m_appDomains.RemoveBase(VmPtrToCookie(pEvent->vmAppDomain));
+ }
+
+ break;
+
+ case DB_IPCE_LOAD_ASSEMBLY:
+ {
+ LOG((LF_CORDB, LL_INFO100,
+ "RCET::HRCE: load assembly on thread %#x Asm:0x%08x AD:0x%08x \n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->AssemblyData.vmDomainAssembly),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ // Determine if this Assembly is cached.
+ CordbAssembly * pAssembly = pAppDomain->LookupOrCreateAssembly(pEvent->AssemblyData.vmDomainAssembly);
+ _ASSERTE(pAssembly != NULL); // throws on error
+
+ // If created, or have, an Assembly, notify callback.
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->LoadAssembly(pAppDomain, pAssembly);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_UNLOAD_ASSEMBLY:
+ {
+ LOG((LF_CORDB, LL_INFO100, "RCET::DRCE: unload assembly on thread %#x Asm:0x%x AD:0x%x\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->AssemblyData.vmDomainAssembly),
+ VmPtrToCookie(pEvent->vmAppDomain)));
+
+ _ASSERTE (pAppDomain != NULL);
+
+ CordbAssembly * pAssembly = pAppDomain->LookupOrCreateAssembly(pEvent->AssemblyData.vmDomainAssembly);
+
+ if (pAssembly == NULL)
+ {
+ // No assembly. This could happen if we attach right before an unload event is sent.
+ return;
+ }
+ _ASSERTE(pAssembly != NULL);
+ INDEBUG(pAssembly->DbgAssertAssemblyDeleted());
+
+ // Ensure the assembly gets neutered when we call continue.
+ AddToNeuterOnContinueList(pAssembly); // throws
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->UnloadAssembly(pAppDomain, pAssembly);
+ }
+
+ pAppDomain->RemoveAssemblyFromCache(pEvent->AssemblyData.vmDomainAssembly);
+ }
+
+ break;
+
+ case DB_IPCE_FUNC_EVAL_COMPLETE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::DRCE: func eval complete.\n"));
+
+ CordbEval *pEval = NULL;
+ {
+ pEval = pEvent->FuncEvalComplete.funcEvalKey.UnWrapAndRemove(this);
+ if (pEval == NULL)
+ {
+ _ASSERTE(!"Bogus FuncEval handle in IPC block.");
+ // Bogus handle in IPC block.
+ break;
+ }
+ }
+ _ASSERTE(pEval != NULL);
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ CONSISTENCY_CHECK_MSGF(pEval->m_DbgAppDomainStarted == pAppDomain,
+ ("AppDomain changed from Func-Eval. Eval=%p, Started=%p, Now=%p\n",
+ pEval, pEval->m_DbgAppDomainStarted, (void*) pAppDomain));
+
+ // Hold the data about the result in the CordbEval for later.
+ pEval->m_complete = true;
+ pEval->m_successful = !!pEvent->FuncEvalComplete.successful;
+ pEval->m_aborted = !!pEvent->FuncEvalComplete.aborted;
+ pEval->m_resultAddr = pEvent->FuncEvalComplete.resultAddr;
+ pEval->m_vmObjectHandle = pEvent->FuncEvalComplete.vmObjectHandle;
+ pEval->m_resultType = pEvent->FuncEvalComplete.resultType;
+ pEval->m_resultAppDomainToken = pEvent->FuncEvalComplete.vmAppDomain;
+
+ CordbAppDomain *pResultAppDomain = LookupOrCreateAppDomain(pEvent->FuncEvalComplete.vmAppDomain);
+
+ _ASSERTE(OutstandingEvalCount() > 0);
+ DecrementOutstandingEvalCount();
+
+ CONSISTENCY_CHECK_MSGF(pEval->m_DbgAppDomainStarted == pAppDomain,
+ ("AppDomain changed from Func-Eval. Eval=%p, Started=%p, Now=%p\n",
+ pEval, pEval->m_DbgAppDomainStarted, (void*) pAppDomain));
+
+ // If we did this func eval with this thread stopped at an excpetion, then we need to pretend as if we
+ // really didn't continue from the exception, since, of course, we really didn't on the Left Side.
+ if (pEval->IsEvalDuringException())
+ {
+ pThread->SetExInfo(pEval->m_vmThreadOldExceptionHandle);
+ }
+
+ bool fEvalCompleted = pEval->m_successful || pEval->m_aborted;
+
+ // If a CallFunction() is aborted, the LHS may not complete the abort
+ // immediately and hence we cant do a SendCleanup() at that point. Also,
+ // the debugger may (incorrectly) release the CordbEval before this
+ // DB_IPCE_FUNC_EVAL_COMPLETE event is received. Hence, we maintain an
+ // extra ref-count to determine when this can be done.
+ // Note that this can cause a two-way DB_IPCE_FUNC_EVAL_CLEANUP event
+ // to be sent. Hence, it has to be done before the Continue (see issue 102745).
+
+
+ // Note that if the debugger has already (incorrectly) released the CordbEval,
+ // pEval will be pointing to garbage and should not be used by the debugger.
+ if (fEvalCompleted)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, eval=0x%p. (Complete)", pThread, pEval);
+ pCallback1->EvalComplete(pResultAppDomain, pThread, pEval);
+ }
+ else
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "pThread=0x%p, eval=0x%p. (Exception)", pThread, pEval);
+ pCallback1->EvalException(pResultAppDomain, pThread, pEval);
+ }
+
+ // This release may send an DB_IPCE_FUNC_EVAL_CLEANUP IPC event. That's ok b/c
+ // we're still synced even if if Continue was called inside the callback.
+ // That's because the StopContinueHolder bumped up the stopcount.
+ // Corresponding AddRef() in CallFunction().
+ // @todo - this is leaked if we don't get an EvalComplete event (eg, process exits with
+ // in middle of func-eval).
+ pEval->Release();
+ }
+ break;
+
+
+ case DB_IPCE_NAME_CHANGE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::HRCE: Name Change %d 0x%p\n",
+ dwVolatileThreadId,
+ VmPtrToCookie(pEvent->NameChange.vmAppDomain)));
+
+ pThread = NULL;
+ pAppDomain.Clear();
+ if (pEvent->NameChange.eventType == THREAD_NAME_CHANGE)
+ {
+ // Lookup the CordbThread that matches this runtime thread.
+ if (!pEvent->NameChange.vmThread.IsNull())
+ {
+ pThread = LookupOrCreateThread(pEvent->NameChange.vmThread);
+ }
+ }
+ else
+ {
+ _ASSERTE (pEvent->NameChange.eventType == APP_DOMAIN_NAME_CHANGE);
+ pAppDomain.Assign(LookupOrCreateAppDomain(pEvent->NameChange.vmAppDomain));
+ if (pAppDomain)
+ {
+ pAppDomain->InvalidateName();
+ }
+ }
+
+ if (pThread || pAppDomain)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback1->NameChange(pAppDomain, pThread);
+ }
+ }
+
+ break;
+
+ case DB_IPCE_UPDATE_MODULE_SYMS:
+ {
+ RSExtSmartPtr<IStream> pStream;
+
+ // Find the app domain the module lives in.
+ _ASSERTE (pAppDomain != NULL);
+
+ // Find the Right Side module for this module.
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->UpdateModuleSymsData.vmDomainFile);
+ _ASSERTE(pModule != NULL);
+
+ // This is a legacy event notification for updated PDBs.
+ // Creates a new IStream object. Ownership is handed off via callback.
+ IDacDbiInterface::SymbolFormat symFormat = pModule->GetInMemorySymbolStream(&pStream);
+
+ // We shouldn't get this event if there aren't PDB symbols waiting. Specifically we don't want
+ // to incur the cost of copying over ILDB symbols here without the debugger asking for them.
+ // Eventually we may remove this callback as well and always rely on explicit requests.
+ _ASSERTE(symFormat == IDacDbiInterface::kSymbolFormatPDB);
+
+ if (symFormat == IDacDbiInterface::kSymbolFormatPDB)
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+
+ _ASSERTE(pStream != NULL); // Shouldn't send the event if we don't have a stream.
+
+ pCallback1->UpdateModuleSymbols(pAppDomain, pModule, pStream);
+ }
+
+ }
+ break;
+
+ case DB_IPCE_MDA_NOTIFICATION:
+ {
+ RSInitHolder<CordbMDA> pMDA(new CordbMDA(this, &pEvent->MDANotification)); // throws
+
+ // Ctor leaves both internal + ext Ref at 0, adding to neuter list bumps int-ref up to 1.
+ // Neutering will dump it back down to zero.
+ this->AddToNeuterOnExitList(pMDA);
+
+ // We bump up and down the external ref so that even if the callback doensn't touch the refs,
+ // our Ext-Release here will still cause a 1->0 ext-ref transition, which will get it
+ // swept on the neuter list.
+ RSExtSmartPtr<ICorDebugMDA> pExternalMDARef;
+ pMDA.TransferOwnershipExternal(&pExternalMDARef);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+
+ pCallback2->MDANotification(
+ this,
+ pThread, // may be null
+ pExternalMDARef);
+
+ // pExternalMDARef's dtor will do an external release,
+ // which is very significant because it may be the one that does the 1->0 ext ref transition,
+ // which may mean cause the "NeuterAtWill" bit to get flipped on this CordbMDA object.
+ // Since this is an external release, do it in the PUBLIC_CALLBACK scope.
+ pExternalMDARef.Clear();
+ }
+
+ break;
+ }
+
+ case DB_IPCE_CONTROL_C_EVENT:
+ {
+ hr = S_FALSE;
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ hr = pCallback1->ControlCTrap((ICorDebugProcess*) this);
+ }
+
+ {
+ RSLockHolder ch(this->GetStopGoLock());
+
+ DebuggerIPCEvent eventControlCResult;
+
+ InitIPCEvent(&eventControlCResult,
+ DB_IPCE_CONTROL_C_EVENT_RESULT,
+ false,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate whether the debugger has handled the event.
+ eventControlCResult.hr = hr;
+
+ // Send the reply to the LS.
+ SendIPCEvent(&eventControlCResult, sizeof(eventControlCResult));
+ } // release SG lock
+
+ }
+ break;
+
+ // EnC Remap opportunity
+ case DB_IPCE_ENC_REMAP:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC Remap!.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(NULL != pAppDomain);
+
+ CordbModule * pModule = pAppDomain->LookupOrCreateModule(pEvent->EnCRemap.vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ CordbFunction * pCurFunction = NULL;
+ CordbFunction * pResumeFunction = NULL;
+
+ // lookup the version of the function that we are mapping from
+ // this is the one that is currently running
+ pCurFunction = pModule->LookupOrCreateFunction(
+ pEvent->EnCRemap.funcMetadataToken, pEvent->EnCRemap.currentVersionNumber);
+
+ // lookup the version of the function that we are mapping to
+ // it will always be the most recent
+ pResumeFunction = pModule->LookupOrCreateFunction(
+ pEvent->EnCRemap.funcMetadataToken, pEvent->EnCRemap.resumeVersionNumber);
+
+ _ASSERTE(pCurFunction->GetEnCVersionNumber() < pResumeFunction->GetEnCVersionNumber());
+
+ RSSmartPtr<CordbFunction> pRefCurFunction(pCurFunction);
+ RSSmartPtr<CordbFunction> pRefResumeFunction(pResumeFunction);
+
+ // Verify we're not about to overwrite an outstanding remap IP
+ // This should only be set while a remap opportunity is being handled,
+ // and cleared (by CordbThread::MarkStackFramesDirty) on Continue.
+ // We want to be absolutely sure we don't accidentally keep a stale pointer
+ // around because it would point to arbitrary stack space in the CLR potentially
+ // leading to stack corruption.
+ _ASSERTE( pThread->m_EnCRemapFunctionIP == NULL );
+
+ // Stash the address of the remap IP buffer. This indicates that calling
+ // RemapFunction is valid and provides a communications channel between the RS
+ // and LS for the remap IL offset.
+ pThread->m_EnCRemapFunctionIP = pEvent->EnCRemap.resumeILOffset;
+
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->FunctionRemapOpportunity(
+ pAppDomain,
+ pThread,
+ pCurFunction,
+ pResumeFunction,
+ (ULONG32)pEvent->EnCRemap.currentILOffset);
+ }
+
+ // Implicit release on pCurFunction and pResumeFunction.
+ }
+ break;
+
+ // EnC Remap complete
+ case DB_IPCE_ENC_REMAP_COMPLETE:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::DRCE: EnC Remap Complete!.\n",
+ GetCurrentThreadId()));
+
+ _ASSERTE(NULL != pAppDomain);
+
+ CordbModule* pModule = pAppDomain->LookupOrCreateModule(pEvent->EnCRemap.vmDomainFile);
+ PREFIX_ASSUME(pModule != NULL);
+
+ // Find the function we're remapping to, which must be the latest version
+ CordbFunction *pRemapFunction=
+ pModule->LookupFunctionLatestVersion(pEvent->EnCRemapComplete.funcMetadataToken);
+ PREFIX_ASSUME(pRemapFunction != NULL);
+
+ // Dispatch the FunctionRemapComplete callback
+ RSSmartPtr<CordbFunction> pRef(pRemapFunction);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE(this, pLockHolder, pEvent);
+ pCallback2->FunctionRemapComplete(pAppDomain, pThread, pRemapFunction);
+ }
+ // Implicit release on pRemapFunction via holder
+ }
+ break;
+
+ case DB_IPCE_BREAKPOINT_SET_ERROR:
+ {
+ LOG((LF_CORDB, LL_INFO1000, "RCET::DRCE: breakpoint set error.\n"));
+
+ RSSmartPtr<CordbBreakpoint> pRef;
+
+ _ASSERTE(pThread != NULL);
+ _ASSERTE(pAppDomain != NULL);
+
+ // Find the breakpoint object on this side.
+ CordbBreakpoint * pBreakpoint = NULL;
+
+
+ if (pThread == NULL)
+ {
+ // We've found cases out in the wild where we get this event on a thread we don't recognize.
+ // We're not sure how this happens. Add a runtime check to protect ourselves to avoid the
+ // an AV. We still assert because this should not be happening.
+ // It likely means theres some issue where we failed to send a CreateThread notification.
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "BreakpointSetError on unrecognized thread. %p\n", pBreakpoint);
+
+ _ASSERTE(!"Missing thread on bp set error");
+ break;
+ }
+
+ pBreakpoint = pAppDomain->m_breakpoints.GetBase(LsPtrToCookie(pEvent->BreakpointSetErrorData.breakpointToken));
+
+ if (pBreakpoint != NULL)
+ {
+ ICorDebugBreakpoint * pIBreakpoint = CordbBreakpointToInterface(pBreakpoint);
+ _ASSERTE(pIBreakpoint != NULL);
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE2(this, pLockHolder, pEvent, "thread=0x%p, bp=0x%p", pThread, pBreakpoint);
+ pCallback1->BreakpointSetError(pAppDomain, pThread, pIBreakpoint, 0);
+ }
+ }
+ // Implicit release on pRef.
+ }
+ break;
+
+
+ case DB_IPCE_EXCEPTION_CALLBACK2:
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO100,
+ "RCET::DRCE: Exception2 0x%p 0x%X 0x%X 0x%X\n",
+ pEvent->ExceptionCallback2.framePointer.GetSPValue(),
+ pEvent->ExceptionCallback2.nOffset,
+ pEvent->ExceptionCallback2.eventType,
+ pEvent->ExceptionCallback2.dwFlags
+ );
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This could be a thread that
+ // has never run any managed code, so let's just ignore the exception. We should have
+ // already sent a log message about this situation for the EXCEPTION callback above.
+ _ASSERTE( pEvent->ExceptionCallback2.eventType == DEBUG_EXCEPTION_UNHANDLED );
+ break;
+ }
+
+ pThread->SetExInfo(pEvent->ExceptionCallback2.vmExceptionHandle);
+
+ //
+ // Send all the information back to the debugger.
+ //
+ RSSmartPtr<CordbFrame> pFrame;
+
+ FramePointer fp = pEvent->ExceptionCallback2.framePointer;
+ if (fp != LEAF_MOST_FRAME)
+ {
+ // The interface forces us to to pass a FramePointer via an ICorDebugFrame.
+ // However, we can't get a real ICDFrame without a stackwalk, and we don't
+ // want to do a stackwalk now. so pass a netuered proxy frame. The shim
+ // can map this to a real frame.
+ // See comments at CordbPlaceHolderFrame class for details.
+ pFrame.Assign(new CordbPlaceholderFrame(this, fp));
+ }
+
+ CorDebugExceptionCallbackType type = pEvent->ExceptionCallback2.eventType;
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE3(this, pLockHolder, pEvent, "pThread=0x%p, frame=%p, type=%d", pThread, (ICorDebugFrame*) pFrame, type);
+ hr = pCallback2->Exception(
+ pThread->m_pAppDomain,
+ pThread,
+ pFrame,
+ (ULONG32)(pEvent->ExceptionCallback2.nOffset),
+ type,
+ pEvent->ExceptionCallback2.dwFlags);
+ }
+ }
+ break;
+
+ case DB_IPCE_EXCEPTION_UNWIND:
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO100,
+ "RCET::DRCE: Exception Unwind 0x%X 0x%X\n",
+ pEvent->ExceptionCallback2.eventType,
+ pEvent->ExceptionCallback2.dwFlags
+ );
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This probably should never
+ // happen (if it's unwinding, then we expect a managed frame on the stack, and so we should
+ // know about the thread), but if it does fall back to ignoring the exception.
+ _ASSERTE( !"Got unwind event for unknown exception" );
+ break;
+ }
+
+ //
+ // Send all the information back to the debugger.
+ //
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "pThread=0x%p", pThread);
+ hr = pCallback2->ExceptionUnwind(
+ pThread->m_pAppDomain,
+ pThread,
+ pEvent->ExceptionUnwind.eventType,
+ pEvent->ExceptionUnwind.dwFlags);
+ }
+ }
+ break;
+
+
+ case DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE:
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO100, "RCET::DRCE: Exception Interception Complete.\n");
+
+ if (pThread == NULL)
+ {
+ // We've got an exception on a thread we don't know about. This probably should never
+ // happen (if it's unwinding, then we expect a managed frame on the stack, and so we should
+ // know about the thread), but if it does fall back to ignoring the exception.
+ _ASSERTE( !"Got complete event for unknown exception" );
+ break;
+ }
+
+ //
+ // Tell the debugger that the exception has been intercepted. This is similar to the
+ // notification we give when we start unwinding for a non-intercepted exception, except that the
+ // interception has been completed at this point, which means that we are conceptually at the end
+ // of the second pass.
+ //
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE1(this, pLockHolder, pEvent, "pThread=0x%p", pThread);
+ hr = pCallback2->ExceptionUnwind(
+ pThread->m_pAppDomain,
+ pThread,
+ DEBUG_EXCEPTION_INTERCEPTED,
+ 0);
+ }
+ }
+ break;
+#ifdef TEST_DATA_CONSISTENCY
+ case DB_IPCE_TEST_CRST:
+ {
+ EX_TRY
+ {
+ // the left side has signaled that we should test whether pEvent->TestCrstData.vmCrst is held
+ GetDAC()->TestCrst(pEvent->TestCrstData.vmCrst);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (pEvent->TestCrstData.fOkToTake)
+ {
+ _ASSERTE(hr == S_OK);
+ if (hr != S_OK)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ else // the lock was already held
+ {
+ // see if we threw because the lock was held
+ _ASSERTE(hr == CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
+ if (hr != CORDBG_E_PROCESS_NOT_SYNCHRONIZED)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+
+ }
+ break;
+
+ case DB_IPCE_TEST_RWLOCK:
+ {
+ EX_TRY
+ {
+ // the left side has signaled that we should test whether pEvent->TestRWLockData.vmRWLock is held
+ GetDAC()->TestRWLock(pEvent->TestRWLockData.vmRWLock);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (pEvent->TestRWLockData.fOkToTake)
+ {
+ _ASSERTE(hr == S_OK);
+ if (hr != S_OK)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ else // the lock was already held
+ {
+ // see if we threw because the lock was held
+ _ASSERTE(hr == CORDBG_E_PROCESS_NOT_SYNCHRONIZED);
+ if (hr != CORDBG_E_PROCESS_NOT_SYNCHRONIZED)
+ {
+ // we want to catch this in retail builds too
+ ThrowHR(E_FAIL);
+ }
+ }
+ }
+ break;
+#endif
+
+ default:
+ _ASSERTE(!"Unknown event");
+ LOG((LF_CORDB, LL_INFO1000,
+ "[%x] RCET::HRCE: Unknown event: 0x%08x\n",
+ GetCurrentThreadId(), pEvent->type));
+ }
+
+
+ FinishEventDispatch();
+}
+#ifdef _PREFAST_
+#pragma warning(pop)
+#endif
+
+//---------------------------------------------------------------------------------------
+// Callback for prepopulating threads.
+//
+// Arugments:
+// vmThread - thread as part of the eunmeration.
+// pUserData - data supplied with callback. It's a CordbProcess* object.
+//
+
+// static
+void CordbProcess::ThreadEnumerationCallback(VMPTR_Thread vmThread, void * pUserData)
+{
+ CordbProcess * pThis = reinterpret_cast<CordbProcess *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pThis);
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "ThreadEnumerationCallback()\n");
+
+ // Do lookup / lazy-create.
+ pThis->LookupOrCreateThread(vmThread);
+}
+
+//---------------------------------------------------------------------------------------
+// Fully build up the CordbThread cache to match VM state.
+void CordbProcess::PrepopulateThreadsOrThrow()
+{
+ RSLockHolder lockHolder(GetProcessLock());
+ if (IsDacInitialized())
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "PrepopulateThreadsOrThrow()\n");
+ GetDAC()->EnumerateThreads(ThreadEnumerationCallback, this);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Create a Thread enumerator
+//
+// Arguments:
+// pOwnerObj - object (a CordbProcess or CordbThread) that will own the enumerator.
+// pOwnerList - the neuter list that the enumerator will live on
+// pHolder - an outparameter for the enumerator to be initialized.
+//
+void CordbProcess::BuildThreadEnum(CordbBase * pOwnerObj, NeuterList * pOwnerList, RSInitHolder<CordbHashTableEnum> * pHolder)
+{
+ CordbHashTableEnum::BuildOrThrow(
+ pOwnerObj,
+ pOwnerList,
+ &m_userThreads,
+ IID_ICorDebugThreadEnum,
+ pHolder);
+}
+
+// Public implementation of ICorDebugProcess::EnumerateThreads
+HRESULT CordbProcess::EnumerateThreads(ICorDebugThreadEnum **ppThreads)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ if (m_detached)
+ {
+ // #Detach_Check:
+ //
+ // FUTURE: Consider adding this IF block to the PUBLIC_API macros so that
+ // typical public APIs fail quickly if we're trying to do a detach. For
+ // now, I'm hand-adding this check only to the few problematic APIs that get
+ // called while queuing the fake attach events. In these cases, it is not
+ // enough to check if CordbProcess::IsNeutered(), as the detaching thread
+ // may have begun the detaching and neutering process, but not be
+ // finished--in which case m_detached is true, but
+ // CordbProcess::IsNeutered() is still false.
+ ThrowHR(CORDBG_E_PROCESS_DETACHED);
+ }
+
+ ValidateOrThrow(ppThreads);
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ InternalEnumerateThreads(pEnum.GetAddr());
+
+ pEnum.TransferOwnershipExternal(ppThreads);
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+// Internal implementation of EnumerateThreads
+VOID CordbProcess::InternalEnumerateThreads(RSInitHolder<CordbHashTableEnum> *ppThreads)
+{
+ INTERNAL_API_ENTRY(this);
+ // Needs to prepopulate
+ PrepopulateThreadsOrThrow();
+ BuildThreadEnum(this, this->GetContinueNeuterList(), ppThreads);
+}
+
+// Implementation of ICorDebugProcess::GetThread
+HRESULT CordbProcess::GetThread(DWORD dwThreadId, ICorDebugThread **ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **);
+
+ // No good pre-existing ATT_* contract for this.
+ // Because for legacy, we have to allow this on the win32 event thread.
+ *ppThread = NULL;
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ if (m_detached)
+ {
+ // See code:CordbProcess::EnumerateThreads#Detach_Check
+ ThrowHR(CORDBG_E_PROCESS_DETACHED);
+ }
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+ if (pThread == NULL)
+ {
+ // This is a common case because we may be looking up an unmanaged thread.
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ *ppThread = static_cast<ICorDebugThread*> (pThread);
+ pThread->ExternalAddRef();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::GT returns id=0x%x hr=0x%x ppThread=0x%p",
+ dwThreadId, hr, *ppThread));
+ return hr;
+}
+
+HRESULT CordbProcess::ThreadForFiberCookie(DWORD fiberCookie,
+ ICorDebugThread **ppThread)
+{
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::GetHelperThreadID(DWORD *pThreadID)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ _ASSERTE(m_pShim != NULL);
+ if (pThreadID == NULL)
+ {
+ return (E_INVALIDARG);
+ }
+
+ HRESULT hr = S_OK;
+ // Return the ID of the current helper thread. There may be no thread in the process, or there may be a true helper
+ // thread.
+ if ((m_helperThreadId != 0) && !m_helperThreadDead)
+ {
+ *pThreadID = m_helperThreadId;
+ }
+ else if ((GetDCB() != NULL) && (GetDCB()->m_helperThreadId != 0))
+ {
+ EX_TRY
+ {
+ // be sure we have the latest information
+ UpdateRightSideDCB();
+ *pThreadID = GetDCB()->m_helperThreadId;
+ }
+ EX_CATCH_HRESULT(hr);
+
+ }
+ else
+ {
+ *pThreadID = 0;
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Sends IPC event to set all the managed threads, except for the one given, to the given state
+//
+// Arguments:
+// state - The state to set the threads to.
+// pExceptThread - The thread to not set. This is usually the thread that is currently
+// sending an IPC event to the RS, and should be excluded.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+HRESULT CordbProcess::SetAllThreadsDebugState(CorDebugThreadState state,
+ ICorDebugThread * pExceptThread)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_OR_NULL(pExceptThread, ICorDebugThread *);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ if (GetShim() == NULL)
+ {
+ return E_NOTIMPL;
+ }
+ CordbThread * pCordbExceptThread = static_cast<CordbThread *> (pExceptThread);
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::SATDS: except thread=0x%08x 0x%x\n",
+ pExceptThread,
+ (pCordbExceptThread != NULL) ? pCordbExceptThread->m_id : 0));
+
+ // Send one event to the Left Side to twiddle each thread's state.
+ DebuggerIPCEvent event;
+
+ InitIPCEvent(&event, DB_IPCE_SET_ALL_DEBUG_STATE, true, VMPTR_AppDomain::NullPtr());
+
+ event.SetAllDebugState.vmThreadToken = ((pCordbExceptThread != NULL) ?
+ pCordbExceptThread->m_vmThreadToken : VMPTR_Thread::NullPtr());
+
+ event.SetAllDebugState.debugState = state;
+
+ HRESULT hr = SendIPCEvent(&event, sizeof(DebuggerIPCEvent));
+
+ hr = WORST_HR(hr, event.hr);
+
+ // If that worked, then loop over all the threads on this side and set their states.
+ if (SUCCEEDED(hr))
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+ HASHFIND hashFind;
+ CordbThread * pThread;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (pThread = m_userThreads.FindFirst(&hashFind);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&hashFind))
+ {
+ if (pThread != pCordbExceptThread)
+ {
+ pThread->m_debugState = state;
+ }
+ }
+ }
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::EnumerateObjects(ICorDebugObjectEnum **ppObjects)
+{
+ /* !!! */
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObjects, ICorDebugObjectEnum **);
+
+ return E_NOTIMPL;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Determines if the target address is a "CLR transition stub".
+//
+// Arguments:
+// address - The address of an instruction to check in the target address space.
+// pfTransitionStub - Space to store the result, TRUE if the address belongs to a
+// transition stub, FALSE if not. Only valid if this method returns a success code.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::IsTransitionStub(CORDB_ADDRESS address, BOOL *pfTransitionStub)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pfTransitionStub, BOOL *);
+
+ // Default to FALSE
+ *pfTransitionStub = FALSE;
+
+ if (this->m_helperThreadDead)
+ {
+ return S_OK;
+ }
+
+ // If we're not initialized, then it can't be a stub...
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ DebuggerIPCEvent eventData;
+
+ InitIPCEvent(&eventData, DB_IPCE_IS_TRANSITION_STUB, true, VMPTR_AppDomain::NullPtr());
+
+ eventData.IsTransitionStub.address = CORDB_ADDRESS_TO_PTR(address);
+
+ hr = SendIPCEvent(&eventData, sizeof(eventData));
+ hr = WORST_HR(hr, eventData.hr);
+ IfFailThrow(hr);
+
+ _ASSERTE(eventData.type == DB_IPCE_IS_TRANSITION_STUB_RESULT);
+
+ *pfTransitionStub = eventData.IsTransitionStubResult.isStub;
+ LOG((LF_CORDB, LL_INFO1000, "CP::ITS: addr=0x%p result=%d\n", address, *pfTransitionStub));
+ // @todo - beware that IsTransitionStub has a very important sideeffect - it synchronizes the runtime!
+ // This for example covers an OS bug where SetThreadContext may silently fail if we're not synchronized.
+ // (See IMDArocess::SetThreadContext for details on that bug).
+ // If we ever stop using IPC events here and only use DAC; we need to be aware of that.
+
+ // Check against DAC primitives
+ {
+ BOOL fIsStub2 = GetDAC()->IsTransitionStub(address);
+ (void)fIsStub2; //prevent "unused variable" error from GCC
+ CONSISTENCY_CHECK_MSGF(*pfTransitionStub == fIsStub2, ("IsStub2 failed, DAC2:%d, IPC:%d, addr:0x%p", (int) fIsStub2, (int) *pfTransitionStub, CORDB_ADDRESS_TO_PTR(address)));
+
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if(FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::ITS: FAILED hr=0x%x\n", hr));
+ }
+ return hr;
+}
+
+
+HRESULT CordbProcess::SetStopState(DWORD threadID, CorDebugThreadState state)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ return E_NOTIMPL;
+}
+
+HRESULT CordbProcess::IsOSSuspended(DWORD threadID, BOOL *pbSuspended)
+{
+ PUBLIC_API_ENTRY(this);
+ // Gotta have a place for the result!
+ if (!pbSuspended)
+ return E_INVALIDARG;
+
+ FAIL_IF_NEUTERED(this);
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // Have we seen this thread?
+ CordbUnmanagedThread *ut = GetUnmanagedThread(threadID);
+
+ // If we have, and if we've suspended it, then say so.
+ if (ut && ut->IsSuspended())
+ {
+ *pbSuspended = TRUE;
+ }
+ else
+ {
+ *pbSuspended = FALSE;
+ }
+#else
+ // Not interop-debugging, we never OS suspend.
+ *pbSuspended = FALSE;
+#endif
+ return S_OK;
+}
+
+//
+// This routine reads a thread context from the process being debugged, taking into account the fact that the context
+// record may be a different size than the one we compiled with. On systems < NT5, then OS doesn't usually allocate
+// space for the extended registers. However, the CONTEXT struct that we compile with does have this space.
+//
+HRESULT CordbProcess::SafeReadThreadContext(LSPTR_CONTEXT pContext, DT_CONTEXT * pCtx)
+{
+ HRESULT hr = S_OK;
+
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ EX_TRY
+ {
+
+ void *pRemoteContext = pContext.UnsafeGet();
+ TargetBuffer tbFull(pRemoteContext, sizeof(DT_CONTEXT));
+
+ // The context may have 2 parts:
+ // 1. Base register, which are always present.
+ // 2. Optional extended registers, which are only present if CONTEXT_EXTENDED_REGISTERS is set
+ // in the flags.
+
+ // At a minimum we have room for a whole context up to the extended registers.
+ #if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ ULONG32 minContextSize = offsetof(DT_CONTEXT, ExtendedRegisters);
+ #else
+ ULONG32 minContextSize = sizeof(DT_CONTEXT);
+ #endif
+
+ // Read the minimum part.
+ TargetBuffer tbMin = tbFull.SubBuffer(0, minContextSize);
+ SafeReadBuffer(tbMin, (BYTE*) pCtx);
+
+ #if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ void *pCurExtReg = (void*)((UINT_PTR)pCtx + minContextSize);
+ TargetBuffer tbExtended = tbFull.SubBuffer(minContextSize);
+
+ // Now, read the extended registers if the context contains them. If the context does not have extended registers,
+ // just set them to zero.
+ if (SUCCEEDED(hr) && (pCtx->ContextFlags & CONTEXT_EXTENDED_REGISTERS) == CONTEXT_EXTENDED_REGISTERS)
+ {
+ SafeReadBuffer(tbExtended, (BYTE*) pCurExtReg);
+ }
+ else
+ {
+ memset(pCurExtReg, 0, tbExtended.cbSize);
+ }
+ #endif
+
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//
+// This routine writes a thread context to the process being debugged, taking into account the fact that the context
+// record may be a different size than the one we compiled with. On systems < NT5, then OS doesn't usually allocate
+// space for the extended registers. However, the CONTEXT struct that we compile with does have this space.
+//
+HRESULT CordbProcess::SafeWriteThreadContext(LSPTR_CONTEXT pContext, const DT_CONTEXT * pCtx)
+{
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ HRESULT hr = S_OK;
+ DWORD sizeToWrite = sizeof(DT_CONTEXT);
+
+ BYTE * pRemoteContext = (BYTE*) pContext.UnsafeGet();
+ BYTE * pCtxSource = (BYTE*) pCtx;
+
+
+#if defined(DT_CONTEXT_EXTENDED_REGISTERS)
+ // If our context has extended registers, then write the whole thing. Otherwise, just write the minimum part.
+ if ((pCtx->ContextFlags & DT_CONTEXT_EXTENDED_REGISTERS) != DT_CONTEXT_EXTENDED_REGISTERS)
+ {
+ sizeToWrite = offsetof(DT_CONTEXT, ExtendedRegisters);
+ }
+#endif
+
+// 64 bit windows puts space for the first 6 stack parameters in the CONTEXT structure so that
+// kernel to usermode transitions don't have to allocate a CONTEXT and do a seperate sub rsp
+// to allocate stack spill space for the arguments. This means that writing to P1Home - P6Home
+// will overwrite the arguments of some function higher on the stack, very bad. Conceptually you
+// can think of these members as not being part of the context, ie they don't represent something
+// which gets saved or restored on context switches. They are just space we shouldn't overwrite.
+// See issue 630276 for more details.
+#if defined DBG_TARGET_AMD64
+ pRemoteContext += offsetof(CONTEXT, ContextFlags); // immediately follows the 6 parameters P1-P6
+ pCtxSource += offsetof(CONTEXT, ContextFlags);
+ sizeToWrite -= offsetof(CONTEXT, ContextFlags);
+#endif
+
+ EX_TRY
+ {
+ // Write the context.
+ TargetBuffer tb(pRemoteContext, sizeToWrite);
+ SafeWriteBuffer(tb, (const BYTE*) pCtxSource);
+ }
+ EX_CATCH_HRESULT(hr);
+
+ return hr;
+}
+
+
+HRESULT CordbProcess::GetThreadContext(DWORD threadID, ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+ DT_CONTEXT * pContext;
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x\n", threadID));
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ if (contextSize != sizeof(DT_CONTEXT))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x, context size is invalid.\n", threadID));
+ return E_INVALIDARG;
+ }
+
+ pContext = reinterpret_cast<DT_CONTEXT *>(context);
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(context, BYTE, contextSize, true, true);
+
+#if !defined(FEATURE_INTEROP_DEBUGGING)
+ return E_NOTIMPL;
+#else
+ // Find the unmanaged thread
+ CordbUnmanagedThread *ut = GetUnmanagedThread(threadID);
+
+ if (ut == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::GTC: thread=0x%x, thread id is invalid.\n", threadID));
+
+ return E_INVALIDARG;
+ }
+
+ return ut->GetThreadContext((DT_CONTEXT*)context);
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+// Public implementation of ICorDebugProcess::SetThreadContext.
+// @dbgtodo interop-debugging: this should go away in V3. Use the data-target instead. This is
+// interop-debugging aware (and cooperates with hijacks)
+HRESULT CordbProcess::SetThreadContext(DWORD threadID, ULONG32 contextSize, BYTE context[])
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+#if !defined(FEATURE_INTEROP_DEBUGGING)
+ return E_NOTIMPL;
+#else
+ HRESULT hr = S_OK;
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ CordbUnmanagedThread *ut = NULL;
+
+ if (contextSize != sizeof(DT_CONTEXT))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::STC: thread=0x%x, context size is invalid.\n", threadID));
+ hr = E_INVALIDARG;
+ goto Label_Done;
+ }
+
+ // @todo - could we look at the context flags and return E_INVALIDARG if they're bad?
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(context, BYTE, contextSize, true, true);
+
+ // Find the unmanaged thread
+ ut = GetUnmanagedThread(threadID);
+
+ if (ut == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::STC: thread=0x%x, thread is invalid.\n", threadID));
+ hr = E_INVALIDARG;
+ goto Label_Done;
+ }
+
+ hr = ut->SetThreadContext((DT_CONTEXT*)context);
+
+
+ // Update the register set for the leaf-unmanaged chain so that it's consistent w/ the context.
+ // We may not necessarily be synchronized, and so these frames may be stale. Even so, no harm done.
+ if (SUCCEEDED(hr))
+ {
+ // @dbgtodo stackwalk: this should all disappear with V3 stackwalker and getting rid of SetThreadContext.
+ EX_TRY
+ {
+ // Find the managed thread. Returns NULL if thread is not managed.
+ // If we don't have a thread prveiously cached, then there's no state to update.
+ CordbThread * pThread = TryLookupThreadByVolatileOSId(threadID);
+
+ if (pThread != NULL)
+ {
+ // In V2, we used to update the CONTEXT of the leaf chain if the chain is an unmanaged chain.
+ // In Arrowhead, we just force a cleanup of the stackwalk cache. This is a more correct
+ // thing to do anyway, since the CONTEXT being set could be anything.
+ pThread->CleanupStack();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+Label_Done:
+ return ErrWrapper(hr);
+
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+// @dbgtodo ICDProcess - When we DACize this function, we should use code:DacReplacePatches
+HRESULT CordbProcess::ReadMemory(CORDB_ADDRESS address,
+ DWORD size,
+ BYTE buffer[],
+ SIZE_T *read)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+ // A read of 0 bytes is okay.
+ if (size == 0)
+ return S_OK;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, size, true, true);
+ VALIDATE_POINTER_TO_OBJECT(buffer, SIZE_T *);
+
+ if (address == NULL)
+ return E_INVALIDARG;
+
+ // If no read parameter is supplied, we ignore it. This matches the semantics of kernel32!ReadProcessMemory.
+ SIZE_T dummyRead;
+ if (read == NULL)
+ {
+ read = &dummyRead;
+ }
+ *read = 0;
+
+ HRESULT hr = S_OK;
+
+ CORDBRequireProcessStateOK(this);
+
+ // Grab the memory we want to read
+ // Note that this will return success on a partial read
+ ULONG32 cbRead;
+ hr = GetDataTarget()->ReadVirtual(address, buffer, size, &cbRead);
+ if (FAILED(hr))
+ {
+ hr = CORDBG_E_READVIRTUAL_FAILURE;
+ goto LExit;
+ }
+
+ // Read at least one byte
+ *read = (SIZE_T) cbRead;
+
+ // There seem to be strange cases where ReadProcessMemory will return a seemingly negative number into *read, which
+ // is an unsigned value. So we check the sanity of *read by ensuring that its no bigger than the size we tried to
+ // read.
+ if ((*read > 0) && (*read <= size))
+ {
+ LOG((LF_CORDB, LL_INFO100000, "CP::RM: read %d bytes from 0x%08x, first byte is 0x%x\n",
+ *read, (DWORD)address, buffer[0]));
+
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ // If m_pPatchTable is NULL, then it's been cleaned out b/c of a Continue for the left side. Get the table
+ // again. Only do this, of course, if the managed state of the process is initialized.
+ if (m_pPatchTable == NULL)
+ {
+ hr = RefreshPatchTable(address, *read, buffer);
+ }
+ else
+ {
+ // The previously fetched table is still good, so run through it & see if any patches are applicable
+ hr = AdjustBuffer(address, *read, buffer, NULL, AB_READ);
+ }
+ }
+ }
+
+LExit:
+ if (FAILED(hr))
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearPatchTable();
+ }
+ else if (*read < size)
+ {
+ // Unlike the DT api, our API is supposed to return an error on partial read
+ hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return hr;
+}
+
+// Update patches & buffer to make the left-side's usage of patches transparent
+// to our client. Behavior depends on AB_MODE:
+// AB_READ:
+// - use the RS patch table structure to replace patch opcodes in buffer.
+// AB_WRITE:
+// - update the RS patch table structure w/ new replace-opcode values
+// if we've written over them. And put the int3 back in for write-memory.
+//
+// Note: If we're writing memory over top of a patch, then it must be JITted or stub code.
+// Writing over JITed or Stub code can be dangerous since the CLR may not expect it
+// (eg. JIT data structures about the code layout may be incorrect), but in certain
+// narrow cases it may be safe (eg. replacing a constant). VS says they wouldn't expect
+// this to work, but we'll keep the support in for legacy reasons.
+//
+// address, size - describe buffer in LS memory
+// buffer - local copy of buffer that will be read/written from/to LS.
+// bufferCopy - for writeprocessmemory, copy of original buffer (w/o injected patches)
+// pbUpdatePatchTable - flag if patchtable got dirty and needs to be updated.
+HRESULT CordbProcess::AdjustBuffer( CORDB_ADDRESS address,
+ SIZE_T size,
+ BYTE buffer[],
+ BYTE **bufferCopy,
+ AB_MODE mode,
+ BOOL *pbUpdatePatchTable)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ if ( address == NULL
+ || size == NULL
+ || buffer == NULL
+ || (mode != AB_READ && mode != AB_WRITE) )
+ return E_INVALIDARG;
+
+ if (pbUpdatePatchTable != NULL )
+ *pbUpdatePatchTable = FALSE;
+
+ // If we don't have a patch table loaded, then return S_OK since there are no patches to adjust
+ if (m_pPatchTable == NULL)
+ return S_OK;
+
+ //is the requested memory completely out-of-range?
+ if ((m_minPatchAddr > (address + (size - 1))) ||
+ (m_maxPatchAddr < address))
+ {
+ return S_OK;
+ }
+
+ // Without runtime offsets, we can't adjust - this should only ever happen on dumps, where there's
+ // no W32ET to get the offsets, and so they stay zeroed
+ if (!m_runtimeOffsetsInitialized)
+ return S_OK;
+
+ LOG((LF_CORDB,LL_INFO10000, "CordbProcess::AdjustBuffer at addr 0x%p\n", address));
+
+ if (mode == AB_WRITE)
+ {
+ // We don't want to mess up the original copy of the buffer, so
+ // for right now, just copy it wholesale.
+ (*bufferCopy) = new (nothrow) BYTE[size];
+ if (NULL == (*bufferCopy))
+ return E_OUTOFMEMORY;
+
+ memmove((*bufferCopy), buffer, size);
+ }
+
+ ULONG iNextFree = m_iFirstPatch;
+ while( iNextFree != DPT_TERMINATING_INDEX )
+ {
+ BYTE *DebuggerControllerPatch = m_pPatchTable + m_runtimeOffsets.m_cbPatch*iNextFree;
+ PRD_TYPE opcode = *(PRD_TYPE *)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode);
+ CORDB_ADDRESS patchAddress = PTR_TO_CORDB_ADDRESS(*(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr));
+
+ if (IsPatchInRequestedRange(address, size, patchAddress))
+ {
+ if (mode == AB_READ)
+ {
+ CORDbgSetInstructionEx(buffer, address, patchAddress, opcode, size);
+ }
+ else if (mode == AB_WRITE)
+ {
+ _ASSERTE( pbUpdatePatchTable != NULL );
+ _ASSERTE( bufferCopy != NULL );
+
+ //There can be multiple patches at the same address: we don't want 2nd+ patches to get the
+ // break opcode, so we read from the unmodified copy.
+ m_rgUncommitedOpcode[iNextFree] =
+ CORDbgGetInstructionEx(*bufferCopy, address, patchAddress, opcode, size);
+
+ //put the breakpoint into the memory itself
+ CORDbgInsertBreakpointEx(buffer, address, patchAddress, opcode, size);
+
+ *pbUpdatePatchTable = TRUE;
+ }
+ else
+ _ASSERTE( !"CordbProcess::AdjustBuffergiven non(Read|Write) mode!" );
+ }
+
+ iNextFree = m_rgNextPatch[iNextFree];
+ }
+
+ // If we created a copy of the buffer but didn't modify it, then free it now.
+ if( ( mode == AB_WRITE ) && ( !*pbUpdatePatchTable ) )
+ {
+ delete [] *bufferCopy;
+ *bufferCopy = NULL;
+ }
+
+ return S_OK;
+}
+
+
+void CordbProcess::CommitBufferAdjustments( CORDB_ADDRESS start,
+ CORDB_ADDRESS end )
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ ULONG iPatch = m_iFirstPatch;
+ while( iPatch != DPT_TERMINATING_INDEX )
+ {
+ BYTE *DebuggerControllerPatch = m_pPatchTable +
+ m_runtimeOffsets.m_cbPatch*iPatch;
+
+ BYTE *patchAddress = *(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr);
+
+ if (IsPatchInRequestedRange(start, (SIZE_T)(end - start), PTR_TO_CORDB_ADDRESS(patchAddress)) &&
+ !PRDIsBreakInst(&(m_rgUncommitedOpcode[iPatch])))
+ {
+ //copy this back to the copy of the patch table
+ *(PRD_TYPE *)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode) =
+ m_rgUncommitedOpcode[iPatch];
+ }
+
+ iPatch = m_rgNextPatch[iPatch];
+ }
+}
+
+void CordbProcess::ClearBufferAdjustments( )
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ ULONG iPatch = m_iFirstPatch;
+ while( iPatch != DPT_TERMINATING_INDEX )
+ {
+ InitializePRDToBreakInst(&(m_rgUncommitedOpcode[iPatch]));
+ iPatch = m_rgNextPatch[iPatch];
+ }
+}
+
+void CordbProcess::ClearPatchTable(void )
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ if (m_pPatchTable != NULL )
+ {
+ delete [] m_pPatchTable;
+ m_pPatchTable = NULL;
+
+ delete [] m_rgNextPatch;
+ m_rgNextPatch = NULL;
+
+ delete [] m_rgUncommitedOpcode;
+ m_rgUncommitedOpcode = NULL;
+
+ m_iFirstPatch = DPT_TERMINATING_INDEX;
+ m_minPatchAddr = MAX_ADDRESS;
+ m_maxPatchAddr = MIN_ADDRESS;
+ m_rgData = NULL;
+ m_cPatch = 0;
+ }
+}
+
+HRESULT CordbProcess::RefreshPatchTable(CORDB_ADDRESS address, SIZE_T size, BYTE buffer[])
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_initialized);
+ _ASSERTE(this->ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+ BYTE *rgb = NULL;
+
+ // All of m_runtimeOffsets will be zeroed out if there's been no call to code:CordbProcess::GetRuntimeOffsets.
+ // Thus for things to work, we'd have to have a live target that went and got the real values.
+ // For dumps, things are still all zeroed out because we don't have any events sent to the W32ET, don't
+ // have a live process to investigate, etc.
+ if (!m_runtimeOffsetsInitialized)
+ return S_OK;
+
+ _ASSERTE( m_runtimeOffsets.m_cbOpcode == sizeof(PRD_TYPE) );
+
+ CORDBRequireProcessStateOK(this);
+
+ if (m_pPatchTable == NULL )
+ {
+ // First, check to be sure the patch table is valid on the Left Side. If its not, then we won't read it.
+ BOOL fPatchTableValid = FALSE;
+
+ hr = SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_runtimeOffsets.m_pPatchTableValid), &fPatchTableValid);
+ if (FAILED(hr) || !fPatchTableValid)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ return S_OK;
+ }
+
+ SIZE_T offStart = 0;
+ SIZE_T offEnd = 0;
+ UINT cbTableSlice = 0;
+
+ // Grab the patch table info
+ offStart = min(m_runtimeOffsets.m_offRgData, m_runtimeOffsets.m_offCData);
+ offEnd = max(m_runtimeOffsets.m_offRgData, m_runtimeOffsets.m_offCData) + sizeof(SIZE_T);
+ cbTableSlice = (UINT)(offEnd - offStart);
+
+ if (cbTableSlice == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ return S_OK;
+ }
+
+ EX_TRY
+ {
+ rgb = new BYTE[cbTableSlice]; // throws
+
+ TargetBuffer tbSlice((BYTE*)m_runtimeOffsets.m_pPatches + offStart, cbTableSlice);
+ this->SafeReadBuffer(tbSlice, rgb); // Throws;
+
+ // Note that rgData is a pointer in the left side address space
+ m_rgData = *(BYTE**)(rgb + m_runtimeOffsets.m_offRgData - offStart);
+ m_cPatch = *(ULONG*)(rgb + m_runtimeOffsets.m_offCData - offStart);
+
+ // Grab the patch table
+ UINT cbPatchTable = (UINT)(m_cPatch * m_runtimeOffsets.m_cbPatch);
+
+ if (cbPatchTable == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Wont refresh patch table because its not valid now.\n"));
+ _ASSERTE(hr == S_OK);
+ goto LExit; // can't return since we're in a Try/Catch
+ }
+
+ // Throwing news
+ m_pPatchTable = new BYTE[ cbPatchTable ];
+ m_rgNextPatch = new ULONG[m_cPatch];
+ m_rgUncommitedOpcode = new PRD_TYPE[m_cPatch];
+
+ TargetBuffer tb(m_rgData, cbPatchTable);
+ this->SafeReadBuffer(tb, m_pPatchTable); // Throws
+
+ //As we go through the patch table we do a number of things:
+ //
+ // 1. collect min,max address seen for quick fail check
+ //
+ // 2. Link all valid entries into a linked list, the first entry of which is m_iFirstPatch
+ //
+ // 3. Initialize m_rgUncommitedOpcode, so that we can undo local patch table changes if WriteMemory can't write
+ // atomically.
+ //
+ // 4. If the patch is in the memory we grabbed, unapply it.
+
+ ULONG iDebuggerControllerPatchPrev = DPT_TERMINATING_INDEX;
+
+ m_minPatchAddr = MAX_ADDRESS;
+ m_maxPatchAddr = MIN_ADDRESS;
+ m_iFirstPatch = DPT_TERMINATING_INDEX;
+
+ for (ULONG iPatch = 0; iPatch < m_cPatch;iPatch++)
+ {
+ // <REVISIT_TODO>@todo port: we're making assumptions about the size of opcodes,address pointers, etc</REVISIT_TODO>
+ BYTE *DebuggerControllerPatch = m_pPatchTable + m_runtimeOffsets.m_cbPatch * iPatch;
+ PRD_TYPE opcode = *(PRD_TYPE*)(DebuggerControllerPatch + m_runtimeOffsets.m_offOpcode);
+ CORDB_ADDRESS patchAddress = PTR_TO_CORDB_ADDRESS(*(BYTE**)(DebuggerControllerPatch + m_runtimeOffsets.m_offAddr));
+
+ // A non-zero opcode indicates to us that this patch is valid.
+ if (!PRDIsEmpty(opcode))
+ {
+ _ASSERTE( patchAddress != 0 );
+
+ // (1), above
+ // Note that GetPatchEndAddr() returns the address immediately AFTER the patch,
+ // so we have to subtract 1 from it below.
+ if (m_minPatchAddr > patchAddress )
+ m_minPatchAddr = patchAddress;
+ if (m_maxPatchAddr < patchAddress )
+ m_maxPatchAddr = GetPatchEndAddr(patchAddress) - 1;
+
+ // (2), above
+ if ( m_iFirstPatch == DPT_TERMINATING_INDEX)
+ {
+ m_iFirstPatch = iPatch;
+ _ASSERTE( iPatch != DPT_TERMINATING_INDEX);
+ }
+
+ if (iDebuggerControllerPatchPrev != DPT_TERMINATING_INDEX)
+ {
+ m_rgNextPatch[iDebuggerControllerPatchPrev] = iPatch;
+ }
+
+ iDebuggerControllerPatchPrev = iPatch;
+
+ // (3), above
+ InitializePRDToBreakInst(&(m_rgUncommitedOpcode[iPatch]));
+
+ // (4), above
+ if (IsPatchInRequestedRange(address, size, patchAddress))
+ {
+ _ASSERTE( buffer != NULL );
+ _ASSERTE( size != NULL );
+
+
+ //unapply the patch here.
+ CORDbgSetInstructionEx(buffer, address, patchAddress, opcode, size);
+ }
+
+ }
+ }
+
+ if (iDebuggerControllerPatchPrev != DPT_TERMINATING_INDEX)
+ {
+ m_rgNextPatch[iDebuggerControllerPatchPrev] = DPT_TERMINATING_INDEX;
+ }
+ }
+LExit:
+ ;
+ EX_CATCH_HRESULT(hr);
+ }
+
+
+ if (rgb != NULL )
+ {
+ delete [] rgb;
+ }
+
+ if (FAILED( hr ) )
+ {
+ ClearPatchTable();
+ }
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Given an address, see if there is a patch in the patch table that matches it and return
+// if its an unmanaged patch or not.
+//
+// Arguments:
+// address - The address of an instruction to check in the target address space.
+// pfPatchFound - Space to store the result, TRUE if the address belongs to a
+// patch, FALSE if not. Only valid if this method returns a success code.
+// pfPatchIsUnmanaged - Space to store the result, TRUE if the address is a patch
+// and the patch is unmanaged, FALSE if not. Only valid if this method returns a
+// success code.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+// Note: this method is pretty in-efficient. It refreshes the patch table, then scans it.
+// Refreshing the patch table involves a scan, too, so this method could be folded
+// with that.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::FindPatchByAddress(CORDB_ADDRESS address, bool *pfPatchFound, bool *pfPatchIsUnmanaged)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE((pfPatchFound != NULL) && (pfPatchIsUnmanaged != NULL));
+ _ASSERTE(m_runtimeOffsetsInitialized);
+ FAIL_IF_NEUTERED(this);
+
+ *pfPatchFound = false;
+ *pfPatchIsUnmanaged = false;
+
+ // First things first. If the process isn't initialized, then there can be no patch table, so we know the breakpoint
+ // doesn't belong to the Runtime.
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ // This method is called from the main loop of the win32 event thread in response to a first chance breakpoint event
+ // that we know is not a flare. The process has been runnning, and it may have invalidated the patch table, so we'll
+ // flush it here before refreshing it to make sure we've got the right thing.
+ //
+ // Note: we really should have the Left Side mark the patch table dirty to help optimize this.
+ ClearPatchTable();
+
+ // Refresh the patch table.
+ HRESULT hr = RefreshPatchTable();
+
+ if (FAILED(hr))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: failed to refresh the patch table\n"));
+ return hr;
+ }
+
+ // If there is no patch table yet, then we know there is no patch at the given address, so return S_OK with
+ // *patchFound = false.
+ if (m_pPatchTable == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: no patch table\n"));
+ return S_OK;
+ }
+
+ // Scan the patch table for a matching patch.
+ for (ULONG iNextPatch = m_iFirstPatch; iNextPatch != DPT_TERMINATING_INDEX; iNextPatch = m_rgNextPatch[iNextPatch])
+ {
+ BYTE *patch = m_pPatchTable + (m_runtimeOffsets.m_cbPatch * iNextPatch);
+ BYTE *patchAddress = *(BYTE**)(patch + m_runtimeOffsets.m_offAddr);
+ DWORD traceType = *(DWORD*)(patch + m_runtimeOffsets.m_offTraceType);
+
+ if (address == PTR_TO_CORDB_ADDRESS(patchAddress))
+ {
+ *pfPatchFound = true;
+
+ if (traceType == m_runtimeOffsets.m_traceTypeUnmanaged)
+ {
+ *pfPatchIsUnmanaged = true;
+
+#if defined(_DEBUG)
+ HRESULT hrDac = S_OK;
+ EX_TRY
+ {
+ // We should be able to double check w/ DAC that this really is outside of the runtime.
+ IDacDbiInterface::AddressType addrType = GetDAC()->GetAddressType(address);
+ CONSISTENCY_CHECK_MSGF(addrType == IDacDbiInterface::kAddressUnrecognized, ("Bad address type = %d", addrType));
+ }
+ EX_CATCH_HRESULT(hrDac);
+ CONSISTENCY_CHECK_MSGF(SUCCEEDED(hrDac), ("DAC::GetAddressType failed, hr=0x%08x", hrDac));
+#endif
+ }
+
+ break;
+ }
+ }
+
+ // If we didn't find a patch, its actually still possible that this breakpoint exception belongs to us. There are
+ // races with very large numbers of threads entering the Runtime through the same managed function. We will have
+ // multiple threads adding and removing ref counts to an int 3 in the code stream. Sometimes, this count will go to
+ // zero and the int 3 will be removed, then it will come back up and the int 3 will be replaced. The in-process
+ // logic takes pains to ensure that such cases are handled properly, therefore we need to perform the same check
+ // here to make the correct decision. Basically, the check is to see if there is indeed an int 3 at the exception
+ // address. If there is _not_ an int 3 there, then we've hit this race. We will lie and say a managed patch was
+ // found to cover this case. This is tracking the logic in DebuggerController::ScanForTriggers, where we call
+ // IsPatched.
+ if (*pfPatchFound == false)
+ {
+ // Read one instruction from the faulting address...
+#if defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64)
+ PRD_TYPE TrapCheck = 0;
+#else
+ BYTE TrapCheck = 0;
+#endif
+
+ HRESULT hr2 = SafeReadStruct(address, &TrapCheck);
+
+ if (SUCCEEDED(hr2) && (TrapCheck != CORDbg_BREAK_INSTRUCTION))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: patchFound=true based on odd missing int 3 case.\n"));
+
+ *pfPatchFound = true;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::FPBA: patchFound=%d, patchIsUnmanaged=%d\n", *pfPatchFound, *pfPatchIsUnmanaged));
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::WriteMemory(CORDB_ADDRESS address, DWORD size,
+ BYTE buffer[], SIZE_T *written)
+{
+ PUBLIC_REENTRANT_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ CORDBRequireProcessStateOK(this);
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+
+ if (size == 0 || address == NULL)
+ return E_INVALIDARG;
+
+ VALIDATE_POINTER_TO_OBJECT_ARRAY(buffer, BYTE, size, true, true);
+ VALIDATE_POINTER_TO_OBJECT(written, SIZE_T *);
+
+
+#if defined(_DEBUG) && defined(FEATURE_INTEROP_DEBUGGING)
+ // Shouldn't be using this to write int3. Use UM BP API instead.
+ // This is technically legal (what if the '0xcc' is data or something), so we can't fail in retail.
+ // But we can add this debug-only check to help VS migrate to the new API.
+ static ConfigDWORD configCheckInt3;
+ DWORD fCheckInt3 = configCheckInt3.val(CLRConfig::INTERNAL_DbgCheckInt3);
+ if (fCheckInt3)
+ {
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ if (size == 1 && buffer[0] == 0xCC)
+ {
+ CONSISTENCY_CHECK_MSGF(false,
+ ("You're using ICorDebugProcess::WriteMemory() to write an 'int3' (1 byte 0xCC) at address 0x%p.\n"
+ "If you're trying to set a breakpoint, you should be using ICorDebugProcess::SetUnmanagedBreakpoint() instead.\n"
+ "(This assert is only enabled under the COM+ knob DbgCheckInt3.)\n",
+ CORDB_ADDRESS_TO_PTR(address)));
+ }
+#endif // DBG_TARGET_X86 || DBG_TARGET_AMD64
+
+ // check if we're replaced an opcode.
+ if (size == 1)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ NativePatch * p = GetNativePatch(CORDB_ADDRESS_TO_PTR(address));
+ if (p != NULL)
+ {
+ CONSISTENCY_CHECK_MSGF(false,
+ ("You're using ICorDebugProcess::WriteMemory() to write an 'opcode (0x%x)' at address 0x%p.\n"
+ "There's already a native patch at that address from ICorDebugProcess::SetUnmanagedBreakpoint().\n"
+ "If you're trying to remove the breakpoint, use ICDProcess::ClearUnmanagedBreakpoint() instead.\n"
+ "(This assert is only enabled under the COM+ knob DbgCheckInt3.)\n",
+ (DWORD) (buffer[0]), CORDB_ADDRESS_TO_PTR(address)));
+ }
+ }
+ }
+#endif // _DEBUG && FEATURE_INTEROP_DEBUGGING
+
+
+ *written = 0;
+
+ HRESULT hr = S_OK;
+ HRESULT hrSaved = hr; // this will hold the 'real' hresult in case of a
+ // partially completed operation
+ HRESULT hrPartialCopy = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+
+ BOOL bUpdateOriginalPatchTable = FALSE;
+ BYTE *bufferCopy = NULL;
+
+ // Only update the patch table if the managed state of the process
+ // is initialized.
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ if (m_pPatchTable == NULL )
+ {
+ if (!SUCCEEDED( hr = RefreshPatchTable() ) )
+ {
+ goto LExit;
+ }
+ }
+
+ if ( !SUCCEEDED( hr = AdjustBuffer( address,
+ size,
+ buffer,
+ &bufferCopy,
+ AB_WRITE,
+ &bUpdateOriginalPatchTable)))
+ {
+ goto LExit;
+ }
+ }
+
+ //conveniently enough, SafeWriteBuffer will throw if it can't complete the entire operation
+ EX_TRY
+ {
+ TargetBuffer tb(address, size);
+ SafeWriteBuffer(tb, buffer); // throws
+ *written = tb.cbSize; // DT's Write does everything or fails.
+ }
+ EX_CATCH_HRESULT(hr);
+
+ if (FAILED(hr))
+ {
+ if(hr != hrPartialCopy)
+ goto LExit;
+ else
+ hrSaved = hr;
+ }
+
+
+ LOG((LF_CORDB, LL_INFO100000, "CP::WM: wrote %d bytes at 0x%08x, first byte is 0x%x\n",
+ *written, (DWORD)address, buffer[0]));
+
+ if (bUpdateOriginalPatchTable == TRUE )
+ {
+ {
+ RSLockHolder ch(&this->m_processMutex);
+
+ //don't tweak patch table for stuff that isn't written to LeftSide
+ CommitBufferAdjustments(address, address + *written);
+ }
+
+ // The only way this should be able to fail is if
+ //someone else fiddles with the memory protections on the
+ //left side while it's frozen
+ EX_TRY
+ {
+ TargetBuffer tb(m_rgData, (ULONG) (m_cPatch*m_runtimeOffsets.m_cbPatch));
+ SafeWriteBuffer(tb, m_pPatchTable);
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+ }
+
+ // Since we may have
+ // overwritten anything (objects, code, etc), we should mark
+ // everything as needing to be re-cached.
+ m_continueCounter++;
+
+ LExit:
+ if (m_initialized)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearBufferAdjustments( );
+ }
+
+ //we messed up our local copy, so get a clean copy the next time
+ //we need it
+ if (bUpdateOriginalPatchTable==TRUE)
+ {
+ if (bufferCopy != NULL)
+ {
+ memmove(buffer, bufferCopy, size);
+ delete bufferCopy;
+ }
+ }
+
+ if (FAILED( hr ))
+ {
+ //we messed up our local copy, so get a clean copy the next time
+ //we need it
+ if (bUpdateOriginalPatchTable==TRUE)
+ {
+ RSLockHolder ch(&this->m_processMutex);
+ ClearPatchTable();
+ }
+ }
+ else if( FAILED(hrSaved) )
+ {
+ hr = hrSaved;
+ }
+
+ return hr;
+}
+
+HRESULT CordbProcess::ClearCurrentException(DWORD threadID)
+{
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_INVALIDARG;
+#else
+ PUBLIC_API_ENTRY(this);
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // There's something wrong if you're calling this an there are no queued unmanaged events.
+ if ((m_unmanagedEventQueue == NULL) && (m_outOfBandEventQueue == NULL))
+ return E_INVALIDARG;
+
+ // Grab the unmanaged thread object.
+ CordbUnmanagedThread *pUThread = GetUnmanagedThread(threadID);
+
+ if (pUThread == NULL)
+ return E_INVALIDARG;
+
+ LOG((LF_CORDB, LL_INFO1000, "CP::CCE: tid=0x%x\n", threadID));
+
+ // We clear both the IB and OOB event.
+ if (pUThread->HasIBEvent() && !pUThread->IBEvent()->IsEventUserContinued())
+ {
+ pUThread->IBEvent()->SetState(CUES_ExceptionCleared);
+ }
+
+ if (pUThread->HasOOBEvent())
+ {
+ // must decide exception status _before_ we continue the event.
+ _ASSERTE(!pUThread->OOBEvent()->IsEventContinuedUnhijacked());
+ pUThread->OOBEvent()->SetState(CUES_ExceptionCleared);
+ }
+
+ // If the thread is hijacked, then set the thread's debugger word to 0 to indicate to it that the
+ // exception has been cleared.
+ if (pUThread->IsGenericHijacked())
+ {
+ HRESULT hr = pUThread->SetEEDebuggerWord(0);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+
+ return S_OK;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+CordbUnmanagedThread *CordbProcess::HandleUnmanagedCreateThread(DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase)
+{
+ INTERNAL_API_ENTRY(this);
+ CordbUnmanagedThread *ut = new (nothrow) CordbUnmanagedThread(this, dwThreadId, hThread, lpThreadLocalBase);
+
+ if (ut != NULL)
+ {
+ HRESULT hr = m_unmanagedThreads.AddBase(ut); // InternalAddRef, release on EXIT_THREAD events.
+
+ if (!SUCCEEDED(hr))
+ {
+ delete ut;
+
+ LOG((LF_CORDB, LL_INFO10000, "Failed adding unmanaged thread to process!\n"));
+ CORDBSetUnrecoverableError(this, hr, 0);
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "New CordbThread failed!\n"));
+ CORDBSetUnrecoverableError(this, E_OUTOFMEMORY, 0);
+ }
+
+ return ut;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+//-----------------------------------------------------------------------------
+// Initializes the DAC
+// Arguments: none--initializes the DAC for this CordbProcess instance
+// Note: Throws on error
+//-----------------------------------------------------------------------------
+void CordbProcess::InitDac()
+{
+ // Go-Go DAC power!!
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ InitializeDac();
+ }
+ EX_CATCH_HRESULT(hr);
+
+ // We Need DAC to debug for both Managed & Interop.
+ if (FAILED(hr))
+ {
+ // We assert here b/c we're trying to be friendly. Most likely, the cause is either:
+ // - a bad installation
+ // - a CLR dev built mscorwks but didn't build DAC.
+ SIMPLIFYING_ASSUMPTION_MSGF(false, ("Failed to load DAC while for debugging. hr=0x%08x", hr));
+ ThrowHR(hr);
+ }
+} //CordbProcess::InitDac
+
+// Update the entire RS copy of the debugger control block by reading the LS copy. The RS copy is treated as
+// a throw-away temporary buffer, rather than a true cache. That is, we make no assumptions about the
+// validity of the information over time. Thus, before using any of the values, we need to update it. We
+// update everything for simplicity; any perf hit we take by doing this instead of updating the individual
+// fields we want at any given point isn't significant, particularly if we are updating multiple fields.
+
+// Arguments:
+// none, but reads process memory from the LS debugger control block
+// Return Value: none (copies from LS DCB to RS buffer GetDCB())
+// Note: throws if SafeReadBuffer fails
+void CordbProcess::UpdateRightSideDCB()
+{
+ IfFailThrow(m_pEventChannel->UpdateRightSideDCB());
+} // CordbProcess::UpdateRightSideDCB
+
+// Update a single field with a value stored in the RS copy of the DCB. We can't update the entire LS DCB
+// because in some cases, the LS and RS are simultaneously initializing the DCB. If we initialize a field on
+// the RS and write back the whole thing, we may overwrite something the LS has initialized in the interim.
+
+// Arguments:
+// input: rsFieldAddr - the address of the field in the RS copy of the DCB that we want to write back to
+// the LS DCB. We use this to compute the offset of the field from the beginning of the
+// DCB and then add this offset to the starting address of the LS DCB to get the LS
+// address of the field we are updating
+// size - the size of the field we're updating.
+// Return value: none
+// Note: throws if SafeWriteBuffer fails
+void CordbProcess::UpdateLeftSideDCBField(void * rsFieldAddr, SIZE_T size)
+{
+ IfFailThrow(m_pEventChannel->UpdateLeftSideDCBField(rsFieldAddr, size));
+} // CordbProcess::UpdateRightSideDCB
+
+
+//-----------------------------------------------------------------------------
+// Gets the remote address of the event block for the Target and verifies that it's valid.
+// We use this address when we need to read from or write to the debugger control block.
+// Also allocates the RS buffer used for temporary storage for information from the DCB and
+// copies the LS DCB into the RS buffer.
+// Arguments:
+// output: pfBlockExists - true iff the LS DCB has been successfully allocated. Note that
+// we need this information even if the function throws, so we can't simply send it back
+// as a return value.
+// Return value:
+// None, but allocates GetDCB() on success. If the LS DCB has not
+// been successfully initialized or if this throws, GetDCB() will be NULL.
+//
+// Notes:
+// Throws on error
+//
+//-----------------------------------------------------------------------------
+void CordbProcess::GetEventBlock(BOOL * pfBlockExists)
+{
+ if (GetDCB() == NULL) // we only need to do this once
+ {
+ _ASSERTE(m_pShim != NULL);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // This will Initialize the DAC/DBI interface.
+ BOOL fDacReady = TryInitializeDac();
+
+ if (fDacReady)
+ {
+ // Ensure that we have a DAC interface.
+ _ASSERTE(m_pDacPrimitives != NULL);
+
+ // This is not technically necessary for Mac debugging. The event channel doesn't rely on
+ // knowing the target address of the DCB on the LS.
+ CORDB_ADDRESS pLeftSideDCB = NULL;
+ pLeftSideDCB = (GetDAC()->GetDebuggerControlBlockAddress());
+ if (pLeftSideDCB == NULL)
+ {
+ *pfBlockExists = false;
+ ThrowHR(CORDBG_E_DEBUGGING_NOT_POSSIBLE);
+ }
+
+ IfFailThrow(NewEventChannelForThisPlatform(pLeftSideDCB,
+ m_pMutableDataTarget,
+ GetPid(),
+ m_pShim->GetMachineInfo(),
+ &m_pEventChannel));
+ _ASSERTE(m_pEventChannel != NULL);
+
+ // copy information from left side DCB
+ UpdateRightSideDCB();
+
+ // Verify that the control block is valid.
+ // This will throw on error.
+ VerifyControlBlock();
+
+ *pfBlockExists = true;
+ }
+ else
+ {
+ // we can't initialize the DAC, so we can't get the block
+ *pfBlockExists = false;
+ }
+ }
+ else // we got the block before
+ {
+ *pfBlockExists = true;
+ }
+
+} // CordbProcess::GetEventBlock()
+
+
+//
+// Verify that the version info in the control block matches what we expect. The minimum supported protocol from the
+// Left Side must be greater or equal to the minimum required protocol of the Right Side. Note: its the Left Side's job
+// to conform to whatever protocol the Right Side requires, so long as minimum is supported.
+//
+void CordbProcess::VerifyControlBlock()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_pShim != NULL);
+
+ if (GetDCB()->m_DCBSize == 0)
+ {
+ // the LS is still initializing the DCB
+ ThrowHR(CORDBG_E_DEBUGGING_NOT_POSSIBLE);
+ }
+
+ // Fill in the protocol numbers for the Right Side and update the LS DCB.
+ GetDCB()->m_rightSideProtocolCurrent = CorDB_RightSideProtocolCurrent;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideProtocolCurrent), sizeof(GetDCB()->m_rightSideProtocolCurrent));
+
+ GetDCB()->m_rightSideProtocolMinSupported = CorDB_RightSideProtocolMinSupported;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideProtocolMinSupported),
+ sizeof(GetDCB()->m_rightSideProtocolMinSupported));
+
+ // For Telesto, Dbi and Wks have a more flexible versioning allowed, as described by the Debugger
+ // Version Protocol String in DEBUGGER_PROTOCOL_STRING in DbgIpcEvents.h. This allows different build
+ // numbers, but the other protocol numbers should still match.
+#if !defined(FEATURE_CORECLR)
+ bool fSkipVerCheck = false;
+#if _DEBUG
+ // In debug builds, allow us to disable the version check to help with applying hotfixes.
+ // The hotfix may be built against a compatible IPC protocol, but have a slightly different build number.
+ fSkipVerCheck = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgSkipVerCheck) != 0;
+#endif
+
+ if (!fSkipVerCheck)
+ {
+ //
+ // These asserts double check that the version of the Right Side matches the version of the left side.
+ //
+ // If you hit these asserts, it is probably because you rebuilt mscordbi without rebuilding mscorwks, or rebuilt
+ // mscorwks without rebuilding mscordbi. You might be able to ignore these asserts, but proceed at your own risk.
+ //
+ CONSISTENCY_CHECK_MSGF(VER_PRODUCTBUILD == GetDCB()->m_verMajor,
+ ("version of %s (%d) in the debuggee does not match version of mscordbi.dll (%d) in the debugger.\n"
+ "This means your setup is wrong. You can ignore this but proceed at your own risk.\n",
+ MAIN_CLR_DLL_NAME_A, GetDCB()->m_verMajor, VER_PRODUCTBUILD));
+ CONSISTENCY_CHECK_MSGF(VER_PRODUCTBUILD_QFE == GetDCB()->m_verMinor,
+ ("QFE version of %s (%d) in the debuggee does not match QFE version of mscordbi.dll (%d) in the debugger.\n"
+ "Both dlls have build # (%d).\n"
+ "This means your setup is wrong. You can ignore this but proceed at your own risk.\n",
+ MAIN_CLR_DLL_NAME_A, GetDCB()->m_verMinor, VER_PRODUCTBUILD_QFE, VER_PRODUCTBUILD));
+ }
+#endif // !FEATURE_CORECLR
+
+ // These assertions verify that the debug manager is behaving correctly.
+ // An assertion failure here means that the runtime version of the debuggee is different from the runtime version of
+ // the debugger is capable of debugging.
+
+ // The Debug Manager should properly match LS & RS, and thus guarantee that this assert should never fire.
+ // But just in case the installation is corrupted, we'll check it.
+ if (GetDCB()->m_DCBSize != sizeof(DebuggerIPCControlBlock))
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("DCB in LS is %d bytes, in RS is %d bytes. Version mismatch!!\n",
+ GetDCB()->m_DCBSize, sizeof(DebuggerIPCControlBlock)));
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+ // The Left Side has to support at least our minimum required protocol.
+ if (GetDCB()->m_leftSideProtocolCurrent < GetDCB()->m_rightSideProtocolMinSupported)
+ {
+ _ASSERTE(GetDCB()->m_leftSideProtocolCurrent >= GetDCB()->m_rightSideProtocolMinSupported);
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+ // The Left Side has to be able to emulate at least our minimum required protocol.
+ if (GetDCB()->m_leftSideProtocolMinSupported > GetDCB()->m_rightSideProtocolCurrent)
+ {
+ _ASSERTE(GetDCB()->m_leftSideProtocolMinSupported <= GetDCB()->m_rightSideProtocolCurrent);
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+ }
+
+#ifdef _DEBUG
+ char buf[MAX_LONGPATH];
+ DWORD len = GetEnvironmentVariableA("CORDBG_NotCompatibleTest", buf, sizeof(buf));
+ _ASSERTE(len < sizeof(buf));
+
+ if (len > 0)
+ ThrowHR(CORDBG_E_INCOMPATIBLE_PROTOCOL);
+#endif
+
+ if (GetDCB()->m_bHostingInFiber)
+ {
+ ThrowHR(CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS);
+ }
+
+ _ASSERTE(!GetDCB()->m_rightSideShouldCreateHelperThread);
+} // CordbProcess::VerifyControlBlock
+
+//-----------------------------------------------------------------------------
+// This is the CordbProcess objects chance to inspect the DCB and intialize stuff
+//
+// Return Value:
+// Typical HRESULT return values, nothing abnormal.
+// If succeeded, then the block exists and is valid.
+//
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::GetRuntimeOffsets()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_pShim != NULL);
+ UpdateRightSideDCB();
+
+ // Can't get a handle to the helper thread if the target is remote.
+
+ // If we got this far w/o failing, then we should be able to get the helper thread handle.
+ // RS will handle not having the helper-thread handle, so we just make a best effort here.
+ DWORD dwHelperTid = GetDCB()->m_realHelperThreadId;
+ _ASSERTE(dwHelperTid != 0);
+
+
+ {
+#if !defined FEATURE_CORESYSTEM
+ // kernel32!OpenThread does not exist on all platforms (missing on Win98).
+ // So we need to delay load it.
+ typedef HANDLE (WINAPI *FPOPENTHREAD)(DWORD dwDesiredAccess,
+ BOOL bInheritHandle,
+ DWORD dwThreadId);
+
+
+
+ HMODULE mod = WszGetModuleHandle(W("kernel32.dll"));
+
+ _ASSERTE(mod != NULL); // can't fail since Kernel32.dll is already loaded.
+
+ const FPOPENTHREAD pfnOpenThread = (FPOPENTHREAD)GetProcAddress(mod, "OpenThread");
+
+ if (pfnOpenThread != NULL)
+ {
+ m_hHelperThread = pfnOpenThread(SYNCHRONIZE, FALSE, dwHelperTid);
+ CONSISTENCY_CHECK_MSGF(m_hHelperThread != NULL, ("Failed to get helper-thread handle. tid=0x%x\n", dwHelperTid));
+ }
+#elif FEATURE_PAL
+ m_hHelperThread = NULL; //RS is supposed to be able to live without a helper thread handle.
+#else
+ m_hHelperThread = OpenThread(SYNCHRONIZE, FALSE, dwHelperTid);
+ CONSISTENCY_CHECK_MSGF(m_hHelperThread != NULL, ("Failed to get helper-thread handle. tid=0x%x\n", dwHelperTid));
+#endif
+ }
+
+ // get the remote address of the runtime offsets structure and read the structure itself
+ HRESULT hrRead = SafeReadStruct(PTR_TO_CORDB_ADDRESS(GetDCB()->m_pRuntimeOffsets), &m_runtimeOffsets);
+
+ if (FAILED(hrRead))
+ {
+ return hrRead;
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::GRO: got runtime offsets: \n"));
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ LOG((LF_CORDB, LL_INFO10000, " m_genericHijackFuncAddr= 0x%p\n",
+ m_runtimeOffsets.m_genericHijackFuncAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_signalHijackStartedBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_signalHijackStartedBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_excepNotForRuntimeBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_excepNotForRuntimeBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_notifyRSOfSyncCompleteBPAddr= 0x%p\n",
+ m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_raiseException= 0x%p\n",
+ m_runtimeOffsets.m_raiseExceptionAddr));
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ LOG((LF_CORDB, LL_INFO10000, " m_TLSIndex= 0x%08x\n",
+ m_runtimeOffsets.m_TLSIndex));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadStateOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadStateOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadStateNCOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadStateNCOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadPGCDisabledOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadPGCDisabledOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadPGCDisabledValue= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadPGCDisabledValue));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadDebuggerWordOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadDebuggerWordOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadFrameOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadFrameOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadMaxNeededSize= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadMaxNeededSize));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadSteppingStateMask= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadSteppingStateMask));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEMaxFrameValue= 0x%08x\n",
+ m_runtimeOffsets.m_EEMaxFrameValue));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadDebuggerFilterContextOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadDebuggerFilterContextOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEThreadCantStopOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEThreadCantStopOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEFrameNextOffset= 0x%08x\n",
+ m_runtimeOffsets.m_EEFrameNextOffset));
+ LOG((LF_CORDB, LL_INFO10000, " m_EEIsManagedExceptionStateMask= 0x%08x\n",
+ m_runtimeOffsets.m_EEIsManagedExceptionStateMask));
+ LOG((LF_CORDB, LL_INFO10000, " m_pPatches= 0x%08x\n",
+ m_runtimeOffsets.m_pPatches));
+ LOG((LF_CORDB, LL_INFO10000, " m_offRgData= 0x%08x\n",
+ m_runtimeOffsets.m_offRgData));
+ LOG((LF_CORDB, LL_INFO10000, " m_offCData= 0x%08x\n",
+ m_runtimeOffsets.m_offCData));
+ LOG((LF_CORDB, LL_INFO10000, " m_cbPatch= 0x%08x\n",
+ m_runtimeOffsets.m_cbPatch));
+ LOG((LF_CORDB, LL_INFO10000, " m_offAddr= 0x%08x\n",
+ m_runtimeOffsets.m_offAddr));
+ LOG((LF_CORDB, LL_INFO10000, " m_offOpcode= 0x%08x\n",
+ m_runtimeOffsets.m_offOpcode));
+ LOG((LF_CORDB, LL_INFO10000, " m_cbOpcode= 0x%08x\n",
+ m_runtimeOffsets.m_cbOpcode));
+ LOG((LF_CORDB, LL_INFO10000, " m_offTraceType= 0x%08x\n",
+ m_runtimeOffsets.m_offTraceType));
+ LOG((LF_CORDB, LL_INFO10000, " m_traceTypeUnmanaged= 0x%08x\n",
+ m_runtimeOffsets.m_traceTypeUnmanaged));
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Flares are only used for interop debugging.
+
+ // Do check that the flares are all at unique offsets.
+ // Since this is determined at link-time, we need a run-time check (an
+ // assert isn't good enough, since this would only happen in a super
+ // optimized / bbt run).
+ {
+ const void * flares[] = {
+ m_runtimeOffsets.m_signalHijackStartedBPAddr,
+ m_runtimeOffsets.m_excepForRuntimeHandoffStartBPAddr,
+ m_runtimeOffsets.m_excepForRuntimeHandoffCompleteBPAddr,
+ m_runtimeOffsets.m_signalHijackCompleteBPAddr,
+ m_runtimeOffsets.m_excepNotForRuntimeBPAddr,
+ m_runtimeOffsets.m_notifyRSOfSyncCompleteBPAddr,
+ };
+
+ const int NumFlares = NumItems(flares);
+
+ // Ensure that all of the flares are unique.
+ for(int i = 0; i < NumFlares; i++)
+ {
+ for(int j = i+1; j < NumFlares; j++)
+ {
+ if (flares[i] == flares[j])
+ {
+ // If we ever fail here, that means the LS build is busted.
+
+ // This assert is useful if we drop a checked RS onto a retail
+ // LS (that's legal).
+ _ASSERTE(!"LS has matching Flares.");
+ LOG((LF_CORDB, LL_ALWAYS, "Failing because of matching flares.\n"));
+ return CORDBG_E_INCOMPATIBLE_PROTOCOL;
+ }
+ }
+ }
+ }
+
+#endif // FEATURE_INTEROP_DEBUGGING
+ m_runtimeOffsetsInitialized = true;
+ return S_OK;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// Resume hijacked threads.
+//-----------------------------------------------------------------------------
+void CordbProcess::ResumeHijackedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_pShim != NULL);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::RHT: entered\n"));
+ if (this->m_state & (CordbProcess::PS_SOME_THREADS_SUSPENDED | CordbProcess::PS_HIJACKS_IN_PLACE))
+ {
+ // On XP, This will also resume the threads suspended for Sync.
+ this->ResumeUnmanagedThreads();
+ }
+
+ // Hijacks send their ownership flares and then wait on this event. By setting this
+ // we let the hijacks run free.
+ if (this->m_leftSideUnmanagedWaitEvent != NULL)
+ {
+ SetEvent(this->m_leftSideUnmanagedWaitEvent);
+ }
+ else
+ {
+ // Only reason we expect to not have this event is if the CLR hasn't been loaded yet.
+ // In that case, we won't hijack, so nobody's listening for this event either.
+ _ASSERTE(!m_initialized);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// For debugging support, record the win32 events.
+// Note that although this is for debugging, we want it in retail because we'll
+// be debugging retail most of the time :(
+// pEvent - the win32 debug event we just received
+// pUThread - our unmanaged thread object for the event. We could look it up
+// from pEvent->dwThreadId, but passed in for perf reasons.
+//-----------------------------------------------------------------------------
+void CordbProcess::DebugRecordWin32Event(const DEBUG_EVENT * pEvent, CordbUnmanagedThread * pUThread)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Although we could look up the Unmanaged thread, it's faster to have it just passed in.
+ // So here we do a consistency check.
+ _ASSERTE(pUThread != NULL);
+ _ASSERTE(pUThread->m_id == pEvent->dwThreadId);
+
+ m_DbgSupport.m_TotalNativeEvents++; // bump up the counter.
+
+ MiniDebugEvent * pMiniEvent = &m_DbgSupport.m_DebugEventQueue[m_DbgSupport.m_DebugEventQueueIdx];
+ pMiniEvent->code = (BYTE) pEvent->dwDebugEventCode;
+ pMiniEvent->pUThread = pUThread;
+
+ DWORD tid = pEvent->dwThreadId;
+
+ // Record debug-event specific data.
+ switch(pEvent->dwDebugEventCode)
+ {
+ case LOAD_DLL_DEBUG_EVENT:
+ pMiniEvent->u.ModuleData.pBaseAddress = pEvent->u.LoadDll.lpBaseOfDll;
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=0x%8x, Load Dll. Addr=%p\n",
+ tid,
+ pEvent->u.LoadDll.lpBaseOfDll);
+ break;
+ case UNLOAD_DLL_DEBUG_EVENT:
+ pMiniEvent->u.ModuleData.pBaseAddress = pEvent->u.UnloadDll.lpBaseOfDll;
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=0x%8x, Unload Dll. Addr=%p\n",
+ tid,
+ pEvent->u.UnloadDll.lpBaseOfDll);
+ break;
+ case EXCEPTION_DEBUG_EVENT:
+ pMiniEvent->u.ExceptionData.pAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+ pMiniEvent->u.ExceptionData.dwCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "Win32 Debug Event received: tid=%8x, (1) Exception. Code=0x%08x, Addr=%p\n",
+ tid,
+ pMiniEvent->u.ExceptionData.dwCode,
+ pMiniEvent->u.ExceptionData.pAddress
+ );
+ break;
+ default:
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "Win32 Debug Event received tid=%8x, %d\n", tid, pEvent->dwDebugEventCode);
+ break;
+ }
+
+
+ // Go to the next entry in the queue.
+ m_DbgSupport.m_DebugEventQueueIdx = (m_DbgSupport.m_DebugEventQueueIdx + 1) % DEBUG_EVENTQUEUE_SIZE;
+}
+
+void CordbProcess::QueueUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(m_pShim != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: queued unmanaged event %d for thread 0x%x\n",
+ pEvent->dwDebugEventCode, pUThread->m_id));
+
+
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+
+ // Copy the event into the given thread
+ CordbUnmanagedEvent *ue;
+
+ // Use the primary IB event slot unless this is the special stack overflow event case.
+ if (!pUThread->HasSpecialStackOverflowCase())
+ ue = pUThread->IBEvent();
+ else
+ ue = pUThread->IBEvent2();
+
+ if(pUThread->HasIBEvent() && !pUThread->HasSpecialStackOverflowCase())
+ {
+ // Any event being replaced should at least have been continued outside of the hijack
+ // We don't track whether or not we expect the exception to retrigger but if we are replacing
+ // the event then it did not.
+ _ASSERTE(ue->IsEventContinuedUnhijacked());
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: A previously seen event is being discarded 0x%x 0x%p\n",
+ ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode,
+ ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionAddress));
+ DequeueUnmanagedEvent(ue->m_owner);
+ }
+
+ memcpy(&(ue->m_currentDebugEvent), pEvent, sizeof(DEBUG_EVENT));
+ ue->m_state = CUES_IsIBEvent;
+ ue->m_next = NULL;
+
+ // Enqueue the event.
+ pUThread->SetState(CUTS_HasIBEvent);
+
+ if (m_unmanagedEventQueue == NULL)
+ m_unmanagedEventQueue = ue;
+ else
+ m_lastQueuedUnmanagedEvent->m_next = ue;
+
+ m_lastQueuedUnmanagedEvent = ue;
+}
+
+void CordbProcess::DequeueUnmanagedEvent(CordbUnmanagedThread *ut)
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(m_unmanagedEventQueue != NULL);
+ _ASSERTE(ut->HasIBEvent() || ut->HasSpecialStackOverflowCase());
+ _ASSERTE(ThreadHoldsProcessLock());
+
+
+ CordbUnmanagedEvent *ue;
+
+ if (ut->HasIBEvent())
+ ue = ut->IBEvent();
+ else
+ {
+ ue = ut->IBEvent2();
+
+ // Since we're dequeuing the special stack overflow event, we're no longer in the special stack overflow case.
+ ut->ClearState(CUTS_HasSpecialStackOverflowCase);
+ }
+
+ DWORD ec = ue->m_currentDebugEvent.dwDebugEventCode;
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dequeue unmanaged event %d for thread 0x%x\n", ec, ut->m_id));
+
+ _ASSERTE(ec == EXCEPTION_DEBUG_EVENT);
+
+ CordbUnmanagedEvent **tmp = &m_unmanagedEventQueue;
+ CordbUnmanagedEvent **prev = NULL;
+
+ // Note: this supports out-of-order dequeing of unmanaged events. This is necessary because we queue events even if
+ // we're not clear on the ownership question. When we get the answer, and if the event belongs to the Runtime, we go
+ // ahead and yank the event out of the queue, wherever it may be.
+ while (*tmp && *tmp != ue)
+ {
+ prev = tmp;
+ tmp = &((*tmp)->m_next);
+ }
+
+ _ASSERTE(*tmp == ue);
+
+ *tmp = (*tmp)->m_next;
+
+ if (m_unmanagedEventQueue == NULL)
+ m_lastQueuedUnmanagedEvent = NULL;
+ else if (m_lastQueuedUnmanagedEvent == ue)
+ {
+ _ASSERTE(prev != NULL);
+ m_lastQueuedUnmanagedEvent = *prev;
+ }
+
+ ut->ClearState(CUTS_HasIBEvent);
+
+}
+
+void CordbProcess::QueueOOBUnmanagedEvent(CordbUnmanagedThread *pUThread, const DEBUG_EVENT * pEvent)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(!pUThread->HasOOBEvent());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(m_pShim != NULL);
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::QUE: queued OOB unmanaged event %d for thread 0x%x\n",
+ pEvent->dwDebugEventCode, pUThread->m_id));
+
+ // Copy the event into the given thread
+ CordbUnmanagedEvent *ue = pUThread->OOBEvent();
+ memcpy(&(ue->m_currentDebugEvent), pEvent, sizeof(DEBUG_EVENT));
+ ue->m_state = CUES_None;
+ ue->m_next = NULL;
+
+ // Enqueue the event.
+ pUThread->SetState(CUTS_HasOOBEvent);
+
+ if (m_outOfBandEventQueue == NULL)
+ m_outOfBandEventQueue = ue;
+ else
+ m_lastQueuedOOBEvent->m_next = ue;
+
+ m_lastQueuedOOBEvent = ue;
+}
+
+void CordbProcess::DequeueOOBUnmanagedEvent(CordbUnmanagedThread *ut)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+ _ASSERTE(ut->HasOOBEvent());
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ CordbUnmanagedEvent *ue = ut->OOBEvent();
+ DWORD ec = ue->m_currentDebugEvent.dwDebugEventCode;
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dequeue OOB unmanaged event %d for thread 0x%x\n", ec, ut->m_id));
+
+ CordbUnmanagedEvent **tmp = &m_outOfBandEventQueue;
+ CordbUnmanagedEvent **prev = NULL;
+
+ // Note: this supports out-of-order dequeing of unmanaged events. This is necessary because we queue events even if
+ // we're not clear on the ownership question. When we get the answer, and if the event belongs to the Runtime, we go
+ // ahead and yank the event out of the queue, wherever it may be.
+ while (*tmp && *tmp != ue)
+ {
+ prev = tmp;
+ tmp = &((*tmp)->m_next);
+ }
+
+ _ASSERTE(*tmp == ue);
+
+ *tmp = (*tmp)->m_next;
+
+ if (m_outOfBandEventQueue == NULL)
+ m_lastQueuedOOBEvent = NULL;
+ else if (m_lastQueuedOOBEvent == ue)
+ {
+ _ASSERTE(prev != NULL);
+ m_lastQueuedOOBEvent = *prev;
+ }
+
+ ut->ClearState(CUTS_HasOOBEvent);
+}
+
+HRESULT CordbProcess::SuspendUnmanagedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Iterate over all unmanaged threads...
+ CordbUnmanagedThread* ut;
+ HASHFIND find;
+
+ for (ut = m_unmanagedThreads.FindFirst(&find); ut != NULL; ut = m_unmanagedThreads.FindNext(&find))
+ {
+
+ // Don't suspend any thread in a can't stop region. This includes cooperative mode threads & preemptive
+ // threads that haven't pushed a NativeTransitionFrame. The ultimate problem here is that a thread
+ // in this state is effectively inside the runtime, and thus may take a lock that blocks the helper thread.
+ // IsCan'tStop also includes the helper thread & hijacked threads - which we shouldn't suspend anyways.
+
+ // Only suspend those unmanaged threads that aren't already suspended by us and that aren't already hijacked by
+ // us.
+
+ if (!ut->IsSuspended() &&
+ !ut->IsDeleted() &&
+ !ut->IsCantStop() &&
+ !ut->IsBlockingForSync()
+ )
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::SUT: suspending unmanaged thread 0x%x, handle 0x%x\n", ut->m_id, ut->m_handle));
+
+ DWORD succ = SuspendThread(ut->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ // This is okay... the thread may be dying after an ExitThread event.
+ LOG((LF_CORDB, LL_INFO1000, "CP::SUT: failed to suspend thread 0x%x\n", ut->m_id));
+ }
+ else
+ {
+ m_state |= PS_SOME_THREADS_SUSPENDED;
+
+ ut->SetState(CUTS_Suspended);
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT CordbProcess::ResumeUnmanagedThreads()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ FAIL_IF_NEUTERED(this);
+
+ // Iterate over all unmanaged threads...
+ CordbUnmanagedThread* ut;
+ HASHFIND find;
+
+ for (ut = m_unmanagedThreads.FindFirst(&find); ut != NULL; ut = m_unmanagedThreads.FindNext(&find))
+ {
+ // Only resume those unmanaged threads that were suspended by us.
+ if (ut->IsSuspended())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::RUT: resuming unmanaged thread 0x%x\n", ut->m_id));
+
+ DWORD succ = ResumeThread(ut->m_handle);
+
+ if (succ == 0xFFFFFFFF)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::RUT: failed to resume thread 0x%x\n", ut->m_id));
+ }
+ else
+ ut->ClearState(CUTS_Suspended);
+ }
+ }
+
+ m_state &= ~PS_SOME_THREADS_SUSPENDED;
+
+ return S_OK;
+}
+
+//-----------------------------------------------------------------------------
+// DispatchUnmanagedInBandEvent
+//
+// Handler for Win32 events already known to be Unmanaged and in-band.
+//-----------------------------------------------------------------------------
+void CordbProcess::DispatchUnmanagedInBandEvent()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // There should be no queued OOB events!!! If there are, then we have a breakdown in our protocol, since all OOB
+ // events should be dispatched before attempting to really continue from any in-band event.
+ _ASSERTE(m_outOfBandEventQueue == NULL);
+ _ASSERTE(m_cordb != NULL);
+ _ASSERTE(m_cordb->m_unmanagedCallback != NULL);
+ _ASSERTE(!m_dispatchingUnmanagedEvent);
+
+ CordbUnmanagedThread * pUnmanagedThread = NULL;
+ CordbUnmanagedEvent * pUnmanagedEvent = m_unmanagedEventQueue;
+
+ while (true)
+ {
+ // get the next queued event that isn't dispatched yet
+ while(pUnmanagedEvent != NULL && pUnmanagedEvent->IsDispatched())
+ {
+ pUnmanagedEvent = pUnmanagedEvent->m_next;
+ }
+
+ if(pUnmanagedEvent == NULL)
+ break;
+
+ // Get the thread for this event
+ pUnmanagedThread = pUnmanagedEvent->m_owner;
+
+ // We better not have dispatched it yet!
+ _ASSERTE(!pUnmanagedEvent->IsDispatched());
+
+ // We shouldn't be dispatching IB events on a thread that has exited.
+ // Though it's possible that the thread may exit *after* the IB event has been dispatched
+ // if it gets hijacked.
+ _ASSERTE(!pUnmanagedThread->IsDeleted());
+
+ // Make sure we keep the thread alive while we're playing with it.
+ pUnmanagedThread->InternalAddRef();
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: dispatching unmanaged event %d for thread 0x%x\n",
+ pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, pUnmanagedThread->m_id));
+
+ m_dispatchingUnmanagedEvent = true;
+
+ // Add/Remove a reference which is scoped to the time that m_lastDispatchedIBEvent
+ // is set to pUnmanagedEvent (it is an interior pointer)
+ // see DevDiv issue 818301 for more details
+ if(m_lastDispatchedIBEvent != NULL)
+ {
+ m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ m_lastDispatchedIBEvent = NULL;
+ }
+ pUnmanagedThread->InternalAddRef();
+ m_lastDispatchedIBEvent = pUnmanagedEvent;
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+
+ IncStopCount();
+
+ Unlock();
+
+ {
+ // Interface is semantically const, but does not include const in signature.
+ DEBUG_EVENT * pEvent = const_cast<DEBUG_EVENT *> (&(pUnmanagedEvent->m_currentDebugEvent));
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this,pEvent, FALSE);
+ m_cordb->m_unmanagedCallback->DebugEvent(pEvent, FALSE);
+ }
+
+ Lock();
+
+ // Calling IMDA::Continue() will set m_dispatchingUnmanagedEvent = false.
+ // So if Continue() was called && we have more events, we'll loop and dispatch more events.
+ // Else we'll break out of the while loop.
+ if(m_dispatchingUnmanagedEvent)
+ break;
+
+ // Continue was called in the dispatch callback, but that continue path just
+ // clears the dispatch flag and returns. The continue right here is the logical
+ // completion of the user's continue request
+ // Note it is sometimes the case that these events have already been continued because
+ // they had defered dispatching. At the time of deferal they were immediately continued.
+ // If the event is already continued then this continue becomes a no-op.
+ m_pShim->GetWin32EventThread()->DoDbgContinue(this, pUnmanagedEvent);
+
+ // Release our reference to the unmanaged thread that we dispatched
+ if (pUnmanagedThread)
+ {
+ // This event should have been continued long ago...
+ _ASSERTE(!pUnmanagedThread->IBEvent()->IsEventWaitingForContinue());
+ pUnmanagedThread->InternalRelease();
+ pUnmanagedThread = NULL;
+ }
+ }
+
+ m_dispatchingUnmanagedEvent = false;
+
+ // Release our reference to the last thread that we dispatched now...
+ if(pUnmanagedThread)
+ {
+ pUnmanagedThread->InternalRelease();
+ pUnmanagedThread = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// DispatchUnmanagedOOBEvent
+//
+// Handler for Win32 events already known to be Unmanaged and out-of-band.
+//-----------------------------------------------------------------------------
+void CordbProcess::DispatchUnmanagedOOBEvent()
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+
+ // There should be OOB events queued...
+ _ASSERTE(m_outOfBandEventQueue != NULL);
+ _ASSERTE(m_cordb->m_unmanagedCallback != NULL);
+
+ do
+ {
+ // Get the first event in the OOB Queue...
+ CordbUnmanagedEvent * pUnmanagedEvent = m_outOfBandEventQueue;
+ CordbUnmanagedThread * pUnmanagedThread = pUnmanagedEvent->m_owner;
+
+ // Make sure we keep the thread alive while we're playing with it.
+ RSSmartPtr<CordbUnmanagedThread> pRef(pUnmanagedThread);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::DUE: dispatching OOB unmanaged event %d for thread 0x%x\n",
+ GetCurrentThreadId(), pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, pUnmanagedThread->m_id));
+
+ m_dispatchingOOBEvent = true;
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+ Unlock();
+
+ {
+ // Interface is semantically const, but does not include const in signature.
+ DEBUG_EVENT * pEvent = const_cast<DEBUG_EVENT *> (&(pUnmanagedEvent->m_currentDebugEvent));
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this, pEvent, TRUE);
+ m_cordb->m_unmanagedCallback->DebugEvent(pEvent, TRUE);
+ }
+
+ Lock();
+
+ // If they called Continue from the callback, then continue the OOB event right now before dispatching the next
+ // one.
+ if (!m_dispatchingOOBEvent)
+ {
+ DequeueOOBUnmanagedEvent(pUnmanagedThread);
+
+ // Should not have continued from this debug event yet.
+ _ASSERTE(pUnmanagedEvent->IsEventWaitingForContinue());
+
+ // Do a little extra work if that was an OOB exception event...
+ HRESULT hr = pUnmanagedEvent->m_owner->FixupAfterOOBException(pUnmanagedEvent);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Go ahead and continue now...
+ this->m_pShim->GetWin32EventThread()->DoDbgContinue(this, pUnmanagedEvent);
+ }
+
+ // Implicit release of pUnmanagedThread via pRef
+ }
+ while (!m_dispatchingOOBEvent && (m_outOfBandEventQueue != NULL));
+
+ m_dispatchingOOBEvent = false;
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::DUE: done dispatching OOB events. Queue=0x%08x\n", m_outOfBandEventQueue));
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// StartSyncFromWin32Stop
+//
+// Get the process from a Fozen state or a Live state to a Synchronized State.
+// Note that Process Exit is considered to be synchronized.
+// This is a nop if we're not Interop Debugging.
+// If this function succeeds, we're in a synchronized state.
+//
+// Arguments:
+// pfAsyncBreakSent - returns if this method sent an async-break or not.
+//
+// Return value:
+// typical HRESULT return values, nothing sinister here.
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::StartSyncFromWin32Stop(BOOL * pfAsyncBreakSent)
+{
+ INTERNAL_API_ENTRY(this);
+ if (m_pShim == NULL) // This API is moved off to the shim
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT hr = S_OK;
+
+ // Caller should have taken the stop-go lock. This prevents us from racing w/ a continue.
+ _ASSERTE(m_StopGoLock.HasLock());
+
+ // Process should be init before we try to sync it.
+ _ASSERTE(this->m_initialized);
+
+ // If nobody's listening for an AsyncBreak, and we're not stopped, then our caller
+ // doesn't know if we're sending an AsyncBreak or not; and thus we may not continue.
+ // Failing this assert means that we're stopping but we don't think we're going to get a continue
+ // down the road, and thus we're headed for a deadlock.
+ _ASSERTE((pfAsyncBreakSent != NULL) || (m_stopCount > 0));
+
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = FALSE;
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+ // If we're win32 stopped (but not out-of-band win32 stopped), or if we're running free on the Left Side but we're
+ // just not synchronized (and we're win32 attached), then go ahead and do an internal continue and send an async
+ // break event to get the Left Side sync'd up.
+ //
+ // The process can be running free as far as Win32 events are concerned, but still not synchronized as far as the
+ // Runtime is concerned. This can happen in a lot of cases where we end up with the Runtime not sync'd but with the
+ // process running free due to hijacking, etc...
+ if (((m_state & CordbProcess::PS_WIN32_STOPPED) && (m_outOfBandEventQueue == NULL)) ||
+ (!GetSynchronized() && IsInteropDebugging()))
+ {
+ Lock();
+
+ if (((m_state & CordbProcess::PS_WIN32_STOPPED) && (m_outOfBandEventQueue == NULL)) ||
+ (!GetSynchronized() && IsInteropDebugging()))
+ {
+ // This can't be the win32 ET b/c we need that thread to be alive and pumping win32 DE so that
+ // our Async Break can get across.
+ // So nobody should ever be calling this on the w32 ET. But they could, since we do trickle in from
+ // outside APIs. So we need a retail check.
+ if (IsWin32EventThread())
+ {
+ _ASSERTE(!"Don't call this API on the W32 Event Thread");
+
+ Unlock();
+ return ErrWrapper(CORDBG_E_CANT_CALL_ON_THIS_THREAD);
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sending internal continue\n", GetCurrentThreadId());
+
+ // Can't do this on the win32 event thread.
+ _ASSERTE(!this->IsWin32EventThread());
+
+ // If the helper thread is already dead, then we just return as if we sync'd the process.
+ if (m_helperThreadDead)
+ {
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = TRUE;
+ }
+
+ // Mark the process as synchronized so no events will be dispatched until the thing is
+ // continued. However, the marking here is not a usual marking for synchronized. It has special
+ // semantics when we're interop debugging. We use m_oddSync to remember this so that we can take special
+ // action in Continue().
+ SetSynchronized(true);
+ m_oddSync = true;
+
+ // Get the RC Event Thread to stop listening to the process.
+ m_cordb->ProcessStateChanged();
+
+ Unlock();
+
+ return S_OK;
+ }
+
+ m_stopRequested = true;
+
+ // See ::Stop for why we defer this. The delayed events will be dispatched when some one calls continue.
+ // And we know they'll call continue b/c (stopCount > 0) || (our caller knows we're sending an AsyncBreak).
+ m_specialDeferment = true;
+
+ Unlock();
+
+ // If the process gets synchronized between the Unlock() and here, then SendUnmanagedContinue() will end up
+ // not doing anything at all since a) it holds the process lock when working and b) it gates everything on
+ // if the process is sync'd or not. This is exactly what we want.
+ hr = this->m_pShim->GetWin32EventThread()->SendUnmanagedContinue(this, cInternalUMContinue);
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: internal continue returned\n", GetCurrentThreadId()));
+
+ // Send an async break to the left side now that its running.
+ DebuggerIPCEvent * pEvent = (DebuggerIPCEvent *) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(pEvent, DB_IPCE_ASYNC_BREAK, false, VMPTR_AppDomain::NullPtr());
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sending async stop\n", GetCurrentThreadId()));
+
+ // If the process gets synchronized between the Unlock() and here, then this message will do nothing (Left
+ // Side catches it) and we'll never get a response, and it won't hurt anything.
+ hr = m_cordb->SendIPCEvent(this, pEvent, CorDBIPC_BUFFER_SIZE);
+ // @Todo- how do we handle a failure here?
+
+ // If the send returns with the helper thread being dead, then we know we don't need to wait for the process
+ // to sync.
+ if (!m_helperThreadDead)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: sent async stop, waiting for event\n", GetCurrentThreadId());
+
+ // If we got synchronized between the Unlock() and here its okay since m_stopWaitEvent is still high
+ // from the last sync.
+ DWORD dwWaitResult = SafeWaitForSingleObject(this, m_stopWaitEvent, INFINITE);
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "[%x] CP::SSFW32S: got event, %d\n", GetCurrentThreadId(), dwWaitResult);
+
+ _ASSERTE(dwWaitResult == WAIT_OBJECT_0);
+ }
+
+ Lock();
+
+ m_specialDeferment = false;
+
+ if (pfAsyncBreakSent)
+ {
+ *pfAsyncBreakSent = TRUE;
+ }
+
+ // If the helper thread died while we were trying to send an event to it, then we just do the same odd sync
+ // logic we do above.
+ if (m_helperThreadDead)
+ {
+ SetSynchronized(true);
+ m_oddSync = true;
+ hr = S_OK;
+ }
+
+ m_stopRequested = false;
+ m_cordb->ProcessStateChanged();
+ }
+
+ Unlock();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ return hr;
+}
+
+// Check if the left side has exited. If so, get the right-side
+// into shutdown mode. Only use this to avert us from going into
+// an unrecoverable error.
+bool CordbProcess::CheckIfLSExited()
+{
+// Check by waiting on the handle with no timeout.
+ if (WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0)
+ {
+ Lock();
+ m_terminated = true;
+ m_exiting = true;
+ Unlock();
+ }
+
+ LOG((LF_CORDB, LL_INFO10, "CP::IsLSExited() returning '%s'\n",
+ m_exiting ? "true" : "false"));
+
+ return m_exiting;
+}
+
+// Call this if something really bad happened and we can't do
+// anything meaningful with the CordbProcess.
+void CordbProcess::UnrecoverableError(HRESULT errorHR,
+ unsigned int errorCode,
+ const char *errorFile,
+ unsigned int errorLine)
+{
+ LOG((LF_CORDB, LL_INFO10, "[%x] CP::UE: unrecoverable error 0x%08x "
+ "(%d) %s:%d\n",
+ GetCurrentThreadId(),
+ errorHR, errorCode, errorFile, errorLine));
+
+ // We definitely want to know about any of these.
+ STRESS_LOG3(LF_CORDB, LL_EVERYTHING, "Unrecoverable Error:0x%08x, File=%s, line=%d\n", errorHR, errorFile, errorLine);
+
+ // It's possible for an unrecoverable error to occur if the user detaches the
+ // debugger while inside CordbProcess::DispatchRCEvent() (as that function deliberately
+ // calls Unlock() while calling into the Shim). Detect such cases here & bail before we
+ // try to access invalid fields on this CordbProcess.
+ //
+ // Normally, we'd need to take the cordb process lock around the IsNeutered check
+ // (and the code that follows). And perhaps this is a good thing to do in the
+ // future. But for now we're not for two reasons:
+ //
+ // 1) It's scary. We're in UnrecoverableError() for gosh sake. I don't know all
+ // the possible bad states we can be in to get here. Will taking the process lock
+ // have ordering issues? Will the process lock even be valid to take here (or might
+ // we AV)? Since this is error handling, we should probably be as light as we can
+ // not to cause more errors.
+ //
+ // 2) It's unnecessary. For the Watson dump I investigated that caused this fix in
+ // the first place, we already detached before entering UnrecoverableError()
+ // (indeed, the only reason we're in UnrecoverableError is that we already detached
+ // and that caused a prior API to fail). Thus, there's no timing issue (in that
+ // case, anyway), wrt to entering UnrecoverableError() and detaching / neutering.
+ if (IsNeutered())
+ return;
+
+#ifdef _DEBUG
+ // Ping our error trapping logic
+ HRESULT hrDummy;
+ hrDummy = ErrWrapper(errorHR);
+#endif
+
+ if (m_pShim == NULL)
+ {
+ // @dbgtodo - , shim: Once everything is hoisted, we can remove
+ // this code.
+ // In the v3 case, we should never get an unrecoverable error. Instead, the HR should be propogated
+ // and returned at the top-level public API.
+ _ASSERTE(!"Unrecoverable error dispatched in V3 case.");
+ }
+
+ CONSISTENCY_CHECK_MSGF(IsLegalFatalError(errorHR), ("Unrecoverable internal error: hr=0x%08x!", errorHR));
+
+ if (!IsLegalFatalError(errorHR) || (errorHR != CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS))
+ {
+ // This will throw everything into a Zombie state. The ATT_ macros will check this and fail immediately.
+ m_unrecoverableError = true;
+
+ //
+ // Mark the process as no longer synchronized.
+ //
+ Lock();
+ SetSynchronized(false);
+ IncStopCount();
+ Unlock();
+ }
+
+ // Set the error flags in the process so that if parts of it are
+ // still alive, it will realize that its in this mode and do the
+ // right thing.
+ if (GetDCB() != NULL)
+ {
+ GetDCB()->m_errorHR = errorHR;
+ GetDCB()->m_errorCode = errorCode;
+ EX_TRY
+ {
+ UpdateLeftSideDCBField(&(GetDCB()->m_errorHR), sizeof(GetDCB()->m_errorHR));
+ UpdateLeftSideDCBField(&(GetDCB()->m_errorCode), sizeof(GetDCB()->m_errorCode));
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Writing process memory failed, perhaps due to an unexpected disconnection from the target.");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+
+ //
+ // Let the user know that we've hit an unrecoverable error.
+ //
+ if (m_cordb->m_managedCallback)
+ {
+ // We are about to send DebuggerError call back. The state of RS is undefined.
+ // So we use the special Public Callback. We may be holding locks and stuff.
+ // We may also be deeply nested within the RS.
+ PUBLIC_CALLBACK_IN_THIS_SCOPE_DEBUGGERERROR(this);
+ m_cordb->m_managedCallback->DebuggerError((ICorDebugProcess*) this,
+ errorHR,
+ errorCode);
+ }
+}
+
+
+HRESULT CordbProcess::CheckForUnrecoverableError()
+{
+ HRESULT hr = S_OK;
+
+ if (GetDCB() != NULL)
+ {
+ // be sure we have the latest information
+ UpdateRightSideDCB();
+
+ if (GetDCB()->m_errorHR != S_OK)
+ {
+ UnrecoverableError(GetDCB()->m_errorHR,
+ GetDCB()->m_errorCode,
+ __FILE__, __LINE__);
+
+ hr = GetDCB()->m_errorHR;
+ }
+ }
+
+ return hr;
+}
+
+
+/*
+ * EnableLogMessages enables/disables sending of log messages to the
+ * debugger for logging.
+ */
+HRESULT CordbProcess::EnableLogMessages(BOOL fOnOff)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+ HRESULT hr = S_OK;
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_ENABLE_LOG_MESSAGES, false, VMPTR_AppDomain::NullPtr());
+ event->LogSwitchSettingMessage.iLevel = (int)fOnOff;
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::EnableLogMessages: EnableLogMessages=%d sent.\n",
+ GetCurrentThreadId(), fOnOff));
+
+ return hr;
+}
+
+/*
+ * ModifyLogSwitch modifies the specified switch's severity level.
+ */
+COM_METHOD CordbProcess::ModifyLogSwitch(__in_z WCHAR *pLogSwitchName, LONG lLevel)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+
+ HRESULT hr = S_OK;
+
+ _ASSERTE (pLogSwitchName != NULL);
+
+ DebuggerIPCEvent *event = (DebuggerIPCEvent*) _alloca(CorDBIPC_BUFFER_SIZE);
+ InitIPCEvent(event, DB_IPCE_MODIFY_LOGSWITCH, false, VMPTR_AppDomain::NullPtr());
+ event->LogSwitchSettingMessage.iLevel = lLevel;
+ event->LogSwitchSettingMessage.szSwitchName.SetStringTruncate(pLogSwitchName);
+
+ hr = m_cordb->SendIPCEvent(this, event, CorDBIPC_BUFFER_SIZE);
+ hr = WORST_HR(hr, event->hr);
+
+ LOG((LF_CORDB, LL_INFO10000, "[%x] CP::ModifyLogSwitch: ModifyLogSwitch sent.\n",
+ GetCurrentThreadId()));
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Writes a buffer from the target and performs checks similar to SafeWriteStruct
+//
+// Arguments:
+// tb - TargetBuffer which represents the target memory we want to write to
+// pLocalBuffer - local pointer into source buffer
+// cbSize - the size of local buffer
+//
+// Exceptions
+// On error throws the result of WriteVirtual unless a short write is performed,
+// in which case throws ERROR_PARTIAL_COPY
+//
+void CordbProcess::SafeWriteBuffer(TargetBuffer tb,
+ const BYTE * pLocalBuffer)
+{
+ _ASSERTE(m_pMutableDataTarget != NULL);
+ HRESULT hr = m_pMutableDataTarget->WriteVirtual(tb.pAddress,
+ pLocalBuffer,
+ tb.cbSize);
+ IfFailThrow(hr);
+}
+
+//-----------------------------------------------------------------------------
+// Reads a buffer from the target and performs checks similar to SafeWriteStruct
+//
+// Arguments:
+// tb - TargetBuffer which represents the target memory to read from
+// pLocalBuffer - local pointer into source buffer
+// cbSize - the size of the remote buffer
+// throwOnError - determines whether the function throws exceptions or returns HRESULTs
+// in failure cases
+//
+// Exceptions:
+// If throwOnError is TRUE
+// On error always throws the special CORDBG_E_READVIRTUAL_FAILURE, unless a short write is performed
+// in which case throws ERROR_PARTIAL_COPY
+// If throwOnError is FALSE
+// No exceptions are thrown, and instead the same error codes are returned as HRESULTs
+//
+HRESULT CordbProcess::SafeReadBuffer(TargetBuffer tb, BYTE * pLocalBuffer, BOOL throwOnError)
+{
+ ULONG32 cbRead;
+ HRESULT hr = m_pDACDataTarget->ReadVirtual(tb.pAddress,
+ pLocalBuffer,
+ tb.cbSize,
+ &cbRead);
+
+ if (FAILED(hr))
+ {
+ if (throwOnError)
+ ThrowHR(CORDBG_E_READVIRTUAL_FAILURE);
+ else
+ return CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (cbRead != tb.cbSize)
+ {
+ if (throwOnError)
+ ThrowWin32(ERROR_PARTIAL_COPY);
+ else
+ return HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ return S_OK;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Lookup or create an appdomain.
+//
+// Arguments:
+// vmAppDomain - CLR appdomain to lookup
+//
+// Returns:
+// Instance of CordbAppDomain for the given appdomain. This is a cached instance.
+// If the CordbAppDomain does not yet exist, it will be created and added to the cache.
+// Never returns NULL. Throw on error.
+CordbAppDomain * CordbProcess::LookupOrCreateAppDomain(VMPTR_AppDomain vmAppDomain)
+{
+ CordbAppDomain * pAppDomain = m_appDomains.GetBase(VmPtrToCookie(vmAppDomain));
+ if (pAppDomain != NULL)
+ {
+ return pAppDomain;
+ }
+ return CacheAppDomain(vmAppDomain);
+}
+
+CordbAppDomain * CordbProcess::GetSharedAppDomain()
+{
+ if (m_sharedAppDomain == NULL)
+ {
+ CordbAppDomain *pAD = new CordbAppDomain(this, VMPTR_AppDomain::NullPtr());
+ if (InterlockedCompareExchangeT<CordbAppDomain*>(&m_sharedAppDomain, pAD, NULL) != NULL)
+ {
+ delete pAD;
+ }
+ m_sharedAppDomain->InternalAddRef();
+ }
+
+ return m_sharedAppDomain;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Add a new appdomain to the cache.
+//
+// Arguments:
+// vmAppDomain - appdomain to add.
+//
+// Return Value:
+// Pointer to newly created appdomain, which should be the normal case.
+// Throws on failure. Never returns null.
+//
+// Assumptions:
+// Caller ensure the appdomain is not already cached.
+// Caller should have stop-go lock, which provides thread-safety.
+//
+// Notes:
+// This sets unrecoverable error on failure.
+//
+//---------------------------------------------------------------------------------------
+CordbAppDomain * CordbProcess::CacheAppDomain(VMPTR_AppDomain vmAppDomain)
+{
+ INTERNAL_API_ENTRY(GetProcess());
+
+ _ASSERTE(GetProcessLock()->HasLock());
+
+ RSInitHolder<CordbAppDomain> pAppDomain;
+ pAppDomain.Assign(new CordbAppDomain(this, vmAppDomain)); // throws
+
+ // Add to the hash. This will addref the pAppDomain.
+ // Caller ensures we're not already cached.
+ // The cache will take ownership.
+ m_appDomains.AddBaseOrThrow(pAppDomain);
+
+ // see if this is the default AppDomain
+ IDacDbiInterface * pDac = m_pProcess->GetDAC();
+ BOOL fIsDefaultDomain = FALSE;
+
+ fIsDefaultDomain = pDac->IsDefaultDomain(vmAppDomain); // throws
+
+ if (fIsDefaultDomain)
+ {
+ // If this assert fires, then it likely means the target is corrupted.
+ TargetConsistencyCheck(m_pDefaultAppDomain == NULL);
+ m_pDefaultAppDomain = pAppDomain;
+ }
+
+ CordbAppDomain * pReturn = pAppDomain;
+ pAppDomain.ClearAndMarkDontNeuter();
+
+ _ASSERTE(pReturn != NULL);
+ return pReturn;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Callback for Appdomain enumeration.
+//
+// Arguments:
+// vmAppDomain - new appdomain to add to enumeration
+// pUserData - data passed with callback (a 'this' ptr for CordbProcess)
+//
+//
+// Assumptions:
+// Invoked as callback from code:CordbProcess::PrepopulateAppDomains
+//
+//
+//---------------------------------------------------------------------------------------
+
+// static
+void CordbProcess::AppDomainEnumerationCallback(VMPTR_AppDomain vmAppDomain, void * pUserData)
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ CordbProcess * pProcess = static_cast<CordbProcess *> (pUserData);
+ INTERNAL_DAC_CALLBACK(pProcess);
+
+ pProcess->LookupOrCreateAppDomain(vmAppDomain);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Traverse appdomains in the target and build up our list.
+//
+// Arguments:
+//
+// Return Value:
+// returns on success.
+// Throws on error. AppDomain cache may be partially populated.
+//
+// Assumptions:
+// This is an non-invasive inspection operation called when the debuggee is stopped.
+//
+// Notes:
+// This can be called multiple times. If the list is non-empty, it will nop.
+//---------------------------------------------------------------------------------------
+void CordbProcess::PrepopulateAppDomainsOrThrow()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ INTERNAL_API_ENTRY(this);
+
+ if (!IsDacInitialized())
+ {
+ return;
+ }
+
+ // DD-primitive that invokes a callback. This may throw.
+ GetDAC()->EnumerateAppDomains(
+ CordbProcess::AppDomainEnumerationCallback,
+ this);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// EnumerateAppDomains enumerates all app domains in the process.
+//
+// Arguments:
+// ppAppDomains - get appdomain enumerator
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+//
+//
+// Notes:
+// This operation is non-invasive target.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbProcess::EnumerateAppDomains(ICorDebugAppDomainEnum **ppAppDomains)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this);
+ {
+ ValidateOrThrow(ppAppDomains);
+
+ // Ensure list is populated.
+ PrepopulateAppDomainsOrThrow();
+
+ RSInitHolder<CordbHashTableEnum> pEnum;
+ CordbHashTableEnum::BuildOrThrow(
+ this,
+ GetContinueNeuterList(),
+ &m_appDomains,
+ IID_ICorDebugAppDomainEnum,
+ pEnum.GetAddr());
+
+ *ppAppDomains = static_cast<ICorDebugAppDomainEnum*> (pEnum);
+ pEnum->ExternalAddRef();
+
+ pEnum.ClearAndMarkDontNeuter();
+ }
+ PUBLIC_API_END(hr);
+ return hr;
+}
+
+/*
+ * GetObject returns the runtime process object.
+ * Note: This method is not yet implemented.
+ */
+HRESULT CordbProcess::GetObject(ICorDebugValue **ppObject)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(ppObject, ICorDebugObjectValue **);
+
+ return E_NOTIMPL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Given a taskid, finding the corresponding thread. The function can fail if we do not
+// find any thread with the given taskid
+//
+// Arguments:
+// taskId - The task ID to look for.
+// ppThread - OUT: Space for storing the thread corresponding to the taskId given.
+//
+// Return Value:
+// Typical HRESULT symantics, nothing abnormal.
+//
+HRESULT CordbProcess::GetThreadForTaskID(TASKID taskId, ICorDebugThread2 ** ppThread)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess());
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+
+ if (ppThread == NULL)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+ // On initialization, the task ID of every thread is INVALID_TASK_ID, unless a host is present and
+ // the host calls IClrTask::SetTaskIdentifier(). So we need to explicitly check for INVALID_TASK_ID
+ // here and return NULL if necessary. We return S_FALSE because that's the return value for the case
+ // where we can't find a thread for the specified task ID.
+ if (taskId == INVALID_TASK_ID)
+ {
+ *ppThread = NULL;
+ hr = S_FALSE;
+ }
+ else
+ {
+ PrepopulateThreadsOrThrow();
+
+ // now find the ICorDebugThread corresponding to it
+ CordbThread * pThread;
+ HASHFIND hashFind;
+
+
+ for (pThread = m_userThreads.FindFirst(&hashFind);
+ pThread != NULL;
+ pThread = m_userThreads.FindNext(&hashFind))
+ {
+ if (pThread->GetTaskID() == taskId)
+ {
+ break;
+ }
+ }
+
+ if (pThread == NULL)
+ {
+ *ppThread = NULL;
+ hr = S_FALSE;
+ }
+ else
+ {
+ *ppThread = pThread;
+ pThread->ExternalAddRef();
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+} // CordbProcess::GetThreadForTaskid
+
+HRESULT
+CordbProcess::GetVersion(COR_VERSION* pVersion)
+{
+ if (NULL == pVersion)
+ {
+ return E_INVALIDARG;
+ }
+
+ //
+ // Because we require a matching version of mscordbi.dll to debug a certain version of the runtime,
+ // we can just use constants found in this particular mscordbi.dll to determine the version of the left side.
+ pVersion->dwMajor = VER_MAJORVERSION;
+ pVersion->dwMinor = VER_MINORVERSION;
+ pVersion->dwBuild = VER_PRODUCTBUILD;
+ pVersion->dwSubBuild = VER_PRODUCTBUILD_QFE;
+
+ return S_OK;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//-----------------------------------------------------------------------------
+// Search for a native patch given the address. Return null if not found.
+// Since we return an address, this is only valid until the table is disturbed.
+//-----------------------------------------------------------------------------
+NativePatch * CordbProcess::GetNativePatch(const void * pAddress)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ int cTotal = m_NativePatchList.Count();
+ NativePatch * pTable = m_NativePatchList.Table();
+ if (pTable == NULL)
+ {
+ return NULL;
+ }
+
+ for(int i = 0; i < cTotal; i++)
+ {
+ if (pTable[i].pAddress == pAddress)
+ {
+ return &pTable[i];
+ }
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Is there an break-opcode (int3 on x86) at the address in the debuggee?
+//-----------------------------------------------------------------------------
+bool CordbProcess::IsBreakOpcodeAtAddress(const void * address)
+{
+ // There should have been an int3 there already. Since we already put it in there,
+ // we should be able to safely read it out.
+ BYTE opcodeTest = 0;
+
+ HRESULT hr = SafeReadStruct(PTR_TO_CORDB_ADDRESS(address), &opcodeTest);
+ SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr);
+
+ return (opcodeTest == CORDbg_BREAK_INSTRUCTION);
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//-----------------------------------------------------------------------------
+// CordbProcess::SetUnmanagedBreakpoint
+// Called by a native debugger to add breakpoints during Interop.
+// address - remote address into the debuggee
+// bufsize, buffer[] - initial size & buffer for the opcode that we're replacing.
+// buflen - size of the buffer that we write to.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::SetUnmanagedBreakpoint(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::SetUnBP: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+ _ASSERTE(!ThreadHoldsProcessLock());
+ Lock();
+ HRESULT hr = SetUnmanagedBreakpointInternal(address, bufsize, buffer, bufLen);
+ Unlock();
+ return hr;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// CordbProcess::SetUnmanagedBreakpointInternal
+// The worker behind SetUnmanagedBreakpoint, this function can set both public
+// breakpoints used by the debugger and internal breakpoints used for utility
+// purposes in interop debugging.
+// address - remote address into the debuggee
+// bufsize, buffer[] - initial size & buffer for the opcode that we're replacing.
+// buflen - size of the buffer that we write to.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::SetUnmanagedBreakpointInternal(CORDB_ADDRESS address, ULONG32 bufsize, BYTE buffer[], ULONG32 * bufLen)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::SetUnBPI: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+
+ INTERNAL_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+
+ NativePatch * p = NULL;
+#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64)
+ const BYTE patch = CORDbg_BREAK_INSTRUCTION;
+ BYTE opcode;
+
+ // Make sure args are good
+ if ((buffer == NULL) || (bufsize < sizeof(patch)) || (bufLen == NULL))
+ {
+ hr = E_INVALIDARG;
+ goto ErrExit;
+ }
+
+ // Fail if there's already a patch at this address.
+ if (GetNativePatch(CORDB_ADDRESS_TO_PTR(address)) != NULL)
+ {
+ hr = CORDBG_E_NATIVE_PATCH_ALREADY_AT_ADDR;
+ goto ErrExit;
+ }
+
+ // Preallocate this now so that if are oom, we can fail before we get half-way through.
+ p = m_NativePatchList.Append();
+ if (p == NULL)
+ {
+ hr = E_OUTOFMEMORY;
+ goto ErrExit;
+ }
+
+
+ // Read out opcode. 1 byte on x86
+
+ hr = ApplyRemotePatch(this, CORDB_ADDRESS_TO_PTR(address), &p->opcode);
+ if (FAILED(hr))
+ goto ErrExit;
+
+ // It's all successful, so now update our out-params & internal bookkeaping.
+ opcode = (BYTE) p->opcode;
+ buffer[0] = opcode;
+ *bufLen = sizeof(opcode);
+
+ p->pAddress = CORDB_ADDRESS_TO_PTR(address);
+ p->opcode = opcode;
+
+ _ASSERTE(SUCCEEDED(hr));
+#elif defined(DBG_TARGET_WIN64)
+ PORTABILITY_ASSERT("NYI: CordbProcess::SetUnmanagedBreakpoint, interop debugging NYI on this platform");
+ hr = E_NOTIMPL;
+ goto ErrExit;
+#else
+ hr = E_NOTIMPL;
+ goto ErrExit;
+#endif // DBG_TARGET_X8_
+
+
+ErrExit:
+ // If we failed, then free the patch
+ if (FAILED(hr) && (p != NULL))
+ {
+ m_NativePatchList.Delete(*p);
+ }
+
+ return hr;
+
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//-----------------------------------------------------------------------------
+// CordbProcess::ClearUnmanagedBreakpoint
+// Called by a native debugger to remove breakpoints during Interop.
+// The patch is deleted even if the function fails.
+//-----------------------------------------------------------------------------
+HRESULT
+CordbProcess::ClearUnmanagedBreakpoint(CORDB_ADDRESS address)
+{
+ LOG((LF_CORDB, LL_INFO100, "CP::ClearUnBP: pProcess=%x, address=%p.\n", this, CORDB_ADDRESS_TO_PTR(address)));
+#ifndef FEATURE_INTEROP_DEBUGGING
+ return E_NOTIMPL;
+#else
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ FAIL_IF_MANAGED_ONLY(this);
+
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+ PRD_TYPE opcode;
+
+ Lock();
+
+ // Make sure this is a valid patch.
+ int cTotal = m_NativePatchList.Count();
+ NativePatch * pTable = m_NativePatchList.Table();
+ if (pTable == NULL)
+ {
+ hr = CORDBG_E_NO_NATIVE_PATCH_AT_ADDR;
+ goto ErrExit;
+ }
+
+ int i;
+ for(i = 0; i < cTotal; i++)
+ {
+ if (pTable[i].pAddress == CORDB_ADDRESS_TO_PTR(address))
+ break;
+ }
+
+ if (i >= cTotal)
+ {
+ hr = CORDBG_E_NO_NATIVE_PATCH_AT_ADDR;
+ goto ErrExit;
+ }
+
+ // Found it! Remove it from our table. Note that this may shuffle table contents
+ // around, so don't keep pointers into the table.
+ opcode = pTable[i].opcode;
+
+ m_NativePatchList.Delete(pTable[i]);
+ _ASSERTE(m_NativePatchList.Count() == cTotal - 1);
+
+ // Now remove the patch.
+
+
+
+ // Just call through to Write ProcessMemory
+ hr = RemoveRemotePatch(this, CORDB_ADDRESS_TO_PTR(address), opcode);
+ if (FAILED(hr))
+ goto ErrExit;
+
+
+ // Our internal bookeaping was already updated to remove the patch, so now we're done.
+ // If we had a failure, we should have already bailed.
+ _ASSERTE(SUCCEEDED(hr));
+
+ErrExit:
+ Unlock();
+ return hr;
+#endif // FEATURE_INTEROP_DEBUGGING
+}
+
+
+//------------------------------------------------------------------------------------
+// StopCount, Sync, SyncReceived form our stop-status. This status is super-critical
+// to most hangs, so we stress log it.
+//------------------------------------------------------------------------------------
+void CordbProcess::SetSynchronized(bool fSynch)
+{
+ _ASSERTE(ThreadHoldsProcessLock() || !"Must have process lock to toggle SyncStatus");
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: set sync=%d\n", fSynch);
+ m_synchronized = fSynch;
+}
+
+bool CordbProcess::GetSynchronized()
+{
+ // This can be accessed whether we're Locked or not. This means that the result
+ // may change underneath us.
+ return m_synchronized;
+}
+
+void CordbProcess::IncStopCount()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_stopCount++;
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: Inc StopCount=%d\n", m_stopCount);
+}
+void CordbProcess::DecStopCount()
+{
+ // We can inc w/ just the process lock (b/c we can dispatch events from the W32ET)
+ // But decrementing (eg, Continue), requires the stop-go lock.
+ // This if an operation takes the SG lock, it ensures we don't continue from underneath it.
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ m_stopCount--;
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: Dec StopCount=%d\n", m_stopCount);
+}
+
+// Just gets whether we're stopped or not (m_stopped > 0).
+// You only need the StopGo lock for this.
+bool CordbProcess::IsStopped()
+{
+ // We don't require the process-lock, just the SG-lock.
+ // Holding the SG lock prevents another thread from continuing underneath you.
+ // (see DecStopCount()).
+ // But you could still be running free, and have another thread stop-underneath you.
+ // Thus IsStopped() leans towards returning false.
+ ASSERT_SINGLE_THREAD_ONLY(HoldsLock(&m_StopGoLock));
+
+ return (m_stopCount > 0);
+}
+
+int CordbProcess::GetStopCount()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_stopCount;
+}
+
+bool CordbProcess::GetSyncCompleteRecv()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ return m_syncCompleteReceived;
+}
+
+void CordbProcess::SetSyncCompleteRecv(bool fSyncRecv)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CP:: set syncRecv=%d\n", fSyncRecv);
+ m_syncCompleteReceived = fSyncRecv;
+}
+
+// This can be used if we ever need the RS to emulate old behavior of previous versions.
+// This can not be used in QIs to deny queries for new interfaces.
+// QIs must be consistent across the lifetime of an object. Say CordbThread used this in a QI
+// do deny returning a ICorDebugThread2 interface when emulating v1.1. Once that Thread is neutered,
+// it no longer has a pointer to the process, and it no longer knows if it should be denying
+// the v2.0 query. An object's QI can't start returning new interfaces onces its neutered.
+bool CordbProcess::SupportsVersion(CorDebugInterfaceVersion featureVersion)
+{
+ _ASSERTE(featureVersion == CorDebugVersion_2_0);
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------
+// Add an object to the process's Left-Side resource cleanup list
+//
+// Arguments:
+// pObject - non-null object to be added
+//
+// Notes:
+// This list tracks objects with process-scope that hold left-side
+// resources (like func-eval).
+// See code:CordbAppDomain::GetSweepableExitNeuterList for per-appdomain
+// objects with left-side resources.
+void CordbProcess::AddToLeftSideResourceCleanupList(CordbBase * pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ m_LeftSideResourceCleanupList.Add(this, pObject);
+}
+
+// This list will get actively swept (looking for objects w/ external ref = 0) between continues.
+void CordbProcess::AddToNeuterOnExitList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ this->m_ExitNeuterList.Add(this, pObject);
+ }
+ EX_CATCH_HRESULT(hr);
+ SetUnrecoverableIfFailed(GetProcess(), hr);
+}
+
+// Mark that this object should be neutered the next time we Continue the process.
+void CordbProcess::AddToNeuterOnContinueList(CordbBase *pObject)
+{
+ INTERNAL_API_ENTRY(this);
+ _ASSERTE(pObject != NULL);
+
+ m_ContinueNeuterList.Add(this, pObject); // throws
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Runtime Controller Event Thread class
+ * ------------------------------------------------------------------------- */
+
+//
+// Constructor
+//
+CordbRCEventThread::CordbRCEventThread(Cordb* cordb)
+{
+ _ASSERTE(cordb != NULL);
+
+ m_cordb.Assign(cordb);
+ m_thread = NULL;
+ m_threadId = 0;
+ m_run = TRUE;
+ m_threadControlEvent = NULL;
+ m_processStateChanged = FALSE;
+
+ g_pRSDebuggingInfo->m_RCET = this;
+}
+
+
+//
+// Destructor. Cleans up all of the open handles and such.
+// This expects that the thread has been stopped and has terminated
+// before being called.
+//
+CordbRCEventThread::~CordbRCEventThread()
+{
+ if (m_threadControlEvent != NULL)
+ CloseHandle(m_threadControlEvent);
+
+ if (m_thread != NULL)
+ CloseHandle(m_thread);
+
+ g_pRSDebuggingInfo->m_RCET = NULL;
+}
+
+//
+// Init sets up all the objects that the thread will need to run.
+//
+HRESULT CordbRCEventThread::Init()
+{
+ if (m_cordb == NULL)
+ return E_INVALIDARG;
+
+ m_threadControlEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+
+ if (m_threadControlEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ return S_OK;
+}
+
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+//
+// Helper to duplicate a handle or thorw
+//
+// Arguments:
+// pLocalHandle - handle to duplicate into the remote process
+// pRemoteHandle - RemoteHandle structure in IPC block to hold the remote handle.
+// Return value:
+// None. Throws on error.
+//
+void CordbProcess::DuplicateHandleToLocalProcess(HANDLE * pLocalHandle, RemoteHANDLE * pRemoteHandle)
+{
+ _ASSERTE(m_pShim != NULL);
+
+ // Dup RSEA and RSER into this process if we don't already have them.
+ // On Launch, we don't have them yet, but on attach we do.
+ if (*pLocalHandle == NULL)
+ {
+ BOOL fSuccess = pRemoteHandle->DuplicateToLocalProcess(m_handle, pLocalHandle);
+ if (!fSuccess)
+ {
+ ThrowLastError();
+ }
+ }
+
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+// Public entry wrapper for code:CordbProcess::FinishInitializeIPCChannelWorker
+void CordbProcess::FinishInitializeIPCChannel()
+{
+ // This is called directly from a shim callback.
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ FinishInitializeIPCChannelWorker();
+}
+
+//
+// Initialize the IPC channel. After this, IPC events can flow in both ways.
+//
+// Return value:
+// Returns S_OK on success.
+//
+// Notes:
+// This will dispatch an UnrecoverableError callback if it fails.
+// This will also initialize key state in the CordbProcess object.
+//
+// @dbgtodo remove helper-thread: this should eventually go away once we get rid of IPC events.
+//
+void CordbProcess::FinishInitializeIPCChannelWorker()
+{
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ _ASSERTE(m_pShim != NULL);
+
+ RSLockHolder lockHolder(&this->m_processMutex);
+
+ // If it's already initialized, then nothing left to do.
+ // this protects us if this function is called multiple times.
+ if (m_initialized)
+ {
+ _ASSERTE(GetDCB() != NULL);
+ return;
+ }
+
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HFRCE: first event..., process %p\n", GetCurrentThreadId(), this));
+
+ BOOL fBlockExists;
+ GetEventBlock(&fBlockExists); // throws on error
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Size of CdbP is %d\n", sizeof(CordbProcess)));
+
+ m_pEventChannel->Init(m_handle);
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ DuplicateHandleToLocalProcess(&m_leftSideUnmanagedWaitEvent, &GetDCB()->m_leftSideUnmanagedWaitEvent);
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // Read the Runtime Offsets struct out of the debuggee.
+ hr = GetRuntimeOffsets();
+ IfFailThrow(hr);
+
+ // we need to be careful here. The LS will have a thread running free that may be initializing
+ // fields of the DCB (specifically it may be setting up the helper thread), so we need to make sure
+ // we don't overwrite any fields that the LS is writing. We need to be sure we only write to RS
+ // status fields.
+ m_initialized = true;
+ GetDCB()->m_rightSideIsWin32Debugger = IsInteropDebugging();
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+
+ LOG((LF_CORDB, LL_INFO1000, "[%x] RCET::HFRCE: ...went fine\n", GetCurrentThreadId()));
+ _ASSERTE(SUCCEEDED(hr));
+
+ } EX_CATCH_HRESULT(hr);
+ if (SUCCEEDED(hr))
+ {
+ return;
+ }
+
+ // We only land here on failure cases.
+ // We must have jumped to this label. Maybe we didn't set HR, so check now.
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "HFCR: FAILED hr=0x%08x\n", hr);
+
+ CloseIPCHandles();
+
+ // Rethrow
+ ThrowHR(hr);
+}
+
+
+//---------------------------------------------------------------------------------------
+// Marshals over a string buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_BaseBuffer::CopyLSDataToRSWorker(ICorDebugDataTarget * pTarget)
+{
+ //
+ const DWORD cbCacheSize = m_cbSize;
+
+ // SHOULD not happen for more than once in well-behaved case.
+ if (m_pbRS != NULL)
+ {
+ SIMPLIFYING_ASSUMPTION(!"m_pbRS is non-null; is this a corrupted event?");
+ ThrowHR(E_INVALIDARG);
+ }
+
+ NewHolder<BYTE> pData(new BYTE[cbCacheSize]);
+
+ ULONG32 cbRead;
+ HRESULT hrRead = pTarget->ReadVirtual(PTR_TO_CORDB_ADDRESS(m_pbLS), pData, cbCacheSize , &cbRead);
+
+ if(FAILED(hrRead))
+ {
+ hrRead = CORDBG_E_READVIRTUAL_FAILURE;
+ }
+
+ if (SUCCEEDED(hrRead) && (cbCacheSize != cbRead))
+ {
+ hrRead = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
+ }
+ IfFailThrow(hrRead);
+
+ // Now do Transfer
+ m_pbRS = pData;
+ pData.SuppressRelease();
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals over a Byte buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_ByteBuffer::CopyLSDataToRS(ICorDebugDataTarget * pTarget)
+{
+ CopyLSDataToRSWorker(pTarget);
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals over a string buffer in a managed event
+//
+// Arguments:
+// pTarget - data-target for read the buffer from the LeftSide.
+//
+// Throws on error
+void Ls_Rs_StringBuffer::CopyLSDataToRS(ICorDebugDataTarget * pTarget)
+{
+ CopyLSDataToRSWorker(pTarget);
+
+ // Ensure we're a valid, well-formed string.
+ // @dbgtodo - this should only happen in corrupted scenarios. Perhaps a better HR here?
+ // - null terminated.
+ // - no embedded nulls.
+
+ const WCHAR * pString = GetString();
+ SIZE_T dwExpectedLenWithNull = m_cbSize / sizeof(WCHAR);
+
+ // Should at least have 1 character for the null-terminator.
+ if (dwExpectedLenWithNull == 0)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ // Ensure that there's a null where we expect it to be.
+ if (pString[dwExpectedLenWithNull-1] != 0)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+
+ // Now we know it's safe to call wcslen. The buffer is local, so we know the pages are there.
+ // And we know there's a null capping the max length of the string.
+ SIZE_T dwActualLenWithNull = wcslen(pString) + 1;
+ if (dwActualLenWithNull != dwExpectedLenWithNull)
+ {
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+}
+
+//---------------------------------------------------------------------------------------
+// Marshals the arguments in a managed-debug event.
+//
+// Arguments:
+// pManagedEvent - (IN/OUT) debug event to marshal. Events are not usable in the host process
+// until they are marshalled. This will marshal the event in-place, and may convert
+// some target addresses to host addresses.
+//
+// Return Value:
+// S_OK on success. Else Error.
+//
+// Assumptions:
+// Target is currently stopped and inspectable.
+// After the event is marshalled, it has resources that must be cleaned up
+// by calling code:DeleteIPCEventHelper.
+//
+// Notes:
+// Call a Copy function (CopyManagedEventFromTarget, CopyRCEventFromIPCBlock)to
+// get the event to marshal.
+// This will marshal args from the target into the host.
+// The debug event is fixed size. But since the debuggee is stopped, this can copy
+// arbitrary-length buffers out of of the debuggee.
+//
+// This could be rolled into code:CordbProcess::RawDispatchEvent
+//---------------------------------------------------------------------------------------
+void CordbProcess::MarshalManagedEvent(DebuggerIPCEvent * pManagedEvent)
+{
+ CONTRACTL
+ {
+ THROWS;
+
+ // Event has already been copied, now we do some quick Marshalling.
+ // Thsi should be a private local copy, and not the one in the IPC block or Target.
+ PRECONDITION(CheckPointer(pManagedEvent));
+ }
+ CONTRACTL_END;
+
+ IfFailThrow(pManagedEvent->hr);
+
+ // This may throw part way through marshalling. But that's ok because
+ // code:DeleteIPCEventHelper can cleanup a partially-marshalled event.
+
+ // Do a pre-processing on the event
+ switch (pManagedEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ case DB_IPCE_MDA_NOTIFICATION:
+ {
+ pManagedEvent->MDANotification.szName.CopyLSDataToRS(this->m_pDACDataTarget);
+ pManagedEvent->MDANotification.szDescription.CopyLSDataToRS(this->m_pDACDataTarget);
+ pManagedEvent->MDANotification.szXml.CopyLSDataToRS(this->m_pDACDataTarget);
+ break;
+ }
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ {
+ pManagedEvent->FirstLogMessage.szContent.CopyLSDataToRS(this->m_pDACDataTarget);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+
+}
+
+
+//---------------------------------------------------------------------------------------
+// Copy a managed debug event from the target process into this local process
+//
+// Arguments:
+// pRecord - native-debug event serving as the envelope for the managed event.
+// pLocalManagedEvent - (dst) required local buffer to hold managed event.
+//
+// Return Value:
+// * True if the event belongs to this runtime. This is very useful when multiple CLRs are
+// loaded into the target and all sending events wit the same exception code.
+// * False if this does not belong to this instance of ICorDebug. (perhaps it's an event
+// intended for another instance of the CLR in the target, or some rogue user code happening
+// to use our exception code).
+// In either case, the event can still be cleaned up via code:DeleteIPCEventHelper.
+//
+// Throws on error. In the error case, the contents of pLocalManagedEvent are undefined.
+// They may have been partially copied from the target. The local managed event does not own
+// any resources until it's marshalled, so the buffer can be ignored if this function fails.
+//
+// Assumptions:
+//
+// Notes:
+// The events are sent form the target via code:Debugger::SendRawEvent
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This should always succeed in the well-behaved case. However, A bad debuggee can
+// always send a poor-formed debug event.
+// We don't distinguish between a badly formed event and an event that's not ours.
+// The event still needs to be Marshaled before being used. (see code:CordbProcess::MarshalManagedEvent)
+//
+//---------------------------------------------------------------------------------------
+#if defined(_MSC_VER) && defined(_TARGET_ARM_)
+// This is a temporary workaround for an ARM specific MS C++ compiler bug (internal LKG build 18.1).
+// Branch < if (ptrRemoteManagedEvent == NULL) > was always taken and the function always returned false.
+// TODO: It should be removed once the bug is fixed.
+#pragma optimize("", off)
+#endif
+bool CordbProcess::CopyManagedEventFromTarget(
+ const EXCEPTION_RECORD * pRecord,
+ DebuggerIPCEvent * pLocalManagedEvent)
+{
+ _ASSERTE(pRecord != NULL);
+ _ASSERTE(pLocalManagedEvent != NULL);
+
+ // Initialize the event enough such backout code can call code:DeleteIPCEventHelper.
+ pLocalManagedEvent->type = DB_IPCE_DEBUGGER_INVALID;
+
+ // Ensure we have a CLR instance ID by now. Either we had one already, or we're in
+ // V2 mode and this is the startup event, and so we'll set it now.
+ HRESULT hr = EnsureClrInstanceIdSet();
+ IfFailThrow(hr);
+ _ASSERTE(m_clrInstanceId != 0);
+
+ // Determine if the event is really a debug event, and for our instance.
+ CORDB_ADDRESS ptrRemoteManagedEvent = IsEventDebuggerNotification(pRecord, m_clrInstanceId);
+
+ if (ptrRemoteManagedEvent == NULL)
+ {
+ return false;
+ }
+
+ // What we are doing on Windows here is dangerous. Any buffer for IPC events must be at least
+ // CorDBIPC_BUFFER_SIZE big, but here we are only copying sizeof(DebuggerIPCEvent). Fortunately, the
+ // only case where an IPC event is bigger than sizeof(DebuggerIPCEvent) is for the second category
+ // described in the comment for code:IEventChannel. In this case, we are just transferring the IPC
+ // event from the native pipeline to the event channel, and the event channel will read it directly from
+ // the send buffer on the LS. See code:CordbRCEventThread::WaitForIPCEventFromProcess.
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ hr = SafeReadStruct(ptrRemoteManagedEvent, pLocalManagedEvent);
+#else
+ // For Mac remote debugging the address returned above is actually a local address.
+ // Also, we need to copy the entire buffer because once a debug event is read from the debugger
+ // transport, it won't be available afterwards.
+ memcpy(reinterpret_cast<BYTE *>(pLocalManagedEvent),
+ CORDB_ADDRESS_TO_PTR(ptrRemoteManagedEvent),
+ CorDBIPC_BUFFER_SIZE);
+ hr = S_OK;
+#endif
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ IfFailThrow(hr);
+
+ return true;
+}
+#if defined(_MSC_VER) && defined(_TARGET_ARM_)
+#pragma optimize("", on)
+#endif
+
+//---------------------------------------------------------------------------------------
+// EnsureClrInstanceIdSet - Ensure we have a CLR Instance ID to debug
+//
+// In Arrowhead scenarios, the debugger is required to pass a valid CLR instance ID
+// to us in OpenVirtualProcess. In V2 scenarios, for compatibility, we'll allow a
+// CordbProcess object to exist for a process that doesn't yet have the CLR loaded.
+// In this case the CLR instance ID will start off as 0, but be filled in when we see the
+// startup exception indicating the CLR has been loaded.
+//
+// If we don't already have an instance ID, this function sets it to the only CLR in the
+// target process. This requires that a CLR be loaded in the target process.
+//
+// Return Value:
+// S_OK - if m_clrInstanceId was already set, or is now set to a valid CLR instance ID
+// an error HRESULT - if m_clrInstanceId was 0, and cannot be set to a valid value
+// (i.e. because we cannot find a CLR in the target process).
+//
+// Note that we need to probe for this on attach, and it's common to attach before the
+// CLR has been loaded, so we avoid using exceptions for this common case.
+//
+HRESULT CordbProcess::EnsureClrInstanceIdSet()
+{
+ // If we didn't expect a specific CLR, then attempt to attach to any.
+ if (m_clrInstanceId == 0)
+ {
+
+#ifdef FEATURE_CORESYSTEM
+ if(m_cordb->GetTargetCLR() != 0)
+ {
+ m_clrInstanceId = PTR_TO_CORDB_ADDRESS(m_cordb->GetTargetCLR());
+ return S_OK;
+ }
+#endif
+
+ // The only case in which we're allowed to request the "default" CLR instance
+ // ID is when we're running in V2 mode. In V3, the client is required to pass
+ // a non-zero value to OpenVirtualProcess.
+ _ASSERTE(m_pShim != NULL);
+
+ HRESULT hr = m_pShim->FindLoadedCLR(&m_clrInstanceId);
+ if (FAILED(hr))
+ {
+ // Couldn't find a loaded clr - no CLR instance ID yet
+ _ASSERTE(m_clrInstanceId == 0);
+ return hr;
+ }
+ }
+
+ // We've (now) got a valid CLR instance id
+ return S_OK;
+}
+
+//---------------------------------------------------------------------------------------
+// // Copy event from IPC block into local.
+//
+// Arguments:
+// pLocalManagedEvent - required local buffer to hold managed event.
+//
+// Return Value:
+// None. Always succeeds.
+//
+// Assumptions:
+// The IPC block has already been opened and filled in with an event.
+//
+// Notes:
+// This is copying from a shared-memory block, which is treated as local memory.
+// This just does a raw Byte copy, but does not do any Marshalling.
+// This does no validation on the event.
+// The event still needs to be Marshaled before being used. (see code:CordbProcess::MarshalManagedEvent)
+//
+//---------------------------------------------------------------------------------------
+void inline CordbProcess::CopyRCEventFromIPCBlock(DebuggerIPCEvent * pLocalManagedEvent)
+{
+ _ASSERTE(pLocalManagedEvent != NULL);
+
+ IfFailThrow(m_pEventChannel->GetEventFromLeftSide(pLocalManagedEvent));
+}
+
+// Return true if this is the RCEvent thread, else false.
+bool CordbRCEventThread::IsRCEventThread()
+{
+ return (m_threadId == GetCurrentThreadId());
+}
+
+//---------------------------------------------------------------------------------------
+// Runtime assert, throws CORDBG_E_TARGET_INCONSISTENT if the expression is not true.
+//
+// Arguments:
+// fExpression - assert parameter. If true, this function is a nop. If false,
+// this will throw a CORDBG_E_TARGET_INCONSISTENT error.
+//
+// Notes:
+// Use this for runtime checks to validate assumptions about the data-target.
+// IcorDebug can't trust that data from the debugee is consistent (perhaps it's
+// corrupted).
+void CordbProcess::TargetConsistencyCheck(bool fExpression)
+{
+ if (!fExpression)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "Target consistency check failed");
+
+ // When debugging possibly corrupt targets, this failure may be expected. For debugging purposes,
+ // assert if we're not expecting any target inconsistencies.
+ CONSISTENCY_CHECK_MSG( !m_fAssertOnTargetInconsistency, "Target consistency check failed unexpectedly");
+
+ ThrowHR(CORDBG_E_TARGET_INCONSISTENT);
+ }
+}
+
+//
+// SendIPCEvent -- send an IPC event to the runtime controller. All this
+// really does is copy the event into the process's send buffer and sets
+// the RSEA then waits on RSER.
+//
+// Note: when sending a two-way event (replyRequired = true), the
+// eventSize must be large enough for both the event sent and the
+// result event.
+//
+// Returns whether the event was sent successfully. This is different than event->eventHr.
+//
+HRESULT CordbRCEventThread::SendIPCEvent(CordbProcess* process,
+ DebuggerIPCEvent* event,
+ SIZE_T eventSize)
+{
+
+ _ASSERTE(process != NULL);
+ _ASSERTE(event != NULL);
+ _ASSERTE(process->GetShim() != NULL);
+
+#ifdef _DEBUG
+ // We need to be synchronized whenever we're sending an IPC Event.
+ // This may require our callers' using a Stop-Continue holder.
+ // Attach + AsyncBreak are the only (obvious) exceptions.
+ // For continue, we set Sync-Status to false before sending, so we exclude that too.
+ // Everybody else should only be sending events when synced. We should never ever ever
+ // send an event from a CorbXYZ dtor (b/c that would be called at any random time). Instead,
+ // use a NeuterList.
+ switch (event->type)
+ {
+ case DB_IPCE_ATTACHING:
+ case DB_IPCE_ASYNC_BREAK:
+ case DB_IPCE_CONTINUE:
+ break;
+
+ default:
+ CONSISTENCY_CHECK_MSGF(process->GetSynchronized(), ("Must by synced while sending IPC event: %s (0x%x)",
+ IPCENames::GetName(event->type), event->type));
+ }
+#endif
+
+
+ LOG((LF_CORDB, LL_EVERYTHING, "SendIPCEvent in CordbRCEventThread called\n"));
+
+ // For simplicity sake, we have the following conservative invariants when sending IPC events:
+ // - Always hold the Stop-Go lock.
+ // - never on the W32ET.
+ // - Never hold the Process-lock (this allows the w32et to take that lock to pump)
+
+ // Must have the stop-go lock to send an IPC event.
+ CONSISTENCY_CHECK_MSGF(process->GetStopGoLock()->HasLock(), ("Must have stop-go lock to send event. proc=%p, event=%s",
+ process, IPCENames::GetName(event->type)));
+
+ // The w32 ET will need to take the process lock. So if we're holding it here, then we'll
+ // deadlock (since W32 ET is blocked on lock, which we would hold; and we're blocked on W32 ET
+ // to keep pumping.
+ _ASSERTE(!process->ThreadHoldsProcessLock() || !"Can't hold P-lock while sending blocking IPC event");
+
+
+ // Can't be on the w32 ET, or we can't be pumping.
+ // Although we can trickle in here from public APIs, our caller should have validated
+ // that we weren't on the w32et, so the assert here is justified. But just in case there's something we missed,
+ // we have a runtime check (as a final backstop against a deadlock).
+ _ASSERTE(!process->IsWin32EventThread());
+ CORDBFailIfOnWin32EventThread(process);
+
+
+ // If this is an async event, then we expect it to be sent while the process is locked.
+ if (event->asyncSend)
+ {
+ // This may be on the w32et, so we can't hold the stop-go lock.
+ _ASSERTE(event->type == DB_IPCE_ATTACHING); // only async event should be attaching.
+ }
+
+
+ // This will catch us if we've detached or exited.
+ // Note if we exited, then we should have been neutered and so shouldn't even be sending an IPC event,
+ // but just in case, we'll check.
+ CORDBRequireProcessStateOK(process);
+
+
+#ifdef _DEBUG
+ // We should never send an Async Break on the RCET. This will deadlock.
+ // - if we're on the RCET, we should be stopped, and thus Stop() should just bump up a stop count,
+ // and not actually send an AsyncBreak.
+ // - Delayed-Continues help enforce this.
+ // This is a special case of the deadlock check below.
+ if (IsRCEventThread())
+ {
+ _ASSERTE(event->type != DB_IPCE_ASYNC_BREAK);
+ }
+#endif
+
+#ifdef _DEBUG
+ // This assert protects us against a deadlock.
+ // 1) (RCET) blocked on (This function): If we're on the RCET, then the RCET is blocked until we return (duh).
+ // 2) (LS) blocked on (RCET): If the LS is not synchronized, then it may be sending an event to the RCET, and thus blocked on the RCET.
+ // 3) (Helper thread) blocked on (LS): That LS thread may be holding a lock that the helper thread needs, thus blocking the helper thread.
+ // 4) (This function) blocked on (Helper Thread): We block until the helper thread can process our IPC event.
+ // #4 is not true for async events.
+ //
+ // If we hit this assert, it means we may get the deadlock above and we're calling SendIPCEvent at a time we shouldn't.
+ // Note this race is as old as dirt.
+ if (IsRCEventThread() && !event->asyncSend)
+ {
+ // Note that w/ Continue & Attach, GetSynchronized() has a different meaning and the race above won't happen.
+ BOOL fPossibleDeadlock = process->GetSynchronized() || (event->type == DB_IPCE_CONTINUE) || (event->type == DB_IPCE_ATTACHING);
+ CONSISTENCY_CHECK_MSGF(fPossibleDeadlock, ("Possible deadlock while sending: '%s'\n", IPCENames::GetName(event->type)));
+ }
+#endif
+
+
+
+ // Cache this process into the MRU so that we can find it if we're debugging in retail.
+ g_pRSDebuggingInfo->m_MRUprocess = process;
+
+ HRESULT hr = S_OK;
+ HRESULT hrEvent = S_OK;
+ _ASSERTE(event != NULL);
+
+ // NOTE: the eventSize parameter is only so you can specify an event size that is SMALLER than the process send
+ // buffer size!!
+ if (eventSize > CorDBIPC_BUFFER_SIZE)
+ return E_INVALIDARG;
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: sending %s to AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(event->type), VmPtrToCookie(event->vmAppDomain), process->m_id, process->m_id);
+
+ // For 2-way events, this check is unnecessary (since we already check for LS exit)
+ // But for async events, we need this.
+ // So just check it up here and make everyone's life easier.
+ if (process->m_terminated)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "CRCET::SIPCE: LS already terminated, shortcut exiting\n");
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ // If the helper thread has died, we can't send an IPC event (and it's never coming back either).
+ // Although we do wait on the thread's handle, there are strange windows where the thread's handle
+ // is not yet signaled even though we've continued from the exit-thread event for the helper.
+ if (process->m_helperThreadDead)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO10000, "CRCET::SIPCE: Helper-thread dead, shortcut exiting\n");
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ BOOL fUnrecoverableError = TRUE;
+ EX_TRY
+ {
+ hr = process->GetEventChannel()->SendEventToLeftSide(event, eventSize);
+ fUnrecoverableError = FALSE;
+ }
+ EX_CATCH_HRESULT(hr);
+
+
+ // If we're sending a Continue() event, then after this, the LS may run free.
+ // If this is the last managed event before the LS exits, (which is the case
+ // if we're responding to either an Exit-Thread or if we respond to a Detach)
+ // the LS may exit at anytime from here on, so we need to be careful.
+
+
+ if (fUnrecoverableError)
+ {
+ _ASSERTE(FAILED(hr));
+ CORDBSetUnrecoverableError(process, hr, 0);
+ }
+ else
+ {
+ // Get a handle to the target process - this call always succeeds
+ HANDLE hLSProcess = NULL;
+ process->GetHandle(&hLSProcess);
+
+ // We take locks to ensure that the CordbProcess object is still alive,
+ // even if the OS process exited.
+ _ASSERTE(hLSProcess != NULL);
+
+ // Check if Sending the IPC event failed
+ if (FAILED(hr))
+ {
+ // The failure to send an event may be due to the target process terminating
+ // (especially, but not exclusively, in the case of async events).
+ // There is a race here - we can't rely on any check above SendEventToLeftSide
+ // to tell us whether the process has exited yet.
+ // Check for that case and return an accurate hresult.
+ DWORD ret = WaitForSingleObject(hLSProcess, 0);
+ if (ret == WAIT_OBJECT_0)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+
+ // Some other failure sending the IPC event - just return it.
+ return hr;
+ }
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: sent...\n");
+
+ // If this is an async send, then don't wait for the left side to acknowledge that its read the event.
+ _ASSERTE(!event->asyncSend || !event->replyRequired);
+
+ if (process->GetEventChannel()->NeedToWaitForAck(event))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000,"CRCET::SIPCE: waiting for left side to read event. (on RSER)\n");
+
+ DWORD ret;
+
+ // Wait for either a reply (common case) or the left side to go away.
+ // We can't detach while waiting for a reply (because detach needs to send events).
+ // All of the outcomes from this wait are completely disjoint.
+ // It's possible for the LS to reply and then exit normally (Thread_Detach, Process_Detach)
+ // and so ExitProcess may have been called, but it doesn't matter.
+
+ enum {
+ ID_RSER = WAIT_OBJECT_0,
+ ID_LSPROCESS,
+ ID_HELPERTHREAD,
+ };
+
+ // Only wait on the helper thread for cases where the process is stopped (and thus we don't expect it do exit on us).
+ // If the process is running and we lose our helper thread, it ought to be during shutdown and we ough to
+ // follow up with an exit.
+ // This includes when we've dispatch Native events, and it includes the AsyncBreak sent to get us from a
+ // win32 frozen state to a synchronized state).
+ HANDLE hHelperThread = NULL;
+ if (process->IsStopped())
+ {
+ hHelperThread = process->GetHelperThreadHandle();
+ }
+
+
+ // Note that in case of a tie (multiple handles signaled), WaitForMultipleObjects gives
+ // priority to the handle earlier in the array.
+ HANDLE waitSet[] = { process->GetEventChannel()->GetRightSideEventAckHandle(), hLSProcess, hHelperThread};
+ DWORD cWaitSet = NumItems(waitSet);
+ if (hHelperThread == NULL)
+ {
+ cWaitSet--;
+ }
+
+ do
+ {
+ ret = WaitForMultipleObjectsEx(cWaitSet, waitSet, FALSE, CordbGetWaitTimeout(), FALSE);
+ // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting.
+ } while ((ret == WAIT_TIMEOUT) && process->IsWaitingForOOBEvent());
+
+ switch(ret)
+ {
+ case ID_RSER:
+ // Normal reply from LS.
+ // This is set iff the LS replied to our event. The LS may have exited since it replied
+ // but we don't care. We still have the reply and we'll pass it on.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side read the event.\n");
+
+ // If this was a two-way event, then the result is already ready for us. Simply copy the result back
+ // over the original event that was sent. Otherwise, the left side has simply read the event and is
+ // processing it...
+ if (event->replyRequired)
+ {
+ process->GetEventChannel()->GetReplyFromLeftSide(event, eventSize);
+ hrEvent = event->hr;
+ }
+ break;
+
+ case ID_LSPROCESS:
+ // Left side exited on us.
+ // ExitProcess may or may not have been called here (since it's on a different thread).
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side exiting while RS was waiting for reply.\n");
+ hr = CORDBG_E_PROCESS_TERMINATED;
+ break;
+
+ case ID_HELPERTHREAD:
+ // We can only send most IPC events while the LS is synchronized. We shouldn't lose our helper thread
+ // when synced under any sort of normal conditions.
+ // This won't fire if the process already exited, because LSPROCESS gets higher priority in the wait
+ // (since it was placed earlier).
+ // Thus the only "legitimate" window where this could happen would be in a shutdown scenario after
+ // the helper is dead but before the process has died. We shouldn't be synced in that scenario,
+ // so we shouldn't be sending IPC events during it.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: lost helper thread.\n");
+
+
+ // Assert because we want to know if we ever actually hit this in any detectable scenario.
+ // However, shutdown can occur in preemptive mode. Thus if the RS does an AsyncBreak late
+ // enough, then the LS will appear to be stopped but may still shutdown.
+ // Since the debuggee can exit asynchronously at any time (eg, suppose somebody forcefully
+ // kills it with taskman), this doesn't introduce a new case.
+ // That aside, it would be great to be able to assert this:
+ //_ASSERTE(!"Potential deadlock - Randomly Lost helper thread");
+
+ // We'll piggy back this on the terminated case.
+ hr = CORDBG_E_PROCESS_TERMINATED;
+ break;
+
+ default:
+ {
+ // If we timed out/failed, check the left side to see if it is in the unrecoverable error mode. If it is,
+ // return the HR from the left side that caused the error. Otherwise, return that we timed out and that
+ // we don't really know why.
+ HRESULT realHR = (ret == WAIT_FAILED) ? HRESULT_FROM_GetLastError() : ErrWrapper(CORDBG_E_TIMEOUT);
+
+ hr = process->CheckForUnrecoverableError();
+
+ if (hr == S_OK)
+ {
+ CORDBSetUnrecoverableError(process, realHR, 0);
+ hr = realHR;
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: left side timeout/fail while RS waiting for reply. hr = 0x%08x\n", hr);
+ }
+ break;
+ }
+
+ // If the LS picked up RSEA, it will be reset (since it's an auto event).
+ // But in the case that the wait failed or that the LS exited, we need to explicitly reset RSEA
+ if (hr != S_OK)
+ {
+ process->GetEventChannel()->ClearEventForLeftSide();
+ }
+
+ // Done waiting for reply.
+
+ }
+ }
+
+ process->ForceDacFlush();
+
+ // The hr and hrEvent are 2 very different things.
+ // hr tells us whether the event was sent successfully.
+ // hrEvent tells us how the LS responded to it.
+ // if FAILED(hr), then hrEvent is useless b/c the LS never got it.
+ // But if SUCCEEDED(hr), then hrEvent may still have failed and that could be
+ // valuable information.
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// FlushQueuedEvents flushes a process's event queue.
+//
+// Arguments:
+// pProcess - non-null process object whose queue will be drained
+//
+// Notes:
+// @dbgtodo shim: this should be part of the shim.
+// This dispatches events that are queued up. The queue is populated by
+// the shim's proxy callback (see code:ShimProxyCallback). This will dispatch events
+// to the 'real' callback supplied by the debugger. This will dispatch events
+// as long as the debugger keeps calling continue.
+//
+// This requires that the process lock be held, although it will toggle the lock.
+void CordbRCEventThread::FlushQueuedEvents(CordbProcess* process)
+{
+ CONTRACTL
+ {
+ NOTHROW; // This is happening on the RCET thread, so there's no place to propogate an error back up.
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG0(LF_CORDB,LL_INFO10000, "CRCET::FQE: Beginning to flush queue\n");
+
+ _ASSERTE(process->GetShim() != NULL);
+
+ // We should only call this is we already have queued events
+ _ASSERTE(!process->GetShim()->GetManagedEventQueue()->IsEmpty());
+
+ //
+ // Dispatch queued events so long as they keep calling Continue()
+ // before returning from their callback. If they call Continue(),
+ // process->m_synchronized will be false again and we know to
+ // loop around and dispatch the next event.
+ //
+ _ASSERTE(process->ThreadHoldsProcessLock());
+
+
+ // Give shim a chance to queue any faked attach events. Grab a pointer to the
+ // ShimProcess now, while we still hold the process lock. Once we release the lock,
+ // GetShim() may not work.
+ RSExtSmartPtr<ShimProcess> pShim(process->GetShim());
+
+ // Release lock before we call out to shim to Queue fake events.
+ {
+ RSInverseLockHolder inverseLockHolder(process->GetProcessLock());
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(pProcess);
+
+ // Because we've released the lock, at any point from here forward the
+ // CorDbProcess may suddenly get neutered if the user detaches the debugger.
+
+ pShim->QueueFakeAttachEventsIfNeeded(false);
+ }
+ }
+
+ // Now that we're holding the process lock again, we can safely check whether
+ // process has become neutered
+ if (process->IsNeutered())
+ {
+ return;
+ }
+
+ {
+
+ // Main dispatch loop here. DispatchRCEvent will take events out of the
+ // queue and invoke callbacks
+ do
+ {
+ // DispatchRCEvent will mark the process as stopped before dispatching.
+ process->DispatchRCEvent();
+
+ LOG((LF_CORDB,LL_INFO10000, "CRCET::FQE: Finished w/ "
+ "DispatchRCEvent\n"));
+ }
+ while (process->GetSyncCompleteRecv() &&
+ (process->GetSynchronized() == false) &&
+ (process->GetShim() != NULL) && // may have lost Shim if we detached while dispatch
+ (!process->GetShim()->GetManagedEventQueue()->IsEmpty()) &&
+ (process->m_unrecoverableError == false));
+ }
+
+ //
+ // If they returned from a callback without calling Continue() then
+ // the process is still synchronized, so let the rc event thread
+ // know that it need to update its process list and remove the
+ // process's event.
+ //
+ if (process->GetSynchronized())
+ {
+ ProcessStateChanged();
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "CRCET::FQE: finished\n"));
+}
+
+//---------------------------------------------------------------------------------------
+// Preliminary Handle an Notification event from the target. This may queue the event,
+// but does not actually dispatch the event.
+//
+// Arguments:
+// pManagedEvent - local managed-event. On success, this function assumes ownership of the
+// event and will delete its memory. Assumed that caller allocated via 'new'.
+// pCallback - callback obecjt to dispatch events on.
+//
+// Return Value:
+// None. Throws on error. On error, caller still owns the pManagedEvent and must free it.
+//
+// Assumptions:
+// This should be called once a notification event is received from the target.
+//
+// Notes:
+// HandleRCEvent -- handle an IPC event received from the runtime controller.
+// This will update ICorDebug state and immediately dispatch the event.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::HandleRCEvent(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback)
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(CheckPointer(pManagedEvent));
+ PRECONDITION(CheckPointer(pCallback));
+ PRECONDITION(ThreadHoldsProcessLock());
+ }
+ CONTRACTL_END;
+
+ if (!this->IsSafeToSendEvents() || this->m_exiting)
+ {
+ return;
+ }
+
+ // Marshals over some standard data from event.
+ MarshalManagedEvent(pManagedEvent);
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "RCET::TP: Got %s for AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(pManagedEvent->type), VmPtrToCookie(pManagedEvent->vmAppDomain), this->m_id, this->m_id);
+
+ RSExtSmartPtr<ICorDebugManagedCallback2> pCallback2;
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback2, reinterpret_cast<void **> (&pCallback2));
+
+ RSExtSmartPtr<ICorDebugManagedCallback3> pCallback3;
+ pCallback->QueryInterface(IID_ICorDebugManagedCallback3, reinterpret_cast<void **> (&pCallback3));
+
+ // Dispatch directly. May not necessarily dispatch an event.
+ // Toggles the lock to dispatch callbacks.
+ RawDispatchEvent(pManagedEvent, pLockHolder, pCallback, pCallback2, pCallback3);
+}
+
+//
+// ProcessStateChanged -- tell the rc event thread that the ICorDebug's
+// process list has changed by setting its flag and thread control event.
+// This will cause the rc event thread to update its set of handles to wait
+// on.
+//
+void CordbRCEventThread::ProcessStateChanged()
+{
+ m_cordb->LockProcessList();
+ STRESS_LOG0(LF_CORDB, LL_INFO100000, "CRCET::ProcessStateChanged\n");
+ m_processStateChanged = TRUE;
+ SetEvent(m_threadControlEvent);
+ m_cordb->UnlockProcessList();
+}
+
+
+//---------------------------------------------------------------------------------------
+// Primary loop of the Runtime Controller event thread. This routine loops during the
+// debug session taking IPC events from the IPC block and calling out to process them.
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// @dbgtodo shim: eventually hoist the entire RCET into the shim.
+//---------------------------------------------------------------------------------------
+void CordbRCEventThread::ThreadProc()
+{
+ HANDLE waitSet[MAXIMUM_WAIT_OBJECTS];
+ CordbProcess * rgProcessSet[MAXIMUM_WAIT_OBJECTS];
+ unsigned int waitCount;
+
+#ifdef _DEBUG
+ memset(&rgProcessSet, NULL, MAXIMUM_WAIT_OBJECTS * sizeof(CordbProcess *));
+ memset(&waitSet, NULL, MAXIMUM_WAIT_OBJECTS * sizeof(HANDLE));
+#endif
+
+
+ // First event to wait on is always the thread control event.
+ waitSet[0] = m_threadControlEvent;
+ rgProcessSet[0] = NULL;
+ waitCount = 1;
+
+ while (m_run)
+ {
+ DWORD dwStatus = WaitForMultipleObjectsEx(waitCount, waitSet, FALSE, 2000, FALSE);
+
+ if (dwStatus == WAIT_FAILED)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "CordbRCEventThread::ThreadProc WaitFor"
+ "MultipleObjects failed: 0x%x\n", GetLastError());
+ }
+#ifdef _DEBUG
+ else if ((dwStatus >= WAIT_OBJECT_0) && (dwStatus < WAIT_OBJECT_0 + waitCount) && m_run)
+ {
+ // Got an event. Figure out which process it came from.
+ unsigned int procNumber = dwStatus - WAIT_OBJECT_0;
+
+ if (procNumber != 0)
+ {
+ // @dbgtodo shim: rip all of this out. Leave the assert in for now to verify that we're not accidentally
+ // going down this codepath. Once we rip this out, we can also simplify some of the code below.
+ // Notification events (including Sync-complete) should be coming from Win32 event thread via
+ // V3 pipeline.
+ _ASSERTE(!"Shouldn't be here");
+
+ }
+ }
+#endif
+
+ // Empty any queued work items.
+ DrainWorkerQueue();
+
+ // Check a flag to see if we need to update our list of processes to wait on.
+ if (m_processStateChanged)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "RCET::TP: refreshing process list.\n");
+
+ unsigned int i;
+
+ //
+ // free the old wait list
+ //
+ for (i = 1; i < waitCount; i++)
+ {
+ rgProcessSet[i]->InternalRelease();
+ }
+
+ // Pass 1: iterate the hash of all processes and collect the unsynchronized ones into the wait list.
+ // Note that Stop / Continue can still be called on a different thread while we're doing this.
+ m_cordb->LockProcessList();
+ m_processStateChanged = FALSE;
+
+ waitCount = 1;
+
+ CordbSafeHashTable<CordbProcess> * pHashTable = m_cordb->GetProcessList();
+ HASHFIND hashFind;
+ CordbProcess * pProcess;
+
+ for (pProcess = pHashTable->FindFirst(&hashFind); pProcess != NULL; pProcess = pHashTable->FindNext(&hashFind))
+ {
+ _ASSERTE(waitCount < MAXIMUM_WAIT_OBJECTS);
+
+ if( waitCount >= MAXIMUM_WAIT_OBJECTS )
+ {
+ break;
+ }
+
+ // Only listen to unsynchronized processes. Processes that are synchronized will not send events without
+ // being asked by us first, so there is no need to async listen to them.
+ //
+ // Note: if a process is not synchronized then there is no way for it to transition to the syncrhonized
+ // state without this thread receiving an event and taking action. So there is no need to lock the
+ // per-process mutex when checking the process's synchronized flag here.
+ if (!pProcess->GetSynchronized() && pProcess->IsSafeToSendEvents())
+ {
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "RCET::TP: listening to process 0x%x(%d)\n",
+ pProcess->m_id, pProcess->m_id);
+
+ waitSet[waitCount] = pProcess->m_leftSideEventAvailable;
+ rgProcessSet[waitCount] = pProcess;
+ rgProcessSet[waitCount]->InternalAddRef();
+ waitCount++;
+ }
+ }
+
+ m_cordb->UnlockProcessList();
+
+ // Pass 2: for each process that we placed in the wait list, determine if there are any existing queued
+ // events that need to be flushed.
+
+ // Start i at 1 to skip the control event...
+ i = 1;
+
+ while(i < waitCount)
+ {
+ pProcess = rgProcessSet[i];
+
+ // Take the process lock so we can check the queue safely
+ pProcess->Lock();
+
+ // Now that we've just locked the processes, we can safely inspect it and dispatch events.
+ // The process may have changed since when we first added it to the process list in Pass 1,
+ // so we can't make any assumptions about whether it's sync, live, or exiting.
+
+ // Flush the queue if necessary. Note, we only do this if we've actually received a SyncComplete message
+ // from this process. If we haven't received a SyncComplete yet, then we don't attempt to drain any
+ // queued events yet. They'll be drained when the SyncComplete event is actually received.
+ if (pProcess->GetSyncCompleteRecv() &&
+ (pProcess->GetShim() != NULL) &&
+ !pProcess->GetSynchronized())
+ {
+ if (pProcess->GetShim()->GetManagedEventQueue()->IsEmpty())
+ {
+ // Effectively what we are doing here is to continue everything without actually
+ // handling an event. We can get here if the event raised by the LS is a duplicate
+ // creation event, which the shim discards without adding it to the event queue.
+ // See code:ShimProcess::IsDuplicateCreationEvent.
+ //
+ // To continue, we need to increment the stop count first. Also, we can't call
+ // Continue() while holding the process lock.
+ pProcess->SetSynchronized(true);
+ pProcess->IncStopCount();
+ pProcess->Unlock();
+ pProcess->ContinueInternal(FALSE);
+ pProcess->Lock();
+ }
+ else
+ {
+ // This may toggle the process-lock
+ FlushQueuedEvents(pProcess);
+ }
+ }
+
+ // Flushing could have left the process synchronized...
+ // Common case is if the callback didn't call Continue().
+ if (pProcess->GetSynchronized())
+ {
+ // remove the process from the wait list by moving all the other processes down one.
+ if ((i + 1) < waitCount)
+ {
+ memcpy(&rgProcessSet[i], &(rgProcessSet[i+1]), sizeof(rgProcessSet[0]) * (waitCount - i - 1));
+ memcpy(&waitSet[i], &waitSet[i+1], sizeof(waitSet[0]) * (waitCount - i - 1));
+ }
+
+ // drop the count of processes to wait on
+ waitCount--;
+
+ pProcess->Unlock();
+
+ // make sure to release the reference we added when the process was added to the wait list.
+ pProcess->InternalRelease();
+
+ // We don't have to increment i because we've copied the next element into
+ // the current value at i.
+ }
+ else
+ {
+ // Even after flushing, its still not syncd, so leave it in the wait list.
+ pProcess->Unlock();
+
+ // Increment i normally.
+ i++;
+ }
+ }
+ } // end ProcessStateChanged
+ } // while (m_run)
+
+#ifdef _DEBUG_IMPL
+ // We intentionally return while leaking some CordbProcess objects inside
+ // rgProcessSet, in some cases (e.g., I've seen this happen when detaching from a
+ // debuggee almost immediately after attaching to it). In the future, we should
+ // really consider not leaking these anymore. However, I'm unsure how safe it is to just
+ // go and InternalRelease() those guys, as above we intentionally DON'T release them when
+ // they're not synchronized. So for now, to make debug builds happy, exclude those
+ // references when we run CheckMemLeaks() later on. In our next side-by-side release,
+ // consider actually doing InternalRelease() on the remaining CordbProcesses on
+ // retail, and then we can remove the following loop.
+ for (UINT i=1; i < waitCount; i++)
+ {
+ InterlockedDecrement(&Cordb::s_DbgMemTotalOutstandingInternalRefs);
+ }
+#endif //_DEBUG_IMPL
+}
+
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the given object.
+//
+/*static*/
+DWORD WINAPI CordbRCEventThread::ThreadProc(LPVOID parameter)
+{
+ CordbRCEventThread * pThread = (CordbRCEventThread *) parameter;
+
+ INTERNAL_THREAD_ENTRY(pThread);
+ pThread->ThreadProc();
+ return 0;
+}
+
+template<typename T>
+InterlockedStack<T>::InterlockedStack()
+{
+ m_pHead = NULL;
+}
+
+template<typename T>
+InterlockedStack<T>::~InterlockedStack()
+{
+ // This is an arbitrary choice. We expect the stacks be drained.
+ _ASSERTE(m_pHead == NULL);
+}
+
+// Thread safe pushes + pops.
+// Many threads can push simultaneously.
+// Only 1 thread can pop.
+template<typename T>
+void InterlockedStack<T>::Push(T * pItem)
+{
+ // InterlockedCompareExchangePointer(&dest, ex, comp).
+ // Really behaves like:
+ // val = *dest;
+ // if (*dest == comp) { *dest = ex; }
+ // return val;
+ //
+ // We can do a thread-safe assign { comp = dest; dest = ex } via:
+ // do { comp = dest } while (ICExPtr(&dest, ex, comp) != comp));
+
+
+ do
+ {
+ pItem->m_next = m_pHead;
+ }
+ while(InterlockedCompareExchangeT(&m_pHead, pItem, pItem->m_next) != pItem->m_next);
+}
+
+// Returns NULL on empty,
+// else returns the head of the list.
+template<typename T>
+T * InterlockedStack<T>::Pop()
+{
+ if (m_pHead == NULL)
+ {
+ return NULL;
+ }
+
+ // This allows 1 thread to Pop() and race against N threads doing a Push().
+ T * pItem = NULL;
+ do
+ {
+ pItem = m_pHead;
+ } while(InterlockedCompareExchangeT(&m_pHead, pItem->m_next, pItem) != pItem);
+
+ return pItem;
+}
+
+
+// RCET will take ownership of this item and delete it.
+// This can be done w/o taking any locks (thus it can be called from any lock context)
+// This may race w/ the RCET draining the queue.
+void CordbRCEventThread::QueueAsyncWorkItem(RCETWorkItem * pItem)
+{
+ // @todo -
+ // Non-blocking insert into queue.
+
+ _ASSERTE(pItem != NULL);
+
+ m_WorkerStack.Push(pItem);
+
+ // Ping the RCET so that it drains the queue.
+ SetEvent(m_threadControlEvent);
+}
+
+// Execute & delete all workitems in the queue.
+// This can be done w/o taking any locks. (though individual items may take locks).
+void CordbRCEventThread::DrainWorkerQueue()
+{
+ _ASSERTE(IsRCEventThread());
+
+ while(true)
+ {
+ RCETWorkItem* pCur = m_WorkerStack.Pop();
+ if (pCur == NULL)
+ {
+ break;
+ }
+
+ pCur->Do();
+ delete pCur;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+// Wait for an reply from the debuggee.
+//
+// Arguments:
+// pProcess - process for debuggee.
+// pAppDomain - not used.
+// pEvent - caller-allocated event to be filled out.
+// This is expected to be at least as big as CorDBIPC_BUFFER_SIZE.
+//
+// Return Value:
+// S_OK on success. else failure.
+//
+// Assumptions:
+// Caller allocates
+//
+// Notes:
+// WaitForIPCEventFromProcess waits for an event from just the specified
+// process. This should only be called when the process is in a synchronized
+// state, which ensures that the RCEventThread isn't listening to the
+// process's event, too, which would get confusing.
+//
+// @dbgtodo - this function should eventually be obsolete once everything
+// is using DAC calls instead of helper-thread.
+//
+//---------------------------------------------------------------------------------------
+HRESULT CordbRCEventThread::WaitForIPCEventFromProcess(CordbProcess * pProcess,
+ CordbAppDomain * pAppDomain,
+ DebuggerIPCEvent * pEvent)
+{
+ CORDBRequireProcessStateOKAndSync(pProcess, pAppDomain);
+
+ DWORD dwStatus;
+ HRESULT hr = S_OK;
+
+ do
+ {
+ dwStatus = SafeWaitForSingleObject(pProcess,
+ pProcess->m_leftSideEventAvailable,
+ CordbGetWaitTimeout());
+
+ if (pProcess->m_terminated)
+ {
+ return CORDBG_E_PROCESS_TERMINATED;
+ }
+ // If we timeout because we're waiting for an uncontinued OOB event, we need to just keep waiting.
+ } while ((dwStatus == WAIT_TIMEOUT) && pProcess->IsWaitingForOOBEvent());
+
+
+
+
+ if (dwStatus == WAIT_OBJECT_0)
+ {
+ pProcess->CopyRCEventFromIPCBlock(pEvent);
+
+ EX_TRY
+ {
+ pProcess->MarshalManagedEvent(pEvent);
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CRCET::SIPCE: Got %s for AD 0x%x, proc 0x%x(%d)\n",
+ IPCENames::GetName(pEvent->type),
+ VmPtrToCookie(pEvent->vmAppDomain),
+ pProcess->m_id,
+ pProcess->m_id);
+
+ }
+ EX_CATCH_HRESULT(hr)
+
+ SetEvent(pProcess->m_leftSideEventRead);
+
+ return hr;
+ }
+ else if (dwStatus == WAIT_TIMEOUT)
+ {
+ //
+ // If we timed out, check the left side to see if it is in the
+ // unrecoverable error mode. If it is, return the HR from the
+ // left side that caused the error. Otherwise, return that we timed
+ // out and that we don't really know why.
+ //
+ HRESULT realHR = ErrWrapper(CORDBG_E_TIMEOUT);
+
+ hr = pProcess->CheckForUnrecoverableError();
+
+ if (hr == S_OK)
+ {
+ CORDBSetUnrecoverableError(pProcess, realHR, 0);
+ return realHR;
+ }
+ else
+ return hr;
+ }
+ else
+ {
+ _ASSERTE(dwStatus == WAIT_FAILED);
+
+ hr = HRESULT_FROM_GetLastError();
+
+ CORDBSetUnrecoverableError(pProcess, hr, 0);
+
+ return hr;
+ }
+}
+
+
+//
+// Start actually creates and starts the thread.
+//
+HRESULT CordbRCEventThread::Start()
+{
+ if (m_threadControlEvent == NULL)
+ {
+ return E_INVALIDARG;
+ }
+
+ m_thread = CreateThread(NULL,
+ 0,
+ &CordbRCEventThread::ThreadProc,
+ (LPVOID) this,
+ 0,
+ &m_threadId);
+
+ if (m_thread == NULL)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+
+ return S_OK;
+}
+
+
+//
+// Stop causes the thread to stop receiving events and exit. It
+// waits for it to exit before returning.
+//
+HRESULT CordbRCEventThread::Stop()
+{
+ if (m_thread != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "CRCET::Stop\n"));
+
+ m_run = FALSE;
+
+ SetEvent(m_threadControlEvent);
+
+ DWORD ret = WaitForSingleObject(m_thread, INFINITE);
+
+ if (ret != WAIT_OBJECT_0)
+ {
+ return HRESULT_FROM_GetLastError();
+ }
+ }
+
+ m_cordb.Clear();
+
+ return S_OK;
+}
+
+
+/* ------------------------------------------------------------------------- *
+ * Win32 Event Thread class
+ * ------------------------------------------------------------------------- */
+
+enum
+{
+ W32ETA_NONE = 0,
+ W32ETA_CREATE_PROCESS = 1,
+ W32ETA_ATTACH_PROCESS = 2,
+ W32ETA_CONTINUE = 3,
+ W32ETA_DETACH = 4
+};
+
+
+
+//---------------------------------------------------------------------------------------
+// Constructor
+//
+// Arguments:
+// pCordb - Pointer to the owning cordb object for this event thread.
+// pShim - Pointer to the shim for supporting V2 debuggers on V3 architecture.
+//
+//---------------------------------------------------------------------------------------
+CordbWin32EventThread::CordbWin32EventThread(
+ Cordb * pCordb,
+ ShimProcess * pShim
+ ) :
+ m_thread(NULL), m_threadControlEvent(NULL),
+ m_actionTakenEvent(NULL), m_run(TRUE),
+ m_action(W32ETA_NONE)
+{
+ m_cordb.Assign(pCordb);
+ _ASSERTE(pCordb != NULL);
+
+ m_pShim = pShim;
+
+ m_pNativePipeline = NULL;
+}
+
+
+//
+// Destructor. Cleans up all of the open handles and such.
+// This expects that the thread has been stopped and has terminated
+// before being called.
+//
+CordbWin32EventThread::~CordbWin32EventThread()
+{
+ if (m_thread != NULL)
+ CloseHandle(m_thread);
+
+ if (m_threadControlEvent != NULL)
+ CloseHandle(m_threadControlEvent);
+
+ if (m_actionTakenEvent != NULL)
+ CloseHandle(m_actionTakenEvent);
+
+ if (m_pNativePipeline != NULL)
+ {
+ m_pNativePipeline->Delete();
+ m_pNativePipeline = NULL;
+ }
+
+ m_sendToWin32EventThreadMutex.Destroy();
+}
+
+
+//
+// Init sets up all the objects that the thread will need to run.
+//
+HRESULT CordbWin32EventThread::Init()
+{
+ if (m_cordb == NULL)
+ return E_INVALIDARG;
+
+ m_sendToWin32EventThreadMutex.Init("Win32-Send lock", RSLock::cLockFlat, RSLock::LL_WIN32_SEND_LOCK);
+
+ m_threadControlEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_threadControlEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ m_actionTakenEvent = WszCreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_actionTakenEvent == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ m_pNativePipeline = NewPipelineWithDebugChecks();
+ if (m_pNativePipeline == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+//
+// Main function of the Win32 Event Thread
+//
+void CordbWin32EventThread::ThreadProc()
+{
+#if defined(RSCONTRACTS)
+ DbgRSThread::GetThread()->SetThreadType(DbgRSThread::cW32ET);
+
+ // The win32 ET conceptually holds a lock (all threads do).
+ DbgRSThread::GetThread()->TakeVirtualLock(RSLock::LL_WIN32_EVENT_THREAD);
+#endif
+
+ // In V2, the debuggee decides what to do if the debugger rudely exits / detaches. (This is
+ // handled by host policy). With the OS native-debuggging pipeline, the debugger by default
+ // kills the debuggee if it exits. To emulate V2 behavior, we need to override that default.
+ BOOL fOk = m_pNativePipeline->DebugSetProcessKillOnExit(FALSE);
+ (void)fOk; //prevent "unused variable" error from GCC
+ _ASSERTE(fOk);
+
+
+ // Run the top-level event loop.
+ Win32EventLoop();
+
+#if defined(RSCONTRACTS)
+ // The win32 ET conceptually holds a lock (all threads do).
+ DbgRSThread::GetThread()->ReleaseVirtualLock(RSLock::LL_WIN32_EVENT_THREAD);
+#endif
+}
+
+// Define a holder that calls code:DeleteIPCEventHelper
+NEW_WRAPPER_TEMPLATE1(DeleteIPCEventHolderHelper, DeleteIPCEventHelper);
+typedef DeleteIPCEventHolderHelper<DebuggerIPCEvent> DeleteIPCEventHolder;
+
+//---------------------------------------------------------------------------------------
+//
+// Helper to clean up IPCEvent before deleting it.
+// This must be called after an event is marshalled via code:CordbProcess::MarshalManagedEvent
+//
+// Arguments:
+// pManagedEvent - managed event to delete.
+//
+// Notes:
+// This can delete a partially marshalled event.
+//
+void DeleteIPCEventHelper(DebuggerIPCEvent *pManagedEvent)
+{
+ CONTRACTL
+ {
+ // This is backout code that shouldn't need to throw.
+ NOTHROW;
+ }
+ CONTRACTL_END;
+ if (pManagedEvent == NULL)
+ {
+ return;
+ }
+ switch (pManagedEvent->type & DB_IPCE_TYPE_MASK)
+ {
+ // so far only this event need to cleanup.
+ case DB_IPCE_MDA_NOTIFICATION:
+ pManagedEvent->MDANotification.szName.CleanUp();
+ pManagedEvent->MDANotification.szDescription.CleanUp();
+ pManagedEvent->MDANotification.szXml.CleanUp();
+ break;
+
+ case DB_IPCE_FIRST_LOG_MESSAGE:
+ pManagedEvent->FirstLogMessage.szContent.CleanUp();
+ break;
+
+ default:
+ break;
+ }
+ delete [] (BYTE *)pManagedEvent;
+}
+
+//---------------------------------------------------------------------------------------
+// Handle a CLR specific notification event.
+//
+// Arguments:
+// pManagedEvent - non-null pointer to a managed event.
+// pLockHolder - hold to process lock that gets toggled if this dispatches an event.
+// pCallback - callback to dispatch potential managed events.
+//
+// Return Value:
+// Throws on error.
+//
+// Assumptions:
+// Target is stopped. Record was already determined to be a CLR event.
+//
+// Notes:
+// This is called after caller does WaitForDebugEvent.
+// Any exception this Filter does not recognize is treated as kNotClr.
+// Currently, this includes both managed-exceptions and unmanaged ones.
+// For interop-debugging, the interop logic will handle all kNotClr and triage if
+// it's really a non-CLR exception.
+//
+//---------------------------------------------------------------------------------------
+void CordbProcess::FilterClrNotification(
+ DebuggerIPCEvent * pManagedEvent,
+ RSLockHolder * pLockHolder,
+ ICorDebugManagedCallback * pCallback)
+{
+ CONTRACTL
+ {
+ THROWS;
+ PRECONDITION(CheckPointer(pManagedEvent));
+ PRECONDITION(CheckPointer(pCallback));
+ PRECONDITION(ThreadHoldsProcessLock());
+ }
+ CONTRACTL_END;
+
+ // There are 3 types of events from the LS:
+ // 1) Replies (eg, corresponding to WaitForIPCEvent)
+ // we need to set LSEA/wait on LSER.
+ // 2) Sync-Complete (kind of like a special notification)
+ // Ping the helper
+ // 3) Notifications (eg, Module-load):
+ // these are dispatched immediately.
+ // 4) Left-side Startup event
+
+
+ // IF we're synced, then we must be getting a "Reply".
+ bool fReply = this->GetSynchronized();
+
+ LOG((LF_CORDB, LL_INFO10000, "CP::FCN - Received event %s; fReply: %d\n",
+ IPCENames::GetName(pManagedEvent->type),
+ fReply));
+
+ if (fReply)
+ {
+ //
+ _ASSERTE(m_pShim != NULL);
+ //
+ // Case 1: Reply
+ //
+
+ pLockHolder->Release();
+ _ASSERTE(!ThreadHoldsProcessLock());
+
+ // Save the IPC event and wake up the thread which is waiting for it from the LS.
+ GetEventChannel()->SaveEventFromLeftSide(pManagedEvent);
+ SetEvent(this->m_leftSideEventAvailable);
+
+ // Some other thread called code:CordbRCEventThread::WaitForIPCEventFromProcess, and
+ // that will respond here and set the event.
+
+ DWORD dwResult = WaitForSingleObject(this->m_leftSideEventRead, CordbGetWaitTimeout());
+ pLockHolder->Acquire();
+ if (dwResult != WAIT_OBJECT_0)
+ {
+ // The wait failed. This is probably WAIT_TIMEOUT which suggests a deadlock/assert on
+ // the RCEventThread.
+ CONSISTENCY_CHECK_MSGF(false, ("WaitForSingleObject failed: %d", dwResult));
+ ThrowHR(CORDBG_E_TIMEOUT);
+ }
+ }
+ else
+ {
+ if (pManagedEvent->type == DB_IPCE_LEFTSIDE_STARTUP)
+ {
+ //
+ // Case 4: Left-side startup event. We'll mark that we're attached from oop.
+ //
+
+ // Now that LS is started, we should definitely be able to instantiate DAC.
+ InitializeDac();
+
+ // @dbgtodo 'attach-bit': we don't want the debugger automatically invading the process.
+ GetDAC()->MarkDebuggerAttached(TRUE);
+ }
+ else if (pManagedEvent->type == DB_IPCE_SYNC_COMPLETE)
+ {
+ // Since V3 doesn't request syncs, it shouldn't get sync-complete.
+ // @dbgtodo sync: this changes when V3 can explicitly request an AsyncBreak.
+ _ASSERTE(m_pShim != NULL);
+
+ //
+ // Case 2: Sync Complete
+ //
+
+ HandleSyncCompleteRecieved();
+ }
+ else
+ {
+ //
+ // Case 3: Notification. This will dispatch the event immediately.
+ //
+
+ // Toggles the process-lock if it dispatches callbacks.
+ HandleRCEvent(pManagedEvent, pLockHolder, pCallback);
+
+ } // end Notification
+ }
+}
+
+
+
+//
+// If the thread has an unhandled managed exception, hijack it.
+//
+// Arguments:
+// dwThreadId - OS Thread id.
+//
+// Returns:
+// True if hijacked; false if not.
+//
+// Notes:
+// This is called from shim to emulate being synchronized at an unhandled
+// exception.
+// Other ICorDebug operations could calls this (eg, func-eval at 2nd chance).
+BOOL CordbProcess::HijackThreadForUnhandledExceptionIfNeeded(DWORD dwThreadId)
+{
+ PUBLIC_API_ENTRY(this); // from Shim
+
+ BOOL fHijacked = FALSE;
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ RSLockHolder lockHolder(GetProcessLock());
+
+ // OS will not execute the Unhandled Exception Filter under native debugger, so
+ // we need to hijack the thread to get it to execute the UEF, which will then do
+ // work for unhandled managed exceptions.
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+ if (pThread != NULL)
+ {
+ // If the thread has a managed exception, then we should have a pThread object.
+
+ if (pThread->HasUnhandledNativeException())
+ {
+ _ASSERTE(pThread->IsThreadExceptionManaged()); // should have been marked earlier
+
+ pThread->HijackForUnhandledException();
+ fHijacked = TRUE;
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+
+ return fHijacked;
+}
+
+//---------------------------------------------------------------------------------------
+// Validate the given exception record or throw.
+//
+// Arguments:
+// pRawRecord - non-null raw bytes of the exception
+// countBytes - number of bytes in pRawRecord buffer.
+// format - format of pRawRecord
+//
+// Returns:
+// A type-safe exception record from the raw buffer.
+//
+// Notes:
+// This is a helper for code:CordbProcess::Filter.
+// This can do consistency checks on the incoming parameters such as:
+// * verify countBytes matches the expected size for the given format.
+// * verify the format is supported.
+//
+// If we let a given ICD understand multiple formats (eg, have x86 understand both Exr32 and
+// Exr64), this would be the spot to allow the conversion.
+//
+const EXCEPTION_RECORD * CordbProcess::ValidateExceptionRecord(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format)
+{
+ ValidateOrThrow(pRawRecord);
+
+ //
+ // Check format against expected platform.
+ //
+
+ // @dbgtodo - , cross-plat: Once we do cross-plat, these should be based off target-architecture not host's.
+#if defined(_WIN64)
+ if (format != FORMAT_WINDOWS_EXCEPTIONRECORD64)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+#else
+ if (format != FORMAT_WINDOWS_EXCEPTIONRECORD32)
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+#endif
+
+ // @dbgtodo cross-plat: once we do cross-plat, need to use correct EXCEPTION_RECORD variant.
+ if (countBytes != sizeof(EXCEPTION_RECORD))
+ {
+ ThrowHR(E_INVALIDARG);
+ }
+
+
+ const EXCEPTION_RECORD * pRecord = reinterpret_cast<const EXCEPTION_RECORD *> (pRawRecord);
+
+ return pRecord;
+};
+
+// Return value: S_OK or indication that no more room exists for enabled types
+HRESULT CordbProcess::SetEnableCustomNotification(ICorDebugClass * pClass, BOOL fEnable)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this); // takes the lock
+
+ ValidateOrThrow(pClass);
+
+ ((CordbClass *)pClass)->SetCustomNotifications(fEnable);
+
+ PUBLIC_API_END(hr);
+ return hr;
+} // CordbProcess::SetEnableCustomNotification
+
+//---------------------------------------------------------------------------------------
+// Public implementation of ICDProcess4::Filter
+//
+// Arguments:
+// pRawRecord - non-null raw bytes of the exception
+// countBytes - number of bytes in pRawRecord buffer.
+// format - format of pRawRecord
+// dwFlags - flags providing auxillary info for exception record.
+// dwThreadId - thread that exception occurred on.
+// pCallback - callback to dispatch potential managed events on.
+// pContinueStatus - Continuation status for exception. This dictates what
+// to pass to kernel32!ContinueDebugEvent().
+//
+// Return Value:
+// S_OK on success.
+//
+// Assumptions:
+// Target is stopped.
+//
+// Notes:
+// The exception could be anything, including:
+// - a CLR notification,
+// - a random managed exception (both from managed code or the runtime),
+// - a non-CLR exception
+//
+// This is cross-platform. The {pRawRecord, countBytes, format} describe events
+// on an arbitrary target architecture. On windows, this will be an EXCEPTION_RECORD.
+//
+HRESULT CordbProcess::Filter(
+ const BYTE pRawRecord[],
+ DWORD countBytes,
+ CorDebugRecordFormat format,
+ DWORD dwFlags,
+ DWORD dwThreadId,
+ ICorDebugManagedCallback * pCallback,
+ DWORD * pContinueStatus
+)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_API_BEGIN(this); // takes the lock
+ {
+ //
+ // Validate parameters
+ //
+
+ // If we don't care about the continue status, we leave it untouched.
+ ValidateOrThrow(pContinueStatus);
+ ValidateOrThrow(pCallback);
+
+ const EXCEPTION_RECORD * pRecord = ValidateExceptionRecord(pRawRecord, countBytes, format);
+
+ DWORD dwFirstChance = (dwFlags & IS_FIRST_CHANCE);
+
+ //
+ // Deal with 2nd-chance exceptions. Don't actually hijack now (that's too invasive),
+ // but mark that we have the exception in case a future operation (eg, func-eval) needs to hijack.
+ //
+ if (!dwFirstChance)
+ {
+ CordbThread * pThread = TryLookupOrCreateThreadByVolatileOSId(dwThreadId);
+
+ // If we don't have a managed-thread object, then it certainly can't have a throwable.
+ // It's possible this is still an exception from the native portion of the runtime,
+ // but that's ok, we'll just treat it as a native exception.
+ // This could be expensive, don't want to do it often... (definitely not on every Filter).
+
+
+ // OS will not execute the Unhandled Exception Filter under native debugger, so
+ // we need to hijack the thread to get it to execute the UEF, which will then do
+ // work for unhandled managed exceptions.
+ if ((pThread != NULL) && pThread->IsThreadExceptionManaged())
+ {
+ // Copy exception record for future use in case we decide to hijack.
+ pThread->SetUnhandledNativeException(pRecord);
+ }
+ // we don't care about 2nd-chance exceptions, unless we decide to hijack it later.
+ }
+
+ //
+ // Deal with CLR notifications
+ //
+ else if (pRecord->ExceptionCode == CLRDBG_NOTIFICATION_EXCEPTION_CODE) // Special event code
+ {
+ //
+ // This may not be for us, or we may not have a managed thread object:
+ // 1. Anybody can raise an exception with this exception code, so can't assume this belongs to us yet.
+ // 2. Notifications may come on unmanaged threads if they're coming from MDAs or CLR internal events
+ // fired before the thread is created.
+ //
+ BYTE * pManagedEventBuffer = new BYTE[CorDBIPC_BUFFER_SIZE];
+ DeleteIPCEventHolder pManagedEvent(reinterpret_cast<DebuggerIPCEvent *>(pManagedEventBuffer));
+
+ bool fOwner = CopyManagedEventFromTarget(pRecord, pManagedEvent);
+ if (fOwner)
+ {
+ // This toggles the lock if it dispatches callbacks
+ FilterClrNotification(pManagedEvent, GET_PUBLIC_LOCK_HOLDER(), pCallback);
+
+ // Cancel any notification events from target. These are just supposed to notify ICD and not
+ // actually be real exceptions in the target.
+ // Canceling here also prevents a VectoredExceptionHandler in the target from picking
+ // up exceptions for the CLR.
+ *pContinueStatus = DBG_CONTINUE;
+ }
+
+ // holder will invoke DeleteIPCEventHelper(pManagedEvent).
+ }
+
+ }
+ PUBLIC_API_END(hr);
+ // we may not find the correct mscordacwks so fail gracefully
+ _ASSERTE(SUCCEEDED(hr) || (hr != HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND)));
+
+ return hr;
+}
+
+//---------------------------------------------------------------------------------------
+// Wrapper to invoke ICorDebugMutableDataTarget::ContinueStatusChanged
+//
+// Arguments:
+// dwContinueStatus - new continue status
+//
+// Returns:
+// None. Throw on error.
+//
+// Notes:
+// Initial continue status is returned from code:CordbProcess::Filter.
+// Some operations (mainly hijacking on a 2nd-chance exception), may need to
+// override that continue status.
+// ICorDebug operations invoke a callback on the data-target to notify the debugger
+// of a change in status. Debugger may fail the request.
+//
+void CordbProcess::ContinueStatusChanged(DWORD dwThreadId, CORDB_CONTINUE_STATUS dwContinueStatus)
+{
+ HRESULT hr = m_pMutableDataTarget->ContinueStatusChanged(dwThreadId, dwContinueStatus);
+ IfFailThrow(hr);
+}
+
+//---------------------------------------------------------------------------------------
+// Request a synchronization to occur after a debug event is dispatched.
+//
+// Note:
+// This is called in response to a managed debug event, and so we know that we have
+// a worker thread in the process (the one that just sent the event!)
+// This can not be called asynchronously.
+//---------------------------------------------------------------------------------------
+void CordbProcess::RequestSyncAtEvent()
+{
+ GetDAC()->RequestSyncAtEvent();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Primary loop of the Win32 debug event thread.
+//
+//
+// Arguments:
+// None.
+//
+// Return Value:
+// None.
+//
+// Notes:
+// This is it, you've found it, the main guy. This function loops as long as the
+// debugger is around calling the OS WaitForDebugEvent() API. It takes the OS Debug
+// Event and filters it thru the right-side, continuing the process if not recognized.
+//
+// @dbgtodo shim: this will become part of the shim.
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::Win32EventLoop()
+{
+ // This must be called from the win32 event thread.
+ _ASSERTE(IsWin32EventThread());
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: entered win32 event loop\n"));
+
+
+ DEBUG_EVENT event;
+
+ // Allow the timeout for WFDE to be adjustable. Default to 25 ms based off perf numbers (see issue VSWhidbey 132368).
+ DWORD dwWFDETimeout = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgWFDETimeout);
+
+ while (m_run)
+ {
+ BOOL fEventAvailable = FALSE;
+
+ // We should not have any locks right now.
+
+
+ // Have to wait on 2 sources:
+ // WaitForMultipleObjects - ping for messages (create, attach, Continue, detach) and also
+ // process exits in the managed-only case.
+ // Native Debug Events - This is a huge perf hit so we want to avoid it whenever we can.
+ // Only wait on these if we're interop debugging and if the process is not frozen.
+ // A frozen process can't send any debug events, so don't bother looking for them.
+
+
+ unsigned int cWaitCount = 1;
+
+ HANDLE rghWaitSet[2];
+
+ rghWaitSet[0] = m_threadControlEvent;
+
+ DWORD dwWaitTimeout = INFINITE;
+
+ if (m_pProcess != NULL)
+ {
+ // Process is always built on Native debugging pipeline, so it needs to always be prepared to call WFDE
+ // As an optimization, if the target is stopped, then we can avoid calling WFDE.
+ {
+#ifndef FEATURE_INTEROP_DEBUGGING
+ // Managed-only, never win32 stopped, so always check for an event.
+ dwWaitTimeout = 0;
+ fEventAvailable = m_pNativePipeline->WaitForDebugEvent(&event, dwWFDETimeout, m_pProcess);
+#else
+ // Wait for a Win32 debug event from any processes that we may be attached to as the Win32 debugger.
+ const bool fIsWin32Stopped = (m_pProcess->m_state & CordbProcess::PS_WIN32_STOPPED) != 0;
+ const bool fSkipWFDE = fIsWin32Stopped;
+
+
+ const bool fIsInteropDebugging = m_pProcess->IsInteropDebugging();
+ (void)fIsInteropDebugging; //prevent "unused variable" error from GCC
+
+ // Assert checks
+ _ASSERTE(fIsInteropDebugging == m_pShim->IsInteropDebugging());
+
+ if (!fSkipWFDE)
+ {
+ dwWaitTimeout = 0;
+ fEventAvailable = m_pNativePipeline->WaitForDebugEvent(&event, dwWFDETimeout, m_pProcess);
+ }
+ else
+ {
+ // If we're managed-only debugging, then the process should always be running,
+ // which means we always need to be calling WFDE to pump potential debug events.
+ // If we're interop-debugging, then the process can be stopped at a native-debug event,
+ // in which case we don't have to call WFDE until we resume it again.
+ // So we can only skip the WFDE when we're interop-debugging.
+ _ASSERTE(fIsInteropDebugging);
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+ }
+
+
+ } // end m_pProcess != NULL
+
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ // While interop-debugging, the process may get killed rudely underneath us, even if we haven't
+ // continued the last debug event. In such cases, The process object will get signalled normally.
+ // If we didn't just get a native-exitProcess event, then listen on the process handle for exit.
+ // (this includes all managed-only debugging)
+ // It's very important to establish this before we go into the WaitForMutlipleObjects below
+ // because the debuggee may exit while we're sitting in that loop (waiting for the debugger to call Continue).
+ bool fDidNotJustGetExitProcessEvent = !fEventAvailable || (event.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT);
+#else
+ // In non-interop scenarios, we'll never get any native debug events, let alone an ExitProcess native event.
+ bool fDidNotJustGetExitProcessEvent = true;
+#endif // FEATURE_INTEROP_DEBUGGING
+
+
+ // The m_pProcess handle will get nulled out after we process the ExitProcess event, and
+ // that will ensure that we only wait for an Exit event once.
+ if ((m_pProcess != NULL) && fDidNotJustGetExitProcessEvent)
+ {
+ rghWaitSet[1] = m_pProcess->UnsafeGetProcessHandle();
+ cWaitCount = 2;
+ }
+
+ // See if any process that we aren't attached to as the Win32 debugger have exited. (Note: this is a
+ // polling action if we are also waiting for Win32 debugger events. We're also looking at the thread
+ // control event here, too, to see if we're supposed to do something, like attach.
+ DWORD dwStatus = WaitForMultipleObjectsEx(cWaitCount, rghWaitSet, FALSE, dwWaitTimeout, FALSE);
+
+ _ASSERTE((dwStatus == WAIT_TIMEOUT) || (dwStatus < cWaitCount));
+
+ if (!m_run)
+ {
+ _ASSERTE(m_action == W32ETA_NONE);
+ break;
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL - got event , ret=%d, has w32 dbg event=%d\n",
+ dwStatus, fEventAvailable));
+
+ // If we haven't timed out, or if it wasn't the thread control event
+ // that was set, then a process has
+ // exited...
+ if ((dwStatus != WAIT_TIMEOUT) && (dwStatus != WAIT_OBJECT_0))
+ {
+ // Grab the process that exited.
+ _ASSERTE((dwStatus - WAIT_OBJECT_0) == 1);
+ ExitProcess(false); // not detach
+ fEventAvailable = false;
+ }
+ // Should we create a process?
+ else if (m_action == W32ETA_CREATE_PROCESS)
+ {
+ CreateProcess();
+ }
+ // Should we attach to a process?
+ else if (m_action == W32ETA_ATTACH_PROCESS)
+ {
+ AttachProcess();
+ }
+ // Should we detach from a process?
+ else if (m_action == W32ETA_DETACH)
+ {
+ ExitProcess(true); // detach case
+
+ // Once we detach, we don't need to continue any outstanding event.
+ // So act like we never got the event.
+ fEventAvailable = false;
+ PREFIX_ASSUME(m_pProcess == NULL); // W32 cleared process pointer
+ }
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+ // Should we continue the process?
+ else if (m_action == W32ETA_CONTINUE)
+ {
+ HandleUnmanagedContinue();
+ }
+#endif // FEATURE_INTEROP_DEBUGGING
+
+ // We don't need to sweep the FCH threads since we never hijack a thread in cooperative mode.
+
+
+ // Only process an event if one is available.
+ if (!fEventAvailable)
+ {
+ continue;
+ }
+
+ // The only ref we have is the one in the ProcessList hash;
+ // If we dispatch an ExitProcess event, we may even lose that.
+ // But since the CordbProcess is our parent object, we know it won't go away until
+ // it neuters us, so we can safely proceed.
+ // Find the process this event is for.
+ PREFIX_ASSUME(m_pProcess != NULL);
+ _ASSERTE(m_pProcess->m_id == GetProcessId(&event)); // should only get events for our proc
+ g_pRSDebuggingInfo->m_MRUprocess = m_pProcess;
+
+ // Must flush the dac cache since we were just running.
+ m_pProcess->ForceDacFlush();
+
+ // So we've filtered out CLR events.
+ // Let the shim handle the remaining events. This will call back into Filter() if appropriate.
+ // This will also ensure the debug event gets continued.
+ HRESULT hrShim = S_OK;
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(NULL);
+ hrShim = m_pShim->HandleWin32DebugEvent(&event);
+ }
+ // Any errors from the shim (eg. failure to load DAC) are unrecoverable
+ SetUnrecoverableIfFailed(m_pProcess, hrShim);
+
+ } // loop
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: exiting event loop\n"));
+
+ return;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Returns if the current thread is the win32 thread.
+//
+// Return Value:
+// true iff this is the win32 event thread.
+//
+//---------------------------------------------------------------------------------------
+bool CordbProcess::IsWin32EventThread()
+{
+ _ASSERTE((m_pShim != NULL) || !"Don't check win32 event thread in V3 cases");
+ return m_pShim->IsWin32EventThread();
+}
+
+//---------------------------------------------------------------------------------------
+// Call when the sync complete event is received and can be processed.
+//
+// Notes:
+// This is called when the RS gets the sync-complete from the LS and can process it.
+//
+// This has a somewhat elaborate contract to fill between Interop-debugging, Async-Break, draining the
+// managed event-queue, and coordinating with the dispatch thread (RCET).
+//
+// @dbgtodo - this should eventually get hoisted into the shim.
+void CordbProcess::HandleSyncCompleteRecieved()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ this->SetSyncCompleteRecv(true);
+
+ // If some thread is waiting for the process to sync, notify that it can go now.
+ if (this->m_stopRequested)
+ {
+ this->SetSynchronized(true);
+ SetEvent(this->m_stopWaitEvent);
+ }
+ else
+ {
+ // Note: we set the m_stopWaitEvent all the time and leave it high while we're stopped. This
+ // must be done after we've checked m_stopRequested.
+ SetEvent(this->m_stopWaitEvent);
+
+ // Otherwise, simply mark that the state of the process has changed and let the
+ // managed event dispatch logic take over.
+ //
+ // Note: process->m_synchronized remains false, which indicates to the RC event
+ // thread that it can dispatch the next managed event.
+ m_cordb->ProcessStateChanged();
+ }
+}
+
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+// Get a Thread's _user_ starting address (the real starting address may be some
+// OS shim.)
+// This may return NULL for the Async-Break thread.
+void* GetThreadUserStartAddr(const DEBUG_EVENT* pCreateThreadEvent)
+{
+ // On Win7 and above, we can trust the lpStartAddress field of the CREATE_THREAD_DEBUG_EVENT
+ // to be the user start address (the actual OS start address is an implementation detail that
+ // doesn't need to be exposed to users). Note that we are assuming that the target process
+ // is running on Win7 if mscordbi is. If we ever have some remoting scenario where the target
+ // can run on a different windows machine with a different OS version we will need a way to
+ // determine the target's OS version
+ if(RunningOnWin7())
+ {
+ return pCreateThreadEvent->u.CreateThread.lpStartAddress;
+ }
+
+ // On pre-Win7 OSes, we rely on an OS implementation detail to get the real user thread start:
+ // it exists in EAX at thread start time.
+ // Note that for a brief period of time there was a GetThreadStartInformation API in Longhorn
+ // we could use for this, but it was removed during the Longhorn reset.
+ HANDLE hThread = pCreateThreadEvent->u.CreateThread.hThread;
+#if defined(DBG_TARGET_X86)
+ // Grab the thread's context.
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(hThread, &c);
+
+ if (succ)
+ {
+ return (void*) c.Eax;
+ }
+#elif defined(DBG_TARGET_AMD64)
+ DT_CONTEXT c;
+ c.ContextFlags = DT_CONTEXT_FULL;
+ BOOL succ = DbiGetThreadContext(hThread, &c);
+
+ if (succ)
+ {
+ return (void*) c.Rcx;
+ }
+#else
+ PORTABILITY_ASSERT("port GetThreadUserStartAddr");
+#endif
+
+ return NULL;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Get (create if needed) the unmanaged thread for an unmanaged debug event.
+//
+// Arguments:
+// event - native debug event.
+//
+// Return Value:
+// Unmanaged thread corresponding to the native debug event.
+//
+//
+// Notes:
+// Thread may be newly allocated, or may be existing. CordbProcess holds
+// list of all CordbUnmanagedThreads, and will handle freeing memory.
+//
+//---------------------------------------------------------------------------------------
+CordbUnmanagedThread * CordbProcess::GetUnmanagedThreadFromEvent(const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ HRESULT hr;
+
+ CordbUnmanagedThread * pUnmanagedThread = NULL;
+
+ // Remember newly created threads.
+ if (pEvent->dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
+ {
+ // We absolutely should have an unmanaged callback by this point.
+ // That means that the client debugger should have called ICorDebug::SetUnmanagedHandler by now.
+ // However, we can't actually enforce that (see comment in ICorDebug::SetUnmanagedHandler for details),
+ // so we do a runtime check to check this.
+ // This is an extremely gross API misuse and an issue in the client if the callback is not set yet.
+ // Without the unmanaged callback, we absolutely can't do interop-debugging. We assert (checked builds) and
+ // dispatch unrecoverable error (retail builds) to avoid an AV.
+
+
+ if (this->m_cordb->m_unmanagedCallback == NULL)
+ {
+ CONSISTENCY_CHECK_MSGF((this->m_cordb->m_unmanagedCallback != NULL),
+ ("GROSS API misuse!!\nNo unmanaged callback set by the time we've received CreateProcess debug event for proces 0x%x.\n",
+ pEvent->dwProcessId));
+
+ CORDBSetUnrecoverableError(this, CORDBG_E_INTEROP_NOT_SUPPORTED, 0);
+
+ // Returning NULL will tell caller not to dispatch event to client. We have no callback object to dispatch upon.
+ return NULL;
+ }
+
+ pUnmanagedThread = this->HandleUnmanagedCreateThread(pEvent->dwThreadId,
+ pEvent->u.CreateProcessInfo.hThread,
+ pEvent->u.CreateProcessInfo.lpThreadLocalBase);
+
+ // Managed-attach won't start until after Cordbg continues from the loader-bp.
+ }
+ else if (pEvent->dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT)
+ {
+ pUnmanagedThread = this->HandleUnmanagedCreateThread(pEvent->dwThreadId,
+ pEvent->u.CreateThread.hThread,
+ pEvent->u.CreateThread.lpThreadLocalBase);
+
+ BOOL fBlockExists = FALSE;
+ hr = S_OK;
+ EX_TRY
+ {
+ // See if we have the debugger control block yet...
+
+ this->GetEventBlock(&fBlockExists);
+
+ // If we have the debugger control block, and if that control block has the address of the thread proc for
+ // the helper thread, then we're initialized enough on the Left Side to recgonize the helper thread based on
+ // its thread proc's address.
+ if (this->GetDCB() != NULL)
+ {
+ // get the latest LS DCB information
+ UpdateRightSideDCB();
+ if ((this->GetDCB()->m_helperThreadStartAddr != NULL) && (pUnmanagedThread != NULL))
+ {
+ void * pStartAddr = GetThreadUserStartAddr(pEvent);
+
+ if (pStartAddr == this->GetDCB()->m_helperThreadStartAddr)
+ {
+ // Remember the ID of the helper thread.
+ this->m_helperThreadId = pEvent->dwThreadId;
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: Left Side Helper Thread is 0x%x\n", pEvent->dwThreadId));
+ }
+ }
+ }
+ }
+ EX_CATCH_HRESULT(hr)
+ {
+ if (fBlockExists && FAILED(hr))
+ {
+ _ASSERTE(IsLegalFatalError(hr));
+ // Send up the DebuggerError event
+ this->UnrecoverableError(hr, 0, NULL, 0);
+
+ // Kill the process.
+ // RS will pump events until we LS process exits.
+ TerminateProcess(this->m_handle, hr);
+
+ return pUnmanagedThread;
+ }
+ }
+ }
+ else
+ {
+ // Find the unmanaged thread that this event is for.
+ pUnmanagedThread = this->GetUnmanagedThread(pEvent->dwThreadId);
+ }
+
+ return pUnmanagedThread;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Handle a native-debug event representing a managed sync-complete event.
+//
+//
+// Return Value:
+// Reaction telling caller how to respond to the native-debug event.
+//
+// Assumptions:
+// Called within the Triage process after receiving a native-debug event.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageSyncComplete()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TSC: received 'sync complete' flare.\n");
+
+ _ASSERTE(IsInteropDebugging());
+
+ // Note: we really don't need to be suspending Runtime threads that we know have tripped
+ // here. If we ever end up with a nice, quick way to know that about each unmanaged thread, then
+ // we should put that to good use here.
+ this->SuspendUnmanagedThreads();
+
+ this->HandleSyncCompleteRecieved();
+
+ // Let the process run free.
+ return REACTION(cIgnore);
+
+ // At this point, all managed threads are stopped at safe places and all unmanaged
+ // threads are either suspended or hijacked. All stopped managed threads are also hard
+ // suspended (due to the call to SuspendUnmanagedThreads above) except for the thread
+ // that sent the sync complete flare.
+
+ // We've handled this exception, so skip all further processing.
+ UNREACHABLE();
+}
+
+//-----------------------------------------------------------------------------
+// Triage a breakpoint (non-flare) on a "normal" thread.
+//-----------------------------------------------------------------------------
+Reaction CordbProcess::TriageBreakpoint(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ HRESULT hr = S_OK;
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+ _ASSERTE(dwExCode == STATUS_BREAKPOINT);
+
+ // There are three cases here:
+ //
+ // 1. The breakpoint definetly belongs to the Runtime. (I.e., a BP in our patch table that
+ // is in managed code.) In this case, we continue the process with
+ // DBG_EXCEPTION_NOT_HANDLED, which lets the in-process exception logic kick in as if we
+ // weren't here.
+ //
+ // 2. The breakpoint is definetly not ours. (I.e., a BP that is not in our patch table.) We
+ // pass these up as regular exception events, doing the can't stop check as usual.
+ //
+ // 3. We're not sure. (I.e., a BP in our patch table, but set in unmangaed code.) In this
+ // case, we hijack as usual, also with can't stop check as usual.
+
+ bool fPatchFound = false;
+ bool fPatchIsUnmanaged = false;
+
+ hr = this->FindPatchByAddress(PTR_TO_CORDB_ADDRESS(pExAddress),
+ &fPatchFound,
+ &fPatchIsUnmanaged);
+
+ if (SUCCEEDED(hr))
+ {
+ if (fPatchFound)
+ {
+#ifdef _DEBUG
+ // What if managed & native patch the same address? That could happen on a step out M --> U.
+ {
+ NativePatch * pNativePatch = GetNativePatch(pExAddress);
+ SIMPLIFYING_ASSUMPTION_MSGF(pNativePatch == NULL, ("Have Managed & native patch at 0x%p", pExAddress));
+ }
+#endif
+
+ // BP could be ours... if its unmanaged, then we still need to hijack, so fall
+ // through to that logic. Otherwise, its ours.
+ if (!fPatchIsUnmanaged)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "belongs to runtime due to patch table match.\n"));
+
+ return REACTION(cCLR);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "matched in patch table, but its unmanaged so might hijack anyway.\n"));
+
+ // If we're in cooperative mode, then we must have a inproc handler, and don't need to hijack
+ // One way this can happen is the patch placed for a func-eval complete is hit in coop-mode.
+ if (pUnmanagedThread->GetEEPGCDisabled())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Already in coop-mode, don't need to hijack\n"));
+ return REACTION(cCLR);
+ }
+ else
+ {
+ return REACTION(cBreakpointRequiringHijack);
+ }
+ }
+
+ UNREACHABLE();
+ }
+ else // Patch not found
+ {
+ // If we're here, then we have a BP that's not in the managed patch table, and not
+ // in the native patch list. This should be rare. Perhaps an int3 / DebugBreak() / Assert in
+ // the native code stream.
+ // Anyway, we don't know about this patch so we can't skip it. The only thing we can do
+ // is chuck it up to Cordbg and hope they can help us. Note that this is the same case
+ // we were in w. V1.
+
+ // BP doesn't belong to CLR ... so dispatch it to Cordbg as either make it IB or OOB.
+ // @todo - make the runtime 1 giant Can't stop region.
+ bool fCantStop = pUnmanagedThread->IsCantStop();
+
+#ifdef _DEBUG
+ // We rarely expect a raw int3 here. Add a debug check that will assert.
+ // Tests that know they don't have raw int3 can enable this regkey to get
+ // extra coverage.
+ static DWORD s_fBreakOnRawInt3 = -1;
+
+ if (s_fBreakOnRawInt3 == -1)
+ s_fBreakOnRawInt3 = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgBreakOnRawInt3);
+
+ if (s_fBreakOnRawInt3)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("Unexpected Raw int3 at:%p on tid 0x%x (%d). CantStop=%d."
+ "This assert is used by specific tests to get extra checks."
+ "For normal cases it's ignorable and is enabled by setting DbgBreakOnRawInt3==1.",
+ pExAddress, pEvent->dwThreadId, pEvent->dwThreadId, fCantStop));
+ }
+#endif
+
+ if (fCantStop)
+ {
+ // If we're in a can't stop region, then its OOB no matter what at this point.
+ return REACTION(cOOB);
+ }
+ else
+ {
+ // PGC must be enabled if we're going to stop for an IB event.
+ bool PGCDisabled = pUnmanagedThread->GetEEPGCDisabled();
+ _ASSERTE(!PGCDisabled);
+
+ // Bp is definitely not ours, and PGC is not disabled, so in-band exception.
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::W32EL: breakpoint exception "
+ "does not belong to the runtime due to failed patch table match.\n"));
+
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+ UNREACHABLE();
+ }
+ else
+ {
+ // Patch table lookup failed? Only on OOM or if ReadProcessMemory fails...
+ _ASSERTE(!"Patch table lookup failed!");
+ CORDBSetUnrecoverableError(this, hr, 0);
+ return REACTION(cOOB);
+ }
+
+ UNREACHABLE();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a "normal" 1st chance exception on a "normal" thread.
+// Not hijacked, not the helper thread, not a flare, etc.. This is the common
+// case for a native exception from native code.
+//
+// Arguments:
+// pUnmanagedThread - Pointer to the CordbUnmanagedThread object that we want to hijack.
+// pEvent - Pointer to the debug event which contains the exception code and address.
+//
+// Return Value:
+// The Reaction tells if the event is in-band, out-of-band, CLR specific or ignorable.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::Triage1stChanceNonSpecial(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ CONTRACTL
+ {
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+ // This had better not be a flare. If it is, that means we have some race that unmarked
+ // the hijacks.
+ _ASSERTE(!ExceptionIsFlare(dwExCode, pExAddress));
+
+ // Any first chance exception could belong to the Runtime, so long as the Runtime has actually been
+ // initialized. Here we'll setup a first-chance hijack for this thread so that it can give us the
+ // true answer that we need.
+
+ // But none of those exceptions could possibly be ours unless we have a managed thread to go with
+ // this unmanaged thread. A non-NULL EEThreadPtr tells us that there is indeed a managed thread for
+ // this unmanaged thread, even if the Right Side hasn't received a managed ThreadCreate message yet.
+ REMOTE_PTR pEEThread;
+ hr = pUnmanagedThread->GetEEThreadPtr(&pEEThread);
+ _ASSERTE(SUCCEEDED(hr));
+
+ if (pEEThread == NULL)
+ {
+ // No managed thread, so it can't possibly belong to the runtime!
+ // But it may still be in a can't-stop region (think some goofy shutdown case).
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+
+
+
+ // We have to be careful here. A Runtime thread may be in a place where we cannot let an
+ // unmanaged exception stop it. For instance, an unmanaged user breakpoint set on
+ // WaitForSingleObject will prevent Runtime threads from sending events to the Right Side. So at
+ // various points below, we check to see if this Runtime thread is in a place were we can't let
+ // it stop, and if so then we jump over to the out-of-band dispatch logic and treat this
+ // exception as out-of-band. The debugger is supposed to continue from the out-of-band event
+ // properly and help us avoid this problem altogether.
+
+ // Grab a few flags from the thread's state...
+ bool fThreadStepping = false;
+ bool fSpecialManagedException = false;
+
+ pUnmanagedThread->GetEEState(&fThreadStepping, &fSpecialManagedException);
+
+ // If we've got a single step exception, and if the Left Side has indicated that it was
+ // stepping the thread, then the exception is ours.
+ if (dwExCode == STATUS_SINGLE_STEP)
+ {
+ if (fThreadStepping)
+ {
+ // Yup, its the Left Side that was stepping the thread...
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: single step exception belongs to the runtime.\n");
+
+ return REACTION(cCLR);
+ }
+
+ // Any single step that is triggered when the thread's state doesn't indicate that
+ // we were stepping the thread automatically gets passed out as an unmanged event.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: single step exception "
+ "does not belong to the runtime.\n");
+
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+#ifdef CorDB_Short_Circuit_First_Chance_Ownership
+ // If the runtime indicates that this is a special exception being thrown within the runtime,
+ // then its ours no matter what.
+ else if (fSpecialManagedException)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: exception belongs to the runtime due to "
+ "special managed exception marking.\n");
+
+ return REACTION(cCLR);
+ }
+ else if ((dwExCode == EXCEPTION_COMPLUS) || (dwExCode == EXCEPTION_HIJACK))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000,
+ "W32ET::W32EL: exception belongs to Runtime due to match on built in exception code\n");
+
+ return REACTION(cCLR);
+ }
+ else if (dwExCode == EXCEPTION_MSVC)
+ {
+ // The runtime may use C++ exceptions internally. We can still report these
+ // to the debugger as long as we're outside of a can't-stop region.
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+ else if (dwExCode == STATUS_BREAKPOINT)
+ {
+ return TriageBreakpoint(pUnmanagedThread, pEvent);
+ }// end BP case
+#endif
+
+ // It's not a breakpoint or single-step. Now it just comes down to the address from where
+ // the exception is coming from. If it's managed, we give it back to the CLR. If it's
+ // from native, then we dispatch to Cordbg.
+ // We can use DAC to figure this out from Out-of-process.
+ _ASSERTE(dwExCode != STATUS_BREAKPOINT); // BP were already handled.
+
+
+ // Use DAC to decide if it's ours or not w/o going inproc.
+ CORDB_ADDRESS address = PTR_TO_CORDB_ADDRESS(pExAddress);
+
+ IDacDbiInterface::AddressType addrType;
+
+ addrType = GetDAC()->GetAddressType(address);
+ bool fIsCorCode =((addrType == IDacDbiInterface::kAddressManagedMethod) ||
+ (addrType == IDacDbiInterface::kAddressRuntimeManagedCode) ||
+ (addrType == IDacDbiInterface::kAddressRuntimeUnmanagedCode));
+
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "W32ET::W32EL: IsCorCode(0x%I64p)=%d\n", address, fIsCorCode);
+
+
+ if (fIsCorCode)
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ if (pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+ }
+
+ UNREACHABLE();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a 1st-chance exception when the CLR is initialized.
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// A 1st-chance event has a wide spectrum of possibility including:
+// - It may be unmanaged or managed.
+// - Or it may be an execution control exception for managed-exceution
+// - thread skipping an OOB event.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageExcep1stChanceAndInit(CordbUnmanagedThread * pUnmanagedThread,
+ const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ NativePatch * pNativePatch = NULL;
+ DebuggerIPCRuntimeOffsets * pIPCRuntimeOffsets = &(this->m_runtimeOffsets);
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+ const void * pExAddress = pEvent->u.Exception.ExceptionRecord.ExceptionAddress;
+
+
+#ifdef _DEBUG
+ // Some Interop bugs involve threads that land at a crazy IP. Since we're interop-debugging, we can't
+ // attach a debugger to the LS. So we have some debug mode where we enable the SS flag and thus
+ // produce a trace of where a thread is going.
+ if (pUnmanagedThread->IsDEBUGTrace() && (dwExCode == STATUS_SINGLE_STEP))
+ {
+ pUnmanagedThread->ClearState(CUTS_DEBUG_SingleStep);
+ LOG((LF_CORDB, LL_INFO10000, "DEBUG TRACE, thread %4x at IP: 0x%p\n", pUnmanagedThread->m_id, pExAddress));
+
+ // Clear the exception and pretend this never happened.
+ return REACTION(cIgnore);
+ }
+#endif
+
+ // If we were stepping for exception retrigger and got the single step and it should be hidden then just ignore it.
+ // Anything that isn't cInbandExceptionRetrigger will cause the debug event to be dequeued, stepping turned off, and
+ // it will count as not retriggering
+ // TODO: I don't think the IsSSFlagNeeded() check is needed here though it doesn't break anything
+ if (pUnmanagedThread->IsSSFlagNeeded() && pUnmanagedThread->IsSSFlagHidden() && (dwExCode == STATUS_SINGLE_STEP))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "CP::TE1stCAI: ignoring hidden single step\n"));
+ return REACTION(cIgnore);
+ }
+
+ // Is this a breakpoint indicating that the Left Side is now synchronized?
+ if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_notifyRSOfSyncCompleteBPAddr))
+ {
+ return TriageSyncComplete();
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_excepForRuntimeHandoffCompleteBPAddr))
+ {
+ _ASSERTE(!"This should be unused now");
+
+ // This notification means that a thread that had been first-chance hijacked is now
+ // finally leaving the hijack.
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'first chance hijack handoff complete' flare.\n");
+
+ // Let the process run.
+ return REACTION(cIgnore);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == pIPCRuntimeOffsets->m_signalHijackCompleteBPAddr))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'hijack complete' flare.\n");
+ return REACTION(cInbandHijackComplete);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) &&
+ (pExAddress == m_runtimeOffsets.m_signalHijackStartedBPAddr))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: received 'hijack started' flare.\n");
+ return REACTION(cFirstChanceHijackStarted);
+ }
+ else if ((dwExCode == STATUS_BREAKPOINT) && ((pNativePatch = GetNativePatch(pExAddress)) != NULL) )
+ {
+ // We hit a native BP placed by Cordbg. This could happen on any thread (including helper)
+ bool fCantStop = pUnmanagedThread->IsCantStop();
+
+ // REVISIT_TODO: if the user also set a breakpoint here then we should dispatch to the debugger
+ // and rely on the debugger to get us past this. Should be a rare case though.
+ if (fCantStop)
+ {
+ // Need to skip it completely; never dispatch.
+ pUnmanagedThread->SetupForSkipBreakpoint(pNativePatch);
+
+ // Debuggee will single step over the patch, and fire a SS exception.
+ // We'll then call FixupForSkipBreakpoint, and continue the process.
+ return REACTION(cIgnore);
+ }
+ else
+ {
+ // Native patch in native code. A very common scenario.
+ // Dispatch as an IB event to Cordbg.
+ STRESS_LOG1(LF_CORDB, LL_INFO10000, "Native patch in native code (at %p), dispatching as IB event.\n", pExAddress);
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+
+ else if ((dwExCode == STATUS_BREAKPOINT) && !IsBreakOpcodeAtAddress(pExAddress))
+ {
+ // If we got an int3 exception, but there's not actually an int3 at the address, then just reset the IP
+ // to the address. This can happen if the int 3 is cleared after the thread has dispatched it (in which case
+ // WFDE will pick it up) but before we realize it's one of ours.
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: Phantom Int3: Tid=0x%x, addr=%p\n", pEvent->dwThreadId, pExAddress);
+
+ DT_CONTEXT context;
+
+ context.ContextFlags = DT_CONTEXT_FULL;
+
+ BOOL fSuccess = DbiGetThreadContext(pUnmanagedThread->m_handle, &context);
+
+ _ASSERTE(fSuccess);
+
+ if (fSuccess)
+ {
+ // Backup IP to point to the instruction we need to execute. Continuing from a breakpoint exception
+ // continues execution at the instruction after the breakpoint, but we need to continue where the
+ // breakpoint was.
+ CORDbgSetIP(&context, (LPVOID) pExAddress);
+
+ fSuccess = DbiSetThreadContext(pUnmanagedThread->m_handle, &context);
+ _ASSERTE(fSuccess);
+ }
+
+ return REACTION(cIgnore);
+ }
+ else if (pUnmanagedThread->IsSkippingNativePatch())
+ {
+ // If we Single-Step over an exception, then the OS never gives us the single-step event.
+ // Thus if we're skipping a native patch, we don't care what exception event we got.
+ LOG((LF_CORDB, LL_INFO100000, "Done skipping native patch. Ex=0x%x\n, IsSS=%d",
+ dwExCode,
+ (dwExCode == STATUS_SINGLE_STEP)));
+
+ // This is the 2nd half of skipping a native patch.
+ // This could happen on any thread (including helper)
+ // We've already removed the opcode and now we just finished a single-step over it.
+ // So put the patch back in, and continue the process.
+ pUnmanagedThread->FixupForSkipBreakpoint();
+
+ return REACTION(cIgnore);
+ }
+ else if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ // We should never ever get a single-step event from the helper thread.
+ CONSISTENCY_CHECK_MSGF(dwExCode != STATUS_SINGLE_STEP, (
+ "Single-Step exception on helper thread (tid=0x%x/%d) in debuggee process (pid=0x%x/%d).\n"
+ "For more information, attach a debuggee non-invasively to the LS to get the callstack.\n",
+ pUnmanagedThread->m_id,
+ pUnmanagedThread->m_id,
+ this->m_id,
+ this->m_id));
+
+ // We ignore any first chance exceptions from the helper thread. There are lots of places
+ // on the left side where we attempt to dereference bad object refs and such that will be
+ // handled by exception handlers already in place.
+ //
+ // Note: we check this after checking for the sync complete notification, since that can
+ // come from the helper thread.
+ //
+ // Note: we do let single step and breakpoint exceptions go through to the debugger for processing.
+ if ((dwExCode != STATUS_BREAKPOINT) && (dwExCode != STATUS_SINGLE_STEP))
+ {
+ return REACTION(cCLR);
+ }
+ else
+ {
+ // Since the helper thread is part of the "can't stop" region, we should have already
+ // skipped any BPs on it.
+ // However, any Assert on the helper thread will hit this case.
+ CONSISTENCY_CHECK_MSGF((dwExCode != STATUS_BREAKPOINT), (
+ "Assert on helper thread (tid=0x%x/%d) in debuggee process (pid=0x%x/%d).\n"
+ "For more information, attach a debuggee non-invasively to the LS to get the callstack.\n",
+ pUnmanagedThread->m_id,
+ pUnmanagedThread->m_id,
+ this->m_id,
+ this->m_id));
+
+ // These breakpoint and single step exceptions have to be dispatched to the debugger as
+ // out-of-band events. This tells the debugger that they must continue from these events
+ // immediatly, and that no interaction with the Left Side is allowed until they do so. This
+ // makes sense, since these events are on the helper thread.
+ return REACTION(cOOB);
+ }
+ UNREACHABLE();
+ }
+ else if (pUnmanagedThread->IsFirstChanceHijacked() && this->ExceptionIsFlare(dwExCode, pExAddress))
+ {
+ _ASSERTE(!"This should be unused now");
+ }
+ else if (pUnmanagedThread->IsGenericHijacked())
+ {
+ if (this->ExceptionIsFlare(dwExCode, pExAddress))
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TE1stCAI: fixing up from generic hijack.\n");
+
+ _ASSERTE(dwExCode == STATUS_BREAKPOINT);
+
+ // Fixup the thread from the generic hijack.
+ pUnmanagedThread->FixupFromGenericHijack();
+
+ // We force continue from this flare, since its only purpose was to notify us that we had to
+ // fixup the thread from a generic hijack.
+ return REACTION(cIgnore);
+ }
+ else
+ {
+ // We might reach here due to the stack overflow issue, due to target
+ // memory corruption, or even due to an exception thrown during hijacking
+
+ BOOL bStackOverflow = FALSE;
+
+ if (dwExCode == STATUS_ACCESS_VIOLATION || dwExCode == STATUS_STACK_OVERFLOW)
+ {
+ CORDB_ADDRESS stackLimit;
+ CORDB_ADDRESS stackBase;
+ if (pUnmanagedThread->GetStackRange(&stackBase, &stackLimit))
+ {
+ TADDR addr = pEvent->u.Exception.ExceptionRecord.ExceptionInformation[1];
+ if (stackLimit <= addr && addr < stackBase)
+ bStackOverflow = TRUE;
+ }
+ else
+ {
+ // to limit the impact of the change we'll consider failure to retrieve the stack
+ // bounds as stack overflow as well
+ bStackOverflow = TRUE;
+ }
+ }
+
+ if (!bStackOverflow)
+ {
+ // generic hijack means we're in CantStop, so return cOOB
+ return REACTION(cOOB);
+ }
+
+ // If generichijacked and its not a flare, and the address referenced is on the stack then we've
+ // got our special stack overflow case. Take off generic hijacked, mark that the helper thread
+ // is dead, throw this event on the floor, and pop anyone in SendIPCEvent out of their wait.
+ pUnmanagedThread->ClearState(CUTS_GenericHijacked);
+
+ this->m_helperThreadDead = true;
+
+ // This only works on Windows, not on Mac. We don't support interop-debugging on Mac anyway.
+ SetEvent(m_pEventChannel->GetRightSideEventAckHandle());
+
+ // Note: we remember that this was a second chance event from one of the special stack overflow
+ // cases with CUES_ExceptionUnclearable. This tells us to force the process to terminate when we
+ // continue from the event. Since for some odd reason the OS decides to re-raise this exception
+ // (first chance then second chance) infinitely.
+
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+
+ pUnmanagedThread->IBEvent()->SetState(CUES_ExceptionUnclearable);
+
+ //newEvent = false;
+ return REACTION(cInband_NotNewEvent);
+ }
+ }
+ else
+ {
+ Reaction r(REACTION(cOOB));
+ HRESULT hrCheck = S_OK;;
+ EX_TRY
+ {
+ r = Triage1stChanceNonSpecial(pUnmanagedThread, pEvent);
+ }
+ EX_CATCH_HRESULT(hrCheck);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrCheck));
+ SetUnrecoverableIfFailed(this, hrCheck);
+
+ return r;
+
+ }
+
+ // At this point, any first-chance exceptions that could be special have been handled. Any
+ // first-chance exception that we're still processing at this point is destined to be
+ // dispatched as an unmanaged event.
+ UNREACHABLE();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a 2nd-chance exception when the CLR is initialized.
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// We already hijacked 2nd-chance managed exceptions, so this is just handling
+// some V2 Interop corner cases.
+// @dbgtodo interop: this should eventually completely go away with the V3 design.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageExcep2ndChanceAndInit(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ DWORD dwExCode = pEvent->u.Exception.ExceptionRecord.ExceptionCode;
+
+#ifdef _DEBUG
+ // For debugging, add an extra knob that let us break on any 2nd chance exceptions.
+ // Most tests don't throw 2nd-chance, so we could have this enabled most of the time and
+ // catch bogus 2nd chance exceptions
+ static DWORD dwNo2ndChance = -1;
+
+ if (dwNo2ndChance == -1)
+ {
+ dwNo2ndChance = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgNo2ndChance);
+ }
+
+ if (dwNo2ndChance)
+ {
+ CONSISTENCY_CHECK_MSGF(false, ("2nd chance exception occurred on LS thread=0x%x, code=0x%08x, address=0x%p\n"
+ "This assert is firing b/c you explicitly requested it by having the 'DbgNo2ndChance' knob enabled.\n"
+ "Disable it to avoid asserts on 2nd chance.",
+ pUnmanagedThread->m_id,
+ dwExCode,
+ pEvent->u.Exception.ExceptionRecord.ExceptionAddress));
+ }
+#endif
+
+
+ // Second chance exception, Runtime initialized. It could belong to the Runtime, so we'll check. If it
+ // does, then we'll hijack the thread. Otherwise, well just fall through and let it get
+ // dispatched. Note: we do this so that the CLR's unhandled exception logic gets a chance to run even
+ // though we've got a win32 debugger attached. But the unhandled exception logic never touches
+ // breakpoint or single step exceptions, so we ignore those here, too.
+
+ // There are strange cases with stack overflow exceptions. If a nieve application catches a stack
+ // overflow exception and handles it, without resetting the guard page, then the app will get an AV when
+ // it overflows the stack a second time. We will get the first chance AV, but when we continue from it the
+ // OS won't run any SEH handlers, so our FCH won't actually work. Instead, we'll get the AV back on
+ // second chance right away, and we'll end up right here.
+ if (this->IsSpecialStackOverflowCase(pUnmanagedThread, pEvent))
+ {
+ // IsSpecialStackOverflowCase will queue the event for us, so its no longer a "new event". Setting
+ // newEvent = false here basically prevents us from playing with the event anymore and we fall down
+ // to the dispatch logic below, which will get our already queued first chance AV dispatched for
+ // this thread.
+ //newEvent = false;
+ return REACTION(cInband_NotNewEvent);
+ }
+ else if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ // A second chance exception from the helper thread. This is pretty bad... we just force continue
+ // from them and hope for the best.
+ return REACTION(cCLR);
+ }
+
+ if(pUnmanagedThread->IsCantStop())
+ {
+ return REACTION(cOOB);
+ }
+ else
+ {
+ return REACTION(cInband);
+ }
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Triage a win32 Debug event to get a reaction
+//
+// Arguments:
+// pUnmanagedThread - thread that the event has occurred on.
+// pEvent - native debug event for the exception that occurred that this is triaging.
+//
+// Return Value:
+// Reaction for how to handle this event.
+//
+// Assumptions:
+// Called when receiving a debug event when the process is stopped.
+//
+// Notes:
+// This is the main triage routine for Win32 debug events, this delegates to the
+// 1st and 2nd chance routines above appropriately.
+//
+//---------------------------------------------------------------------------------------
+Reaction CordbProcess::TriageWin32DebugEvent(CordbUnmanagedThread * pUnmanagedThread, const DEBUG_EVENT * pEvent)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ // Lots of special cases for exception events. The vast majority of hybrid debugging work that takes
+ // place is in response to exception events. The work below will consider certian exception events
+ // special cases and rather than letting them be queued and dispatched, they will be handled right
+ // here.
+ if (pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "CP::TW32DE: unmanaged exception on "
+ "tid 0x%x, code 0x%08x, addr 0x%08x, chance %d\n",
+ pEvent->dwThreadId,
+ pEvent->u.Exception.ExceptionRecord.ExceptionCode,
+ pEvent->u.Exception.ExceptionRecord.ExceptionAddress,
+ 2-pEvent->u.Exception.dwFirstChance);
+
+#ifdef LOGGING
+ if (pEvent->u.Exception.ExceptionRecord.ExceptionCode == STATUS_ACCESS_VIOLATION)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "\t<%s> address 0x%08x\n",
+ pEvent->u.Exception.ExceptionRecord.ExceptionInformation[0] ? "write to" : "read from",
+ pEvent->u.Exception.ExceptionRecord.ExceptionInformation[1]));
+ }
+#endif
+
+ // Mark the loader bp for kicks. We won't start managed attach until native attach is finished.
+ if (!this->m_loaderBPReceived)
+ {
+ // If its a first chance breakpoint, and its the first one, then its the loader breakpoint.
+ if (pEvent->u.Exception.dwFirstChance &&
+ (pEvent->u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "CP::TW32DE: loader breakpoint received.\n"));
+
+ // Remember that we've received the loader BP event.
+ this->m_loaderBPReceived = true;
+
+ // We never hijack the loader BP anymore (CLR 2.0+).
+ // This is b/c w/ interop-attach, we don't start the managed-attach until _after_ Cordbg
+ // continues from the loader-bp.
+ }
+ } // end of loader bp.
+
+ // This event might be the retriggering of an event we already saw but previously had to hijack
+ if(pUnmanagedThread->HasIBEvent())
+ {
+ const EXCEPTION_RECORD* pRecord1 = &(pEvent->u.Exception.ExceptionRecord);
+ const EXCEPTION_RECORD* pRecord2 = &(pUnmanagedThread->IBEvent()->m_currentDebugEvent.u.Exception.ExceptionRecord);
+ if(pRecord1->ExceptionCode == pRecord2->ExceptionCode &&
+ pRecord1->ExceptionFlags == pRecord2->ExceptionFlags &&
+ pRecord1->ExceptionAddress == pRecord2->ExceptionAddress)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "CP::TW32DE: event is continuation of previously hijacked event.\n");
+ // if we continued from the hijack then we should have already dispatched this event
+ _ASSERTE(pUnmanagedThread->IBEvent()->IsDispatched());
+ return REACTION(cInbandExceptionRetrigger);
+ }
+ }
+
+ // We only care about exception events if they are first chance events and if the Runtime is
+ // initialized within the process. Otherwise, we don't do anything special with them.
+ if (pEvent->u.Exception.dwFirstChance && this->m_initialized)
+ {
+ return TriageExcep1stChanceAndInit(pUnmanagedThread, pEvent);
+ }
+ else if (!pEvent->u.Exception.dwFirstChance && this->m_initialized)
+ {
+ return TriageExcep2ndChanceAndInit(pUnmanagedThread, pEvent);
+ }
+ else
+ {
+ // An exception event, but the Runtime hasn't been initialize. I.e., its an exception event
+ // that we will never try to hijack.
+ return REACTION(cInband);
+ }
+
+ UNREACHABLE();
+ }
+ else
+ // OOB
+ {
+ return REACTION(cOOB);
+ }
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Top-level handler for a win32 debug event during Interop-debugging.
+//
+// Arguments:
+// event - native debug event to handle.
+//
+// Assumptions:
+// The process just got a native debug event via WaitForDebugEvent
+//
+// Notes:
+// The function will Triage the excpetion and then handle it based on the
+// appropriate reaction (see: code:Reaction).
+//
+// @dbgtodo interop: this should all go into the shim.
+//---------------------------------------------------------------------------------------
+void CordbProcess::HandleDebugEventForInteropDebugging(const DEBUG_EVENT * pEvent)
+{
+ PUBLIC_API_ENTRY_FOR_SHIM(this);
+ _ASSERTE(IsInteropDebugging() || !"Only do this in real interop handling path");
+
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "W32ET::W32EL: got unmanaged event %d on thread 0x%x, proc 0x%x\n",
+ pEvent->dwDebugEventCode, pEvent->dwThreadId, pEvent->dwProcessId);
+
+ // Get the Lock.
+ _ASSERTE(!this->ThreadHoldsProcessLock());
+
+ RSSmartPtr<CordbProcess> pRef(this); // make sure we're alive...
+
+ RSLockHolder processLockHolder(&this->m_processMutex);
+
+ // If we get a new Win32 Debug event, then we need to flush any cached oop data structures.
+ // This includes refreshing DAC and our patch table.
+ ForceDacFlush();
+ ClearPatchTable();
+
+#ifdef _DEBUG
+ // We want to detect if we've deadlocked. Unfortunately, w/ interop debugging, there can be a lot of
+ // deadtime since we need to wait for a debug event. Thus the CPU usage may appear to be at 0%, but
+ // we're not deadlocked b/c we're still receiving debug events.
+ // So ping every X debug events.
+ static int s_cCount = 0;
+ static int s_iPingLevel = -1;
+ if (s_iPingLevel == -1)
+ {
+ s_iPingLevel = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgPingInterop);
+ }
+ if (s_iPingLevel != 0)
+ {
+ s_cCount++;
+ if (s_cCount >= s_iPingLevel)
+ {
+ s_cCount = 0;
+ ::Beep(1000,100);
+
+ // Refresh so we can adjust ping level midstream.
+ s_iPingLevel = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgPingInterop);
+ }
+ }
+#endif
+
+ bool fNewEvent = true;
+
+ // Mark the process as stopped.
+ this->m_state |= CordbProcess::PS_WIN32_STOPPED;
+
+ CordbUnmanagedThread * pUnmanagedThread = GetUnmanagedThreadFromEvent(pEvent);
+
+ // In retail, if there is no unmanaged thread then we just continue and loop back around. UnrecoverableError has
+ // already been set in this case. Note: there is an issue in the Win32 debugging API that can cause duplicate
+ // ExitThread events. We therefore must handle not finding an unmanaged thread gracefully.
+
+ _ASSERTE((pUnmanagedThread != NULL) || (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT));
+
+ if (pUnmanagedThread == NULL)
+ {
+ // Note: we use ContinueDebugEvent directly here since our continue is very simple and all of our other
+ // continue mechanisms rely on having an UnmanagedThread object to play with ;)
+ STRESS_LOG2(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Continuing without thread on tid 0x%x, code=0x%x\n",
+ pEvent->dwThreadId,
+ pEvent->dwDebugEventCode);
+
+ this->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+
+ BOOL fOk = ContinueDebugEvent(pEvent->dwProcessId, pEvent->dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
+
+ _ASSERTE(fOk || !"ContinueDebugEvent failed when he have no thread. Debuggee is likely hung");
+
+ return;
+ }
+
+ // There's an innate race such that we can get a Debug Event even after we've suspended a thread.
+ // This can happen if the thread has already dispatched the debug event but we haven't called WFDE to pick it up
+ // yet. This is sufficiently goofy that we want to stress log it.
+ if (pUnmanagedThread->IsSuspended())
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Thread 0x%x is suspended\n", pEvent->dwThreadId);
+ }
+
+ // For debugging crazy races in retail, we'll keep a rolling queue of win32 debug events.
+ this->DebugRecordWin32Event(pEvent, pUnmanagedThread);
+
+
+ // Check to see if shutdown of the in-proc debugging services has begun. If it has, then we know we'll no longer
+ // be running any managed code, and we know that we can stop hijacking threads. We remember this by setting
+ // m_initialized to false, thus preventing most things from happening elsewhere.
+ // Don't even bother checking the DCB fields until it's been verified (m_initialized == true)
+ if (this->m_initialized && (this->GetDCB() != NULL))
+ {
+ UpdateRightSideDCB();
+ if (this->GetDCB()->m_shutdownBegun)
+ {
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: shutdown begun...\n");
+ this->m_initialized = false;
+ }
+ }
+
+#ifdef _DEBUG
+ //Verify that GetThreadContext agrees with the exception address
+ if (pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ DT_CONTEXT tempDebugContext;
+ tempDebugContext.ContextFlags = DT_CONTEXT_FULL;
+ DbiGetThreadContext(pUnmanagedThread->m_handle, &tempDebugContext);
+ CordbUnmanagedThread::LogContext(&tempDebugContext);
+ _ASSERTE(CORDbgGetIP(&tempDebugContext) == pEvent->u.Exception.ExceptionRecord.ExceptionAddress ||
+ (DWORD)CORDbgGetIP(&tempDebugContext) == ((DWORD)pEvent->u.Exception.ExceptionRecord.ExceptionAddress)+1);
+ }
+#endif
+
+ // This call will decide what to do w/ the the win32 event we just got. It does a lot of work.
+ Reaction reaction = TriageWin32DebugEvent(pUnmanagedThread, pEvent);
+
+
+ // Stress-log the reaction.
+#ifdef _DEBUG
+ STRESS_LOG3(LF_CORDB, LL_INFO1000, "Reaction: %d (%s), line=%d\n",
+ reaction.GetType(),
+ reaction.GetReactionName(),
+ reaction.GetLine());
+#else
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Reaction: %d\n", reaction.GetType());
+#endif
+
+ // Make sure the lock wasn't accidentally released.
+ _ASSERTE(ThreadHoldsProcessLock());
+ CordbWin32EventThread * pW32EventThread = this->m_pShim->GetWin32EventThread();
+ _ASSERTE(pW32EventThread != NULL);
+
+ // if we were waiting for a retriggered exception but recieved any other event then turn
+ // off the single stepping and dequeue the IB event. Right now we only use the SS flag internally
+ // for stepping during possible retrigger.
+ if(reaction.GetType() != Reaction::cInbandExceptionRetrigger && pUnmanagedThread->IsSSFlagNeeded())
+ {
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ LOG((LF_CORDB, LL_INFO100000, "CP::HDEFID: IB event did not retrigger ue=0x%p\n", pUnmanagedEvent));
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ pUnmanagedThread->EndStepping();
+ }
+
+ switch(reaction.GetType())
+ {
+ // Common for flares.
+ case Reaction::cIgnore:
+
+ // Shouldn't be suspending in the first place with outstanding flares.
+ _ASSERTE(!pUnmanagedThread->IsSuspended());
+
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+
+ case Reaction::cCLR:
+ // Don't care if thread is suspended here. We'll just let the thread continue whatever it's doing.
+
+ this->m_DbgSupport.m_TotalCLR++;
+
+ // If this is for the CLR, then we just continue unhandled and know that the CLR has
+ // a handler inplace to deal w/ this exception.
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_EXCEPTION_NOT_HANDLED, false);
+ goto LDone;
+
+
+ case Reaction::cInband_NotNewEvent:
+ fNewEvent = false;
+
+ // fall through to Inband case...
+
+ case Reaction::cInband:
+ {
+ this->m_DbgSupport.m_TotalIB++;
+
+ // Hijack in-band events (exception events, exit threads) if there is already an event at the head
+ // of the queue or if the process is currently synchronized. Of course, we only do this if the
+ // process is initialized.
+ //
+ // Note: we also hijack these left over in-band events if we're activley trying to send the
+ // managed continue message to the Left Side. This is controlled by m_specialDeferment below.
+
+ // Only exceptions can be IB events - everything else is OOB.
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+
+ // CLR internal exceptions should be sent back to the CLR and never treated as inband events.
+ // If this assert fires, the event was triaged wrong.
+ CONSISTENCY_CHECK_MSGF((pEvent->u.Exception.ExceptionRecord.ExceptionCode != EXCEPTION_COMPLUS),
+ ("Attempting to dispatch a CLR internal exception as an Inband event. Reaction line=%d\n",
+ reaction.GetLine()));
+
+
+ _ASSERTE(!pUnmanagedThread->IsCantStop());
+
+ // We need to decide whether or not to dispatch this event immediately
+ // We defer it to enforce that we only dispatch 1 IB event at a time (managed events are
+ // considered IB here).
+ // This means if:
+ // 1) there's already an outstanding unmanaged inband event (an event the user has not continued from)
+ // 2) If the process is synchronized (since that means we've already dispatched a managed event).
+ // 3) If we've received a SyncComplete event, but aren't yet Sync. This will almost always be the same as
+ // whether we're synced, but has a distict quality. It's always set by the w32 event thread in Interop,
+ // and so it's guaranteed to be serialized against this check here (also on the w32et).
+ // 4) Special deferment - This covers the region where we're sending a Stop/Continue IPC event across.
+ // We defer it here to keep the Helper thread alive so that it can handle these IPC events.
+ // Queued events will be dispatched when continue is called.
+ BOOL fHasUserUncontinuedNativeEvents = HasUserUncontinuedNativeEvents();
+ bool fDeferInbandEvent = (fHasUserUncontinuedNativeEvents ||
+ GetSynchronized() ||
+ GetSyncCompleteRecv() ||
+ m_specialDeferment);
+
+ // If we've got a new event, queue it.
+ if (fNewEvent)
+ {
+ this->QueueUnmanagedEvent(pUnmanagedThread, pEvent);
+ }
+
+ if (fNewEvent && this->m_initialized && fDeferInbandEvent)
+ {
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "W32ET::W32EL: Needed to defer dispatching event: %d %d %d %d\n",
+ fHasUserUncontinuedNativeEvents,
+ GetSynchronized(),
+ GetSyncCompleteRecv(),
+ m_specialDeferment);
+
+ // this continues the IB debug event into the hijack
+ // the process is now running again
+ pW32EventThread->DoDbgContinue(this, pUnmanagedThread->IBEvent());
+
+ // Since we've hijacked this event, we don't need to do any further processing.
+ goto LDone;
+ }
+ else
+ {
+ // No need to defer the dispatch, do it now
+ this->DispatchUnmanagedInBandEvent();
+
+ goto LDone;
+ }
+ UNREACHABLE();
+ }
+
+ case Reaction::cFirstChanceHijackStarted:
+ {
+ // determine the logical event we are handling, if any
+ CordbUnmanagedEvent* pUnmanagedEvent = NULL;
+ if(pUnmanagedThread->HasIBEvent())
+ {
+ pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ }
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB hijack starting, ue=0x%p\n", pUnmanagedEvent));
+
+ // fetch the LS memory set up for this hijack
+ REMOTE_PTR pDebuggerWord = NULL;
+ DebuggerIPCFirstChanceData fcd;
+ pUnmanagedThread->GetEEDebuggerWord(&pDebuggerWord);
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: old fcd DebugCounter=0x%x\n", fcd.debugCounter));
+
+ // determine what action the LS should take
+ if(pUnmanagedThread->IsBlockingForSync())
+ {
+ // there should be an event we hijacked in this case
+ _ASSERTE(pUnmanagedEvent != NULL);
+
+ // block that event
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: blocking\n"));
+ fcd.action = HIJACK_ACTION_WAIT;
+ fcd.debugCounter = 0x2;
+ SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+ }
+ else
+ {
+ // we don't need to block. We want the vectored handler to just exit
+ // as if it wasn't there
+ _ASSERTE(fcd.action == HIJACK_ACTION_EXIT_UNHANDLED);
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: not blocking\n"));
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: continuing from flare\n"));
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+ }
+
+ case Reaction::cInbandHijackComplete:
+ {
+ // We now execute the hijack worker even when not actually hijacked
+ // so can't assert this
+ //_ASSERTE(pUnmanagedThread->IsFirstChanceHijacked());
+
+ // we should not be stepping at the end of hijacks
+ _ASSERTE(!pUnmanagedThread->IsSSFlagHidden());
+ _ASSERTE(!pUnmanagedThread->IsSSFlagNeeded());
+
+ // if we were hijacked then clean up
+ if(pUnmanagedThread->IsFirstChanceHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: hijack complete will restore context...\n"));
+ DT_CONTEXT tempContext = { 0 };
+ tempContext.ContextFlags = DT_CONTEXT_FULL;
+ HRESULT hr = pUnmanagedThread->GetThreadContext(&tempContext);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // The sync hijack returns normally but the m2uHandoff hijack needs to have the IP
+ // deliberately restored
+ if(!pUnmanagedThread->IsBlockingForSync())
+ {
+ // restore the context to the current un-hijacked context
+ BOOL succ = DbiSetThreadContext(pUnmanagedThread->m_handle, &tempContext);
+ _ASSERTE(succ);
+
+ // Because hijacks don't return normally they might have pushed handlers without poping them
+ // back off. To take care of that we explicitly restore the old SEH chain.
+ #ifdef DBG_TARGET_X86
+ hr = pUnmanagedThread->RestoreLeafSeh();
+ _ASSERTE(SUCCEEDED(hr));
+ #endif
+ }
+ else
+ {
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB hijack completing, continuing unhijacked ue=0x%p\n", pUnmanagedEvent));
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedHijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ _ASSERTE(pUnmanagedEvent->IsEventUserContinued());
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+
+ // fetch the LS memory set up for this hijack
+ REMOTE_PTR pDebuggerWord = NULL;
+ DebuggerIPCFirstChanceData fcd;
+ pUnmanagedThread->GetEEDebuggerWord(&pDebuggerWord);
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: pDebuggerWord is 0x%p\n", pDebuggerWord));
+
+ //set the correct continuation action based upon the user's selection
+ if(pUnmanagedEvent->IsExceptionCleared())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: exception cleared\n"));
+ fcd.action = HIJACK_ACTION_EXIT_HANDLED;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: exception not cleared\n"));
+ fcd.action = HIJACK_ACTION_EXIT_UNHANDLED;
+ }
+
+ //
+ // LS context is restored here so that execution continues from next instruction that caused the hijack.
+ // We shouldn't always restore the LS context though.
+ // Consider the following case where this can cause issues:
+ // Debuggee process hits an exception and calls KERNELBASE!RaiseException, debugger gets the notification and
+ // prepares for first-chance hijack. Debugger(DBI) saves the current thread context (see SetupFirstChanceHijackForSync) which is restored
+ // later below (see SafeWriteThreadContext call) when the process is in VEH (CLRVectoredExceptionHandlerShim->FirstChanceSuspendHijackWorker).
+ // The thread context that got saved(by SetupFirstChanceHijackForSync) was for when the thread was executing RaiseException and when
+ // this context gets restored in VEH, the thread resumes after the exception handler with a context that is not same as one with which
+ // it entered. This inconsistency can lead to bad execution code-paths or even a debuggee crash.
+ //
+ // Example case where we should definitely update the LS context:
+ // After a DbgBreakPoint call, IP gets updated to point to the instruction after int 3 and this is the context saved by debugger.
+ // The IP in context passed to VEH still points to int 3 though and if we don't update the LS context in VEH, the breakpoint
+ // instruction will get executed again.
+ //
+ // Here's a list of cases when we update the LS context:
+ // * we know that context was explicitly updated during this hijack, OR
+ // * if single-stepping flag was set on it originally, OR
+ // * if this was a breakpoint event
+ // Note that above list is a heuristic and it is possible that we need to add more such cases in future.
+ //
+ BOOL isBreakPointEvent = (pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT &&
+ pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT);
+ if (pUnmanagedThread->IsContextSet() || IsSSFlagEnabled(&tempContext) || isBreakPointEvent)
+ {
+ _ASSERTE(fcd.pLeftSideContext != NULL);
+ LOG((LF_CORDB, LL_INFO10000, "W32ET::W32EL: updating LS context at 0x%p\n", fcd.pLeftSideContext));
+ // write the new context over the old one on the LS
+ SafeWriteThreadContext(fcd.pLeftSideContext, &tempContext);
+ }
+
+ // Write the new Fcd data to the LS
+ fcd.debugCounter = 0x1;
+ SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+
+ fcd.debugCounter = 0;
+ SafeReadStruct(PTR_TO_CORDB_ADDRESS(pDebuggerWord), &fcd);
+ _ASSERTE(fcd.debugCounter == 1);
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ }
+
+ _ASSERTE(m_cFirstChanceHijackedThreads > 0);
+ m_cFirstChanceHijackedThreads--;
+ if(m_cFirstChanceHijackedThreads == 0)
+ {
+ m_state &= ~PS_HIJACKS_IN_PLACE;
+ }
+
+ pUnmanagedThread->ClearState(CUTS_FirstChanceHijacked);
+ pUnmanagedThread->ClearState(CUTS_BlockingForSync);
+
+ // if the user set the context it either was already applied (m2uHandoff hijack)
+ // or is about to be applied when the hijack returns (sync hijack).
+ // There may still a small window where it won't appear accurate that
+ // we just have to live with
+ pUnmanagedThread->ClearState(CUTS_HasContextSet);
+ }
+
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+
+ case Reaction::cBreakpointRequiringHijack:
+ {
+ HRESULT hr = pUnmanagedThread->SetupFirstChanceHijack(EHijackReason::kM2UHandoff, &(pEvent->u.Exception.ExceptionRecord));
+ _ASSERTE(SUCCEEDED(hr));
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread, DBG_CONTINUE, false);
+ goto LDone;
+ }
+
+ case Reaction::cInbandExceptionRetrigger:
+ {
+ // this should be unused now
+ _ASSERTE(FALSE);
+ _ASSERTE(pUnmanagedThread->HasIBEvent());
+ CordbUnmanagedEvent* pUnmanagedEvent = pUnmanagedThread->IBEvent();
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+ _ASSERTE(pUnmanagedEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::W32EL: IB event completing, continuing ue=0x%p\n", pUnmanagedEvent));
+
+ DequeueUnmanagedEvent(pUnmanagedThread);
+ // If this event came from RaiseException then flush the context to ensure we won't use it until we re-enter
+ if(pUnmanagedEvent->m_owner->IsRaiseExceptionHijacked())
+ {
+ pUnmanagedEvent->m_owner->RestoreFromRaiseExceptionHijack();
+ pUnmanagedEvent->m_owner->ClearRaiseExceptionEntryContext();
+ }
+ else // otherwise we should have been stepping
+ {
+ pUnmanagedThread->EndStepping();
+ }
+ pW32EventThread->ForceDbgContinue(this, pUnmanagedThread,
+ pUnmanagedEvent->IsExceptionCleared() ? DBG_CONTINUE : DBG_EXCEPTION_NOT_HANDLED, false);
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+
+ case Reaction::cOOB:
+ {
+ // Don't care if this thread claimed to be suspended or not. Dispatch event anyways. After all,
+ // OOB events can come at *any* time.
+
+ // This thread may be suspended. We don't care.
+ this->m_DbgSupport.m_TotalOOB++;
+
+ // Not an inband event. This includes ALL non-exception events (including EXIT_THREAD) as
+ // well as any exception that can't be hijacked (ex, an exception on the helper thread).
+
+ // If this is an exit thread or exit process event, then we need to mark the unmanaged thread as
+ // exited for later.
+ if ((pEvent->dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) ||
+ (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ pUnmanagedThread->SetState(CUTS_Deleted);
+ }
+
+ // If we get an exit process or exit thread event on the helper thread, then we know we're loosing
+ // the Left Side, so go ahead and remember that the helper thread has died.
+ if (this->IsHelperThreadWorked(pUnmanagedThread->GetOSTid()))
+ {
+ if ((pEvent->dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) ||
+ (pEvent->dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ this->m_helperThreadDead = true;
+ }
+ }
+
+ // Queue the current out-of-band event.
+ this->QueueOOBUnmanagedEvent(pUnmanagedThread, pEvent);
+
+ // Go ahead and dispatch the event if its the first one.
+ if (this->m_outOfBandEventQueue == pUnmanagedThread->OOBEvent())
+ {
+ // Set this to true to indicate to Continue() that we're in the unamnaged callback.
+ CordbUnmanagedEvent * pUnmanagedEvent = pUnmanagedThread->OOBEvent();
+
+ this->m_dispatchingOOBEvent = true;
+
+ pUnmanagedEvent->SetState(CUES_Dispatched);
+
+ this->Unlock();
+
+ // Handler should have been registered by now.
+ _ASSERTE(this->m_cordb->m_unmanagedCallback != NULL);
+
+ // Call the callback with fIsOutOfBand = TRUE.
+ {
+ PUBLIC_WIN32_CALLBACK_IN_THIS_SCOPE(this, pEvent, TRUE);
+ this->m_cordb->m_unmanagedCallback->DebugEvent(const_cast<DEBUG_EVENT*> (pEvent), TRUE);
+ }
+
+ this->Lock();
+
+ // If m_dispatchingOOBEvent is false, that means that the user called Continue() from within
+ // the callback. We know that we can go ahead and continue the process now.
+ if (this->m_dispatchingOOBEvent == false)
+ {
+ // Note: this call will dispatch more OOB events if necessary.
+ pW32EventThread->UnmanagedContinue(this, cOobUMContinue);
+ }
+ else
+ {
+ // We're not dispatching anymore, so set this back to false.
+ this->m_dispatchingOOBEvent = false;
+ }
+ }
+
+ // We've handled this event. Skip further processing.
+ goto LDone;
+ }
+ } // end Switch on Reaction
+
+ UNREACHABLE();
+
+LDone:
+ // Process Lock implicitly released by holder.
+
+ STRESS_LOG0(LF_CORDB, LL_INFO1000, "W32ET::W32EL: done processing event.\n");
+
+ return;
+}
+
+//
+// Returns true if the exception is a flare from the left side, false otherwise.
+//
+bool CordbProcess::ExceptionIsFlare(DWORD exceptionCode, const void *exceptionAddress)
+{
+ _ASSERTE(m_runtimeOffsetsInitialized);
+
+ // Can't have a flare if the left side isn't initialized
+ if (m_initialized)
+ {
+ DebuggerIPCRuntimeOffsets *pRO = &m_runtimeOffsets;
+
+ // All flares are breakpoints...
+ if (exceptionCode == STATUS_BREAKPOINT)
+ {
+ // Does the breakpoint address match a flare address?
+ if ((exceptionAddress == pRO->m_signalHijackStartedBPAddr) ||
+ (exceptionAddress == pRO->m_excepForRuntimeHandoffStartBPAddr) ||
+ (exceptionAddress == pRO->m_excepForRuntimeHandoffCompleteBPAddr) ||
+ (exceptionAddress == pRO->m_signalHijackCompleteBPAddr) ||
+ (exceptionAddress == pRO->m_excepNotForRuntimeBPAddr) ||
+ (exceptionAddress == pRO->m_notifyRSOfSyncCompleteBPAddr))
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+// Allocate a buffer in the target and copy data into it.
+//
+// Arguments:
+// pDomain - an appdomain associated with the allocation request.
+// bufferSize - size of the buffer in bytes
+// bufferFrom - local buffer of data (bufferSize bytes) to copy data from.
+// ppRes - address into target of allocated buffer
+//
+// Returns:
+// S_OK on success, else error.
+HRESULT CordbProcess::GetAndWriteRemoteBuffer(CordbAppDomain *pDomain, unsigned int bufferSize, const void *bufferFrom, void **ppRes)
+{
+ _ASSERTE(ppRes != NULL);
+ *ppRes = NULL;
+
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ TargetBuffer tbTarget = GetRemoteBuffer(bufferSize); // throws
+ SafeWriteBuffer(tbTarget, (const BYTE*) bufferFrom); // throws
+
+ // Succeeded.
+ *ppRes = CORDB_ADDRESS_TO_PTR(tbTarget.pAddress);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+
+//
+// Checks to see if the given second chance exception event actually signifies the death of the process due to a second
+// stack overflow special case.
+//
+// There are strange cases with stack overflow exceptions. If a nieve application catches a stack overflow exception and
+// handles it, without resetting the guard page, then the app will get an AV when it overflows the stack a second time. We
+// will get the first chance AV, but when we continue from it the OS won't run any SEH handlers, so our FCH won't
+// actually work. Instead, we'll get the AV back on second chance right away.
+//
+bool CordbProcess::IsSpecialStackOverflowCase(CordbUnmanagedThread *pUThread, const DEBUG_EVENT *pEvent)
+{
+ _ASSERTE(pEvent->dwDebugEventCode == EXCEPTION_DEBUG_EVENT);
+ _ASSERTE(pEvent->u.Exception.dwFirstChance == 0);
+
+ // If this is not an AV, it can't be our special case.
+ if (pEvent->u.Exception.ExceptionRecord.ExceptionCode != STATUS_ACCESS_VIOLATION)
+ return false;
+
+ // If the thread isn't already first chance hijacked, it can't be our special case.
+ if (!pUThread->IsFirstChanceHijacked())
+ return false;
+
+ // The first chance hijack didn't take, so we're not FCH anymore and we're not waiting for an answer
+ // anymore... Note: by leaving this thread completely unhijacked, we'll report its true context, which is correct.
+ pUThread->ClearState(CUTS_FirstChanceHijacked);
+
+ // The process is techincally dead as a door nail here, so we'll mark that the helper thread is dead so our managed
+ // API bails nicely.
+ m_helperThreadDead = true;
+
+ // Remember we're in our special case.
+ pUThread->SetState(CUTS_HasSpecialStackOverflowCase);
+
+ // Now, remember the second chance AV event in the second IB event slot for this thread and add it to the end of the
+ // IB event queue.
+ QueueUnmanagedEvent(pUThread, pEvent);
+
+ // Note: returning true will ensure that the queued first chance AV for this thread is dispatched.
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Longhorn broke ContinueDebugEvent.
+// In previous OS releases, DBG_CONTINUE would continue a non-continuable exception.
+// In longhorn, we need to pass the DBG_FORCE_CONTINUE flag to do that.
+// Note that all CLR exceptions are non-continuable.
+// Now instead of DBG_CONTINUE, we need to pass DBG_FORCE_CONTINUE.
+//-----------------------------------------------------------------------------
+
+// Currently we don't have headers for the longhorn winnt.h. So we need to privately declare
+// this here. We have a check such that if we do get headers, the value won't change underneath us.
+#define MY_DBG_FORCE_CONTINUE ((DWORD )0x00010003L)
+#ifndef DBG_FORCE_CONTINUE
+#define DBG_FORCE_CONTINUE MY_DBG_FORCE_CONTINUE
+#else
+static_assert_no_msg(DBG_FORCE_CONTINUE == MY_DBG_FORCE_CONTINUE);
+#endif
+
+DWORD GetDbgContinueFlag()
+{
+ // Currently, default to not using the new DBG_FORCE_CONTINUE flag.
+ static ConfigDWORD fNoFlagKey;
+ bool fNoFlag = fNoFlagKey.val(CLRConfig::UNSUPPORTED_DbgNoForceContinue) != 0;
+
+
+ if (!fNoFlag)
+ {
+ return DBG_FORCE_CONTINUE;
+ }
+ else
+ {
+ return DBG_CONTINUE;
+ }
+}
+
+
+// Some Interop bugs involve threads that land at a crazy IP. Since we're interop-debugging, we can't
+// attach a debugger to the LS. So we have some debug mode where we enable the SS flag and thus
+// produce a trace of where a thread is going.
+#ifdef _DEBUG
+void EnableDebugTrace(CordbUnmanagedThread *ut)
+{
+ // To enable, attach w/ a debugger and either set fTrace==true, or setip.
+ static bool fTrace = false;
+ if (!fTrace)
+ return;
+
+ // Give us a nop so that we can setip in the optimized case.
+#ifdef _TARGET_X86_
+ __asm {
+ nop
+ }
+#endif
+
+ fTrace = true;
+ CordbProcess *pProcess = ut->GetProcess();
+
+ // Get the context
+ HRESULT hr = S_OK;
+ DT_CONTEXT context;
+ context.ContextFlags = DT_CONTEXT_FULL;
+
+
+ hr = pProcess->GetThreadContext((DWORD) ut->m_id, sizeof(context), (BYTE*)&context);
+ if (FAILED(hr))
+ return;
+
+ // If the flag is already set, then don't set it again - that will just get confusing.
+ if (IsSSFlagEnabled(&context))
+ {
+ return;
+ }
+ _ASSERTE(CORDbgGetIP(&context) != 0);
+ SetSSFlag(&context);
+
+ // If SS flag not set, enable it. And remeber that it's us so we know how to handle
+ // it when we get the debug event.
+ hr = pProcess->SetThreadContext((DWORD)ut->m_id, sizeof(context), (BYTE*)&context);
+ ut->SetState(CUTS_DEBUG_SingleStep);
+}
+#endif // _DEBUG
+
+//-----------------------------------------------------------------------------
+// DoDbgContinue
+//
+// Continues from a specific Win32 DEBUG_EVENT.
+//
+// Arguments:
+// pProcess - The process to continue.
+// pUnmanagedEvent - The event to continue.
+//
+//-----------------------------------------------------------------------------
+void CordbWin32EventThread::DoDbgContinue(CordbProcess *pProcess,
+ CordbUnmanagedEvent *pUnmanagedEvent)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(pUnmanagedEvent != NULL);
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+
+ STRESS_LOG3(LF_CORDB, LL_INFO1000,
+ "W32ET::DDC: continue with ue=0x%p, thread=0x%p, tid=0x%x\n",
+ pUnmanagedEvent,
+ pUnmanagedEvent->m_owner,
+ pUnmanagedEvent->m_owner->m_id);
+
+#ifdef _DEBUG
+ EnableDebugTrace(pUnmanagedEvent->m_owner);
+#endif
+
+
+ if (pUnmanagedEvent->IsEventContinuedHijacked())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Skiping DoDbgContinue because event was already"
+ " continued hijacked, ue=0x%p\n", pUnmanagedEvent));
+ return;
+ }
+
+ BOOL threadIsHijacked = (pUnmanagedEvent->m_owner->IsFirstChanceHijacked() ||
+ pUnmanagedEvent->m_owner->IsGenericHijacked());
+
+ BOOL eventIsIB = (pUnmanagedEvent->m_owner->HasIBEvent() &&
+ pUnmanagedEvent->m_owner->IBEvent() == pUnmanagedEvent);
+
+ _ASSERTE((DWORD) pProcess->m_id == pUnmanagedEvent->m_currentDebugEvent.dwProcessId);
+ _ASSERTE(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED);
+
+ DWORD dwContType;
+ if(eventIsIB)
+ {
+ // 3 cases here...
+ // event was already hijacked
+ if(threadIsHijacked)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, already hijacked, ue=0x%p\n", pUnmanagedEvent));
+ pUnmanagedEvent->SetState(CUES_EventContinuedHijacked);
+ dwContType = !pUnmanagedEvent->m_owner->IsBlockingForSync() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+ // event was not hijacked but has been dispatched
+ else if(!threadIsHijacked && pUnmanagedEvent->IsDispatched())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, not hijacked, ue=0x%p\n", pUnmanagedEvent));
+ _ASSERTE(pUnmanagedEvent->IsDispatched());
+ _ASSERTE(pUnmanagedEvent->IsEventUserContinued());
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedUnhijacked());
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+ dwContType = pUnmanagedEvent->IsExceptionCleared() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+
+ // The event was never hijacked and so will never need to retrigger, get rid
+ // of it right now. If it had been hijacked then we would dequeue it either after the
+ // hijack complete flare or one instruction after that when it has had a chance to retrigger
+ pProcess->DequeueUnmanagedEvent(pUnmanagedEvent->m_owner);
+ }
+ // event was not hijacked nor dispatched
+ else // if(!threadIsHijacked && !pUnmanagedEvent->IsDispatched())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing IB, now hijacked, ue=0x%p\n", pUnmanagedEvent));
+ HRESULT hr = pProcess->HijackIBEvent(pUnmanagedEvent);
+ _ASSERTE(SUCCEEDED(hr));
+ pUnmanagedEvent->SetState(CUES_EventContinuedHijacked);
+ dwContType = !pUnmanagedEvent->m_owner->IsBlockingForSync() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "W32ET::DDC: Continuing OB, ue=0x%p\n", pUnmanagedEvent));
+ // we might actually be hijacked here, but if we are it should be for a previous IB event
+ // we just mark all OB events as continued unhijacked
+ pUnmanagedEvent->SetState(CUES_EventContinuedUnhijacked);
+ dwContType = pUnmanagedEvent->IsExceptionCleared() ? GetDbgContinueFlag() : DBG_EXCEPTION_NOT_HANDLED;
+ }
+
+ // If the exception is marked as unclearable, then make sure the continue type is correct and force the process
+ // to terminate.
+ if (pUnmanagedEvent->IsExceptionUnclearable())
+ {
+ TerminateProcess(pProcess->UnsafeGetProcessHandle(), pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode);
+ dwContType = DBG_EXCEPTION_NOT_HANDLED;
+ }
+
+ // If we're continuing from the loader-bp, then send the managed attach here.
+ // (Note this will only be set if the runtime was loaded when we first tried to attach).
+ // We assume that the loader-bp is the 1st BP exception. This is naive,
+ // since it's not 100% accurate (someone could CreateThread w/ a threadproc of DebugBreak).
+ // But it's the best we can do.
+ // Note that it's critical we do this BEFORE continuing the process. If this is mixed-mode, we've already
+ // told VS about this breakpoint, and so it's set the attach-complete event. As soon as we continue this debug
+ // event the process can start moving again, so the CLR needs to know to wait for a managed attach.
+ DWORD dwEventCode = pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode;
+ if (dwEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ EXCEPTION_DEBUG_INFO * pDebugInfo = &pUnmanagedEvent->m_currentDebugEvent.u.Exception;
+ if (pDebugInfo->dwFirstChance && pDebugInfo->ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT)
+ {
+ HRESULT hrIgnore = S_OK;
+ EX_TRY
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::DDC: Continuing from LdrBp, doing managed attach.\n"));
+ pProcess->QueueManagedAttachIfNeededWorker();
+ }
+ EX_CATCH_HRESULT(hrIgnore);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hrIgnore));
+ }
+ }
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000,
+ "W32ET::DDC: calling ContinueDebugEvent(0x%x, 0x%x, 0x%x), process state=0x%x\n",
+ pProcess->m_id, pUnmanagedEvent->m_owner->m_id, dwContType, pProcess->m_state);
+
+ // Actually continue the debug event
+ pProcess->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+ BOOL fSuccess = m_pNativePipeline->ContinueDebugEvent((DWORD)pProcess->m_id, (DWORD)pUnmanagedEvent->m_owner->m_id, dwContType);
+
+ // ContinueDebugEvent may 'fail' if we force kill the debuggee while stopped at the exit-process event.
+ if (!fSuccess && (dwEventCode != EXIT_PROCESS_DEBUG_EVENT))
+ {
+ _ASSERTE(!"ContinueDebugEvent failed!");
+ CORDBSetUnrecoverableError(pProcess, HRESULT_FROM_GetLastError(), 0);
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::DDC: Last error after ContinueDebugEvent is %d\n", GetLastError());
+ }
+
+ // If this thread is marked for deletion (exit thread or exit process event on it), then we need to delete the
+ // unmanaged thread object.
+ if ((dwEventCode == EXIT_PROCESS_DEBUG_EVENT) || (dwEventCode == EXIT_THREAD_DEBUG_EVENT))
+ {
+ CordbUnmanagedThread * pUnmanagedThread = pUnmanagedEvent->m_owner;
+ _ASSERTE(pUnmanagedThread->IsDeleted());
+
+
+ // Thread may have a hijacked inband event on it. Thus it's actually running free from the OS perspective,
+ // and fair game to be terminated. In that case, we need to auto-dequeue the event.
+ // This will just prevent the RS from making the underlying call to ContinueDebugEvent on this thread
+ // for the inband event. Since we've already lost the thread, that's actually exactly what we want.
+ if (pUnmanagedThread->HasIBEvent())
+ {
+ pProcess->DequeueUnmanagedEvent(pUnmanagedThread);
+ }
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "Removing thread 0x%x (%d) from process list\n", pUnmanagedThread->m_id);
+ pProcess->m_unmanagedThreads.RemoveBase((ULONG_PTR)pUnmanagedThread->m_id);
+ }
+
+
+ // If we just continued from an exit process event, then its time to do the exit processing.
+ if (dwEventCode == EXIT_PROCESS_DEBUG_EVENT)
+ {
+ pProcess->Unlock();
+ ExitProcess(false); // not detach case
+ pProcess->Lock();
+ }
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// ForceDbgContinue continues from the last Win32 DEBUG_EVENT on the given thread, no matter what it was.
+//
+// Arguments:
+// pProcess - process object to continue
+// pUnmanagedThread - unmanaged thread object (maybe null if we're doing a raw cotninue)
+// contType - continuation status (DBG_CONTINUE or DBG_EXCEPTION_NOT_HANDLED)
+// fContinueProcess - do we resume hijacks?
+//
+void CordbWin32EventThread::ForceDbgContinue(CordbProcess *pProcess, CordbUnmanagedThread *pUnmanagedThread, DWORD contType,
+ bool fContinueProcess)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(pUnmanagedThread != NULL);
+ STRESS_LOG4(LF_CORDB, LL_INFO1000,
+ "W32ET::FDC: force continue with 0x%x (%s), contProcess=%d, tid=0x%x\n",
+ contType,
+ (contType == DBG_CONTINUE) ? "DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED",
+ fContinueProcess,
+ pUnmanagedThread->m_id);
+
+ if (fContinueProcess)
+ {
+ pProcess->ResumeHijackedThreads();
+ }
+
+ if (contType == DBG_CONTINUE)
+ {
+ contType = GetDbgContinueFlag();
+ }
+
+ _ASSERTE(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED);
+
+ // Remove the Win32 stopped flag so long as the OOB event queue is empty. We're forcing a continue here, so by
+ // definition this should be the case...
+ _ASSERTE(pProcess->m_outOfBandEventQueue == NULL);
+
+ pProcess->m_state &= ~CordbProcess::PS_WIN32_STOPPED;
+
+ STRESS_LOG4(LF_CORDB, LL_INFO1000, "W32ET::FDC: calling ContinueDebugEvent(0x%x, 0x%x, 0x%x), process state=0x%x\n",
+ pProcess->m_id, pUnmanagedThread->m_id, contType, pProcess->m_state);
+
+
+ #ifdef _DEBUG
+ EnableDebugTrace(pUnmanagedThread);
+ #endif
+ BOOL ret = m_pNativePipeline->ContinueDebugEvent((DWORD)pProcess->m_id, (DWORD)pUnmanagedThread->m_id, contType);
+
+ if (!ret)
+ {
+ // This could in theory fail from Process exit, but that really would only be on the DoDbgContinue path.
+ _ASSERTE(!"ContinueDebugEvent failed #2!");
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "W32ET::DDC: Last error after ContinueDebugEvent is %d\n", GetLastError());
+ }
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+//
+// This is the thread's real thread proc. It simply calls to the
+// thread proc on the given object.
+//
+/*static*/ DWORD WINAPI CordbWin32EventThread::ThreadProc(LPVOID parameter)
+{
+ CordbWin32EventThread* t = (CordbWin32EventThread*) parameter;
+ INTERNAL_THREAD_ENTRY(t);
+ t->ThreadProc();
+ return 0;
+}
+
+
+//
+// Send a CreateProcess event to the Win32 thread to have it create us
+// a new process.
+//
+HRESULT CordbWin32EventThread::SendCreateProcessEvent(
+ MachineInfo machineInfo,
+ LPCWSTR programName,
+ __in_z LPWSTR programArgs,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ PVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation,
+ CorDebugCreateProcessFlags corDebugFlags)
+{
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+ LOG((LF_CORDB, LL_EVERYTHING, "CordbWin32EventThread::SCPE Called\n"));
+ m_actionData.createData.machineInfo = machineInfo;
+ m_actionData.createData.programName = programName;
+ m_actionData.createData.programArgs = programArgs;
+ m_actionData.createData.lpProcessAttributes = lpProcessAttributes;
+ m_actionData.createData.lpThreadAttributes = lpThreadAttributes;
+ m_actionData.createData.bInheritHandles = bInheritHandles;
+ m_actionData.createData.dwCreationFlags = dwCreationFlags;
+ m_actionData.createData.lpEnvironment = lpEnvironment;
+ m_actionData.createData.lpCurrentDirectory = lpCurrentDirectory;
+ m_actionData.createData.lpStartupInfo = lpStartupInfo;
+ m_actionData.createData.lpProcessInformation = lpProcessInformation;
+ m_actionData.createData.corDebugFlags = corDebugFlags;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_CREATE_PROCESS;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "Process Handle is: %x, m_threadControlEvent is %x\n",
+ (UINT_PTR)m_actionData.createData.lpProcessInformation->hProcess, (UINT_PTR)m_threadControlEvent));
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Create a process
+//
+// Assumptions:
+// This occurs on the win32 event thread. It is invokved via
+// a message sent from code:CordbWin32EventThread::SendCreateProcessEvent
+//
+// Notes:
+// Create a new process. This is called in the context of the Win32
+// event thread to ensure that if we're Win32 debugging the process
+// that the same thread that waits for debugging events will be the
+// thread that creates the process.
+//
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::CreateProcess()
+{
+ m_action = W32ETA_NONE;
+ HRESULT hr = S_OK;
+
+ DWORD dwCreationFlags = m_actionData.createData.dwCreationFlags;
+
+ // If the creation flags has DEBUG_PROCESS in them, then we're
+ // Win32 debugging this process. Otherwise, we have to create
+ // suspended to give us time to setup up our side of the IPC
+ // channel.
+ BOOL fInteropDebugging =
+#if defined(FEATURE_INTEROP_DEBUGGING)
+ (dwCreationFlags & (DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS));
+#else
+ false; // Interop not supported.
+#endif
+
+ // Have Win32 create the process...
+ hr = m_pNativePipeline->CreateProcessUnderDebugger(
+ m_actionData.createData.machineInfo,
+ m_actionData.createData.programName,
+ m_actionData.createData.programArgs,
+ m_actionData.createData.lpProcessAttributes,
+ m_actionData.createData.lpThreadAttributes,
+ m_actionData.createData.bInheritHandles,
+ dwCreationFlags,
+ m_actionData.createData.lpEnvironment,
+ m_actionData.createData.lpCurrentDirectory,
+ m_actionData.createData.lpStartupInfo,
+ m_actionData.createData.lpProcessInformation);
+
+ if (SUCCEEDED(hr))
+ {
+ // Process ID is filled in after process is succesfully created.
+ DWORD dwProcessId = m_actionData.createData.lpProcessInformation->dwProcessId;
+
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+ hr = m_pShim->InitializeDataTarget(dwProcessId);
+
+ if (SUCCEEDED(hr))
+ {
+ // To emulate V2 semantics, we pass 0 for the clrInstanceID into
+ // OpenVirtualProcess. This will then connect to the first CLR
+ // loaded.
+ const ULONG64 cFirstClrLoaded = 0;
+ hr = CordbProcess::OpenVirtualProcess(cFirstClrLoaded, m_pShim->GetDataTarget(), NULL, m_cordb, dwProcessId, m_pShim, &pProcess);
+ }
+
+ // Shouldn't happen on a create, only an attach
+ _ASSERTE(hr != CORDBG_E_DEBUGGER_ALREADY_ATTACHED);
+
+ // Remember the process in the global list of processes.
+ if (SUCCEEDED(hr))
+ {
+ EX_TRY
+ {
+ // Mark if we're interop-debugging
+ if (fInteropDebugging)
+ {
+ pProcess->EnableInteropDebugging();
+ }
+
+ m_cordb->AddProcess(pProcess); // will take ref if it succeeds
+ }
+ EX_CATCH_HRESULT(hr);
+ }
+
+ // If we're Win32 attached to this process, then increment the
+ // proper count, otherwise add this process to the wait set
+ // and resume the process's main thread.
+ if (SUCCEEDED(hr))
+ {
+ _ASSERTE(m_pProcess == NULL);
+ m_pProcess.Assign(pProcess);
+ }
+ }
+
+
+ //
+ // Signal the hr to the caller.
+ //
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+
+//
+// Send a DebugActiveProcess event to the Win32 thread to have it attach to
+// a new process.
+//
+HRESULT CordbWin32EventThread::SendDebugActiveProcessEvent(
+ MachineInfo machineInfo,
+ DWORD pid,
+ bool fWin32Attach,
+ CordbProcess *pProcess)
+{
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.attachData.machineInfo = machineInfo;
+ m_actionData.attachData.processId = pid;
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ m_actionData.attachData.fWin32Attach = fWin32Attach;
+#endif
+ m_actionData.attachData.pProcess = pProcess;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_ATTACH_PROCESS;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Is the given thread id a helper thread (real or worker?)
+//-----------------------------------------------------------------------------
+bool CordbProcess::IsHelperThreadWorked(DWORD tid)
+{
+ // Check against the id gained by sniffing Thread-Create events.
+ if (tid == this->m_helperThreadId)
+ {
+ return true;
+ }
+
+ // Now check for potential datate in the IPC block. If not there,
+ // then we know it can't be the helper.
+ DebuggerIPCControlBlock * pDCB = this->GetDCB();
+
+ if (pDCB == NULL)
+ {
+ return false;
+ }
+
+ // get the latest information from the LS DCB
+ UpdateRightSideDCB();
+ return
+ (tid == pDCB->m_realHelperThreadId) ||
+ (tid == pDCB->m_temporaryHelperThreadId);
+
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Cleans up the Left Side's DCB after a failed attach attempt.
+//
+// Assumptions:
+// Called when the left-site failed initialization
+//
+// Notes:
+// This can be called multiple times.
+//---------------------------------------------------------------------------------------
+void CordbProcess::CleanupHalfBakedLeftSide()
+{
+ if (GetDCB() != NULL)
+ {
+ EX_TRY
+ {
+ GetDCB()->m_rightSideIsWin32Debugger = false;
+ UpdateLeftSideDCBField(&(GetDCB()->m_rightSideIsWin32Debugger), sizeof(GetDCB()->m_rightSideIsWin32Debugger));
+
+ if (m_pEventChannel != NULL)
+ {
+ m_pEventChannel->Delete();
+ m_pEventChannel = NULL;
+ }
+ }
+ EX_CATCH
+ {
+ _ASSERTE(!"Writing process memory failed, perhaps due to an unexpected disconnection from the target.");
+ }
+ EX_END_CATCH(SwallowAllExceptions);
+ }
+
+ // Close and null out the various handles and events, including our process handle m_handle.
+ CloseIPCHandles();
+
+ m_cordb.Clear();
+
+ // This process object is Dead-On-Arrival, so it doesn't really have anything to neuter.
+ // But for safekeeping, we'll mark it as neutered.
+ UnsafeNeuterDeadObject();
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Attach to an existing process.
+//
+//
+// Assumptions:
+// Called on W32Event Thread, in response to event sent by
+// code:CordbWin32EventThread::SendDebugActiveProcessEvent
+//
+// Notes:
+// Attach to a process. This is called in the context of the Win32
+// event thread to ensure that if we're Win32 debugging the process
+// that the same thread that waits for debugging events will be the
+// thread that attaches the process.
+//
+// @dbgtodo shim: this will be part of the shim
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::AttachProcess()
+{
+ _ASSERTE(IsWin32EventThread());
+
+ RSUnsafeExternalSmartPtr<CordbProcess> pProcess;
+
+ m_action = W32ETA_NONE;
+
+ HRESULT hr = S_OK;
+
+ DWORD dwProcessId = m_actionData.attachData.processId;
+ bool fNativeAttachSucceeded = false;
+
+
+ // Always do OS attach to the target.
+ // By this point, the pid should be valid (because OpenProcess above), pending some race where the process just exited.
+ // The OS will enforce that only 1 debugger is attached.
+ // Common failure paths here would be: access denied, double-attach
+ {
+ hr = m_pNativePipeline->DebugActiveProcess(m_actionData.attachData.machineInfo,
+ dwProcessId);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+ fNativeAttachSucceeded = true;
+ }
+
+
+ hr = m_pShim->InitializeDataTarget(m_actionData.attachData.processId);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+
+ // To emulate V2 semantics, we pass 0 for the clrInstanceID into
+ // OpenVirtualProcess. This will then connect to the first CLR
+ // loaded.
+ {
+ const ULONG64 cFirstClrLoaded = 0;
+ hr = CordbProcess::OpenVirtualProcess(cFirstClrLoaded, m_pShim->GetDataTarget(), NULL, m_cordb, dwProcessId, m_pShim, &pProcess);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+ }
+
+ // Remember the process in the global list of processes.
+ // The caller back in code:Cordb::DebugActiveProcess will then get this by fetching it from the list.
+
+ EX_TRY
+ {
+ // Mark interop-debugging
+ if (m_actionData.attachData.IsInteropDebugging())
+ {
+ pProcess->EnableInteropDebugging(); // Throwing
+ }
+
+ m_cordb->AddProcess(pProcess); // will take ref if it succeeds
+
+
+ // Queue fake Attach event for CreateProcess
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(pProcess);
+ m_pShim->BeginQueueFakeAttachEvents();
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ if (FAILED(hr))
+ {
+ goto LExit;
+ }
+
+ _ASSERTE(m_pProcess == NULL);
+ m_pProcess.Assign(pProcess);
+ pProcess.Clear(); // ownership transfered to m_pProcess
+
+ // Should have succeeded if we got to this point.
+ _ASSERTE(SUCCEEDED(hr));
+
+
+LExit:
+ if (FAILED(hr))
+ {
+ // If we succeed to do a native-attach, but then failed elsewhere, try to native-detach.
+ if (fNativeAttachSucceeded)
+ {
+ m_pNativePipeline->DebugActiveProcessStop(dwProcessId);
+ }
+
+ if (pProcess != NULL)
+ {
+ // Safe to call this even if the process wasn't added.
+ m_cordb->RemoveProcess(pProcess);
+ pProcess->CleanupHalfBakedLeftSide();
+ pProcess.Clear();
+ }
+ m_pProcess.Clear();
+ }
+
+ //
+ // Signal the hr to the caller.
+ //
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+
+// Note that the actual 'DetachProcess' method is really ExitProcess with CW32ET_UNKNOWN_PROCESS_SLOT ==
+// processSlot
+HRESULT CordbWin32EventThread::SendDetachProcessEvent(CordbProcess *pProcess)
+{
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::SDPE\n"));
+ HRESULT hr = S_OK;
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.detachData.pProcess = pProcess;
+
+ // m_action is set last so that the win32 event thread can inspect it and take action without actually
+ // having to take any locks. The lock around this here is simply to prevent multiple threads from making
+ // requests at the same time.
+ m_action = W32ETA_DETACH;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+#ifdef FEATURE_INTEROP_DEBUGGING
+//
+// Send a UnmanagedContinue event to the Win32 thread to have it
+// continue from an unmanged debug event.
+//
+HRESULT CordbWin32EventThread::SendUnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType)
+{
+ HRESULT hr = S_OK;
+
+ // If this were being called on the win32 EventThread, we'd deadlock.
+ _ASSERTE(!IsWin32EventThread());
+
+ // This can't hold the process lock, b/c we're making a cross-thread call,
+ // and our target will need the process lock.
+ _ASSERTE(!pProcess->ThreadHoldsProcessLock());
+
+ LockSendToWin32EventThreadMutex();
+
+ m_actionData.continueData.process = pProcess;
+ m_actionData.continueData.eContType = eContType;
+
+ // m_action is set last so that the win32 event thread can inspect
+ // it and take action without actually having to take any
+ // locks. The lock around this here is simply to prevent multiple
+ // threads from making requests at the same time.
+ m_action = W32ETA_CONTINUE;
+
+ BOOL succ = SetEvent(m_threadControlEvent);
+
+ if (succ)
+ {
+ DWORD ret = WaitForSingleObject(m_actionTakenEvent, INFINITE);
+
+ if (ret == WAIT_OBJECT_0)
+ hr = m_actionResult;
+ else
+ hr = HRESULT_FROM_GetLastError();
+ }
+ else
+ hr = HRESULT_FROM_GetLastError();
+
+ UnlockSendToWin32EventThreadMutex();
+
+ return hr;
+}
+
+
+//
+// Handle unmanaged continue. Continue an unmanaged debug
+// event. Deferes to UnmanagedContinue. This is called in the context
+// of the Win32 event thread to ensure that if we're Win32 debugging
+// the process that the same thread that waits for debugging events
+// will be the thread that continues the process.
+//
+void CordbWin32EventThread::HandleUnmanagedContinue()
+{
+ _ASSERTE(IsWin32EventThread());
+
+ m_action = W32ETA_NONE;
+ HRESULT hr = S_OK;
+
+ // Continue the process
+ CordbProcess *pProcess = m_actionData.continueData.process;
+
+ // If we lost the process object, we must have exited.
+ if (m_pProcess != NULL)
+ {
+ _ASSERTE(m_pProcess != NULL);
+ _ASSERTE(pProcess == m_pProcess);
+
+ _ASSERTE(!pProcess->ThreadHoldsProcessLock());
+
+ RSSmartPtr<CordbProcess> proc(pProcess);
+ RSLockHolder ch(&pProcess->m_processMutex);
+
+ hr = UnmanagedContinue(pProcess, m_actionData.continueData.eContType);
+ }
+
+ // Signal the hr to the caller.
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+}
+
+//
+// Continue an unmanaged debug event. This is called in the context of the Win32 Event thread to ensure that the same
+// thread that waits for debug events will be the thread that continues the process.
+//
+HRESULT CordbWin32EventThread::UnmanagedContinue(CordbProcess *pProcess,
+ EUMContinueType eContType)
+{
+ _ASSERTE(pProcess->ThreadHoldsProcessLock());
+ _ASSERTE(IsWin32EventThread());
+ _ASSERTE(m_pShim != NULL);
+
+ HRESULT hr = S_OK;
+
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "UM Continue. type=%d\n", eContType);
+
+ if (eContType == cOobUMContinue)
+ {
+ _ASSERTE(pProcess->m_outOfBandEventQueue != NULL);
+
+ // Dequeue the OOB event.
+ CordbUnmanagedEvent *ue = pProcess->m_outOfBandEventQueue;
+ CordbUnmanagedThread *ut = ue->m_owner;
+ pProcess->DequeueOOBUnmanagedEvent(ut);
+
+ // Do a little extra work if that was an OOB exception event...
+ hr = ue->m_owner->FixupAfterOOBException(ue);
+ _ASSERTE(SUCCEEDED(hr));
+
+ // Continue from the event.
+ DoDbgContinue(pProcess, ue);
+
+ // If there are more queued OOB events, dispatch them now.
+ if (pProcess->m_outOfBandEventQueue != NULL)
+ pProcess->DispatchUnmanagedOOBEvent();
+
+ // Note: if we previously skipped letting the entire process go on an IB continue due to a blocking OOB event,
+ // and if the OOB event queue is now empty, then go ahead and let the process continue now...
+ if ((pProcess->m_doRealContinueAfterOOBBlock == true) &&
+ (pProcess->m_outOfBandEventQueue == NULL))
+ goto doRealContinue;
+ }
+ else if (eContType == cInternalUMContinue)
+ {
+ // We're trying to get into a synced state which means we need the process running (potentially
+ // with some threads hijacked) in order to have the helper thread do the sync.
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue.\n"));
+
+ if (!pProcess->GetSynchronized())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, !sync'd.\n"));
+ pProcess->ResumeUnmanagedThreads();
+
+ // the event we may need to hijack and continue;
+ CordbUnmanagedEvent* pEvent = pProcess->m_lastQueuedUnmanagedEvent;
+
+ // It is possible to be stopped at either an IB or an OOB event here. We only want to
+ // continue from an IB event here though
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED && pEvent != NULL &&
+ pEvent->IsEventWaitingForContinue())
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, frozen on IB event.\n"));
+
+ // There should be a uncontinued IB event at the head of the queue
+ _ASSERTE(pEvent->IsIBEvent());
+ _ASSERTE(!pEvent->IsEventContinuedUnhijacked());
+ _ASSERTE(!pEvent->IsEventContinuedHijacked());
+
+ // Ensure that the event is hijacked now (it may not have been before) so that the
+ // thread does not slip forward during the sync process. After that we can safely continue
+ // it.
+ pProcess->HijackIBEvent(pEvent);
+ m_pShim->GetWin32EventThread()->DoDbgContinue(pProcess, pEvent);
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: internal continue, done.\n"));
+ }
+ else
+ {
+ // If we're here, then we know 100% for sure that we've successfully gotten the managed continue event to the
+ // Left Side, so we can stop force hijacking left over in-band events now. Note: if we had hijacked any such
+ // events, they'll be dispatched below since they're properly queued.
+ pProcess->m_specialDeferment = false;
+
+ // We don't actually do any work if there is an outstanding out-of-band event. When we do continue from the
+ // out-of-band event, we'll do this work, too.
+ if (pProcess->m_outOfBandEventQueue != NULL)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: ignoring real continue due to block by out-of-band event(s).\n"));
+
+ _ASSERTE(pProcess->m_doRealContinueAfterOOBBlock == false);
+ pProcess->m_doRealContinueAfterOOBBlock = true;
+ }
+ else
+ {
+doRealContinue:
+ // This is either the Frozen -> Running transition or a
+ // Synced -> Running transition
+ _ASSERTE(pProcess->m_outOfBandEventQueue == NULL);
+
+
+ pProcess->m_doRealContinueAfterOOBBlock = false;
+
+ LOG((LF_CORDB, LL_INFO1000, "W32ET::UC: continuing the process.\n"));
+ // Dispatch any more queued in-band events, or if there are none then just continue the process.
+ //
+ // Note: don't dispatch more events if we've already sent up the ExitProcess event... those events are just
+ // lost.
+ if ((pProcess->HasUndispatchedNativeEvents()) && (pProcess->m_exiting == false))
+ {
+ pProcess->DispatchUnmanagedInBandEvent();
+ }
+ else
+ {
+ // If the unmanaged event queue is empty now, and the process is synchronized, and there are queued
+ // managed events, then go ahead and get more managed events dispatched.
+ //
+ // Note: don't dispatch more events if we've already sent up the ExitProcess event... those events are
+ // just lost.
+ if (pProcess->GetSynchronized() && (!m_pShim->GetManagedEventQueue()->IsEmpty()) && (pProcess->m_exiting == false))
+ {
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED)
+ {
+ DoDbgContinue(pProcess, pProcess->m_lastDispatchedIBEvent);
+
+ // This if should not be necessary, I am just being extra careful because this
+ // fix is going in late - see issue 818301
+ _ASSERTE(pProcess->m_lastDispatchedIBEvent != NULL);
+ if(pProcess->m_lastDispatchedIBEvent != NULL)
+ {
+ pProcess->m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ pProcess->m_lastDispatchedIBEvent = NULL;
+ }
+ }
+
+ // Now, get more managed events dispatched.
+ pProcess->SetSynchronized(false);
+ pProcess->MarkAllThreadsDirty();
+ m_cordb->ProcessStateChanged();
+ }
+ else
+ {
+ // free all the hijacked threads that hit native debug events
+ pProcess->ResumeHijackedThreads();
+
+ // after continuing the here the process should be running completely
+ // free... no hijacks, no suspended threads, and of course not frozen
+ if(pProcess->m_state & CordbProcess::PS_WIN32_STOPPED)
+ {
+ DoDbgContinue(pProcess, pProcess->m_lastDispatchedIBEvent);
+ // This if should not be necessary, I am just being extra careful because this
+ // fix is going in late - see issue 818301
+ _ASSERTE(pProcess->m_lastDispatchedIBEvent != NULL);
+ if(pProcess->m_lastDispatchedIBEvent != NULL)
+ {
+ pProcess->m_lastDispatchedIBEvent->m_owner->InternalRelease();
+ pProcess->m_lastDispatchedIBEvent = NULL;
+ }
+ }
+ }
+ }
+
+ // Implicit Release on UT
+ }
+ }
+
+ return hr;
+}
+#endif // FEATURE_INTEROP_DEBUGGING
+
+void ExitProcessWorkItem::Do()
+{
+ STRESS_LOG1(LF_CORDB, LL_INFO1000, "ExitProcessWorkItem proc=%p\n", GetProcess());
+
+ // This is being called on the RCET.
+ // That's the thread that dispatches managed events. Since it's calling us now, we know
+ // it can't be dispatching a managed event, and so we don't need to both waiting for it
+
+ {
+ // Get the SG lock here to coordinate against any other continues.
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ RSLockHolder ch2(&(GetProcess()->m_processMutex));
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: ExitProcess callback\n"));
+
+ // We're synchronized now, so mark the process as such.
+ GetProcess()->SetSynchronized(true);
+ GetProcess()->IncStopCount();
+
+ // By the time we release the SG + Process locks here, the process object has been
+ // marked as exiting + terminated (by the w32et which queued us). Future attemps to
+ // continue should fail, and thus we should remain synchronized.
+ }
+
+
+ // Just to be safe, neuter any children before the exit process callback.
+ {
+ RSLockHolder ch(GetProcess()->GetProcessLock());
+
+ // Release the process.
+ GetProcess()->NeuterChildren();
+ }
+
+ RSSmartPtr<Cordb> pCordb(NULL);
+
+ // There is a race condition here where the debuggee process is killed while we are processing a process
+ // detach. We queue the process exit event for the Win32 event thread before queueing the process detach
+ // event. By the time this function is executed, we may have neutered the CordbProcess already as a
+ // result of code:CordbProcess::Detach. Detect that case here under the SG lock.
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+ if (!GetProcess()->IsNeutered())
+ {
+ _ASSERTE(GetProcess()->m_cordb != NULL);
+ pCordb.Assign(GetProcess()->m_cordb);
+ }
+ }
+
+ // Move this into Shim?
+
+ // Invoke the ExitProcess callback. This is very important since the a shell
+ // may rely on it for proper shutdown and may hang if they don't get it.
+ // We don't expect Cordbg to continue from this (we're certainly not going to wait for it).
+ if ((pCordb != NULL) && (pCordb->m_managedCallback != NULL))
+ {
+ PUBLIC_CALLBACK_IN_THIS_SCOPE0_NO_LOCK(GetProcess());
+ pCordb->m_managedCallback->ExitProcess(GetProcess());
+ }
+
+ // This CordbProcess object now has no reservations against a client calling ICorDebug::Terminate.
+ // That call may race against the CordbProcess::Neuter below, but since we already neutered the children,
+ // that neuter call will not do anything interesting that will conflict with Terminate.
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: returned from ExitProcess callback\n"));
+
+ {
+ RSLockHolder ch(GetProcess()->GetStopGoLock());
+
+ // Release the process.
+ GetProcess()->Neuter();
+ }
+
+ // Our dtor will release the Process object.
+ // This may be the final release on the process.
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Handles process exiting and detach cases
+//
+// Arguments:
+// fDetach - true if detaching, false if process is exiting.
+//
+// Return Value:
+// The type of the next argument in the signature,
+// normalized.
+//
+// Assumptions:
+// On exit, the process has already exited and we detected this by either an EXIT_PROCESS
+// native debug event, or by waiting on the process handle.
+// On detach, the process is stil live.
+//
+// Notes:
+// ExitProcess is called when a process exits or detaches.
+// This does our final cleanup and removes the process from our wait sets.
+// We're either here because we're detaching (fDetach == TRUE), or because the process has really exited,
+// and we're doing shutdown logic.
+//
+//---------------------------------------------------------------------------------------
+void CordbWin32EventThread::ExitProcess(bool fDetach)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Consider the following when you're modifying this function:
+ // - The OS can kill the debuggee at any time.
+ // - ExitProcess can race with detach.
+
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: begin ExitProcess, detach=%d\n", fDetach));
+
+
+ // For the Mac remote debugging transport, DebugActiveProcessStop() is a nop. The transport will be
+ // shut down later when we neuter the CordbProcess.
+#if !defined(FEATURE_DBGIPC_TRANSPORT_DI)
+ // @dbgtodo shim: this is a primitive workaround for interop-detach
+ // Eventually, the Debugger owns the detach pipeline, so this won't be necessary.
+ if (fDetach && (m_pProcess != NULL))
+ {
+ HRESULT hr = m_pNativePipeline->DebugActiveProcessStop(m_pProcess->GetPid());
+
+ // We don't expect detach to fail (we check earlier for common conditions that
+ // may cause it to fail)
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ if( FAILED(hr) )
+ {
+ m_actionResult = hr;
+ SetEvent(m_actionTakenEvent);
+ return;
+ }
+ }
+#endif // !FEATURE_DBGIPC_TRANSPORT_DI
+
+
+ // We don't really care if we're on the Win32 thread or not here. We just want to be sure that
+ // the LS Exit case and the Detach case both occur on the same thread. This makes it much easier
+ // to assert that if we exit while detaching, EP is only called once.
+ // If we ever decide to make the RCET listen on the LS process handle for EP(exit), then we should also
+ // make the EP(detach) handled on the RCET (via DoFavor() ).
+ _ASSERTE(IsWin32EventThread());
+
+ // So either the Exit case or Detach case must happen first.
+ // 1) If Detach first, then LS process is removed from wait set and so EP(Exit) will never happen
+ // because we check wait set after returning from EP(Detach).
+ // 2) If Exit is first, m_pProcess gets set=NULL. EP(detach) will still get called, so explicitly check that.
+ if (fDetach && ((m_pProcess == NULL) || m_pProcess->m_terminated))
+ {
+ // m_terminated is only set after the LS exits.
+ // So the only way (fDetach && m_terminated) is true is if the LS exited while detaching. In that case
+ // we already called EP(exit) and we don't want to call it again for EP(detach). So return here.
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: In EP(detach), but EP(exit) already called. Early failure\n"));
+
+ m_actionResult = CORDBG_E_PROCESS_TERMINATED;
+ SetEvent(m_actionTakenEvent);
+
+ return;
+ }
+
+ // We null m_pProcess at the end here, so
+ // Only way we could get here w/ null process is if we're called twice. We can only be called
+ // by detach or exit. Can't detach twice, can't exit twice, so must have been one of each.
+ // If exit is first, we got removed from the wait set, so 2nd call must be detach and we'd catch
+ // that above. If detach is first, we'd get removed from the wait set and so exit would never happen.
+ _ASSERTE(m_pProcess != NULL);
+ _ASSERTE(!m_pProcess->ThreadHoldsProcessLock());
+
+
+
+ // Mark the process teminated. After this, the RCET will never call FlushQueuedEvents. It will
+ // ignore all events it receives (including a SyncComplete) and the RCET also does not listen
+ // to terminated processes (so ProcessStateChange() won't cause a FQE either).
+ m_pProcess->Terminating(fDetach);
+
+ // Take care of the race where the process exits right after the user calls Continue() from the last
+ // managed event but before the handler has actually returned.
+ //
+ // Also, To get through this lock means that either:
+ // 1. FlushQueuedEvents is not currently executing and no one will call FQE.
+ // 2. FQE is exiting but is in the middle of a callback (so AreDispatchingEvent = true)
+ //
+ m_pProcess->Lock();
+
+ m_pProcess->m_exiting = true;
+
+ if (fDetach)
+ {
+ m_pProcess->SetSynchronized(false);
+ }
+
+ // If we are exiting, we *must* dispatch the ExitProcess callback, but we will delete all the events
+ // in the queue and not bother dispatching anything else. If (and only if) we are currently dispatching
+ // an event, then we will wait while that event is finished before invoking ExitProcess.
+ // (Note that a dispatched event has already been removed from the queue)
+
+ // Remove the process from the global list of processes.
+ m_cordb->RemoveProcess(m_pProcess);
+
+ if (fDetach)
+ {
+ // Signal the hr to the caller.
+ LOG((LF_CORDB, LL_INFO1000,"W32ET::EP: Detach: send result back!\n"));
+
+ m_actionResult = S_OK;
+ SetEvent(m_actionTakenEvent);
+ }
+
+ m_pProcess->Unlock();
+
+ // Delete all queued events
+ m_pProcess->DeleteQueuedEvents();
+
+
+ // If we're detaching, then the Detach already neutered everybody, so nothing here.
+ // If we're exiting, then we still need to neuter things, but we can't do that on this thread,
+ // so we queue it. We also need to dispatch an exit process callback. We'll queue that onto the RCET
+ // and dispatch it inband w/the other callbacks.
+ if (!fDetach)
+ {
+#ifdef FEATURE_PAL
+ // Cleanup the transport pipe and semaphore files that might be left by the target (LS) process.
+ m_pNativePipeline->CleanupTargetProcess();
+#endif
+ ExitProcessWorkItem * pItem = new (nothrow) ExitProcessWorkItem(m_pProcess);
+ if (pItem != NULL)
+ {
+ m_cordb->m_rcEventThread->QueueAsyncWorkItem(pItem);
+ }
+ }
+
+ // This will remove the process from our wait lists (so that we don't send multiple ExitProcess events).
+ m_pProcess.Clear();
+}
+
+
+//
+// Start actually creates and starts the thread.
+//
+HRESULT CordbWin32EventThread::Start()
+{
+ HRESULT hr = S_OK;
+ if (m_threadControlEvent == NULL)
+ return E_INVALIDARG;
+
+ // Create the thread suspended to make sure that m_threadId is set
+ // before CordbWin32EventThread::ThreadProc runs
+ // Stack size = 0x80000 = 512KB
+ m_thread = CreateThread(NULL, 0x80000, &CordbWin32EventThread::ThreadProc,
+ (LPVOID) this, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, &m_threadId);
+
+ if (m_thread == NULL)
+ return HRESULT_FROM_GetLastError();
+
+ DWORD succ = ResumeThread(m_thread);
+ if (succ == (DWORD)-1)
+ return HRESULT_FROM_GetLastError();
+ return hr;
+}
+
+
+//
+// Stop causes the thread to stop receiving events and exit. It
+// waits for it to exit before returning.
+//
+HRESULT CordbWin32EventThread::Stop()
+{
+ HRESULT hr = S_OK;
+
+ // m_pProcess may be NULL from CordbWin32EventThread::ExitProcess
+
+ // Can't block on W32ET while holding the process-lock since the W32ET may need that to exit.
+ // But since m_pProcess may be null, we can't enforce that.
+
+ if (m_thread != NULL)
+ {
+ LockSendToWin32EventThreadMutex();
+ m_action = W32ETA_NONE;
+ m_run = FALSE;
+
+ SetEvent(m_threadControlEvent);
+ UnlockSendToWin32EventThreadMutex();
+
+ DWORD ret = WaitForSingleObject(m_thread, INFINITE);
+
+ if (ret != WAIT_OBJECT_0)
+ hr = HRESULT_FROM_GetLastError();
+ }
+
+ m_pProcess.Clear();
+ m_cordb.Clear();
+
+ return hr;
+}
+
+
+
+
+
+
+
+
+// Allocate a buffer of cbBuffer bytes in the target.
+//
+// Arguments:
+// cbBuffer - count of bytes for the buffer.
+//
+// Returns:
+// a TargetBuffer describing the new memory region in the target.
+// Throws on error.
+TargetBuffer CordbProcess::GetRemoteBuffer(ULONG cbBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this); //
+
+ // Create and initialize the event as synchronous
+ DebuggerIPCEvent event;
+ InitIPCEvent(&event,
+ DB_IPCE_GET_BUFFER,
+ true,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate the buffer size wanted
+ event.GetBuffer.bufSize = cbBuffer;
+
+ // Make the request, which is synchronous
+ HRESULT hr = SendIPCEvent(&event, sizeof(event));
+ IfFailThrow(hr);
+ _ASSERTE(event.type == DB_IPCE_GET_BUFFER_RESULT);
+
+ IfFailThrow(event.GetBufferResult.hr);
+
+ // The request succeeded. Return the newly allocated range.
+ return TargetBuffer(event.GetBufferResult.pBuffer, cbBuffer);
+}
+
+/*
+ * This will release a previously allocated left side buffer.
+ */
+HRESULT CordbProcess::ReleaseRemoteBuffer(void **ppBuffer)
+{
+ INTERNAL_SYNC_API_ENTRY(this); //
+
+ _ASSERTE(m_pShim != NULL);
+
+ // Create and initialize the event as synchronous
+ DebuggerIPCEvent event;
+ InitIPCEvent(&event,
+ DB_IPCE_RELEASE_BUFFER,
+ true,
+ VMPTR_AppDomain::NullPtr());
+
+ // Indicate the buffer to release
+ event.ReleaseBuffer.pBuffer = (*ppBuffer);
+
+ // Make the request, which is synchronous
+ HRESULT hr = SendIPCEvent(&event, sizeof(event));
+ TESTANDRETURNHR(hr);
+
+ (*ppBuffer) = NULL;
+
+ // Indicate success
+ return event.ReleaseBufferResult.hr;
+}
+
+HRESULT CordbProcess::SetDesiredNGENCompilerFlags(DWORD dwFlags)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+
+#if defined(FEATURE_PREJIT)
+ if ((dwFlags != CORDEBUG_JIT_DEFAULT) && (dwFlags != CORDEBUG_JIT_DISABLE_OPTIMIZATION))
+ {
+ return E_INVALIDARG;
+ }
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ // Left-side checks that this is a valid time to set the Ngen flags.
+ hr = pProcess->GetDAC()->SetNGENCompilerFlags(dwFlags);
+ if (!SUCCEEDED(hr) && GetShim() != NULL)
+ {
+ // Emulate V2 error semantics.
+ hr = GetShim()->FilterSetNgenHresult(hr);
+ }
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+
+#else // !FEATURE_PREJIT
+ return CORDBG_E_NGEN_NOT_SUPPORTED;
+
+#endif // FEATURE_PREJIT
+}
+
+HRESULT CordbProcess::GetDesiredNGENCompilerFlags(DWORD *pdwFlags )
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ VALIDATE_POINTER_TO_OBJECT(pdwFlags, DWORD*);
+ *pdwFlags = 0;
+
+ CordbProcess *pProcess = GetProcess();
+ ATT_REQUIRE_STOPPED_MAY_FAIL(pProcess);
+ HRESULT hr = S_OK;
+ EX_TRY
+ {
+ hr = pProcess->GetDAC()->GetNGENCompilerFlags(pdwFlags);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Get an ICorDebugReference Value for the GC handle.
+// handle - raw bits for the GC handle.
+// pOutHandle
+//-----------------------------------------------------------------------------
+HRESULT CordbProcess::GetReferenceValueFromGCHandle(
+ UINT_PTR gcHandle,
+ ICorDebugReferenceValue **pOutValue)
+{
+ PUBLIC_API_ENTRY(this);
+ FAIL_IF_NEUTERED(this);
+ ATT_REQUIRE_STOPPED_MAY_FAIL(this);
+ VALIDATE_POINTER_TO_OBJECT(pOutValue, ICorDebugReferenceValue*);
+
+ *pOutValue = NULL;
+ HRESULT hr = S_OK;
+
+ EX_TRY
+ {
+ if (gcHandle == NULL)
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+
+ IDacDbiInterface* pDAC = GetProcess()->GetDAC();
+ VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetVmObjectHandle(gcHandle);
+ if(!pDAC->IsVmObjectHandleValid(vmObjHandle))
+ {
+ ThrowHR(CORDBG_E_BAD_REFERENCE_VALUE);
+ }
+ ULONG appDomainId = pDAC->GetAppDomainIdFromVmObjectHandle(vmObjHandle);
+ VMPTR_AppDomain vmAppDomain = pDAC->GetAppDomainFromId(appDomainId);
+
+ RSLockHolder lockHolder(GetProcessLock());
+ CordbAppDomain * pAppDomain = LookupOrCreateAppDomain(vmAppDomain);
+ lockHolder.Release();
+
+ // Now that we finally have the AppDomain, we can go ahead and get a ReferenceValue
+ // from the ObjectHandle.
+ hr = CordbReferenceValue::BuildFromGCHandle(pAppDomain, vmObjHandle, pOutValue);
+ _ASSERTE(SUCCEEDED(hr) == (*pOutValue != NULL));
+ IfFailThrow(hr);
+ }
+ EX_CATCH_HRESULT(hr);
+ return hr;
+}
+
+//-----------------------------------------------------------------------------
+// Return count of outstanding GC handles held by CordbHandleValue objects
+LONG CordbProcess::OutstandingHandles()
+{
+ return m_cOutstandingHandles;
+}
+
+//-----------------------------------------------------------------------------
+// Increment the outstanding handle count for code:CordbProces::OutstandingHandles
+// This is the inverse of code:CordbProces::DecrementOutstandingHandles
+void CordbProcess::IncrementOutstandingHandles()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_cOutstandingHandles++;
+}
+
+//-----------------------------------------------------------------------------
+// Decrement the outstanding handle count for code:CordbProces::OutstandingHandles
+// This is the inverse of code:CordbProces::IncrementOutstandingHandles
+void CordbProcess::DecrementOutstandingHandles()
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+ m_cOutstandingHandles--;
+}
+
+
+/*
+ * IsReadyForDetach
+ *
+ * This method encapsulates all logic for deciding if it is ok for a debugger to
+ * detach from the process at this time.
+ *
+ * Parameters: None.
+ *
+ * Returns: S_OK if it is ok to detach, else a specific HRESULT describing why it
+ * is not ok to detach.
+ *
+ */
+HRESULT CordbProcess::IsReadyForDetach()
+{
+ INTERNAL_API_ENTRY(this);
+
+ // Always safe to detach in V3 case.
+ if (m_pShim == NULL)
+ {
+ return S_OK;
+ }
+
+ // If not initialized yet, then there are no detach liabilities.
+ if (!m_initialized)
+ {
+ return S_OK;
+ }
+
+ RSLockHolder lockHolder(&this->m_processMutex);
+
+ //
+ // If there are any outstanding func-evals then fail the detach.
+ //
+ if (OutstandingEvalCount() != 0)
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_EVALS;
+ }
+
+ // V2 didn't check outstanding handles (code:CordbProcess::OutstandingHandles)
+ // because it could automatically clean those up on detach.
+
+ //
+ // If there are any outstanding steppers then fail the detach.
+ //
+ if (m_steppers.IsInitialized() && (m_steppers.GetCount() > 0))
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_STEPPERS;
+ }
+
+ //
+ // If there are any outstanding breakpoints then fail the detach.
+ //
+ HASHFIND foundAppDomain;
+ CordbAppDomain *pAppDomain = m_appDomains.FindFirst(&foundAppDomain);
+
+ while (pAppDomain != NULL)
+ {
+ if (pAppDomain->m_breakpoints.IsInitialized() && (pAppDomain->m_breakpoints.GetCount() > 0))
+ {
+ return CORDBG_E_DETACH_FAILED_OUTSTANDING_BREAKPOINTS;
+ }
+
+ // Check for any outstanding EnC modules.
+ HASHFIND foundModule;
+ CordbModule * pModule = pAppDomain->m_modules.FindFirst(&foundModule);
+ while (pModule != NULL)
+ {
+ if (pModule->m_EnCCount > 0)
+ {
+ return CORDBG_E_DETACH_FAILED_ON_ENC;
+ }
+ pModule = pAppDomain->m_modules.FindNext(&foundModule);
+ }
+
+
+ pAppDomain = m_appDomains.FindNext(&foundAppDomain);
+ }
+
+ // If we're using the shim, give a chance to early-out if the OS doesn't support detach
+ // so that the user can continue to debug in that case.
+ // Ideally we'd just rely on the failure from DebugActiveProcessStop, but by then it's too late
+ // to recover. This function is our only chance to distinguish between graceful detach failures
+ // and hard detach failures (after which the process object is neutered).
+ if (m_pShim != NULL)
+ {
+#if !defined(FEATURE_CORESYSTEM) // CORESYSTEM TODO
+ 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"));
+ if (pDebugActiveProcessStop == NULL)
+ return COR_E_PLATFORMNOTSUPPORTED;
+#endif
+ }
+
+ return S_OK;
+}
+
+
+/*
+ * Look for any thread which was last seen in the specified AppDomain.
+ * The CordbAppDomain object is about to be neutered due to an AD Unload
+ * So the thread must no longer be considered to be in that domain.
+ * Note that this is a workaround due to the existance of the (possibly incorrect)
+ * cached AppDomain value. Ideally we would remove the cached value entirely
+ * and there would be no need for this.
+ *
+ * @dbgtodo: , appdomain: We should remove CordbThread::m_pAppDomain in the V3 architecture.
+ * If we need the thread's current domain, we should get it accurately with DAC.
+ */
+void CordbProcess::UpdateThreadsForAdUnload(CordbAppDomain * pAppDomain)
+{
+ INTERNAL_API_ENTRY(this);
+
+ // If we're doing an AD unload then we should have already seen the ATTACH
+ // notification for the default domain.
+ //_ASSERTE( m_pDefaultAppDomain != NULL );
+ // @dbgtodo appdomain: fix Default domain invariants with DAC-izing Appdomain work.
+
+ RSLockHolder lockHolder(GetProcessLock());
+
+ CordbThread* t;
+ HASHFIND find;
+
+ // We don't need to prepopulate here (to collect LS state) because we're just updating RS state.
+ for (t = m_userThreads.FindFirst(&find);
+ t != NULL;
+ t = m_userThreads.FindNext(&find))
+ {
+ if( t->GetAppDomain() == pAppDomain )
+ {
+ // This thread cannot actually be in this AppDomain anymore (since it's being
+ // unloaded). Reset it to point to the default AppDomain
+ t->m_pAppDomain = m_pDefaultAppDomain;
+ }
+ }
+}
+
+// CordbProcess::LookupClass
+// Looks up a previously constructed CordbClass instance without creating. May return NULL if the
+// CordbClass instance doesn't exist.
+// Argument: (in) vmDomainFile - pointer to the domainfile for the module
+// (in) mdTypeDef - metadata token for the class
+// Return value: pointer to a previously created CordbClass instance or NULL in none exists
+CordbClass * CordbProcess::LookupClass(ICorDebugAppDomain * pAppDomain, VMPTR_DomainFile vmDomainFile, mdTypeDef classToken)
+{
+ _ASSERTE(ThreadHoldsProcessLock());
+
+ if (pAppDomain != NULL)
+ {
+ CordbModule * pModule = ((CordbAppDomain *)pAppDomain)->m_modules.GetBase(VmPtrToCookie(vmDomainFile));
+ if (pModule != NULL)
+ {
+ return pModule->LookupClass(classToken);
+ }
+ }
+ return NULL;
+} // CordbProcess::LookupClass
+
+//---------------------------------------------------------------------------------------
+// Look for a specific module in the process.
+//
+// Arguments:
+// vmDomainFile - non-null module to lookup
+//
+// Returns:
+// a CordbModule object for the given cookie. Object may be from the cache, or created
+// lazily.
+// Never returns null. Throws on error.
+//
+// Notes:
+// A VMPTR_DomainFile has appdomain affinity, but is ultimately scoped to a process.
+// So if we get a raw VMPTR_DomainFile (eg, from the stackwalker or from some other
+// lookup function), then we need to do a process wide lookup since we don't know which
+// appdomain it's in. If you know the appdomain, you can use code:CordbAppDomain::LookupOrCreateModule.
+//
+CordbModule * CordbProcess::LookupOrCreateModule(VMPTR_DomainFile vmDomainFile)
+{
+ INTERNAL_API_ENTRY(this);
+
+ RSLockHolder lockHolder(GetProcess()->GetProcessLock());
+ _ASSERTE(!vmDomainFile.IsNull());
+
+ DomainFileInfo data;
+ GetDAC()->GetDomainFileData(vmDomainFile, &data); // throws
+
+ CordbAppDomain * pAppDomain = LookupOrCreateAppDomain(data.vmAppDomain);
+ return pAppDomain->LookupOrCreateModule(vmDomainFile);
+}
+
+//---------------------------------------------------------------------------------------
+// Determine if the process has any in-band queued events which have not been dispatched
+//
+// Returns:
+// TRUE iff there are undispatched IB events
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+BOOL CordbProcess::HasUndispatchedNativeEvents()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CordbUnmanagedEvent* pEvent = m_unmanagedEventQueue;
+ while(pEvent != NULL && pEvent->IsDispatched())
+ {
+ pEvent = pEvent->m_next;
+ }
+
+ return pEvent != NULL;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Determine if the process has any in-band queued events which have not been user continued
+//
+// Returns:
+// TRUE iff there are user uncontinued IB events
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+BOOL CordbProcess::HasUserUncontinuedNativeEvents()
+{
+ INTERNAL_API_ENTRY(this);
+
+ CordbUnmanagedEvent* pEvent = m_unmanagedEventQueue;
+ while(pEvent != NULL && pEvent->IsEventUserContinued())
+ {
+ pEvent = pEvent->m_next;
+ }
+
+ return pEvent != NULL;
+}
+#endif
+
+//---------------------------------------------------------------------------------------
+// Hijack the thread which had this event. If the thread is already hijacked this method
+// has no effect.
+//
+// Arguments:
+// pUnmanagedEvent - the debug event which requires us to hijack
+//
+// Returns:
+// S_OK on success, failing HRESULT if the hijack could not be set up
+//
+#ifdef FEATURE_INTEROP_DEBUGGING
+HRESULT CordbProcess::HijackIBEvent(CordbUnmanagedEvent * pUnmanagedEvent)
+{
+ // Can't hijack after the event has already been continued hijacked
+ _ASSERTE(!pUnmanagedEvent->IsEventContinuedHijacked());
+ // Can only hijack IB events
+ _ASSERTE(pUnmanagedEvent->IsIBEvent());
+
+ // If we already hijacked the event then there is nothing left to do
+ if(pUnmanagedEvent->m_owner->IsFirstChanceHijacked() ||
+ pUnmanagedEvent->m_owner->IsGenericHijacked())
+ {
+ return S_OK;
+ }
+
+ ResetEvent(this->m_leftSideUnmanagedWaitEvent);
+ if (pUnmanagedEvent->m_currentDebugEvent.u.Exception.dwFirstChance)
+ {
+ HRESULT hr = pUnmanagedEvent->m_owner->SetupFirstChanceHijackForSync();
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ return hr;
+ }
+ else // Second chance exceptions must be generic hijacked.
+ {
+ HRESULT hr = pUnmanagedEvent->m_owner->SetupGenericHijack(pUnmanagedEvent->m_currentDebugEvent.dwDebugEventCode, &pUnmanagedEvent->m_currentDebugEvent.u.Exception.ExceptionRecord);
+ SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr));
+ return hr;
+ }
+}
+#endif
+
+// Sets a bitfield reflecting the managed debugging state at the time of
+// the jit attach.
+HRESULT CordbProcess::GetAttachStateFlags(CLR_DEBUGGING_PROCESS_FLAGS *pFlags)
+{
+ HRESULT hr = S_OK;
+ PUBLIC_REENTRANT_API_BEGIN(this)
+ {
+ if(pFlags == NULL)
+ hr = E_POINTER;
+ else
+ *pFlags = GetDAC()->GetAttachStateFlags();
+ }
+ PUBLIC_API_END(hr);
+
+ return hr;
+}
+
+// Determine if this version of ICorDebug is compatibile with the ICorDebug in the specified major CLR version
+bool CordbProcess::IsCompatibleWith(DWORD clrMajorVersion)
+{
+ // The debugger versioning policy is that debuggers generally need to opt-in to supporting major new
+ // versions of the CLR. Often new versions of the CLR violate some invariant that previous debuggers assume
+ // (eg. hot/cold splitting in Whidbey, multiple CLRs in a process in CLR v4), and neither VS or the CLR
+ // teams generally want the support burden of forward compatibility.
+
+ //
+ // If this assert is firing for you, its probably because the major version
+ // number of the clr.dll has changed. This assert is here to remind you to do a bit of other
+ // work you may not have realized you needed to do so that our versioning works smoothly
+ // for debugging. You probably want to contact the CLR DST team if you are a
+ // non-debugger person hitting this. DON'T JUST DELETE THIS ASSERT!!!
+ //
+ // 1) You should ensure new versions of all ICorDebug users in DevDiv (VS Debugger, MDbg, etc.)
+ // are using a creation path that explicitly specifies that they support this new major
+ // version of the CLR.
+ // 2) You should file an issue to track blocking earlier debuggers from targetting this
+ // version of the CLR (i.e. update requiredVersion to the new CLR major
+ // version). To enable a smooth internal transition, this often isn't done until absolutely
+ // necessary (sometimes as late as Beta2).
+ // 3) You can consider updating the CLR_ID guid used by the shim to recognize a CLR, but only
+ // if it's important to completely hide newer CLRs from the shim. The expectation now
+ // is that we won't need to do this (i.e. we'd like VS to give a nice error message about
+ // needed a newer version of the debugger, rather than just acting as if a process has no CLR).
+ // 4) Update this assert so that it no longer fires for your new CLR version or any of
+ // the previous versions, but don't delete the assert...
+ // the next CLR version after yours will probably need the same reminder
+
+ _ASSERTE_MSG(clrMajorVersion <= 4,
+ "Found major CLR version greater than 4 in mscordbi.dll from CLRv4 - contact CLRDST");
+
+ // This knob lets us enable forward compatibility for internal scenarios, and also simulate new
+ // versions of the runtime for testing the failure user-experience in a version of the debugger
+ // before it is shipped.
+ // We don't want to risk customers getting this, so for RTM builds this must be CHK-only.
+ // To aid in internal transition, we may temporarily enable this in RET builds, but when
+ // doing so must file a bug to track making it CHK only again before RTM.
+ // For example, Dev10 Beta2 shipped with this knob, but it was made CHK-only at the start of RC.
+ // In theory we might have a point release someday where we break debugger compat, but
+ // it seems unlikely and since this knob is unsupported anyway we can always extend it
+ // then (support reading a string value, etc.). So for now we just map the number
+ // to the major CLR version number.
+ DWORD requiredVersion = 0;
+#ifdef _DEBUG
+ requiredVersion = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_Debugging_RequiredVersion);
+#endif
+
+ // If unset (the only supported configuration), then we require a debugger designed for CLRv4
+ // for desktop, where we do not allow forward compat.
+ // For SL, we allow forward compat. Right now, that means SLv2+ debugger requests can be
+ // honored for SLv4.
+ if (requiredVersion <= 0)
+ {
+#if defined(FEATURE_CORECLR)
+ requiredVersion = 2;
+#else
+ requiredVersion = 4;
+#endif
+ }
+
+ // Compare the version we were created for against the minimum required
+ return (clrMajorVersion >= requiredVersion);
+}
+
+bool CordbProcess::IsThreadSuspendedOrHijacked(ICorDebugThread * pICorDebugThread)
+{
+ // An RS lock can be held while this is called. Specifically,
+ // CordbThread::EnumerateChains may be on the stack, and it uses
+ // ATT_REQUIRE_STOPPED_MAY_FAIL, which holds the CordbProcess::m_StopGoLock lock for
+ // its entire duration. As a result, this needs to be considered a reentrant API. See
+ // comments above code:PrivateShimCallbackHolder for more info.
+ PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(this);
+
+ CordbThread * pCordbThread = static_cast<CordbThread *> (pICorDebugThread);
+ return GetDAC()->IsThreadSuspendedOrHijacked(pCordbThread->m_vmThreadToken);
+}