diff options
Diffstat (limited to 'src/debug/di/rsthread.cpp')
-rw-r--r-- | src/debug/di/rsthread.cpp | 10981 |
1 files changed, 10981 insertions, 0 deletions
diff --git a/src/debug/di/rsthread.cpp b/src/debug/di/rsthread.cpp new file mode 100644 index 0000000000..8966b7675b --- /dev/null +++ b/src/debug/di/rsthread.cpp @@ -0,0 +1,10981 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** + +// +// File: rsthread.cpp +// +//***************************************************************************** +#include "stdafx.h" +#include "primitives.h" +#include <float.h> +#include <tls.h> + +// Stack-based holder for RSPTRs that we allocated to give to the LS. +// If LS successfully takes ownership of them, then call SuppressRelease(). +// Else, dtor will free them up. +// This is using a table protected by the ProcessLock(). +template <class T> +class RsPtrHolder +{ + T * m_pObject; + RsPointer<T> m_ptr; +public: + RsPtrHolder(T* pObject) + { + _ASSERTE(pObject != NULL); + m_ptr.AllocHandle(pObject->GetProcess(), pObject); + m_pObject = pObject; + } + + // If owner didn't call SuppressRelease() to take ownership, then have dtor free it. + ~RsPtrHolder() + { + if (!m_ptr.IsNull()) + { + // @dbgtodo synchronization - push this up. Note that since this is in a dtor; + // need to order it well against RSLockHolder. + RSLockHolder lockHolder(m_pObject->GetProcess()->GetProcessLock()); + T* pObjTest = m_ptr.UnWrapAndRemove(m_pObject->GetProcess()); + (void)pObjTest; //prevent "unused variable" error from GCC + _ASSERTE(pObjTest == m_pObject); + } + } + + RsPointer<T> Ptr() + { + return m_ptr; + } + void SuppressRelease() + { + m_ptr = RsPointer<T>::NullPtr(); + } + +}; + +/* ------------------------------------------------------------------------- * + * Managed Thread classes + * ------------------------------------------------------------------------- */ + + +//--------------------------------------------------------------------------------------- +// +// Instantiate a CordbThread object, which represents a managed thread. +// +// Arguments: +// process - non-null process object that this thread lives in. +// id - OS thread id of this thread. +// handle - OS Handle to the native thread in the debuggee. +// +//--------------------------------------------------------------------------------------- + +CordbThread::CordbThread(CordbProcess * pProcess, VMPTR_Thread vmThread) : + CordbBase(pProcess, + VmPtrToCookie(vmThread), + enumCordbThread), + m_pContext(NULL), + m_fContextFresh(false), + m_pAppDomain(NULL), + m_debugState(THREAD_RUN), + m_fFramesFresh(false), +#if !defined(DBG_TARGET_ARM) // @ARMTODO + m_fFloatStateValid(false), + m_floatStackTop(0), +#endif // !DBG_TARGET_ARM @ARMTODO + m_fException(false), + m_fCreationEventQueued(false), + m_EnCRemapFunctionIP(NULL), + m_userState(kInvalidUserState), + m_hCachedThread(INVALID_HANDLE_VALUE), + m_hCachedOutOfProcThread(INVALID_HANDLE_VALUE) +{ + m_fHasUnhandledException = FALSE; + m_pExceptionRecord = NULL; + + // Thread id may be a "fake" OS id for a CLRHosted thread. + m_vmThreadToken = vmThread; + + // This id must be unique for the thread. V2 uses the current OS thread id. + // If we ever support fibers, then we need to use something more unique than that. + m_dwUniqueID = pProcess->GetDAC()->GetUniqueThreadID(vmThread); // may throw + + LOG((LF_CORDB, LL_INFO1000, "CT::CT new thread 0x%p vmptr=0x%p id=0x%x\n", + this, m_vmThreadToken, m_dwUniqueID)); + + // Unique ID should never be 0. + _ASSERTE(m_dwUniqueID != 0); + + m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr(); + m_vmExcepObjHandle = VMPTR_OBJECTHANDLE::NullPtr(); + +#if defined(_DEBUG) && !defined(DBG_TARGET_ARM) // @ARMTODO + for (unsigned int i = 0; + i < (sizeof(m_floatValues) / sizeof(m_floatValues[0])); + i++) + { + m_floatValues[i] = 0; + } +#endif + + // Set AppDomain + VMPTR_AppDomain vmAppDomain = pProcess->GetDAC()->GetCurrentAppDomain(vmThread); + m_pAppDomain = pProcess->LookupOrCreateAppDomain(vmAppDomain); + _ASSERTE(m_pAppDomain != NULL); +} + + +CordbThread::~CordbThread() +{ + // We've already been neutered, thus we don't need to call CleanupStack(). + // That will have neutered + cleared frames + chains. + _ASSERTE(IsNeutered()); + + // Cleared in neuter + _ASSERTE(m_pContext == NULL); + _ASSERTE(m_hCachedThread == INVALID_HANDLE_VALUE); + _ASSERTE(m_pExceptionRecord == NULL); +} + +// Neutered by the CordbProcess +void CordbThread::Neuter() +{ + if (IsNeutered()) + { + return; + } + + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + delete m_pExceptionRecord; + m_pExceptionRecord = NULL; + + // Neuter frames & Chains. + CleanupStack(); + + + if (m_hCachedThread != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hCachedThread); + m_hCachedThread = INVALID_HANDLE_VALUE; + } + + if( m_pContext != NULL ) + { + delete [] m_pContext; + m_pContext = NULL; + } + + ClearStackFrameCache(); + + CordbBase::Neuter(); +} + +HRESULT CordbThread::QueryInterface(REFIID id, void ** ppInterface) +{ + if (id == IID_ICorDebugThread) + { + *ppInterface = static_cast<ICorDebugThread *>(this); + } + else if (id == IID_ICorDebugThread2) + { + *ppInterface = static_cast<ICorDebugThread2 *>(this); + } + else if (id == IID_ICorDebugThread3) + { + *ppInterface = static_cast<ICorDebugThread3*>(this); + } + else if (id == IID_ICorDebugThread4) + { + *ppInterface = static_cast<ICorDebugThread4*>(this); + } + else if (id == IID_IUnknown) + { + *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugThread *>(this)); + } + else + { + *ppInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + + +#ifdef _DEBUG +// Callback helper for code:CordbThread::DbgAssertThreadDeleted +// +// Arguments: +// vmThread - thread from enumeration of threads in the target. +// pUserData - the CordbThread for the thread that's deleted +// +// static +void CordbThread::DbgAssertThreadDeletedCallback(VMPTR_Thread vmThread, void * pUserData) +{ + CordbThread * pThis = reinterpret_cast<CordbThread *>(pUserData); + INTERNAL_DAC_CALLBACK(pThis->GetProcess()); + + VMPTR_Thread vmThreadDelete = pThis->m_vmThreadToken; + + CONSISTENCY_CHECK_MSGF((vmThread != vmThreadDelete), + ("A Thread Exit event was sent, but it still shows up in the enumeration.\n vmThreadDelete=%p\n", + VmPtrToCookie(vmThreadDelete))); +} + +// Debug-only helper to Assert that this thread is no longer discoverable in DacDbi enumerations +// This is designed to enforce the code:IDacDbiInterface#Enumeration rules for enumerations. +void CordbThread::DbgAssertThreadDeleted() +{ + // Enumerate through all threads and ensure the deleted threads don't show up. + GetProcess()->GetDAC()->EnumerateThreads( + DbgAssertThreadDeletedCallback, + this); +} +#endif // _DEBUG + + +//--------------------------------------------------------------------------------------- +// Mark that this thread has an unhandled native exception on it. +// +// Arguments +// pRecord - exception record of 2nd-chance exception that we're hijacking at. This will +// get deep copied into the CordbThread object in case it's needed for hijacking later. +// +// Notes: +// This bit is cleared in code:CordbThread::HijackForUnhandledException +void CordbThread::SetUnhandledNativeException(const EXCEPTION_RECORD * pExceptionRecord) +{ + m_fHasUnhandledException = true; + + if (m_pExceptionRecord == NULL) + { + m_pExceptionRecord = new EXCEPTION_RECORD(); // throws + } + memcpy(m_pExceptionRecord, pExceptionRecord, sizeof(EXCEPTION_RECORD)); +} + +//----------------------------------------------------------------------------- +// Returns true if the thread has an unhandled exception +// This is during the window after code:CordbThread::SetUnhandledNativeException is called, +// but before code:CordbThread::HijackForUnhandledException +bool CordbThread::HasUnhandledNativeException() +{ + return m_fHasUnhandledException; +} + + +//--------------------------------------------------------------------------------------- +// Determine if the thread's latest exception is a managed exception +// +// Notes: +// The CLR's UnhandledExceptionFilter has to make this same determination. +// +BOOL CordbThread::IsThreadExceptionManaged() +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + // A Thread's latest exception is managed if the VM Thread object has a managed object + // for the thread's Current Exception property. The CLR's Exception system is very diligent + // about tracking and clearing the thread's managed exception property. The runtime will clear + // the object if the exception is caught by unmanaged code (it can do this in the 2nd-pass). + + // It's the presence of a throwable that makes the difference between a managed + // exception event and an unmanaged exception event. + + VMPTR_OBJECTHANDLE vmObject = GetProcess()->GetDAC()->GetCurrentException(m_vmThreadToken); + + bool fHasThrowable = !vmObject.IsNull(); + + return fHasThrowable; + +} + +// ---------------------------------------------------------------------------- +// CordbThread::CreateCordbRegisterSet +// +// Description: +// This is a private hook for the shim to create a CordbRegisterSet for a ShimChain. +// +// Arguments: +// * pContext - the CONTEXT to be converted; this must be the leaf CONTEXT of a chain +// * fLeaf - whether the chain is the leaf chain or not +// * reason - the chain reason; this is needed for legacy reasons (see below) +// * ppRegSet - out parameter; return the newly created ICDRegisterSet +// +// Notes: +// * Note that the fQuickUnwind argument of the ctor of CordbRegisterSet is only true +// for an enter-managed chain. We need to keep the same behaviour here. That's why we need the +// chain reason. +// + +void CordbThread::CreateCordbRegisterSet(DT_CONTEXT * pContext, + BOOL fLeaf, + CorDebugChainReason reason, + ICorDebugRegisterSet ** ppRegSet) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess()); + + IfFailThrow(EnsureThreadIsAlive()); + + // The CordbRegisterSet is responsible for freeing this memory. + NewHolder<DebuggerREGDISPLAY> pDRD(new DebuggerREGDISPLAY()); + + // convert the CONTEXT to a DebuggerREGDISPLAY + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + pDAC->ConvertContextToDebuggerRegDisplay(pContext, pDRD, fLeaf); + + // create the CordbRegisterSet + RSInitHolder<CordbRegisterSet> pRS(new CordbRegisterSet(pDRD, + this, + (fLeaf == TRUE), + (reason == CHAIN_ENTER_MANAGED), + true)); + pDRD.SuppressRelease(); + + pRS.TransferOwnershipExternal(ppRegSet); +} + +// ---------------------------------------------------------------------------- +// CordbThread::ConvertFrameForILMethodWithoutMetadata +// +// Description: +// This is a private hook for the shim to convert an ICDFrame into an ICDInternalFrame for a dynamic +// method. There are two cases where we need this: +// 1) In Arrowhead, dynamic methods are exposed as first-class stack frames, not internal frames. Thus, +// the shim needs a way to convert an ICDNativeFrame for a dynamic method in Arrowhead to an +// ICDInternalFrame of type STUBFRAME_LIGHTWEIGHT_FUNCTION in V2. Furthermore, IL stubs, +// which are also considered as a type of dynamic methods, are not exposed in V2 at all. +// +// 2) In V2, PrestubMethodFrames (PMFs) can be exposed as one of two things: a chain of type +// CHAIN_CLASS_INIT in most cases, or an internal frame of type STUBFRAME_LIGHTWEIGHT_FUNCTION if +// the method being jitted is a dynamic method. There is no way to make this distinction at the +// public ICD level. +// +// Arguments: +// * pNativeFrame - the native frame to be converted +// * ppInternalFrame - out parameter; the converted internal frame; could be NULL (see Notes below) +// +// Returns: +// Return TRUE if conversion has occurred. Note that even if the return value is TRUE, ppInternalFrame +// could be NULL. See Notes below. +// +// Notes: +// * There are two main types of dynamic methods: ones which are generated by the runtime itself for +// internal purposes (i.e. IL stubs), and ones which are generated by the user. ppInternalFrame +// is NULL for IL stubs. We need this functionality because IL stubs are not exposed at all in V2. +// + +BOOL CordbThread::ConvertFrameForILMethodWithoutMetadata(ICorDebugFrame * pFrame, + ICorDebugInternalFrame2 ** ppInternalFrame2) +{ + PUBLIC_REENTRANT_API_ENTRY_FOR_SHIM(GetProcess()); + + _ASSERTE(ppInternalFrame2 != NULL); + *ppInternalFrame2 = NULL; + + HRESULT hr = E_FAIL; + + CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame); + + CordbInternalFrame * pInternalFrame = pRealFrame->GetAsInternalFrame(); + if (pInternalFrame != NULL) + { + // The input is an internal frame. + + // Check its frame type. + CorDebugInternalFrameType type; + hr = pInternalFrame->GetFrameType(&type); + IfFailThrow(hr); + + if (type != STUBFRAME_JIT_COMPILATION) + { + // No conversion is necessary. + return FALSE; + } + else + { + // We are indeed dealing with a PrestubMethodFrame. + return pInternalFrame->ConvertInternalFrameForILMethodWithoutMetadata(ppInternalFrame2); + } + } + else + { + // The input is a native frame. + CordbNativeFrame * pNativeFrame = pRealFrame->GetAsNativeFrame(); + _ASSERTE(pNativeFrame != NULL); + + return pNativeFrame->ConvertNativeFrameForILMethodWithoutMetadata(ppInternalFrame2); + } +} + +//----------------------------------------------------------------------------- +// Hijack a thread at an unhandled exception. This lets it execute the +// CLR's Unhandled Exception Filter (which will send the managed 2nd-chance exception event) +// +// Notes: +// OS will not execute Unhandled Exception Filter (UEF) when debugger is attached. +// The CLR's UEF does useful work, like dispatching 2nd-chance managed exception event +// and allowing Func-eval and Continuable Exceptions for unhandled exceptions. +// So hijack the thread, and the hijack will then execute the CLR's UEF just +// like the OS would. +void CordbThread::HijackForUnhandledException() +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + _ASSERTE(m_pExceptionRecord != NULL); + + _ASSERTE(m_fHasUnhandledException); + m_fHasUnhandledException = false; + + + ULONG32 dwThreadId = GetVolatileOSThreadID(); + + // Note that the data-target is not atomic, and we have no rollback mechanism. + // We have to do several writes. If the data-target fails the writes half-way through the + // target will be inconsistent. + + // We don't bother remembering the original context. LS hijack will have the + // context on its stack and will pass it to RS just like it does for filter-context. + GetProcess()->GetDAC()->Hijack( + m_vmThreadToken, + dwThreadId, + m_pExceptionRecord, + NULL, // LS will have the context. + 0, // size of context + EHijackReason::kUnhandledException, + NULL, + NULL); + + // Notify debugger to clear the exception. + // This will invoke the data-target. + GetProcess()->ContinueStatusChanged(dwThreadId, DBG_CONTINUE); +} + + +HRESULT CordbThread::GetProcess(ICorDebugProcess ** ppProcess) +{ + PUBLIC_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppProcess, ICorDebugProcess **); + FAIL_IF_NEUTERED(this); + + *ppProcess = GetProcess(); + GetProcess()->ExternalAddRef(); + + return S_OK; +} + +// Public implementation of ICorDebugThread::GetID +// Back in V1.0, GetID originally meant the OS thread ID that this managed thread was running on. +// In theory, that can change (fibers, logical thread scheduling, etc). However, in practice, in V1.0, it would +// not. Thus debuggers took a depedency on GetID being constant. +// In V2, this returns an opaque handle that is unique to this thread and stable for this thread's lifetime. +// +// Compare to code:CordbThread::GetVolatileOSThreadID, which returns the actual OS thread Id (which may change). +HRESULT CordbThread::GetID(DWORD * pdwThreadId) +{ + PUBLIC_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(pdwThreadId, DWORD *); + FAIL_IF_NEUTERED(this); + + *pdwThreadId = GetUniqueId(); + + return S_OK; +} + +// Returns a unique ID that's stable for the life of this thread. +// In a non-hosted scenarios, this can be the OS thread id. +DWORD CordbThread::GetUniqueId() +{ + return m_dwUniqueID; +} + +// Implementation of public API, ICorDebugThread::GetHandle +// @dbgtodo ICDThread - deprecate in V3, offload to Shim +HRESULT CordbThread::GetHandle(HANDLE * phThreadHandle) +{ + PUBLIC_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(phThreadHandle, HANDLE *); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (GetProcess()->GetShim() == NULL) + { + _ASSERTE(!"CordbThread::GetHandle() should be not be called on the new architecture"); + *phThreadHandle = NULL; + return E_NOTIMPL; + } + +#if !defined(FEATURE_DBGIPC_TRANSPORT_DI) + HRESULT hr = S_OK; + EX_TRY + { + HANDLE hThread; + InternalGetHandle(&hThread); // throws on error + *phThreadHandle = hThread; + } + EX_CATCH_HRESULT(hr); +#else // FEATURE_DBGIPC_TRANSPORT_DI + // In the old SL implementation of Mac debugging, we return a thread handle faked up by the PAL on the Mac. + // The returned handle is meaningless. Here we explicitly return E_NOTIMPL. We plan to deprecate this + // function in Dev10 anyway. + // + // @dbgtodo Mac - Check with VS to see if they need the thread handle, e.g. for waiting on thread + // termination. + HRESULT hr = E_NOTIMPL; +#endif // !FEATURE_DBGIPC_TRANSPORT_DI + + return hr; +} + +// Note that we can return invalid handle +void CordbThread::InternalGetHandle(HANDLE * phThread) +{ + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + RefreshHandle(phThread); +} + +//--------------------------------------------------------------------------------------- +// +// This is a simple helper to check if a frame lives on the stack of the current thread. +// +// Arguments: +// pFrame - the stack frame to check +// +// Return Value: +// whether the frame lives on the stack of the current thread +// +// Assumption: +// This function assumes that the stack frames are valid, i.e. the stack frames have not been +// made dirty since the last stackwalk. +// + +bool CordbThread::OwnsFrame(CordbFrame * pFrame) +{ + // preliminary checking + if ( (pFrame != NULL) && + (!pFrame->IsNeutered()) && + (pFrame->m_pThread == this) + ) + { + // + // Note that this is one of the two remaining places where we need to use the cached stack frames. + // Theoretically, since this is not an exact check anyway, we could just use the thread's stack + // range instead of looping through all the individual frames. However, since we need to maintain + // the stack frame cache for code:CordbThread::GetActiveFunctions, we might as well use the cache here. + // + + // make sure this thread actually have frames to check + if (m_stackFrames.Count() != 0) + { + // get the stack range of this thread + FramePointer fpLeaf = (*(m_stackFrames.Get(0)))->GetFramePointer(); + FramePointer fpRoot = (*(m_stackFrames.Get(m_stackFrames.Count() - 1)))->GetFramePointer(); + + FramePointer fpCurrent = pFrame->GetFramePointer(); + + // compare the stack range against the frame pointer of the specified frame + if (IsEqualOrCloserToLeaf(fpLeaf, fpCurrent) && IsEqualOrCloserToRoot(fpRoot, fpCurrent)) + { + return true; + } + } + } + + return false; +} + +//--------------------------------------------------------------------------------------- +// +// This routine is a internal helper function for ICorDebugThread2::GetTaskId. +// +// Arguments: +// pHandle - return thread handle here after fetching from the left side. Can return SWITCHOUT_HANDLE_VALUE. +// +// Return Value: +// hr - It can fail with CORDBG_E_THREAD_NOT_SCHEDULED. +// +// Notes: +// This method will most likely be deprecated in V3.0. We can't always return the thread handle. +// For example, what does it mean to return a thread handle in remote debugging scenarios? +// +void CordbThread::RefreshHandle(HANDLE * phThread) +{ + // here is where we will put code in to fetch the thread handle from the left side. + // This should only happen when CLRTask is hosted. + // Make sure that we are setting the right HR when thread is being switched out. + THROW_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + if (phThread == NULL) + { + ThrowHR(E_INVALIDARG); + } + *phThread = INVALID_HANDLE_VALUE; + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + HANDLE hThread = pDAC->GetThreadHandle(m_vmThreadToken); + + if (hThread == SWITCHOUT_HANDLE_VALUE) + { + *phThread = SWITCHOUT_HANDLE_VALUE; + ThrowHR(CORDBG_E_THREAD_NOT_SCHEDULED); + } + + _ASSERTE(hThread != INVALID_HANDLE_VALUE); + PREFAST_ASSUME(hThread != NULL); + + // need to dup handle here + if (hThread == m_hCachedOutOfProcThread) + { + *phThread = m_hCachedThread; + } + else + { + BOOL fSuccess = TRUE; + if (m_hCachedThread != INVALID_HANDLE_VALUE) + { + // clear the previous cache + CloseHandle(m_hCachedThread); + m_hCachedOutOfProcThread = INVALID_HANDLE_VALUE; + m_hCachedThread = INVALID_HANDLE_VALUE; + } + + // now duplicate the out-of-proc handle + fSuccess = DuplicateHandle(GetProcess()->UnsafeGetProcessHandle(), + hThread, + GetCurrentProcess(), + &m_hCachedThread, + NULL, + FALSE, + DUPLICATE_SAME_ACCESS); + *phThread = m_hCachedThread; + + if (fSuccess) + { + m_hCachedOutOfProcThread = hThread; + } + else + { + ThrowLastError(); + } + } +} // CordbThread::RefreshHandle + + +//--------------------------------------------------------------------------------------- +// +// This routine sets the debug state of a thread. +// +// Arguments: +// state - The debug state to set to. +// +// Return Value: +// Normal HRESULT semantics. +// +HRESULT CordbThread::SetDebugState(CorDebugThreadState state) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + LOG((LF_CORDB, LL_INFO1000, "CT::SDS: thread=0x%08x 0x%x, state=%d\n", this, m_id, state)); + + // @dbgtodo- , sync - decide on how to suspend a thread. V2 leverages synchronization + // (see below). For V3, do we just hard suspend the thread? + if (GetProcess()->GetShim() == NULL) + { + return E_NOTIMPL; + } + + HRESULT hr = S_OK; + EX_TRY + { + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + // This lets the debugger suspend / resume threads. This is only called when when the + // target is already synchronized. That means all the threads are already suspended. So + // setting the suspend bit here just means that the debugger's continue logic won't resume + // this thread when we do a Continue. + if ((state != THREAD_SUSPEND) && (state != THREAD_RUN)) + { + ThrowHR(E_INVALIDARG); + } + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + pDAC->SetDebugState(m_vmThreadToken, state); + + m_debugState = state; + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +HRESULT CordbThread::GetDebugState(CorDebugThreadState * pState) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + VALIDATE_POINTER_TO_OBJECT(pState, CorDebugThreadState *); + + *pState = m_debugState; + + return S_OK; +} + + +// Public implementation of ICorDebugThread::GetUserState +// Arguments: +// pState - out parameter; return the user state +// +// Return Value: +// Return S_OK if the operation is successful. +// Return E_INVALIDARG if the out parameter is NULL. +// Return other failure HRs returned by the call to the DDI. +HRESULT CordbThread::GetUserState(CorDebugUserState * pState) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pState, CorDebugUserState *); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + if (pState == NULL) + { + ThrowHR(E_INVALIDARG); + } + *pState = GetUserState(); + } + EX_CATCH_HRESULT(hr); + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Retrieve the user state of the current thread. +// +// Notes: +// This caches results between continues. The cache is cleared when the target continues or is flushed. +// See code:CordbThread::CleanupStack, code:CordbThread::MarkStackFramesDirty +// +CorDebugUserState CordbThread::GetUserState() +{ + if (m_userState == kInvalidUserState) + { + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + m_userState = pDAC->GetUserState(m_vmThreadToken); + } + + return m_userState; +} + + +//--------------------------------------------------------------------------------------- +// +// This routine finds and returns the current exception off of a thread. +// +// Arguments: +// ppExceptionObject - OUT: Space for storing the exception found on the thread as a value. +// +// Return Value: +// Normal HRESULT semantics. +// +HRESULT CordbThread::GetCurrentException(ICorDebugValue ** ppExceptionObject) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppExceptionObject, ICorDebugValue **); + *ppExceptionObject = NULL; + + EX_TRY + { + if (!HasException()) + { + // + // Go to the LS and retrieve any exception object. + // + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetCurrentException(m_vmThreadToken); + + if (vmObjHandle.IsNull()) + { + hr = S_FALSE; + } + else + { +#if defined(_DEBUG) + // Since we know an exception is in progress on this thread, our assumption about the + // thread's current AppDomain should be correct + VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken); + _ASSERTE(GetAppDomain()->GetADToken() == vmAppDomain); +#endif // _DEBUG + + m_vmExcepObjHandle = vmObjHandle; + } + } + + if (hr == S_OK) + { + // We've believe this assert may fire in the wild. + // We've seen m_vmExcepObjHandle null in retail builds after stack overflow. + _ASSERTE(!m_vmExcepObjHandle.IsNull()); + + ICorDebugReferenceValue * pRefValue = NULL; + hr = CordbReferenceValue::BuildFromGCHandle(GetAppDomain(), m_vmExcepObjHandle, &pRefValue); + *ppExceptionObject = pRefValue; + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +HRESULT CordbThread::ClearCurrentException() +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // This API is not implemented. For Continuable Exceptions, see InterceptCurrentException. + // @todo - should it return E_NOTIMPL? + return S_OK; +} + +HRESULT CordbThread::CreateStepper(ICorDebugStepper ** ppStepper) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppStepper, ICorDebugStepper **); + + CordbStepper * pStepper = new (nothrow) CordbStepper(this, NULL); + + if (pStepper == NULL) + { + return E_OUTOFMEMORY; + } + + pStepper->ExternalAddRef(); + *ppStepper = pStepper; + + return S_OK; +} + +//Returns true if current user state of a thread is USER_WAIT_SLEEP_JOIN +bool CordbThread::IsThreadWaitingOrSleeping() +{ + CorDebugUserState userState = m_userState; + if (userState == kInvalidUserState) + { + //If m_userState is not ready, we'll read from DAC only part of it which + //is important for us now, bacuase we don't want possible side effects + //of reading USER_UNSAFE_POINT flag. + //We don't cache the value, because it's potentially incomplete. + IDacDbiInterface *pDAC = GetProcess()->GetDAC(); + userState = pDAC->GetPartialUserState(m_vmThreadToken); + } + + return (userState & USER_WAIT_SLEEP_JOIN) != 0; +} + +//---------------------------------------------------------------------------- +// check if the thread is dead +// +// Returns: true if the thread is dead. +// +bool CordbThread::IsThreadDead() +{ + return GetProcess()->GetDAC()->IsThreadMarkedDead(m_vmThreadToken); +} + +// Helper to return CORDBG_E_BAD_THREAD_STATE if IsThreadDead +// +// Notes: +// IsThreadDead queries the VM Thread's actual state, regardless of what ExitThread +// callbacks have or have not been sent / queued / dispatched. +HRESULT CordbThread::EnsureThreadIsAlive() +{ + if (IsThreadDead()) + { + return CORDBG_E_BAD_THREAD_STATE; + } + else + { + return S_OK; + } +} + +// ---------------------------------------------------------------------------- +// CordbThread::EnumerateChains +// +// Description: +// Create and return an ICDChainEnum for enumerating chains on the stack. Since chains have been +// deprecated in Arrowhead, this function returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppChains - out parameter; return the ICDChainEnum +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppChains is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered. +// Return E_NOTIMPL if there is no shim. +// + +HRESULT CordbThread::EnumerateChains(ICorDebugChainEnum ** ppChains) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + VALIDATE_POINTER_TO_OBJECT(ppChains, ICorDebugChainEnum **); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + *ppChains = NULL; + + if (GetProcess()->GetShim() != NULL) + { + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + // use the shim to create an ICDChainEnum + PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess()); + ShimStackWalk * pSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this); + pSW->EnumerateChains(ppChains); + } + } + else + { + // This is the Arrowhead case, where ICDChain has been deprecated. + hr = E_NOTIMPL; + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbThread::GetActiveChain +// +// Description: +// Retrieve the leaf chain on this thread. Since chains have been deprecated in Arrowhead, +// this function returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppChain - out parameter; return the leaf chain +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppChain is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered. +// Return E_NOTIMPL if there is no shim. +// + +HRESULT CordbThread::GetActiveChain(ICorDebugChain ** ppChain) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + *ppChain = NULL; + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + if (GetProcess()->GetShim() != NULL) + { + // use the shim to retrieve the leaf chain + PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess()); + ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this); + pSSW->GetActiveChain(ppChain); + } + else + { + // This is the Arrowhead case, where ICDChain has been deprecated. + hr = E_NOTIMPL; + } + } + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbThread::GetActiveFrame +// +// Description: +// Retrieve the leaf frame on this thread. Unfortunately, this is one of the cases where we need to +// do different things depending on whether there is a shim. See the Notes below. +// +// Arguments: +// * ppFrame - out parameter; return the leaf frame +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppFrame is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered. +// Also return whatever CreateStackWalk() and GetFrame() return if they fail. +// +// Notes: +// In V2, we return NULL if the leaf frame is not in the leaf chain, i.e. if the leaf chain is +// empty. Note that managed chains are never empty. Also, in V2 it is possible that this API +// will return an internal frame as the active frame on a thread. +// +// The Arrowhead implementation two breaking changes: +// 1) It never returns an internal frame. +// 2) We return a frame if the leaf frame is managed. Otherwise, we return NULL. +// + +HRESULT CordbThread::GetActiveFrame(ICorDebugFrame ** ppFrame) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + *ppFrame = NULL; + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + if (GetProcess()->GetShim() != NULL) + { + PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess()); + ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this); + pSSW->GetActiveFrame(ppFrame); + } + else + { + // This is the Arrowhead case. We could call RefreshStack() here, but since we only need the + // leaf frame, there is no point in walking the entire stack. + RSExtSmartPtr<ICorDebugStackWalk> pSW; + hr = CreateStackWalk(&pSW); + IfFailThrow(hr); + + hr = pSW->GetFrame(ppFrame); + IfFailThrow(hr); + } + } + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbThread::GetActiveRegister +// +// Description: +// In V2, retrieve the ICDRegisterSet for the leaf chain. In Arrowhead, retrieve the ICDRegisterSet +// for the leaf CONTEXT. +// +// Arguments: +// * ppRegisters - out parameter; return the ICDRegister +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppFrame is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbThread is neutered. +// Also return whatever CreateStackWalk() and GetContext() return if they fail. +// + +HRESULT CordbThread::GetRegisterSet(ICorDebugRegisterSet ** ppRegisters) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + *ppRegisters = NULL; + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + if (GetProcess()->GetShim() != NULL) + { + // use the shim to retrieve the active ICDRegisterSet + PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess()); + ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this); + pSSW->GetActiveRegisterSet(ppRegisters); + } + else + { + // This is the Arrowhead case. We could call RefreshStack() here, but since we only need the + // leaf frame, there is no point in walking the entire stack. + RSExtSmartPtr<ICorDebugStackWalk> pSW; + hr = CreateStackWalk(&pSW); + IfFailThrow(hr); + + // retrieve the leaf CONTEXT + DT_CONTEXT ctx; + hr = pSW->GetContext(CONTEXT_FULL, sizeof(ctx), NULL, reinterpret_cast<BYTE *>(&ctx)); + IfFailThrow(hr); + + // the CordbRegisterSet is responsible for freeing this memory + NewHolder<DebuggerREGDISPLAY> pDRD(new DebuggerREGDISPLAY()); + + // convert the CONTEXT to a DebuggerREGDISPLAY + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + pDAC->ConvertContextToDebuggerRegDisplay(&ctx, pDRD, true); + + // create the CordbRegisterSet + RSInitHolder<CordbRegisterSet> pRS(new CordbRegisterSet(pDRD, + this, + true, // active + false, // !fQuickUnwind + true)); // own DRD memory + pDRD.SuppressRelease(); + + pRS.TransferOwnershipExternal(ppRegisters); + } + } + } + EX_CATCH_HRESULT(hr); + return hr; +} + +HRESULT CordbThread::CreateEval(ICorDebugEval ** ppEval) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppEval, ICorDebugEval **); + + CordbEval * pEval = new (nothrow) CordbEval(this); + if (pEval == NULL) + { + return E_OUTOFMEMORY; + } + + pEval->ExternalAddRef(); + *ppEval = static_cast<ICorDebugEval *>(pEval); + + return S_OK; +} + +// DAC check + +// Double check our results w/ DAC. +// This gives DAC some great coverage. +// Given an IP and the md token (that the RS obtained), use DAC to lookup the md token. Then +// we can compare DAC & the RS and make sure DACs working. +void CheckAgainstDAC(CordbFunction * pFunc, void * pIP, mdMethodDef mdExpected) +{ + // This is a hook to add DAC checks agaisnt a {function, ip} +} + + +//--------------------------------------------------------------------------------------- +// +// Internal function to build up a stack trace. +// +// +// Return Value: +// S_OK on success. +// +// Assumptions: +// Process is stopped. +// +// Notes: +// Send a IPC events to the LS to build up the stack. +// +//--------------------------------------------------------------------------------------- +void CordbThread::RefreshStack() +{ + THROW_IF_NEUTERED(this); + + // We must have the Stop-Go lock to change our thread's stack-state. + // Also, our caller should have guaranteed that we're synced. And b/c we hold the stop-go lock, + // that shouldn't have changed. + // INTERNAL_SYNC_API_ENTRY() checks that we have the lock and that we are synced. + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + // bail out early if the stack hasn't changed + if (m_fFramesFresh) + { + return; + } + + HRESULT hr = S_OK; + + // + // Clean up old snapshot. + // + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + + // clear the stack frame cache + ClearStackFrameCache(); + + // + // If we don't have a debugger thread token, then this thread has never + // executed managed code and we have no frame information for it. + // + if (m_vmThreadToken.IsNull()) + { + ThrowHR(E_FAIL); + } + + // walk the stack using the V3 API and populate the stack frame cache + RSInitHolder<CordbStackWalk> pSW(new CordbStackWalk(this)); + pSW->Init(); + do + { + RSExtSmartPtr<ICorDebugFrame> pIFrame; + hr = pSW->GetFrame(&pIFrame); + IfFailThrow(hr); + + if (pIFrame != NULL) + { + // add the stack frame to the cache + CordbFrame ** ppCFrame = m_stackFrames.AppendThrowing(); + *ppCFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame); + + // Now that we have saved the pointer, increment the ref count. + // This has to match the InternalRelease() in code:CordbThread::ClearStackFrameCache. + (*ppCFrame)->InternalAddRef(); + } + + // advance to the next frame + hr = pSW->Next(); + IfFailThrow(hr); + } + while (hr != CORDBG_S_AT_END_OF_STACK); + + m_fFramesFresh = true; +} + + +//--------------------------------------------------------------------------------------- +// +// This function is used to invalidate and clean up the cached stack trace. +// + +void CordbThread::CleanupStack() +{ + _ASSERTE(GetProcess()->GetProcessLock()->HasLock()); + + // Neuter outstanding CordbChainEnums, CordbFrameEnums, some CordbTypeEnums, and some CordbValueEnums. + m_RefreshStackNeuterList.NeuterAndClear(GetProcess()); + + m_fContextFresh = false; // invalide the cached active CONTEXT + m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr(); // set the LS pointer to the active CONTEXT to NULL + m_fFramesFresh = false; // invalidate the cached stack trace (frames & chains) + m_userState = kInvalidUserState; // clear the cached user state + + // tell the shim to flush its caches as well + if (GetProcess()->GetShim() != NULL) + { + GetProcess()->GetShim()->NotifyOnStackInvalidate(); + } +} + +// Notifying the thread that the process is being continued. +// This will cause our caches to get invalidated without actually cleaning the caches. +void CordbThread::MarkStackFramesDirty() +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + +#if !defined(DBG_TARGET_ARM) // @ARMTODO + // invalidate the cached floating point state + m_fFloatStateValid = false; +#endif // !defined(DBG_TARGET_ARM) @ARMTODO + + // This flag is only true between the window when we get an exception callback and + // when we call continue. Since this function is only called when we continue, we + // need to reset this flag here. Note that in the case of an outstanding funceval, + // we'll set this flag again when the funceval is completed. + m_fException = false; + + // Clear the stashed EnC remap IP address if any + // This is important to ensure we don't try to write into LS memory which is no longer + // being used to hold the remap IP. + m_EnCRemapFunctionIP = NULL; + + m_fContextFresh = false; // invalidate the cached active CONTEXT + m_vmLeftSideContext = VMPTR_CONTEXT::NullPtr(); // set the LS pointer to the active CONTEXT to NULL + m_fFramesFresh = false; // invalidate the cached stack trace (frames & chains) + m_userState = kInvalidUserState; // clear the cached user state + + m_RefreshStackNeuterList.NeuterAndClear(GetProcess()); + + // tell the shim to flush its caches as well + if (GetProcess()->GetShim() != NULL) + { + GetProcess()->GetShim()->NotifyOnStackInvalidate(); + } +} + +// Set that there's an outstanding exception on this thread. +// This can be called when the process object receives an exception notification. +// This is cleared in code:CordbThread::MarkStackFramesDirty. +void CordbThread::SetExInfo(VMPTR_OBJECTHANDLE vmExcepObjHandle) +{ + m_fException = true; + m_vmExcepObjHandle = vmExcepObjHandle; + + // CordbThread::GetCurrentException assumes that we always have a m_vmExcepObjHandle when at an exception. + // Push that assert up here. + _ASSERTE(!m_vmExcepObjHandle.IsNull()); +} + + +// ---------------------------------------------------------------------------- +// CordbThread::FindFrame +// +// Description: +// Given a FramePointer, find the matching CordbFrame. +// +// Arguments: +// * ppFrame - out parameter; the CordbFrame to be returned +// * fp - the input FramePointer +// +// Return Value: +// Return S_OK on success. +// Return E_FAIL on failure. +// +// Assumptions: +// * This function is only called from the shim. +// +// Notes: +// * Currently this function is only used by the shim to map the FramePointer it gets via the +// DB_IPCE_EXCEPTION_CALLBACK2 callback. When we figure out what to do with the +// DB_IPCE_EXCEPTION_CALLBACK2, we should remove this function. +// + +HRESULT CordbThread::FindFrame(ICorDebugFrame ** ppFrame, FramePointer fp) +{ + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + _ASSERTE(ppFrame != NULL); + *ppFrame = NULL; + + _ASSERTE(GetProcess()->GetShim() != NULL); + + PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess()); + ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this); + + for (UINT32 i = 0; i < pSSW->GetFrameCount(); i++) + { + ICorDebugFrame * pIFrame = pSSW->GetFrame(i); + CordbFrame * pCFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame); + +#if defined(_WIN64) + // On 64-bit we can simply compare the FramePointer. + if (pCFrame->GetFramePointer() == fp) +#else // !_WIN64 + // On other platforms, we need to do a more elaborate check. + if (pCFrame->IsContainedInFrame(fp)) +#endif // _WIN64 + { + *ppFrame = pIFrame; + (*ppFrame)->AddRef(); + return S_OK; + } + } + + // Cannot find the frame. + return E_FAIL; +} + +#if !defined(DBG_TARGET_ARM) // @ARMTODO + +#if defined(_TARGET_X86_) + +// CordbThread::Get32bitFPRegisters +// Converts the values in the floating point register area of the context to real number values. See +// code:CordbThread::LoadFloatState for more details. +// Arguments: +// input: pContext +// output: none (initializes m_floatValues) + +void CordbThread::Get32bitFPRegisters(CONTEXT * pContext) +{ + // On X86, we get the values by saving our current FPU state, loading + // the other thread's FPU state into our own, saving out each + // value off the FPU stack, and then restoring our FPU state. + // + FLOATING_SAVE_AREA floatarea = pContext->FloatSave; // copy FloatSave + + // + // Take the TOP out of the FPU status word. Note, our version of the + // stack runs from 0->7, not 7->0... + // + unsigned int floatStackTop = 7 - ((floatarea.StatusWord & 0x3800) >> 11); + + FLOATING_SAVE_AREA currentFPUState; + + __asm fnsave currentFPUState // save the current FPU state. + + floatarea.StatusWord &= 0xFF00; // remove any error codes. + floatarea.ControlWord |= 0x3F; // mask all exceptions. + + // the x86 FPU stores real numbers as 10 byte values in IEEE format. Here we use + // the hardware to convert these to doubles. + + // @dbgtodo Microsoft crossplat: the conversion from a series of bytes to a floating + // point value will need to be done with an explicit conversion routine to unpack + // the IEEE format and compute the real number value represented. + + __asm + { + fninit + frstor floatarea ;; reload the threads FPU state. + } + + unsigned int i; + + for (i = 0; i <= floatStackTop; i++) + { + long double td; + __asm fstp td // copy out the double + m_floatValues[i] = td; + } + + __asm + { + fninit + frstor currentFPUState ;; restore our saved FPU state. + } + + m_fFloatStateValid = true; + m_floatStackTop = floatStackTop; +} // CordbThread::Get32bitFPRegisters + +#elif defined(_TARGET_AMD64_) +extern "C" double FPFillR8(void* pFillSlot); + +// CordbThread::Get64bitFPRegisters +// Converts the values in the floating point register area of the context to real number values. See +// code:CordbThread::LoadFloatState for more details. +// Arguments: +// input: pFPRegisterBase - starting address of the floating point register storage of the CONTEXT +// registerSize - the size of a floating point register +// start - the index into m_floatValues where we start initializing. For amd64, we start +// at the beginning, but for ia64, the first two registers have fixed values, +// so we start at two. +// nRegisters - the number of registers to be initialized +// output: none (initializes m_floatValues) + +void CordbThread::Get64bitFPRegisters(FPRegister64 * rgContextFPRegisters, int start, int nRegisters) +{ + // make sure no one has changed the type definition for 64-bit FP registers + _ASSERTE(sizeof(FPRegister64) == 16); + // We convert and copy all the fp registers. + for (int reg = start; reg < nRegisters; reg++) + { + // @dbgtodo Microsoft crossplat: the conversion from a FLOAT128 or M128A struct to a floating + // point value will need to be done with an explicit conversion routine instead + // of the call to FPFillR8 + m_floatValues[reg] = FPFillR8(&rgContextFPRegisters[reg - start]); + } +} // CordbThread::Get64bitFPRegisters + + +#endif // _TARGET_X86_ + +// CordbThread::LoadFloatState +// Initializes the float state members of this instance of CordbThread. This function gets the context and +// converts the floating point values from their context representation to a real number value. Floating +// point numbers are represented in IEEE format on all current platforms. We store them in the context as a +// pair of 64-bit integers (IA64 and AMD64) or a series of bytes (x86). Rather than unpack them explicitly +// and do the appropriate mathematical operations to produce the corresponding floating point value, we let +// the hardware do it instead. We load a floating point register with the representation from the context +// and then store it in m_floatValues. Using the hardware is obviously a huge perf win. If/when we make +// cross-plat work, we should at least code necessary conversion routines in assembly. Even with cross-plat, +// we can probably still use the hardware in most cases, as long as the size is appropriate. +// +// Arguments: none +// Return Value: none (initializes data members) +// Note: Throws + +void CordbThread::LoadFloatState() +{ + THROW_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + DT_CONTEXT tempContext; + GetProcess()->GetDAC()->GetContext(m_vmThreadToken, &tempContext); + +#if defined(_TARGET_X86_) + Get32bitFPRegisters((CONTEXT*) &tempContext); +#elif defined(_TARGET_AMD64_) + // we have no fixed-value registers, so we begin with the first one and initialize all 16 + Get64bitFPRegisters((FPRegister64*) &(tempContext.Xmm0), 0, 16); +#endif // !_TARGET_X86_ + + m_fFloatStateValid = true; +} // CordbThread::LoadFloatState + +#endif // !DBG_TARGET_ARM @ARMTODO + +const bool SetIP_fCanSetIPOnly = TRUE; +const bool SetIP_fSetIP = FALSE; + +const bool SetIP_fIL = TRUE; +const bool SetIP_fNative = FALSE; + +//--------------------------------------------------------------------------------------- +// +// Issues a SetIP command to the left-side and returns the result +// +// Arguments: +// fCanSetIPOnly - TRUE if only to do the setip command and not refresh stacks as well. +// debuggerModule - LS token to the debugger module. +// mdMethod - Metadata token for the method. +// nativeCodeJITInfoToken - LS token to the DebuggerJitInfo for the method. +// offset - Offset within the method to set the IP to. +// fIsIl - Is this an IL offset? +// +// Return Value: +// S_OK on success. +// +HRESULT CordbThread::SetIP(bool fCanSetIPOnly, + CordbNativeCode * pNativeCode, + SIZE_T offset, + bool fIsIL) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VMPTR_DomainFile vmDomainFile = pNativeCode->GetModule()->m_vmDomainFile; + _ASSERTE(!vmDomainFile.IsNull()); + + // If this thread is stopped due to an exception, never allow SetIP + if (HasException()) + { + return (CORDBG_E_SET_IP_NOT_ALLOWED_ON_EXCEPTION); + } + + DebuggerIPCEvent event; + GetProcess()->InitIPCEvent(&event, DB_IPCE_SET_IP, true, GetAppDomain()->GetADToken()); + event.SetIP.fCanSetIPOnly = fCanSetIPOnly; + event.SetIP.vmThreadToken = m_vmThreadToken; + event.SetIP.vmDomainFile = vmDomainFile; + event.SetIP.mdMethod = pNativeCode->GetMetadataToken(); + event.SetIP.vmMethodDesc = pNativeCode->GetVMNativeCodeMethodDescToken(); + event.SetIP.startAddress = pNativeCode->GetAddress(); + event.SetIP.offset = offset; + event.SetIP.fIsIL = fIsIL; + + + LOG((LF_CORDB, LL_INFO10000, "[%x] CT::SIP: Info:thread:0x%x" + "mod:0x%x MethodDef:0x%x offset:0x%x il?:0x%x\n", + GetCurrentThreadId(), + VmPtrToCookie(m_vmThreadToken), + VmPtrToCookie(vmDomainFile), + pNativeCode->GetMetadataToken(), + offset, + fIsIL)); + + LOG((LF_CORDB, LL_INFO10000, "[%x] CT::SIP: sizeof(DebuggerIPCEvent):0x%x **********\n", + sizeof(DebuggerIPCEvent))); + + HRESULT hr = GetProcess()->m_cordb->SendIPCEvent(GetProcess(), &event, sizeof(DebuggerIPCEvent)); + + if (FAILED(hr)) + { + return hr; + } + + _ASSERTE(event.type == DB_IPCE_SET_IP); + + if (!fCanSetIPOnly && SUCCEEDED(event.hr)) + { + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + CleanupStack(); + } + + return ErrWrapper(event.hr); +} + +// Get the context from a thread in managed code. +// This thread should be stopped gracefully by the LS in managed code. +HRESULT CordbThread::GetManagedContext(DT_CONTEXT ** ppContext) +{ + FAIL_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + if (ppContext == NULL) + { + ThrowHR(E_INVALIDARG); + } + + *ppContext = NULL; + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // Each CordbThread object allocates the m_pContext's DT_CONTEXT structure only once, the first time GetContext is + // invoked. + if(m_pContext == NULL) + { + // Throw if the allocation fails. + m_pContext = reinterpret_cast<DT_CONTEXT *>(new BYTE[sizeof(DT_CONTEXT)]); + } + + HRESULT hr = S_OK; + + if (m_fContextFresh == false) + { + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + m_vmLeftSideContext = pDAC->GetManagedStoppedContext(m_vmThreadToken); + + if (m_vmLeftSideContext.IsNull()) + { + // We don't have a context in managed code. + ThrowHR(CORDBG_E_CONTEXT_UNVAILABLE); + } + else + { + LOG((LF_CORDB, LL_INFO1000, "CT::GC: getting context from left side pointer.\n")); + + // The thread we're examining IS handling an exception, So grab the CONTEXT of the exception, NOT the + // currently executing thread's CONTEXT (which would be the context of the exception handler.) + hr = GetProcess()->SafeReadThreadContext(m_vmLeftSideContext.ToLsPtr(), m_pContext); + IfFailThrow(hr); + } + + // m_fContextFresh should be marked false when CleanupStack, MarkAllFramesAsDirty, etc get called. + m_fContextFresh = true; + } + + _ASSERTE(SUCCEEDED(hr)); + (*ppContext) = m_pContext; + + return hr; +} + +HRESULT CordbThread::SetManagedContext(DT_CONTEXT * pContext) +{ + INTERNAL_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + if(pContext == NULL) + { + ThrowHR(E_INVALIDARG); + } + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + m_vmLeftSideContext = pDAC->GetManagedStoppedContext(m_vmThreadToken); + + if (m_vmLeftSideContext.IsNull()) + { + ThrowHR(CORDBG_E_CONTEXT_UNVAILABLE); + } + else + { + // The thread we're examining IS handling an exception, So set the CONTEXT of the exception, NOT the currently + // executing thread's CONTEXT (which would be the context of the exception handler.) + // + // Note: we read the remote context and merge the new one in, then write it back. This ensures that we don't + // write too much information into the remote process. + DT_CONTEXT tempContext = { 0 }; + hr = GetProcess()->SafeReadThreadContext(m_vmLeftSideContext.ToLsPtr(), &tempContext); + IfFailThrow(hr); + + CORDbgCopyThreadContext(&tempContext, pContext); + + hr = GetProcess()->SafeWriteThreadContext(m_vmLeftSideContext.ToLsPtr(), &tempContext); + IfFailThrow(hr); + + // @todo - who's updating the regdisplay to guarantee that's in sync w/ our new context? + } + + _ASSERTE(SUCCEEDED(hr)); + if (m_fContextFresh && (m_pContext != NULL)) + { + *m_pContext = *pContext; + } + + return hr; +} + + +HRESULT CordbThread::GetAppDomain(ICorDebugAppDomain ** ppAppDomain) +{ + // We don't use the cached m_pAppDomain pointer here because it might be incorrect + // if the thread has transitioned to another domain but we haven't received any events + // from it yet. So we need to ask the left-side for the current domain. + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this); + { + ValidateOrThrow(ppAppDomain); + *ppAppDomain = NULL; + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + CordbAppDomain * pAppDomain = NULL; + hr = GetCurrentAppDomain(&pAppDomain); + IfFailThrow(hr); + _ASSERTE( pAppDomain != NULL ); + + *ppAppDomain = static_cast<ICorDebugAppDomain *> (pAppDomain); + pAppDomain->ExternalAddRef(); + } + } + PUBLIC_API_END(hr); + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Issues a get appdomain command and returns it. +// +// Arguments: +// ppAppDomain - OUT: Space for storing the app domain of this thread. +// +// Return Value: +// S_OK on success. +// +HRESULT CordbThread::GetCurrentAppDomain(CordbAppDomain ** ppAppDomain) +{ + FAIL_IF_NEUTERED(this); + INTERNAL_API_ENTRY(GetProcess()); + + *ppAppDomain = NULL; + + HRESULT hr = S_OK; + EX_TRY + { + // @dbgtodo ICDThread - push this up + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken); + + CordbAppDomain * pAppDomain = GetProcess()->LookupOrCreateAppDomain(vmAppDomain); + _ASSERTE(pAppDomain != NULL); // we should be aware of all AppDomains + + *ppAppDomain = pAppDomain; + } + } + EX_CATCH_HRESULT(hr); + + return S_OK; +} + +//--------------------------------------------------------------------------------------- +// +// Issues a get_object command and returns the thread object as a value. +// +// Arguments: +// ppThreadObject - OUT: Space for storing the thread object of this thread as a value +// +// Return Value: +// S_OK on success. +// +HRESULT CordbThread::GetObject(ICorDebugValue ** ppThreadObject) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppThreadObject, ICorDebugObjectValue **); + + // Default to NULL + *ppThreadObject = NULL; + + HRESULT hr = S_OK; + EX_TRY + { + // @dbgtodo ICDThread - push this up + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetThreadObject(m_vmThreadToken); + if (vmObjHandle.IsNull()) + { + ThrowHR(E_FAIL); + } + + // We create the object relative to the current AppDomain of the thread + // Thread objects aren't really agile (eg. their m_Context field is domain-bound and + // fixed up manually during transitions). This means that a thread object can only + // be used in the domain the thread was in when the object was created. + VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken); + + CordbAppDomain * pThreadCurrentDomain = NULL; + pThreadCurrentDomain = GetProcess()->m_appDomains.GetBaseOrThrow(VmPtrToCookie(vmAppDomain)); + _ASSERTE(pThreadCurrentDomain != NULL); // we should be aware of all AppDomains + + if (pThreadCurrentDomain == NULL) + { + // fall back to some domain to avoid crashes in retail - + // safe enough for getting the name of the thread etc. + pThreadCurrentDomain = GetProcess()->GetDefaultAppDomain(); + } + + lockHolder.Release(); + + ICorDebugReferenceValue * pRefValue = NULL; + hr = CordbReferenceValue::BuildFromGCHandle(pThreadCurrentDomain, vmObjHandle, &pRefValue); + *ppThreadObject = pRefValue; + } + } + EX_CATCH_HRESULT(hr); + + // Don't return a null pointer with S_OK. + _ASSERTE((hr != S_OK) || (*ppThreadObject != NULL)); + return hr; +} + +/* + * + * GetActiveFunctions + * + * This routine is the interface function for ICorDebugThread2::GetActiveFunctions. + * + * Parameters: + * cFunctions - the count of the number of COR_ACTIVE_FUNCTION in pFunctions. Zero + * indicates no pFunctions buffer. + * pcFunctions - pointer to storage for the count of elements filled in to pFunctions, or + * count that would be needed to fill pFunctions, if cFunctions is 0. + * pFunctions - buffer to store results. May be NULL. + * + * Return Value: + * HRESULT from the helper routine. + * + */ + +HRESULT CordbThread::GetActiveFunctions( + ULONG32 cFunctions, + ULONG32 * pcFunctions, + COR_ACTIVE_FUNCTION pFunctions[]) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ULONG32 index; + ULONG32 iRealIndex; + ULONG32 last; + + if (((cFunctions != 0) && (pFunctions == NULL)) || (pcFunctions == NULL)) + { + return E_INVALIDARG; + } + + // + // Default to 0 + // + *pcFunctions = 0; + + // @dbgtodo synchronization - The ATT macro may slip the thread to a sychronized state. The + // synchronization feature crew needs to figure out what to do here. Then we can use the + // PUBLIC_API_BEGIN macro in this function. + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + + if (IsThreadDead()) + { + // + // Return zero active functions on this thread. + // + hr = S_OK; + } + else + { + ULONG32 cAllFrames = 0; // the total number of frames (stack frames and internal frames) + ULONG32 cStackFrames = 0; // the number of stack frames + ShimStackWalk * pSSW = NULL; + + if (GetProcess()->GetShim() != NULL) + { + PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess()); + pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(this); + + // initialize the frame counts + cAllFrames = pSSW->GetFrameCount(); + for (ULONG32 i = 0; i < cAllFrames; i++) + { + // filter out internal frames + if (CordbFrame::GetCordbFrameFromInterface(pSSW->GetFrame(i))->GetAsNativeFrame() != NULL) + { + cStackFrames += 1; + } + } + + _ASSERTE(cStackFrames <= cAllFrames); + } + else + { + RefreshStack(); + + cAllFrames = m_stackFrames.Count(); + cStackFrames = cAllFrames; + + // In Arrowhead, the stackwalking API doesn't return internal frames, + // so the frame counts should be equal. + _ASSERTE(cStackFrames == cAllFrames); + } + + *pcFunctions = cStackFrames; + + // + // If all we want is the count, then return that. + // + if ((pFunctions == NULL) || (cFunctions == 0)) + { + hr = S_OK; + } + else + { + // + // Now go down list of frames, storing information + // + last = (cFunctions < cStackFrames) ? cFunctions : cStackFrames; + iRealIndex = 0; + index =0; + + while((index < last) && (iRealIndex < cAllFrames)) + { + CordbFrame * pThisFrame = NULL; + if (GetProcess()->GetShim()) + { + _ASSERTE(pSSW != NULL); + pThisFrame = CordbFrame::GetCordbFrameFromInterface(pSSW->GetFrame(iRealIndex)); + } + else + { + pThisFrame = *(m_stackFrames.Get(iRealIndex)); + _ASSERTE(pThisFrame->GetAsNativeFrame() != NULL); + } + + iRealIndex++; + + CordbNativeFrame * pNativeFrame = pThisFrame->GetAsNativeFrame(); + if (pNativeFrame == NULL) + { + // filter out internal frames + _ASSERTE(pThisFrame->GetAsInternalFrame() != NULL); + continue; + } + + // + // Fill in the easy stuff. + // + CordbFunction * pFunction; + + pFunction = (static_cast<CordbFrame *>(pNativeFrame))->GetFunction(); + ASSERT(pFunction != NULL); + + hr = pFunction->QueryInterface(IID_ICorDebugFunction2, + reinterpret_cast<void **>(&(pFunctions[index].pFunction))); + ASSERT(!FAILED(hr)); + + CordbModule * pModule = pFunction->GetModule(); + pFunctions[index].pModule = pModule; + pModule->ExternalAddRef(); + + CordbAppDomain * pAppDomain = pNativeFrame->GetCurrentAppDomain(); + pFunctions[index].pAppDomain = pAppDomain; + pAppDomain->ExternalAddRef(); + + pFunctions[index].flags = 0; + + // + // Now go to the IL frame (if one exists) to the get the offset. + // + CordbJITILFrame * pJITILFrame; + + pJITILFrame = pNativeFrame->m_JITILFrame; + + if (pJITILFrame != NULL) + { + hr = pJITILFrame->GetIP(&(pFunctions[index].ilOffset), NULL); + ASSERT(!FAILED(hr)); + } + else + { + pFunctions[index].ilOffset = (DWORD) NO_MAPPING; + } + + // Update to the next count. + index++; + } + + // @todo - The spec says that pcFunctions == # of elements in pFunctions, + // but the behavior here is that it's always the total. + // If we want to fix that, we should uncomment the assignment here: + //*pcFunctions = index; + } + } + } + EX_CATCH_HRESULT(hr); + return hr; +} + + +//--------------------------------------------------------------------------------------- +// +// This is the entry point for continuable exceptions. +// It implements ICorDebugThread2::InterceptCurrentException. +// +// Arguments: +// pFrame - the stack frame to intercept at +// +// Return Value: +// HRESULT indicating success or failure +// +// Notes: +// Since we cannot intercept an exception at an internal frame, +// pFrame should not be an ICorDebugInternalFrame. +// + +HRESULT CordbThread::InterceptCurrentException(ICorDebugFrame * pFrame) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + +#if defined(FEATURE_DBGIPC_TRANSPORT_DI) + // Continuable exceptions are not implemented on rotor and Mac. + return E_NOTIMPL; + +#else // !FEATURE_DBGIPC_TRANSPORT_DI + HRESULT hr = S_OK; + EX_TRY + { + DebuggerIPCEvent event; + + if (pFrame == NULL) + { + ThrowHR(E_INVALIDARG); + } + + // + // Verify we were passed a real stack frame, and not an internal + // CLR mocked up one. + // + { + RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame; + hr = pFrame->QueryInterface(IID_ICorDebugInternalFrame, (void **)&pInternalFrame); + + if (!FAILED(hr)) + { + ThrowHR(E_INVALIDARG); + } + } + + + // + // If the thread is detached, then there should be no frames on its stack. + // + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + // + // Refresh the stack frames for this thread and verify pFrame is on it. + // + + RefreshStack(); + + // + // Now check if the frame actually lives on the stack of the current thread. + // + + // "Cast" the ICDFrame pointer to a CordbFrame pointer. + CordbFrame * pRealFrame = CordbFrame::GetCordbFrameFromInterface(pFrame); + if (!OwnsFrame(pRealFrame)) + { + ThrowHR(E_INVALIDARG); + } + + // + // pFrame is on the stack - good. Now tell the LS to intercept at that frame. + // + + GetProcess()->InitIPCEvent(&event, DB_IPCE_INTERCEPT_EXCEPTION, true, VMPTR_AppDomain::NullPtr()); + + event.InterceptException.vmThreadToken = m_vmThreadToken; + event.InterceptException.frameToken = pRealFrame->GetFramePointer(); + + hr = GetProcess()->m_cordb->SendIPCEvent(GetProcess(), &event, sizeof(DebuggerIPCEvent)); + + // + // Stop now if we can't even send the event. + // + if (!SUCCEEDED(hr)) + { + ThrowHR(hr); + } + + _ASSERTE(event.type == DB_IPCE_INTERCEPT_EXCEPTION_RESULT); + + hr = event.hr; + // Since we are going to exit anyway, we don't need to throw here. + } + } + EX_CATCH_HRESULT(hr); + return hr; +#endif // FEATURE_DBGIPC_TRANSPORT_DI +} + +//--------------------------------------------------------------------------------------- +// +// Return S_OK if there is a current exception and it is unhandled, otherwise +// return S_FALSE +// +HRESULT CordbThread::HasUnhandledException() +{ + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_FALSE; + PUBLIC_REENTRANT_API_BEGIN(this) + { + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + if(pDAC->HasUnhandledException(m_vmThreadToken)) + { + hr = S_OK; + } + } + PUBLIC_REENTRANT_API_END(hr); + + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Create a stackwalker on the current thread. Initially, the stackwalker is stopped at the +// managed filter CONTEXT if there is one. Otherwise it is stopped at the leaf CONTEXT. +// +// Arguments: +// ppStackWalk - out parameter; return the new stackwalker +// +// Return Value: +// Return S_OK on succcess. +// Return E_FAIL on error. +// +// Notes: +// The filter CONTEXT will be removed in V3.0. +// + +HRESULT CordbThread::CreateStackWalk(ICorDebugStackWalk ** ppStackWalk) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppStackWalk, ICorDebugStackWalk **); + + HRESULT hr = S_OK; + + EX_TRY + { + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + hr = EnsureThreadIsAlive(); + + if (SUCCEEDED(hr)) + { + RSInitHolder<CordbStackWalk> pSW(new CordbStackWalk(this)); + pSW->Init(); + pSW.TransferOwnershipExternal(ppStackWalk); + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} + + +//--------------------------------------------------------------------------------------- +// +// This is a callback function used to enumerate the internal frames on a thread. +// Each time this callback is invoked, we'll create a new CordbInternalFrame and store it +// in an array. See code:DacDbiInterfaceImpl::EnumerateInternalFrames for more information. +// +// Arguments: +// pFrameData - contains information about the current internal frame in the enumeration +// pUserData - This is a GetActiveInternalFramesData. +// It contains an array of internl frames to be filled. +// + +// static +void CordbThread::GetActiveInternalFramesCallback(const DebuggerIPCE_STRData * pFrameData, + void * pUserData) +{ + // Retrieve the CordbThread. + GetActiveInternalFramesData * pCallbackData = reinterpret_cast<GetActiveInternalFramesData *>(pUserData); + CordbThread * pThis = pCallbackData->pThis; + INTERNAL_DAC_CALLBACK(pThis->GetProcess()); + + // Make sure we are getting invoked for internal frames. + _ASSERTE(pFrameData->eType == DebuggerIPCE_STRData::cStubFrame); + + // Look up the CordbAppDomain. + CordbAppDomain * pAppDomain = NULL; + VMPTR_AppDomain vmCurrentAppDomain = pFrameData->vmCurrentAppDomainToken; + if (!vmCurrentAppDomain.IsNull()) + { + pAppDomain = pThis->GetProcess()->LookupOrCreateAppDomain(vmCurrentAppDomain); + } + + // Create a CordbInternalFrame. + CordbInternalFrame * pInternalFrame = new CordbInternalFrame(pThis, + pFrameData->fp, + pAppDomain, + pFrameData); + + // Store the internal frame in the array and update the index to prepare for the next one. + pCallbackData->pInternalFrames.Assign(pCallbackData->uIndex, pInternalFrame); + pCallbackData->uIndex++; +} + +//--------------------------------------------------------------------------------------- +// +// This function returns an array of ICDInternalFrame2. Each element represents an internal frame +// on the thread. If ppInternalFrames is NULL or cInternalFrames is 0, then we just return +// the number of internal frames on the thread. +// +// Arguments: +// cInternalFrames - the number of elements in ppInternalFrames +// pcInternalFrames - out parameter; return the number of internal frames on the thread +// ppInternalFrames - a buffer to store the array of internal frames +// +// Return Value: +// S_OK on success. +// E_INVALIDARG if +// - ppInternalFrames is NULL but cInternalFrames is not 0 +// - pcInternalFrames is NULL +// - cInternalFrames is smaller than the number of internal frames actually on the thread +// + +HRESULT CordbThread::GetActiveInternalFrames(ULONG32 cInternalFrames, + ULONG32 * pcInternalFrames, + ICorDebugInternalFrame2 * ppInternalFrames[]) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this); + { + if ( ((cInternalFrames != 0) && (ppInternalFrames == NULL)) || + (pcInternalFrames == NULL) ) + { + ThrowHR(E_INVALIDARG); + } + + *pcInternalFrames = 0; + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + ULONG32 cActiveInternalFrames = pDAC->GetCountOfInternalFrames(m_vmThreadToken); + + // Set the count. + *pcInternalFrames = cActiveInternalFrames; + + // Don't need to do anything else if the user is only asking for the count. + if ((cInternalFrames != 0) && (ppInternalFrames != NULL)) + { + if (cInternalFrames < cActiveInternalFrames) + { + ThrowWin32(ERROR_INSUFFICIENT_BUFFER); + } + else + { + // initialize the callback data + GetActiveInternalFramesData data; + data.pThis = this; + data.uIndex = 0; + data.pInternalFrames.AllocOrThrow(cActiveInternalFrames); + // We want to ensure it's automatically cleaned up in all cases + // e.g. if we're debugging a MiniDumpNormal and we fail to + // retrieve memory from the target. The exception will be + // caught above this frame. + data.pInternalFrames.EnableAutoClear(); + + pDAC->EnumerateInternalFrames(m_vmThreadToken, + &CordbThread::GetActiveInternalFramesCallback, + &data); + _ASSERTE(cActiveInternalFrames == data.pInternalFrames.Length()); + + // Copy the internal frames we have accumulated in GetActiveInternalFramesData to the out + // argument. + for (unsigned int i = 0; i < data.pInternalFrames.Length(); i++) + { + RSInitHolder<CordbInternalFrame> pInternalFrame(data.pInternalFrames[i]); + pInternalFrame.TransferOwnershipExternal(&(ppInternalFrames[i])); + } + } + } + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + + +// ICorDebugThread4 + +// ------------------------------------------------------------------------------- +// Gets the current custom notification on this thread or NULL if no such object exists +// Arguments: +// output: ppNotificationObject - current CustomNotification object. +// if we aren't currently inside a CustomNotification callback, this will +// always return NULL. +// return value: +// S_OK on success +// S_FALSE if no object exists +// CORDBG_E_BAD_REFERENCE_VALUE if the reference is bad +HRESULT CordbThread::GetCurrentCustomDebuggerNotification(ICorDebugValue ** ppNotificationObject) +{ + HRESULT hr = S_OK; + PUBLIC_API_NO_LOCK_BEGIN(this); + { + ATT_REQUIRE_STOPPED_MAY_FAIL_OR_THROW(GetProcess(), ThrowHR); + + if (ppNotificationObject == NULL) + { + ThrowHR(E_INVALIDARG); + } + + *ppNotificationObject = NULL; + + // + // Go to the LS and retrieve any notification object. + // + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + VMPTR_OBJECTHANDLE vmObjHandle = pDAC->GetCurrentCustomDebuggerNotification(m_vmThreadToken); + +#if defined(_DEBUG) + // Since we know a notification has occurred on this thread, our assumption about the + // thread's current AppDomain should be correct + VMPTR_AppDomain vmAppDomain = pDAC->GetCurrentAppDomain(m_vmThreadToken); + + _ASSERTE(GetAppDomain()->GetADToken() == vmAppDomain); +#endif // _DEBUG + + if (!vmObjHandle.IsNull()) + { + ICorDebugReferenceValue * pRefValue = NULL; + IfFailThrow(CordbReferenceValue::BuildFromGCHandle(GetAppDomain(), vmObjHandle, &pRefValue)); + *ppNotificationObject = pRefValue; + } + } + PUBLIC_API_END(hr); + return hr; +} + +/* + * + * SetRemapIP + * + * This routine communicate the EnC remap IP to the LS by writing it to process memory using + * the pointer that was set in the thread. If the address is null, then we haven't seen + * a RemapOpportunity call for this frame/function combo yet, so invalid to Remap the function. + * + * Parameters: + * offset - the IL offset to set the IP to + * + * Return Value: + * S_OK or CORDBG_E_NO_REMAP_BREAKPIONT. + * + */ +HRESULT CordbThread::SetRemapIP(SIZE_T offset) +{ + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + // This is only set when we're prepared to do a remap + if (! m_EnCRemapFunctionIP) + { + return CORDBG_E_NO_REMAP_BREAKPIONT; + } + + // Write the value of the remap offset into the left side + HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(m_EnCRemapFunctionIP), &offset); + + // Prevent SetRemapIP from being called twice for the same RemapOpportunity + // If we don't get any calls to RemapFunction, this member will be cleared in + // code:CordbThread::MarkStackFramesDirty when Continue is called + m_EnCRemapFunctionIP = NULL; + + return hr; +} + + +//--------------------------------------------------------------------------------------- +// +// This routine is the interface function for ICorDebugThread2::GetConnectionID. +// +// Arguments: +// pdwConnectionId - return connection id set on the thread. Can return INVALID_CONNECTION_ID +// +// Return Value: +// HRESULT indicating success or failure +// +HRESULT CordbThread::GetConnectionID(CONNID * pConnectionID) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // now retrieve the connection id + HRESULT hr = S_OK; + EX_TRY + { + if (pConnectionID == NULL) + { + ThrowHR(E_INVALIDARG); + } + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + *pConnectionID = pDAC->GetConnectionID(m_vmThreadToken); + + if (*pConnectionID == INVALID_CONNECTION_ID) + { + hr = S_FALSE; + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} // CordbThread::GetConnectionID + +//--------------------------------------------------------------------------------------- +// +// This routine is the interface function for ICorDebugThread2::GetTaskID. +// +// Arguments: +// pTaskId - return task id set on the thread. Can return INVALID_TASK_ID +// +// Return Value: +// HRESULT indicating success or failure +// +HRESULT CordbThread::GetTaskID(TASKID * pTaskID) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // now retrieve the task id + HRESULT hr = S_OK; + EX_TRY + { + if (pTaskID == NULL) + { + ThrowHR(E_INVALIDARG); + } + + *pTaskID = this->GetTaskID(); + + if (*pTaskID == INVALID_TASK_ID) + { + hr = S_FALSE; + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} // CordbThread::GetTaskID + +//--------------------------------------------------------------------------------------- +// Get the task ID for this thread +// +// return: +// task id set on the thread. Can return INVALID_TASK_ID +// +TASKID CordbThread::GetTaskID() +{ + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + return pDAC->GetTaskID(m_vmThreadToken); +} + + + +//--------------------------------------------------------------------------------------- +// +// This routine is the interface function for ICorDebugThread2::GetVolatileOSThreadID. +// +// Arguments: +// pdwTid - return os thread id +// +// Return Value: +// HRESULT indicating success or failure +// +// Notes: +// Compare with code:CordbThread::GetID +HRESULT CordbThread::GetVolatileOSThreadID(DWORD * pdwTID) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // now retrieve the OS thread ID + HRESULT hr = S_OK; + EX_TRY + { + if (pdwTID == NULL) + { + ThrowHR(E_INVALIDARG); + } + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + *pdwTID = pDAC->TryGetVolatileOSThreadID(m_vmThreadToken); + + if (*pdwTID == 0) + { + hr = S_FALSE; // Switched out + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} // CordbThread::GetOSThreadID + +//--------------------------------------------------------------------------------------- +// Get the thread's volatile OS ID. (this is fiber aware) +// +// Returns: +// Thread's current OS id. For fibers / "logical threads", This may change as a thread executes. +// Throws if the managed thread currently is not mapped to an OS thread (ie, not scheduled) +// +DWORD CordbThread::GetVolatileOSThreadID() +{ + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + DWORD dwThreadID = pDAC->TryGetVolatileOSThreadID(m_vmThreadToken); + + if (dwThreadID == 0) + { + ThrowHR(CORDBG_E_THREAD_NOT_SCHEDULED); + } + return dwThreadID; +} + +// ---------------------------------------------------------------------------- +// CordbThread::ClearStackFrameCache +// +// Description: +// Clear the cache of stack frames maintained by the CordbThread. +// +// Notes: +// We are doing an InternalRelease() here to match the InternalAddRef() in code:CordbThread::RefreshStack. +// + +void CordbThread::ClearStackFrameCache() +{ + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + for (int i = 0; i < m_stackFrames.Count(); i++) + { + (*m_stackFrames.Get(i))->Neuter(); + (*m_stackFrames.Get(i))->InternalRelease(); + } + m_stackFrames.Clear(); +} + +// ---------------------------------------------------------------------------- +// EnumerateBlockingObjectsCallback +// +// Description: +// A small helper used by CordbThread::GetBlockingObjects. This callback adds the enumerated items +// to a list +// +// Arguments: +// blockingObject - the object to add to the list +// pUserData - the list to add it to + +VOID EnumerateBlockingObjectsCallback(DacBlockingObject blockingObject, CALLBACK_DATA pUserData) +{ + CQuickArrayList<DacBlockingObject>* pDacBlockingObjs = (CQuickArrayList<DacBlockingObject>*)pUserData; + pDacBlockingObjs->Push(blockingObject); +} + +// ---------------------------------------------------------------------------- +// CordbThread::GetBlockingObjects +// +// Description: +// Returns a list of objects that a thread is blocking on by using Monitor.Enter and +// Monitor.Wait +// +// Arguments: +// ppBlockingObjectEnum - on return this is an enumerator for the list of blocking objects +// +// Return: +// S_OK on success or an appropriate failing HRESULT + +HRESULT CordbThread::GetBlockingObjects(ICorDebugBlockingObjectEnum **ppBlockingObjectEnum) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + VALIDATE_POINTER_TO_OBJECT(ppBlockingObjectEnum, ICorDebugBlockingObjectEnum **); + + HRESULT hr = S_OK; + CorDebugBlockingObject* blockingObjs = NULL; + EX_TRY + { + CQuickArrayList<DacBlockingObject> dacBlockingObjects; + IDacDbiInterface* pDac = GetProcess()->GetDAC(); + pDac->EnumerateBlockingObjects(m_vmThreadToken, + (IDacDbiInterface::FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK) EnumerateBlockingObjectsCallback, + (CALLBACK_DATA) &dacBlockingObjects); + blockingObjs = new CorDebugBlockingObject[dacBlockingObjects.Size()]; + for(SIZE_T i = 0 ; i < dacBlockingObjects.Size(); i++) + { + // ICorDebug API needs to flip the direction of the list from the way DAC stores it + SIZE_T dacObjIndex = dacBlockingObjects.Size()-i-1; + switch(dacBlockingObjects[dacObjIndex].blockingReason) + { + case DacBlockReason_MonitorCriticalSection: + blockingObjs[i].blockingReason = BLOCKING_MONITOR_CRITICAL_SECTION; + break; + case DacBlockReason_MonitorEvent: + blockingObjs[i].blockingReason = BLOCKING_MONITOR_EVENT; + break; + default: + _ASSERTE(!"Should not get here"); + ThrowHR(E_FAIL); + break; + } + blockingObjs[i].dwTimeout = dacBlockingObjects[dacObjIndex].dwTimeout; + CordbAppDomain* pAppDomain; + { + RSLockHolder holder(GetProcess()->GetProcessLock()); + pAppDomain = GetProcess()->LookupOrCreateAppDomain(dacBlockingObjects[dacObjIndex].vmAppDomain); + } + blockingObjs[i].pBlockingObject = CordbValue::CreateHeapValue(pAppDomain, + dacBlockingObjects[dacObjIndex].vmBlockingObject); + } + + CordbBlockingObjectEnumerator* objEnum = new CordbBlockingObjectEnumerator(GetProcess(), + blockingObjs, + (DWORD)dacBlockingObjects.Size()); + GetProcess()->GetContinueNeuterList()->Add(GetProcess(), objEnum); + hr = objEnum->QueryInterface(__uuidof(ICorDebugBlockingObjectEnum), (void**)ppBlockingObjectEnum); + _ASSERTE(SUCCEEDED(hr)); + } + EX_CATCH_HRESULT(hr); + delete [] blockingObjs; + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbThread::SetCreateEventQueued +void CordbThread::SetCreateEventQueued() +{ + m_fCreationEventQueued = true; +} + +// ---------------------------------------------------------------------------- +// CordbThread::CreateEventWasQueued +bool CordbThread::CreateEventWasQueued() +{ + return m_fCreationEventQueued; +} + + +#ifdef FEATURE_INTEROP_DEBUGGING +/* ------------------------------------------------------------------------- * + * Unmanaged Thread classes + * ------------------------------------------------------------------------- */ + +CordbUnmanagedThread::CordbUnmanagedThread(CordbProcess *pProcess, DWORD dwThreadId, HANDLE hThread, void *lpThreadLocalBase) + : CordbBase(pProcess, dwThreadId, enumCordbUnmanagedThread), + m_handle(hThread), + m_threadLocalBase(lpThreadLocalBase), + m_pTLSArray(NULL), + m_pTLSExtendedArray(NULL), + m_state(CUTS_None), + m_originalHandler(NULL), +#ifdef DBG_TARGET_X86 + m_pSavedLeafSeh(NULL), +#endif + m_stackBase(0), + m_stackLimit(0), + m_continueCountCached(0) +{ + m_pLeftSideContext.Set(NULL); + + IBEvent()->m_state = CUES_None; + IBEvent()->m_next = NULL; + IBEvent()->m_owner = this; + + IBEvent2()->m_state = CUES_None; + IBEvent2()->m_next = NULL; + IBEvent2()->m_owner = this; + + OOBEvent()->m_state = CUES_None; + OOBEvent()->m_next = NULL; + OOBEvent()->m_owner = this; + + m_pPatchSkipAddress = NULL; + + this->GetStackRange(NULL, NULL); +} + +CordbUnmanagedThread::~CordbUnmanagedThread() +{ + // CordbUnmanagedThread objects will: + // - never send IPC events. + // - never be exposed to the public. (we assert external-ref is always == 0) + // - always manipulated on W32ET (where we can't do IPC stuff) + + UnsafeNeuterDeadObject(); + + _ASSERTE(this->IsNeutered()); + + // by the time the thread is deleted, it shouldn't have any outstanding debug events. + + // Actually, the thread could get deleted while we have an outstanding IB debug event. We could get the IB event, hijack that thread, + // and then since the process is continued, something could go off and kill the hijacked thread. + // If the event is still in the process's queued list, and it still refers back to a thread, then we'll AV when we try to access the event + // (or continue it). + CONSISTENCY_CHECK_MSGF(!HasIBEvent(), ("Deleting thread w/ outstanding IB event:this=%p,event-code=%d\n", this, IBEvent()->m_currentDebugEvent.dwDebugEventCode)); + + CONSISTENCY_CHECK_MSGF(!HasOOBEvent(), ("Deleting thread w/ outstanding OOB event:this=%p,event-code=%d\n", this, OOBEvent()->m_currentDebugEvent.dwDebugEventCode)); +} + +#define WINNT_TLS_OFFSET_X86 0xe10 // TLS[0] at fs:[WINNT_TLS_OFFSET] +#define WINNT_TLS_OFFSET_AMD64 0x1480 +#define WINNT_TLS_OFFSET_ARM 0xe10 +#define WINNT5_TLSEXPANSIONPTR_OFFSET_X86 0xf94 // TLS[64] at [fs:[WINNT5_TLSEXPANSIONPTR_OFFSET]] +#define WINNT5_TLSEXPANSIONPTR_OFFSET_AMD64 0x1780 +#define WINNT5_TLSEXPANSIONPTR_OFFSET_ARM 0xf94 + +HRESULT CordbUnmanagedThread::LoadTLSArrayPtr(void) +{ + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + + // Just simple math on NT with a small tls index. + // The TLS slots for 0-63 are embedded in the TIB. +#if defined(DBG_TARGET_X86) + m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_X86; +#elif defined(DBG_TARGET_AMD64) + m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_AMD64; +#elif defined(DBG_TARGET_ARM) + m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_ARM; +#elif defined(DBG_TARGET_ARM64) + m_pTLSArray = (BYTE*) m_threadLocalBase + WINNT_TLS_OFFSET_ARM64; +#else + PORTABILITY_ASSERT("Implement OOP TLS on your platform"); +#endif + + // Extended slot is lazily initialized, so check every time. + if (m_pTLSExtendedArray == NULL) + { + // On NT 5 you can have TLS index's greater than 63, so we + // have to grab the ptr to the TLS expansion array first, + // then use that as the base to index off of. This will + // never move once we find it for a given thread, so we + // cache it here so we don't always have to perform two + // ReadProcessMemory's. +#if defined(DBG_TARGET_X86) + void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_X86; +#elif defined(DBG_TARGET_AMD64) + void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_AMD64; +#elif defined(DBG_TARGET_ARM) + void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_ARM; +#elif defined(DBG_TARGET_ARM64) + void *ppTLSArray = (BYTE*) m_threadLocalBase + WINNT5_TLSEXPANSIONPTR_OFFSET_ARM64; +#else + PORTABILITY_ASSERT("Implement OOP TLS on your platform"); +#endif + + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(ppTLSArray), &m_pTLSExtendedArray); + } + + + return hr; +} + +/* +VOID CordbUnmanagedThread::VerifyFSChain() +{ +#if defined(DBG_TARGET_X86) + DT_CONTEXT temp; + temp.ContextFlags = DT_CONTEXT_FULL; + DbiGetThreadContext(m_handle, &temp); + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: 0x%x fs=0x%x TIB=0x%x\n", + m_id, temp.SegFs, m_threadLocalBase)); + REMOTE_PTR pExceptionRegRecordPtr; + HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &pExceptionRegRecordPtr); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read fs:0 value: computed addr=0x%p err=%x\n", + m_id, m_threadLocalBase, hr)); + _ASSERTE(FALSE); + return; + } + while(pExceptionRegRecordPtr != EXCEPTION_CHAIN_END && pExceptionRegRecordPtr != NULL) + { + REMOTE_PTR prev; + REMOTE_PTR handler; + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pExceptionRegRecordPtr), &prev); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read prev value: computed addr=0x%p err=%x\n", + m_id, pExceptionRegRecordPtr, hr)); + return; + } + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS( (VOID*)((DWORD)pExceptionRegRecordPtr+4) ), &handler); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x failed to read handler value: computed addr=0x%p err=%x\n", + m_id, (DWORD)pExceptionRegRecordPtr+4, hr)); + return; + } + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: OK 0x%x record=0x%x prev=0x%x handler=0x%x\n", + m_id, pExceptionRegRecordPtr, prev, handler)); + if(handler == NULL) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x NULL handler found\n", m_id)); + _ASSERTE(FALSE); + return; + } + if(prev == NULL) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x NULL prev found\n", m_id)); + _ASSERTE(FALSE); + return; + } + if(prev == pExceptionRegRecordPtr) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: ERROR 0x%x cyclic prev found\n", m_id)); + _ASSERTE(FALSE); + return; + } + pExceptionRegRecordPtr = prev; + } + + LOG((LF_CORDB, LL_INFO1000, "CUT::VFSC: OK 0x%x\n", m_id)); +#endif + return; +}*/ + +#ifdef DBG_TARGET_X86 +HRESULT CordbUnmanagedThread::SaveCurrentLeafSeh() +{ + _ASSERTE(m_pSavedLeafSeh == NULL); + REMOTE_PTR pExceptionRegRecordPtr; + HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &pExceptionRegRecordPtr); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::SCLS: failed to read fs:0 value: computed addr=0x%p err=%x\n", m_threadLocalBase, hr)); + return hr; + } + m_pSavedLeafSeh = pExceptionRegRecordPtr; + return S_OK; +} + +HRESULT CordbUnmanagedThread::RestoreLeafSeh() +{ + _ASSERTE(m_pSavedLeafSeh != NULL); + HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(m_threadLocalBase), &m_pSavedLeafSeh); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::RLS: failed to write fs:0 value: computed addr=0x%p err=%x\n", m_threadLocalBase, hr)); + return hr; + } + m_pSavedLeafSeh = NULL; + return S_OK; +} +#endif + +// Read the contents from the LS's Predefined TLS block. +// This is an auxillary TLS storage array-of-void*, indexed off the TLS. +// pRead is optional. This makes sense when '0' is a valid default value. +// 1) On success (block exists in LS, we can read it), +// return value of data in the slot, *pRead = true +// 2) On failure to read block (block doens't exist yet, any other failure) +// return value == 0 (assumed default, *pRead = false +REMOTE_PTR CordbUnmanagedThread::GetPreDefTlsSlot(SIZE_T slot, bool * pRead) +{ +#ifdef FEATURE_IMPLICIT_TLS + REMOTE_PTR pBlock = (REMOTE_PTR) GetEETlsDataBlock(); +#else + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + REMOTE_PTR pBlock = (REMOTE_PTR) GetTlsSlot(pRO->m_TLSIndexOfPredefs); +#endif + + REMOTE_PTR data = 0; + + // We don't have a maximum size, but we know it's less than ~200. This assert + // will catch if we're just passsing Garbage. + _ASSERTE(slot < 200); + + bool dummy; + if (pRead == NULL) + { + pRead = &dummy; + } + + if (pBlock != NULL) + { + REMOTE_PTR p = ((BYTE*) pBlock) + slot * sizeof(data); + + // Now read the "special" status out of the PreDef block. + HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(p), &data); + + // The predef block should be valid at this point, so the ReadProcessMemory ought to work. + SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr); + + if (SUCCEEDED(hr)) + { + *pRead = true; + return data; + } + } + + *pRead = false; + return 0; +} + +#ifndef FEATURE_IMPLICIT_TLS + +// Read the contents from a LS threads's TLS slot. +DWORD_PTR CordbUnmanagedThread::GetTlsSlot(SIZE_T slot) +{ + DWORD_PTR ret = 0; + + // Compute the address of the necessary TLS value. + if (FAILED(LoadTLSArrayPtr())) + { + return NULL; + } + + + void * pBase = NULL; + SIZE_T slotAdjusted = slot; + + if (slot < TLS_MINIMUM_AVAILABLE) + { + pBase = m_pTLSArray; + } + else if (slot < TLS_MINIMUM_AVAILABLE + TLS_EXPANSION_SLOTS) + { + pBase = m_pTLSExtendedArray; + slotAdjusted -= TLS_MINIMUM_AVAILABLE; + + // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated, + // then the TLS slot is still the default value, which is 0 (NULL). + if (pBase == NULL) + { + return NULL; + } + } + else + { + // Slot is out of range. Shouldn't happen unless debuggee is corrupted. + _ASSERTE(!"Invalid TLS slot"); + return NULL; + } + + void *pEEThreadTLS = (BYTE*) pBase + (slotAdjusted * sizeof(void*)); + + + // Read the thread's TLS value. + HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), &ret); + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to read TLS value: computed addr=0x%p index=%d, err=%x\n", + pEEThreadTLS, slot, hr)); + + return NULL; + } + + LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETV: EE Thread TLS value is 0x%p for thread 0x%x, slot 0x%x\n", ret, m_id, slot)); + + return ret; +} + +// This does a WriteProcessMemory to write to the debuggee's TLS slot allotted to EEThread +// +// Arguments: +// EETlsValue - the value to write to the remote TLS slot. +// +// Notes: +// The TLS slot is m_TLSIndex. +// +// This is very brittle because the OS can lazily allocates storage for TLS slots. +// In order to gaurantee the storage is available, it must have been written to by the debuggee. +// For managed threads, that's easy because the Thread* is already written to the slot. +// But for pure native threads where GetThread() == NULL, the storage may not yet be allocated. +// +// The saving grace is that the debuggee's hijack filters will force the TLS to be allocated before it +// sends a flare. +// +// Therefore, this function can only be called: +// 1) on a managed thread +// 2) on a native thread after that thread has been hijacked and sent a flare. +// +// This is brittle reasoning, but so is the rest of interop-debugging. +// +HRESULT CordbUnmanagedThread::SetEEThreadValue(REMOTE_PTR EETlsValue) +{ + FAIL_IF_NEUTERED(this); + + // Compute the address of the necessary TLS value. + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + + // Compute the address of the necessary TLS value. + HRESULT hr = LoadTLSArrayPtr(); + if (FAILED(hr)) + { + return hr; + } + + + DWORD slot = (DWORD) pRO->m_TLSIndex; + + void * pBase = NULL; + SIZE_T slotAdjusted = slot; + if (slot < TLS_MINIMUM_AVAILABLE) + { + pBase = m_pTLSArray; + } + else if (slot < TLS_MINIMUM_AVAILABLE+TLS_EXPANSION_SLOTS) + { + pBase = m_pTLSExtendedArray; + slotAdjusted -= TLS_MINIMUM_AVAILABLE; + + // Expansion slot is lazily allocated. If we're trying to read from it, but hasn't been allocated, + // then the TLS slot is still the default value, which is 0. + if (pBase == NULL) + { + // See reasoning in header for why this should succeed. + _ASSERTE(!"Can't set to expansion slots because they haven't been allocated"); + return E_FAIL; + } + } + else + { + // Slot is out of range. Shouldn't happen unless debuggee is corrupted. + _ASSERTE(!"Invalid TLS slot"); + return E_INVALIDARG; + } + + + void *pEEThreadTLS = (BYTE*) pBase + (slotAdjusted * sizeof(void*)); + + + // Write the thread's TLS value. + hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEThreadTLS), &EETlsValue); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: " + "computed addr=0x%p index=%d, err=%x\n", + pEEThreadTLS, pRO->m_TLSIndex, hr)); + + return hr; + } + + LOG((LF_CORDB, LL_INFO1000000, + "CUT::SEETV: EE Thread TLS value is now 0x%p for thread 0x%x\n", + EETlsValue, m_id)); + + return S_OK; +} +#else // FEATURE_IMPLICIT_TLS + +#ifdef DBG_TARGET_X86 +#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c +#elif defined(DBG_TARGET_AMD64) +#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x58 +#elif defined(DBG_TARGET_ARM) +#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c +#elif defined(DBG_TARGET_ARM64) +#define WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer 0x58 +#endif + +// sets the value of gCurrentThreadInfo.m_pThread +HRESULT CordbUnmanagedThread::SetEEThreadValue(REMOTE_PTR EETlsValue) +{ + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread; + if(EEThreadAddr == NULL) + return E_FAIL; + + // Write the thread's TLS value. + hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &EETlsValue); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::SEETV: failed to set TLS value: " + "computed addr=0x%p index=%d, err=%x\n", + EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr)); + + return hr; + } + + LOG((LF_CORDB, LL_INFO1000000, + "CUT::SEETV: EE Thread TLS value is now 0x%p for thread 0x%x\n", + EETlsValue, m_id)); + + return S_OK; + +} + +// gets the value of gCurrentThreadInfo.m_pThread +DWORD_PTR CordbUnmanagedThread::GetEEThreadValue() +{ + DWORD_PTR ret = NULL; + + REMOTE_PTR EEThreadAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_CurrentThread; + if(EEThreadAddr == NULL) + return NULL; + + // Read the thread's TLS value. + HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(EEThreadAddr), &ret); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::GEETV: failed to get TLS value: " + "computed addr=0x%p index=%d, err=%x\n", + EEThreadAddr, GetProcess()->m_runtimeOffsets.m_TLSIndex, hr)); + + return NULL; + } + + LOG((LF_CORDB, LL_INFO1000000, + "CUT::GEETV: EE Thread TLS value is 0x%p for thread 0x%x\n", + ret, m_id)); + + return ret; +} + +// returns the remote address of gCurrentThreadInfo +REMOTE_PTR CordbUnmanagedThread::GetClrModuleTlsDataAddress() +{ + HRESULT hr = S_OK; + + REMOTE_PTR tlsArrayAddr; + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_threadLocalBase + WINNT_OFFSETOF__TEB__ThreadLocalStoragePointer), &tlsArrayAddr); + if (FAILED(hr)) + { + return NULL; + } + + if (tlsArrayAddr == NULL) + { + _ASSERTE(!"ThreadLocalStoragePointer is NULL"); + return NULL; + } + + DWORD slot = (DWORD)(GetProcess()->m_runtimeOffsets.m_TLSIndex); + + REMOTE_PTR clrModuleTlsDataAddr; + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)tlsArrayAddr + slot * sizeof(void*)), &clrModuleTlsDataAddr); + if (FAILED(hr)) + { + return NULL; + } + + if (clrModuleTlsDataAddr == NULL) + { + _ASSERTE(!"No clr module data present at _tls_index for this thread"); + return NULL; + } + + return clrModuleTlsDataAddr; +} + +// gets the value of gCurrentThreadInfo.m_EETlsData +REMOTE_PTR CordbUnmanagedThread::GetEETlsDataBlock() +{ + REMOTE_PTR ret; + + REMOTE_PTR blockAddr = (BYTE*) GetClrModuleTlsDataAddress() + OFFSETOF__TLS__tls_EETlsData; + + + HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(blockAddr), &ret); + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::GEETDB: failed to read EETlsData address: computed addr=0x%p offset=%d, err=%x\n", + blockAddr, OFFSETOF__TLS__tls_EETlsData, hr)); + + return NULL; + } + + LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETDB: EETlsData address value is 0x%p for thread 0x%x\n", ret, m_id)); + + return ret; +} + +#endif // FEATURE_IMPLICIT_TLS + +/* + * CacheEEDebuggerWord + * + * NOTE: This routine is inappropriately named at this time because we dont + * actually cache any values. This is because we dont have a way to invalidate + * the cache between purely-native continues. + * + * This routine grabs two pieces of information from the target process via + * ReadProcessMemory. First, if the runtime does not have a thread object for + * this thread it grabs the debugger's value from the TLS slot. If there is a + * runtime thread object, then it saves that away and grabs the debugger's value + * from the thread object. + * + * Parameters: + * None. + * + * Returns: + * None. If it fails, then the Get/Set functions will fail. + */ +void CordbUnmanagedThread::CacheEEDebuggerWord() +{ + LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Entered\n")); + +#ifdef FEATURE_IMPLICIT_TLS + REMOTE_PTR value = (REMOTE_PTR)GetEEThreadValue(); +#else + REMOTE_PTR value = (REMOTE_PTR)GetTlsSlot(GetProcess()->m_runtimeOffsets.m_TLSIndex); +#endif + + if ((((DWORD)value) & 0x1) == 1) + { + m_pEEThread = NULL; + m_pdwTlsValue = (REMOTE_PTR)((BYTE*)value - 0x1); + m_fValidTlsData = TRUE; + } + else if (value != NULL) + { + m_pEEThread = value; + + // Compute the address of the debugger word #2. + void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset; + + // Update the word. + HRESULT hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue); + m_fValidTlsData = SUCCEEDED(hr); + + if (!m_fValidTlsData) + { + LOG((LF_CORDB, LL_INFO1000, "EEDW: failed to read debugger word: 0x%08x + 0x%x = 0x%p, err=%d\n", + m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, GetLastError())); + } + else + { + LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Debugger word is 0x%p\n", m_pdwTlsValue)); + } + } + else + { + m_fValidTlsData = TRUE; + m_pEEThread = NULL; + m_pdwTlsValue = NULL; + } + + LOG((LF_CORDB, LL_INFO1000, "CacheEEDW: Exited\n")); +} + +/* + * GetEEDebuggerWord + * + * This routine returns the value read from the thread + * + * Parameters: + * pValue - Location to store value. + * + * Returns: + * E_INVALIDARG, E_FAIL, S_OK + */ +HRESULT CordbUnmanagedThread::GetEEDebuggerWord(REMOTE_PTR *pValue) +{ + if (pValue == NULL) + { + return E_INVALIDARG; + } + + CacheEEDebuggerWord(); + + if (!m_fValidTlsData) + { + *pValue = NULL; + return E_FAIL; + } + + *pValue = m_pdwTlsValue; + + return S_OK; +} + +// SetEEDebuggerWord +// +// This routine writes the value to the thread +// +// Parameters: +// pValue - Value to write. +// +// Returns: +// HRESULT failure code or S_OK +// +// Notes: +// This function is very dangerous. See code:CordbUnmanagedThread::SetEETlsValue for why. +HRESULT CordbUnmanagedThread::SetEEDebuggerWord(REMOTE_PTR value) +{ + LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Entered - value is 0x%p\n", value)); + + CacheEEDebuggerWord(); + + if (!m_fValidTlsData) + { + return E_FAIL; + } + + m_pdwTlsValue = value; + + // + // If the thread is NULL, bit-or on a 1 and store that. + // + if (m_pEEThread == NULL) + { + REMOTE_PTR pdwTemp = m_pdwTlsValue; + + if (pdwTemp != 0) + { + // actually we add 1, but we only use it for pointers which are + // 8 byte aligned so it is the same thing + _ASSERTE( ((UINT_PTR)pdwTemp & 0x1) == 0); + pdwTemp = (REMOTE_PTR) ((BYTE*)pdwTemp + 0x01); + } + // This will write to the TLS slot. It's only safe to do this after a Flare has been sent from the + // LS (since that's what guarantees the slot is allocated). + return SetEEThreadValue(pdwTemp); + } + else + { + // Compute the address of the debugger word #2. + void *pEEDebuggerWord = (BYTE*)m_pEEThread + GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset; + + // Update the word. + HRESULT hr = GetProcess()->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pEEDebuggerWord), &m_pdwTlsValue); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::SEETDW: failed to write debugger word: 0x%08x + 0x%x = 0x%08x, err=%x\n", + m_pEEThread, GetProcess()->m_runtimeOffsets.m_EEThreadDebuggerWordOffset, pEEDebuggerWord, hr)); + + return hr; + } + } + + LOG((LF_CORDB, LL_INFO1000, "CUT::SEEDW: Exited\n")); + return S_OK; +} + +/* + * GetEEThreadPtr + * + * This routine returns the value read from the thread + * + * Parameters: + * ppEEThread - Location to store value. + * + * Returns: + * E_INVALIDARG, E_FAIL, S_OK + */ +HRESULT CordbUnmanagedThread::GetEEThreadPtr(REMOTE_PTR *ppEEThread) +{ + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + if (ppEEThread == NULL) + { + return E_INVALIDARG; + } + + CacheEEDebuggerWord(); + + if (!m_fValidTlsData) + { + *ppEEThread = NULL; + return E_FAIL; + } + + *ppEEThread = m_pEEThread; + + return S_OK; +} + + + +void CordbUnmanagedThread::GetEEState(bool *threadStepping, bool *specialManagedException) +{ + REMOTE_PTR pEEThread; + + HRESULT hr = GetEEThreadPtr(&pEEThread); + + _ASSERTE(SUCCEEDED(hr)); + _ASSERTE(pEEThread != NULL); + + *threadStepping = false; + *specialManagedException = false; + + // Compute the address of the thread's state + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + void *pEEThreadStateNC = (BYTE*) pEEThread + pRO->m_EEThreadStateNCOffset; + + // Grab the thread state out of the EE Thread. + DWORD EEThreadStateNC; + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadStateNC), &EEThreadStateNC); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread state NC: 0x%p + 0x%x = 0x%p, err=%d\n", + pEEThread, pRO->m_EEThreadStateNCOffset, pEEThreadStateNC, GetLastError())); + + return; + } + + LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread state NC is 0x%08x\n", EEThreadStateNC)); + + // Looks like we've got the state of the thread. + *threadStepping = ((EEThreadStateNC & pRO->m_EEThreadSteppingStateMask) != 0); + *specialManagedException = ((EEThreadStateNC & pRO->m_EEIsManagedExceptionStateMask) != 0); + + return; +} + +// Currently, the EE manually tracks its "can't-stop" regions. This retrieves that manual tracking value. +// @todo - This should eventually become deprecated since the Entire EE will be a can't-stop region. +bool CordbUnmanagedThread::GetEEThreadCantStopHelper() +{ + // Note: any failure to read memory is okay for this method. We simply say that the thread is not is a can't stop + // state, and that's okay. + + REMOTE_PTR pEEThread; + + HRESULT hr = GetEEThreadPtr(&pEEThread); + + _ASSERTE(SUCCEEDED(hr)); + _ASSERTE(pEEThread != NULL); + + // Compute the address of the thread's debugger word #1 + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + void *pEEThreadCantStop = (BYTE*) pEEThread + pRO->m_EEThreadCantStopOffset; + + // Grab the debugger word #1 out of the EE Thread. + DWORD EEThreadCantStop; + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadCantStop), &EEThreadCantStop); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread cant stop: 0x%08x + 0x%x = 0x%08x, err=%d\n", + pEEThread, pRO->m_EEThreadCantStopOffset, pEEThreadCantStop, GetLastError())); + + return false; + } + + LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread cant stop is 0x%08x\n", EEThreadCantStop)); + + // Looks like we've got it. + if (EEThreadCantStop != 0) + return true; + else + return false; +} + + +// Is the thread in a "can't stop" region? +// "Can't-Stop" regions include anything that's "inside" the runtime; ie, the runtime has some +// synchronization mechanism that will halt this thread, and so we don't need to suspend it. +// The interop debugger should leave anything in a can't-stop region alone and just let the runtime +// handle it. +bool CordbUnmanagedThread::IsCantStop() +{ + CONTRACTL + { + NOTHROW; + } + CONTRACTL_END; + + // Definition of a can't stop region: + // - Any "Special" thread that doesn't have an EE Thread (includes the real Helper Thread, + // Concurrent GC thread, ThreadPool thread, etc). + // - Any thread in Cooperative code. + // - Any thread w/ a can't-stop count > 0. + // - Any thread holding a "Debugger" Crst. (This is actually a subset of the + // can't-stop count b/c Enter/Leave adjust that count). + // - Any generic, first chance or RaiseException hijacked thread + + // If the runtime isn't init yet, not a can't-stop. + // We don't even have the DCB yet. + if (!GetProcess()->m_initialized) + { + return false; + } + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + if(IsRaiseExceptionHijacked()) + { + return true; + } + + REMOTE_PTR pEEThread; + HRESULT hr = this->GetEEThreadPtr(&pEEThread); + + if (FAILED(hr)) + { + _ASSERTE(!"Failed to EEThreadPtr in IsCantStop"); + return true; + } + + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + + // @todo- remove this and use the CantStop index below. + // Is this a "special" thread? + // Any thread that can take CLR locks w/o having an EE Thread object should + // be marked as special. These threads are in "can't-stop" regions b/c if we suspend + // them, they may be holding a lock that blocks the helper thread. + // The helper thread is marked as "special". + { + SIZE_T idx = pRO->m_TLSIsSpecialIndex; + REMOTE_PTR special = GetPreDefTlsSlot(idx, NULL); + + // If it's a special thread + if ((special != 0) && (pEEThread == NULL)) + { + return true; + } + } + + // Check for CantStop regions off the FLS. + // This is the biggest way to describe can't-stop regions when we're in preemptive mode + // (or when we don't have a thread object). + // If a LS thread takes a debugger lock, it will increment the Can't-Stop count. + { + SIZE_T idx = pRO->m_TLSCantStopIndex; + REMOTE_PTR count = (REMOTE_PTR) GetPreDefTlsSlot(idx, NULL); + + // Just a sanity check here. There's nothing special about 1000, but if the + // stop-count gets this big, 99% chance it's: + // - we're accessing the wrong memory (an issue) + // - someone on the LS is leaking stop-counts. (an issue). + _ASSERTE(count < (REMOTE_PTR)1000); + + if (count > 0) + { + LOG((LF_CORDB, LL_INFO1000000, "Thread 0x%x is can't-stop b/c count=%d\n", m_id, count)); + return true; + } + } + + EX_TRY + { + GetProcess()->UpdateRightSideDCB(); + } + EX_CATCH + { + _ASSERTE(!"IsCantStop: Failed updating debugger control block"); + } + EX_END_CATCH(SwallowAllExceptions); + + // Helper's canary thread is always can't-stop. + if (this->m_id == GetProcess()->GetDCB()->m_CanaryThreadId) + { + return true; + } + + // Check helper thread / or anyone pretending to be the helper thread. + if ((this->m_id == GetProcess()->GetDCB()->m_helperThreadId) || + (this->m_id == GetProcess()->GetDCB()->m_temporaryHelperThreadId) || + (this->m_id == GetProcess()->m_helperThreadId)) + { + return true; + } + + if (IsGenericHijacked() || IsFirstChanceHijacked()) + return true; + + // If this isn't a EE thread (and not the helper thread, and not hijacked), then it's ok to stop. + if (pEEThread == NULL) + return false; + + // This checks for an explicit "can't" stop region. + // Eventually, these explicit regions should become a complete subset of the other checks. + if (GetEEThreadCantStopHelper()) + return true; + + + // If we're in cooperative mode (either managed code or parts inside the runtime), then don't stop. + // Note we could remove this since the check is made in side of the DAC request below, + // but it's faster to look here. + if (GetEEPGCDisabled()) + return true; + + return false; +} + +bool CordbUnmanagedThread::GetEEPGCDisabled() +{ + // Note: any failure to read memory is okay for this method. We simply say that the thread has PGC disabled, which + // is always the worst case scenario. + + REMOTE_PTR pEEThread; + + HRESULT hr = GetEEThreadPtr(&pEEThread); + + _ASSERTE(SUCCEEDED(hr)); + + // Compute the address of the thread's PGC disabled word + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + void *pEEThreadPGCDisabled = (BYTE*) pEEThread + pRO->m_EEThreadPGCDisabledOffset; + + // Grab the PGC disabled word out of the EE Thread. + DWORD EEThreadPGCDisabled; + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadPGCDisabled), &EEThreadPGCDisabled); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::GEETS: failed to read thread PGC Disabled: 0x%p + 0x%x = 0x%p, err=%d\n", + pEEThread, pRO->m_EEThreadPGCDisabledOffset, pEEThreadPGCDisabled, GetLastError())); + + return true; + } + + LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETS: EE Thread PGC Disabled is 0x%08x\n", EEThreadPGCDisabled)); + + // Looks like we've got it. + if (EEThreadPGCDisabled == pRO->m_EEThreadPGCDisabledValue) + return true; + else + return false; +} + +bool CordbUnmanagedThread::GetEEFrame() +{ + REMOTE_PTR pEEThread; + + HRESULT hr = GetEEThreadPtr(&pEEThread); + + _ASSERTE(SUCCEEDED(hr)); + _ASSERTE(pEEThread != NULL); + + // Compute the address of the thread's frame ptr + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + void *pEEThreadFrame = (BYTE*) pEEThread + pRO->m_EEThreadFrameOffset; + + // Grab the thread's frame out of the EE Thread. + DWORD EEThreadFrame; + hr = GetProcess()->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pEEThreadFrame), &EEThreadFrame); + + if (FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::GEETF: failed to read thread frame: 0x%p + 0x%x = 0x%p, err=%d\n", + pEEThread, pRO->m_EEThreadFrameOffset, pEEThreadFrame, GetLastError())); + + return false; + } + + LOG((LF_CORDB, LL_INFO1000000, "CUT::GEETF: EE Thread's frame is 0x%08x\n", EEThreadFrame)); + + // Looks like we've got the frame of the thread. + if (EEThreadFrame != pRO->m_EEMaxFrameValue) + return true; + else + return false; +} + +// Gets the thread context as if the thread were unhijacked, regardless +// of whether it really is +HRESULT CordbUnmanagedThread::GetThreadContext(DT_CONTEXT* pContext) +{ + // While hijacked there are 3 potential contexts we could be resuming back to + // 1) A context provided in SetThreadContext that we defered applying + // 2) The LS copy of the context on the stack being modified in the handler + // 3) The original context present when the hijack was started + // + // Both #1 and #3 are stored in the GetHijackCtx() space so of course you can't + // have them both. You have have #1 if IsContextSet() is true, otherwise it holds #3 + // + // GenericHijack, FirstChanceHijackForSync, and RaiseExceptionHijack use #1 if available + // and fallback to #3 if not. In other words they use GetHijackCtx() regardless of which thing it holds + // M2UHandoff uses #1 if available and then falls back to #2. + // + // The reasoning here is that the first three hijacks are intended to be transparent. Since + // the debugger shouldn't know they are occuring then it shouldn't see changes potentially + // made on the LS. The M2UHandoff is not transparent, it has to update the context in order + // to get clear of a bp. + // + // If not hijacked call the normal Win32 function. + + HRESULT hr = S_OK; + + LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: thread=0x%p, flags=0x%x.\n", this, pContext->ContextFlags)); + + if(IsContextSet() || IsGenericHijacked() || (IsFirstChanceHijacked() && IsBlockingForSync()) + || IsRaiseExceptionHijacked()) + { + _ASSERTE(IsFirstChanceHijacked() || IsGenericHijacked() || IsRaiseExceptionHijacked()); + LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: hijackCtx case IsContextSet=%d IsGenericHijacked=%d" + "HijackedForSync=%d RaiseExceptionHijacked=%d.\n", + IsContextSet(), IsGenericHijacked(), IsBlockingForSync(), IsRaiseExceptionHijacked())); + LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: hijackCtx is:\n")); + LogContext(GetHijackCtx()); + CORDbgCopyThreadContext(pContext, GetHijackCtx()); + } + // use the LS for M2UHandoff + else if (IsFirstChanceHijacked() && !IsBlockingForSync()) + { + LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: getting LS context for first chance hijack, addr=0x%08x.\n", + m_pLeftSideContext.UnsafeGet())); + + // Read the context into a temp context then copy to the out param. + DT_CONTEXT tempContext = { 0 }; + + hr = GetProcess()->SafeReadThreadContext(m_pLeftSideContext, &tempContext); + + if (SUCCEEDED(hr)) + CORDbgCopyThreadContext(pContext, &tempContext); + } + // no hijack in place so just call straight through + else + { + LOG((LF_CORDB, LL_INFO10000, "CUT::GTC: getting context from win32.\n")); + + BOOL succ = DbiGetThreadContext(m_handle, pContext); + + if (!succ) + hr = HRESULT_FROM_GetLastError(); + } + + if(IsSSFlagHidden()) + { + UnsetSSFlag(pContext); + } + LogContext(pContext); + + return hr; +} + +// Sets the thread context as if the thread were unhijacked, regardless +// of whether it really is. See GetThreadContext above for more details +// on this abstraction +HRESULT CordbUnmanagedThread::SetThreadContext(DT_CONTEXT* pContext) +{ + HRESULT hr = S_OK; + + LOG((LF_CORDB, LL_INFO10000, + "CUT::STC: thread=0x%p, flags=0x%x.\n", this, pContext->ContextFlags)); + + LogContext(pContext); + + // If the thread is first chance hijacked, then write the context into the remote process. If the thread is generic + // hijacked, then update the copy of the context that we already have. Otherwise call the normal Win32 function. + + if (IsGenericHijacked() || IsFirstChanceHijacked() || IsRaiseExceptionHijacked()) + { + if(IsGenericHijacked()) + { + LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from generic/2nd chance hijack.\n")); + } + else if(IsFirstChanceHijacked()) + { + LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from 1st chance hijack.\n")); + } + else + { + LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from RaiseException hijack.\n")); + } + SetState(CUTS_HasContextSet); + CORDbgCopyThreadContext(GetHijackCtx(), pContext); + } + else + { + LOG((LF_CORDB, LL_INFO10000, "CUT::STC: setting context from win32.\n")); + + // If the user is also setting the SS flag then we no longer have to hide it + if(IsSSFlagEnabled(pContext)) + { + ClearState(CUTS_IsSSFlagHidden); + } + // if the user is turning off the SS flag but we still want it on then leave it on + // but hidden + if(!IsSSFlagEnabled(pContext) && IsSSFlagNeeded()) + { + SetState(CUTS_IsSSFlagHidden); + SetSSFlag(pContext); + } + + BOOL succ = DbiSetThreadContext(m_handle, pContext); + + if (!succ) + { + hr = HRESULT_FROM_GetLastError(); + } + } + + return hr; +} + +// Turns on the stepping flag internally and tracks whether or not the flag +// should also be seen by the user +VOID CordbUnmanagedThread::BeginStepping() +{ + _ASSERTE(!IsGenericHijacked() && !IsFirstChanceHijacked()); + _ASSERTE(!IsSSFlagNeeded()); + _ASSERTE(!IsSSFlagHidden()); + + DT_CONTEXT tempContext; + tempContext.ContextFlags = DT_CONTEXT_FULL; + BOOL succ = DbiGetThreadContext(m_handle, &tempContext); + _ASSERTE(succ); + + if(!IsSSFlagEnabled(&tempContext)) + { + SetSSFlag(&tempContext); + SetState(CUTS_IsSSFlagHidden); + } + SetState(CUTS_IsSSFlagNeeded); + + succ = DbiSetThreadContext(m_handle, &tempContext); + _ASSERTE(succ); +} + +// Turns off the stepping flag internally. If the user was also not using it then +// the flag is turned off on the context +VOID CordbUnmanagedThread::EndStepping() +{ + _ASSERTE(!IsGenericHijacked() && !IsFirstChanceHijacked()); + _ASSERTE(IsSSFlagNeeded()); + + DT_CONTEXT tempContext; + tempContext.ContextFlags = DT_CONTEXT_FULL; + BOOL succ = DbiGetThreadContext(m_handle, &tempContext); + _ASSERTE(succ); + + if(IsSSFlagHidden()) + { + UnsetSSFlag(&tempContext); + ClearState(CUTS_IsSSFlagHidden); + } + ClearState(CUTS_IsSSFlagNeeded); + + succ = DbiSetThreadContext(m_handle, &tempContext); + _ASSERTE(succ); +} + + +// Writes some details of the given context into the debugger log +VOID CordbUnmanagedThread::LogContext(DT_CONTEXT* pContext) +{ +#if defined(DBG_TARGET_X86) + LOG((LF_CORDB, LL_INFO10000, + "CUT::LC: Eip=0x%08x, Esp=0x%08x, Eflags=0x%08x\n", pContext->Eip, pContext->Esp, + pContext->EFlags)); +#elif defined(DBG_TARGET_AMD64) + LOG((LF_CORDB, LL_INFO10000, + "CUT::LC: Rip=" FMT_ADDR ", Rsp=" FMT_ADDR ", Eflags=0x%08x\n", + DBG_ADDR(pContext->Rip), + DBG_ADDR(pContext->Rsp), + pContext->EFlags)); // EFlags is still 32bits on AMD64 +#else // DBG_TARGET_X86 + PORTABILITY_ASSERT("LogContext needs a PC and stack pointer."); +#endif // DBG_TARGET_X86 +} + +// Hijacks this thread using the FirstChanceSuspend hijack +HRESULT CordbUnmanagedThread::SetupFirstChanceHijackForSync() +{ + HRESULT hr = S_OK; + + CONSISTENCY_CHECK(!IsBlockingForSync()); // Shouldn't double hijack + CONSISTENCY_CHECK(!IsCantStop()); // must be in stoppable-region. + _ASSERTE(HasIBEvent()); + + // We used to hijack for real here but now we have a vectored exception handler that will always be + // triggered. So we don't have hijack in the sense that we overwrite the thread's IP. However we still + // set the flag so that when we receive the HijackStartedSignal from the LS we know that this thread + // should block in there rather than continuing. + //hr = SetupFirstChanceHijack(EHijackReason::kFirstChanceSuspend, &(IBEvent()->m_currentDebugEvent.u.Exception.ExceptionRecord)); + + _ASSERTE(!IsFirstChanceHijacked()); + _ASSERTE(!IsGenericHijacked()); + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + // We'd better not be hijacking in a can't stop region! + // This also means we can't hijack in coopeative (since that's a can't-stop) + _ASSERTE(!IsCantStop()); + + // we should not be stepping into hijacks + _ASSERTE(!IsSSFlagHidden()); + _ASSERTE(!IsSSFlagNeeded()); + _ASSERTE(!IsContextSet()); + + // snapshot the current context so we can start spoofing it + LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: hijackCtx started as:\n")); + LogContext(GetHijackCtx()); + + // Save the thread's full context. + DT_CONTEXT context; + context.ContextFlags = DT_CONTEXT_FULL; + BOOL succ = DbiGetThreadContext(m_handle, &context); + _ASSERTE(succ); + // for debugging when GetThreadContext fails + if(!succ) + { + DWORD error = GetLastError(); + LOG((LF_CORDB, LL_ERROR, "CUT::SFCHFS: DbiGetThreadContext error=0x%x\n", error)); + } + + GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL; + CORDbgCopyThreadContext(GetHijackCtx(), &context); + LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: thread=0x%x Hijacking for sync. Original context is:\n", this)); + LogContext(GetHijackCtx()); + + // We're hijacking now... + SetState(CUTS_FirstChanceHijacked); + GetProcess()->m_state |= CordbProcess::PS_HIJACKS_IN_PLACE; + + // We'll decrement this once the hijack returns + GetProcess()->m_cFirstChanceHijackedThreads++; + this->SetState(CUTS_BlockingForSync); + + // we don't want to single step into the vectored exception handler + // we will restore the SS flag after returning from the hijack + if(IsSSFlagEnabled(&context)) + { + LOG((LF_CORDB, LL_INFO10000, "CUT::SFCHFS: thread=0x%x Clearing SS flag\n", this)); + UnsetSSFlag(&context); + succ = DbiSetThreadContext(m_handle, &context); + _ASSERTE(succ); + } + + + + // There's a bizarre race where the thread was suspended right as the thread was about to dispatch a + // debug event. We still get the debug event, and then may try to hijack. Resume the thread so that + // it can run to the hijack. + if (this->IsSuspended()) + { + LOG((LF_CORDB, LL_ERROR, "CUT::SFCHFS: thread was suspended... resuming\n")); + DWORD success = ResumeThread(this->m_handle); + + if (success == 0xFFFFFFFF) + { + // Since we suspended it, we should be able to resume it in this window. + CONSISTENCY_CHECK_MSGF(false, ("Failed to resume thread: tid=0x%x!", this->m_id)); + } + else + { + this->ClearState(CUTS_Suspended); + } + } + + return hr; + +} + +HRESULT CordbUnmanagedThread::SetupFirstChanceHijack(EHijackReason::EHijackReason reason, const EXCEPTION_RECORD * pExceptionRecord) +{ + _ASSERTE(!IsFirstChanceHijacked()); + _ASSERTE(!IsGenericHijacked()); + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + // We'd better not be hijacking in a can't stop region! + // This also means we can't hijack in coopeative (since that's a can't-stop) + _ASSERTE(!IsCantStop()); + + // we should not be stepping into hijacks + _ASSERTE(!IsSSFlagHidden()); + _ASSERTE(!IsSSFlagNeeded()); + + // There's a bizarre race where the thread was suspended right as the thread was about to dispatch a + // debug event. We still get the debug event, and then may try to hijack. Resume the thread so that + // it can run to the hijack. + if (this->IsSuspended()) + { + DWORD succ = ResumeThread(this->m_handle); + + if (succ == 0xFFFFFFFF) + { + // Since we suspended it, we should be able to resume it in this window. + CONSISTENCY_CHECK_MSGF(false, ("Failed to resume thread: tid=0x%x!", this->m_id)); + } + else + { + this->ClearState(CUTS_Suspended); + } + } + + HRESULT hr = S_OK; + EX_TRY + { +// We save off the SEH handler on X86 to make sure we restore it properly after the hijack is complete +// The hijacks don't return normally and the SEH chain might have handlers added that don't get removed by default +#ifdef DBG_TARGET_X86 + hr = SaveCurrentLeafSeh(); + if(FAILED(hr)) + ThrowHR(hr); +#endif + CORDB_ADDRESS LSContextAddr; + GetProcess()->GetDAC()->Hijack(VMPTR_Thread::NullPtr(), + GetOSTid(), + pExceptionRecord, + (CONTEXT*) GetHijackCtx(), + sizeof(CONTEXT), + reason, + NULL, + &LSContextAddr); + LOG((LF_CORDB, LL_INFO10000, "CUT::SFCH: pLeftSideContext=0x%p\n", LSContextAddr)); + m_pLeftSideContext.Set(CORDB_ADDRESS_TO_PTR(LSContextAddr)); + } + EX_CATCH_HRESULT(hr); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO10000, "CUT::SFCH: Error setting up hijack context hr=0x%x\n", hr)); + return hr; + } + + + // We're hijacked now... + SetState(CUTS_FirstChanceHijacked); + GetProcess()->m_state |= CordbProcess::PS_HIJACKS_IN_PLACE; + + // We'll decrement this once the hijack returns + GetProcess()->m_cFirstChanceHijackedThreads++; + + return S_OK; +} + +HRESULT CordbUnmanagedThread::SetupGenericHijack(DWORD eventCode, const EXCEPTION_RECORD * pRecord) +{ + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + _ASSERTE(eventCode == EXCEPTION_DEBUG_EVENT); + + _ASSERTE(!IsFirstChanceHijacked()); + _ASSERTE(!IsGenericHijacked()); + _ASSERTE(!IsContextSet()); + + // Save the thread's full context. + GetHijackCtx()->ContextFlags = DT_CONTEXT_FULL; + + BOOL succ = DbiGetThreadContext(m_handle, GetHijackCtx()); + + if (!succ) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::SGH: couldn't get thread context: %d\n", GetLastError())); + return HRESULT_FROM_WIN32(GetLastError()); + } + +#if defined(DBG_TARGET_AMD64) + + // On X86 Debugger::GenericHijackFunc() ensures the stack is walkable + // by simply using the EBP chain, therefore we can execute the hijack + // by setting the thread's context EIP to point to this function. + // On X64, however, we first attempt to set up a "proper" hijack, with + // a function that allows the OS to unwind the stack (ExceptionHijack). + // If this fails we'll use the same method as on X86, even though the + // stack will become un-walkable + + ULONG32 dwThreadId = GetOSTid(); + CordbThread * pThread = GetProcess()->TryLookupOrCreateThreadByVolatileOSId(dwThreadId); + + // For threads in the thread store we set up the full size + // hijack, otherwise we fallback to hijacking by SetIP. + if (pThread != NULL) + { + HRESULT hr = S_OK; + EX_TRY + { + // Note that the data-target is not atomic, and we have no rollback mechanism. + // We have to do several writes. If the data-target fails the writes half-way through the + // target will be inconsistent. + GetProcess()->GetDAC()->Hijack( + pThread->m_vmThreadToken, + dwThreadId, + pRecord, + (CONTEXT*) GetHijackCtx(), + sizeof(CONTEXT), + EHijackReason::kGenericHijack, + NULL, + NULL); + } + EX_CATCH_HRESULT(hr); + if (SUCCEEDED(hr)) + { + // Remember that we've hijacked the thread. + SetState(CUTS_GenericHijacked); + + return S_OK; + } + + STRESS_LOG1(LF_CORDB, LL_INFO1000, "CUT::SGH: Error setting up hijack context hr=0x%x\n", hr); + // fallthrough (above hijack might have failed due to stack overflow, for example) + + } + // else (non-threadstore threads) fallthrough + +#endif // DBG_TARGET_AMD64 + + // Remember that we've hijacked the guy. + SetState(CUTS_GenericHijacked); + + LOG((LF_CORDB, LL_INFO1000000, "CUT::SGH: Current IP is 0x%08x\n", CORDbgGetIP(GetHijackCtx()))); + + DebuggerIPCRuntimeOffsets *pRO = &(GetProcess()->m_runtimeOffsets); + + // Wack the IP over to our generic hijack function. + LPVOID holdIP = CORDbgGetIP(GetHijackCtx()); + CORDbgSetIP(GetHijackCtx(), pRO->m_genericHijackFuncAddr); + + LOG((LF_CORDB, LL_INFO1000000, "CUT::SGH: New IP is 0x%08x\n", CORDbgGetIP(GetHijackCtx()))); + + // We should never single step into the hijack + BOOL isSSFlagOn = IsSSFlagEnabled(GetHijackCtx()); + if(isSSFlagOn) + { + UnsetSSFlag(GetHijackCtx()); + } + + succ = DbiSetThreadContext(m_handle, GetHijackCtx()); + + if (!succ) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::SGH: couldn't set thread context: %d\n", GetLastError())); + + return HRESULT_FROM_WIN32(GetLastError()); + } + + // Put the original IP back into the local context copy for later. + CORDbgSetIP(GetHijackCtx(), holdIP); + // Set the original SS flag into the local context copy for later + if(isSSFlagOn) + { + SetSSFlag(GetHijackCtx()); + } + return S_OK; +} + +HRESULT CordbUnmanagedThread::FixupFromGenericHijack() +{ + LOG((LF_CORDB, LL_INFO1000, "CUT::FFGH: fixing up from generic hijack. Eip=0x%p, Esp=0x%p\n", + CORDbgGetIP(GetHijackCtx()), CORDbgGetSP(GetHijackCtx()))); + + // We're no longer hijacked + _ASSERTE(IsGenericHijacked()); + ClearState(CUTS_GenericHijacked); + + // Clear the exception so we do a DBG_CONTINUE with the original context. Note: we only do generic hijacks on + // in-band events. + IBEvent()->SetState(CUES_ExceptionCleared); + + // Using the context we saved when the event came in originally or the new context if set by user, + // reset the thread as if it were never hijacked. + BOOL succ = DbiSetThreadContext(m_handle, GetHijackCtx()); + // if the user set the context it has been applied now + ClearState(CUTS_HasContextSet); + + if (!succ) + { + LOG((LF_CORDB, LL_INFO1000, "CUT::FFGH: couldn't set thread context: %d\n", GetLastError())); + + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +DT_CONTEXT * CordbUnmanagedThread::GetHijackCtx() +{ + return &m_context; +} + + +// Enable Single-Step (and bump the eip back one) +// This can only be called after a bp. (because we assume that we executed a bp when we adjust the eip). +HRESULT CordbUnmanagedThread::EnableSSAfterBP() +{ + DT_CONTEXT c; + c.ContextFlags = DT_CONTEXT_FULL; + + BOOL succ = DbiGetThreadContext(m_handle, &c); + + if (!succ) + return HRESULT_FROM_WIN32(GetLastError()); + + SetSSFlag(&c); + + // 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. + CORDbgAdjustPCForBreakInstruction(&c); + + succ = DbiSetThreadContext(m_handle, &c); + + if (!succ) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +// +// FixupAfterOOBException automatically gets the debuggee past an OOB exception event. These are only BP or SS +// events. For SS, we just clear it, assuming that the only reason the thread was stepped in such place was to get it +// off of a BP. For a BP, we clear and backup the IP by one, and turn the trace flag on under the assumption that the +// only thing a debugger is allowed to do with an OOB BP exception is to get us off of it. +// +HRESULT CordbUnmanagedThread::FixupAfterOOBException(CordbUnmanagedEvent *ue) +{ + // We really should only be doing things to single steps and breakpoint exceptions. + if (ue->m_currentDebugEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) + { + DWORD ec = ue->m_currentDebugEvent.u.Exception.ExceptionRecord.ExceptionCode; + + if ((ec == STATUS_BREAKPOINT) || (ec == STATUS_SINGLE_STEP)) + { + // Automatically clear the exception. + ue->SetState(CUES_ExceptionCleared); + + // Don't bother about toggling the single-step flag. OOB BPs should only be called + // for raw int3 instructions, so no need to rewind and reexecute. + } + } + + return S_OK; +} + + +//----------------------------------------------------------------------------- +// Setup to skip an native breakpoint +//----------------------------------------------------------------------------- +void CordbUnmanagedThread::SetupForSkipBreakpoint(NativePatch * pNativePatch) +{ + _ASSERTE(pNativePatch != NULL); + _ASSERTE(!IsSkippingNativePatch()); + _ASSERTE(m_pPatchSkipAddress == NULL); + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + SetState(CUTS_SkippingNativePatch); + +#ifdef _DEBUG + // For debugging, provide a way that Cordbg devs can see if we're silently skipping BPs. + static DWORD fTrapOnSkip = -1; + if (fTrapOnSkip == -1) + fTrapOnSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTrapOnSkip); + + if (fTrapOnSkip) + { + CONSISTENCY_CHECK_MSGF(false, ("The CLR is skipping a native BP at %p on thread 0x%x (%d)." + "\nYou're getting this notification in debug builds b/c you have com+ var 'DbgTrapOnSkip' enabled.", + pNativePatch->pAddress, this->m_id, this->m_id)); + + // We skipped this BP b/c IsCantStop was true. For debugging convenience, call IsCantStop here + // (in case we break at the assert above and want to trace why we're in a CS region) + bool fCantStop = this->IsCantStop(); + LOG((LF_CORDB, LL_INFO1000, "In Can'tStopRegion = %d\n", fCantStop)); + + // Refresh the reg key + fTrapOnSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgTrapOnSkip); + } +#endif +#if defined(DBG_TARGET_X86) + STRESS_LOG2(LF_CORDB, LL_INFO100, "CUT::SetupSkip. adddr=%p. Opcode=%x\n", pNativePatch->pAddress, (DWORD) pNativePatch->opcode); +#endif + + // Replace the BP w/ the opcode. + RemoveRemotePatch(GetProcess(), pNativePatch->pAddress, pNativePatch->opcode); + + // Enable the SS flag & Adjust IP. + HRESULT hr = this->EnableSSAfterBP(); + SIMPLIFYING_ASSUMPTION(SUCCEEDED(hr)); + + + // Now we return, + // Process continues, LS will single step past BP, and fire a SS exception. + // When we get the SS, we res + + + // We need to remember this so we can make sure we fixup at the proper address. + // The address of a ss exception is the instruction we finish on, not where + // we originally placed the BP. Since instructions can be variable length, + // we can't work backwards. + m_pPatchSkipAddress = pNativePatch->pAddress; +} + +//----------------------------------------------------------------------------- +// Second half of skipping a native bp. +// Note we pass the address in b/c our caller has (from the debug_evet), and +// we don't want to waste storage to remember it ourselves. +//----------------------------------------------------------------------------- +void CordbUnmanagedThread::FixupForSkipBreakpoint() +{ + _ASSERTE(m_pPatchSkipAddress != NULL); + _ASSERTE(IsSkippingNativePatch()); + _ASSERTE(GetProcess()->ThreadHoldsProcessLock()); + + ClearState(CUTS_SkippingNativePatch); + + // Only reapply the int3 if it hasn't been removed yet. + if (GetProcess()->GetNativePatch(m_pPatchSkipAddress) != NULL) + { + ApplyRemotePatch(GetProcess(), m_pPatchSkipAddress); + STRESS_LOG1(LF_CORDB, LL_INFO100, "CUT::FixupSetupSkip. adddr=%p\n", m_pPatchSkipAddress); + } + else + { + STRESS_LOG1(LF_CORDB, LL_INFO100, "CUT::FixupSetupSkip. Patch removed. Not-readding. adddr=%p\n", m_pPatchSkipAddress); + } + + m_pPatchSkipAddress = NULL; +} + +inline TADDR GetSP(DT_CONTEXT* context) +{ +#if defined(DBG_TARGET_X86) + return (TADDR)context->Esp; +#elif defined(DBG_TARGET_AMD64) + return (TADDR)context->Rsp; +#elif defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64) + return (TADDR)context->Sp; +#else + _ASSERTE(!"nyi for platform"); +#endif +} + +BOOL CordbUnmanagedThread::GetStackRange(CORDB_ADDRESS *pBase, CORDB_ADDRESS *pLimit) +{ +#if !defined(FEATURE_DBGIPC_TRANSPORT) + + if (m_stackBase == 0 && m_stackLimit == 0) + { + HANDLE hProc; + DT_CONTEXT tempContext; + MEMORY_BASIC_INFORMATION mbi; + + tempContext.ContextFlags = DT_CONTEXT_FULL; + if (SUCCEEDED(GetProcess()->GetHandle(&hProc)) && + SUCCEEDED(GetThreadContext(&tempContext)) && + ::VirtualQueryEx(hProc, (LPCVOID)GetSP(&tempContext), &mbi, sizeof(mbi)) != 0) + { + // the lowest stack address is the AllocationBase + TADDR limit = PTR_TO_TADDR(mbi.AllocationBase); + + // Now, on to find the stack base: + // Closest to the AllocationBase we might have a MEM_RESERVED block + // for all the as yet unallocated pages... + TADDR regionBase = limit; + if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0 + || mbi.Type != MEM_PRIVATE) + goto Exit; + + if (mbi.State == MEM_RESERVE) + regionBase += mbi.RegionSize; + + // Next we might have a few guard pages + if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0 + || mbi.Type != MEM_PRIVATE) + goto Exit; + + if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_GUARD) != 0) + regionBase += mbi.RegionSize; + + // And finally the "regular" stack region + if (::VirtualQueryEx(hProc, (LPCVOID) regionBase, &mbi, sizeof(mbi)) == 0 + || mbi.Type != MEM_PRIVATE) + goto Exit; + + if (mbi.State == MEM_COMMIT && (mbi.Protect & PAGE_READWRITE) != 0) + regionBase += mbi.RegionSize; + + if (limit == regionBase) + goto Exit; + + m_stackLimit = limit; + m_stackBase = regionBase; + } + } + +Exit: + if (pBase != NULL) + *pBase = m_stackBase; + if (pLimit != NULL) + *pLimit = m_stackLimit; + + return (m_stackBase != 0 || m_stackLimit != 0); + +#else + + if (pBase != NULL) + *pBase = 0; + if (pLimit != NULL) + *pLimit = 0; + + return FALSE; + +#endif // FEATURE_DBGIPC_TRANSPORT +} + +//----------------------------------------------------------------------------- +// Returns the thread context to the state it was in when it last entered RaiseException +// This allows the thread to retrigger an exception caused by RaiseException +//----------------------------------------------------------------------------- +void CordbUnmanagedThread::HijackToRaiseException() +{ + LOG((LF_CORDB, LL_INFO1000, "CP::HTRE: hijacking to RaiseException\n")); + _ASSERTE(HasRaiseExceptionEntryCtx()); + _ASSERTE(!IsRaiseExceptionHijacked()); + _ASSERTE(!IsGenericHijacked()); + _ASSERTE(!IsFirstChanceHijacked()); + _ASSERTE(!IsContextSet()); + + BOOL succ = DbiGetThreadContext(m_handle, GetHijackCtx()); + _ASSERTE(succ); + succ = DbiSetThreadContext(m_handle, &m_raiseExceptionEntryContext); + _ASSERTE(succ); + SetState(CUTS_IsRaiseExceptionHijacked); +} + +//---------------------------------------------------------------------------- +// Returns the context to its unhijacked state. +//---------------------------------------------------------------------------- +void CordbUnmanagedThread::RestoreFromRaiseExceptionHijack() +{ + LOG((LF_CORDB, LL_INFO1000, "CP::RFREH: ending RaiseException hijack\n")); + _ASSERTE(IsRaiseExceptionHijacked()); + + DT_CONTEXT restoreContext; + restoreContext.ContextFlags = DT_CONTEXT_FULL; + HRESULT hr = GetThreadContext(&restoreContext); + _ASSERTE(SUCCEEDED(hr)); + + ClearState(CUTS_IsRaiseExceptionHijacked); + hr = SetThreadContext(&restoreContext); + _ASSERTE(SUCCEEDED(hr)); +} + +//----------------------------------------------------------------------------- +// Attempts to store the state of a thread currently entering RaiseException +// This grabs both a full context and enough state to determine what exception +// RaiseException should be raising. If any of the state can not be retrieved +// then this entrance to RaiseException is silently ignored +//----------------------------------------------------------------------------- +void CordbUnmanagedThread::SaveRaiseExceptionEntryContext() +{ + _ASSERTE(FALSE); // should be unused now + LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: saving raise exception context.\n")); + _ASSERTE(!HasRaiseExceptionEntryCtx()); + _ASSERTE(!IsRaiseExceptionHijacked()); + HRESULT hr = S_OK; + DT_CONTEXT context; + context.ContextFlags = DT_CONTEXT_FULL; + DbiGetThreadContext(m_handle, &context); + // if the flag is set, unset it + // we don't want to be single stepping through RaiseException the second time + // sending out OOB SS events. Ultimately we will rethrow the exception which would + // cleared the SS flag anyways. + UnsetSSFlag(&context); + memcpy(&m_raiseExceptionEntryContext, &context, sizeof(DT_CONTEXT)); + + // calculate the exception that we would expect to come from this invocation of RaiseException + REMOTE_PTR pExceptionInformation = NULL; +#if defined(DBG_TARGET_AMD64) + m_raiseExceptionExceptionCode = (DWORD)m_raiseExceptionEntryContext.Rcx; + m_raiseExceptionExceptionFlags = (DWORD)m_raiseExceptionEntryContext.Rdx; + m_raiseExceptionNumberParameters = (DWORD)m_raiseExceptionEntryContext.R8; + pExceptionInformation = (REMOTE_PTR)m_raiseExceptionEntryContext.R9; +#elif defined(DBG_TARGET_X86) + hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+4), &m_raiseExceptionExceptionCode); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception code.\n")); + return; + } + hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+8), &m_raiseExceptionExceptionFlags); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception flags.\n")); + return; + } + hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+12), &m_raiseExceptionNumberParameters); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read number of parameters.\n")); + return; + } + hr = m_pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS((BYTE*)m_raiseExceptionEntryContext.Esp+16), &pExceptionInformation); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception information pointer.\n")); + return; + } +#elif + _ASSERTE(!"Implement this for your platform"); + return; +#endif + LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: RaiseException parameters are 0x%x 0x%x 0x%x 0x%p.\n", + m_raiseExceptionExceptionCode, m_raiseExceptionExceptionFlags, + m_raiseExceptionNumberParameters, pExceptionInformation)); + TargetBuffer exceptionInfoTargetBuffer(pExceptionInformation, sizeof(REMOTE_PTR)*m_raiseExceptionNumberParameters); + EX_TRY + { + m_pProcess->SafeReadBuffer(exceptionInfoTargetBuffer, (BYTE*)m_raiseExceptionExceptionInformation); + } + EX_CATCH_HRESULT(hr); + if(FAILED(hr)) + { + LOG((LF_CORDB, LL_INFO1000, "CP::SREEC: failed to read exception information.\n")); + return; + } + + // If everything was succesful then set this flag, otherwise none of the above data is considered valid + SetState(CUTS_HasRaiseExceptionEntryCtx); + return; +} + +//----------------------------------------------------------------------------- +// Clears all the state saved in SaveRaiseExceptionContext and returns the thread +// to the state as if RaiseException has yet to be called. This is typically called +// after an exception retriggers or after determining that the exception never will +// retrigger. +//----------------------------------------------------------------------------- +void CordbUnmanagedThread::ClearRaiseExceptionEntryContext() +{ + _ASSERTE(FALSE); // should be unused now + LOG((LF_CORDB, LL_INFO1000, "CP::CREEC: clearing raise exception context.\n")); + _ASSERTE(HasRaiseExceptionEntryCtx()); + ClearState(CUTS_HasRaiseExceptionEntryCtx); +} + +//----------------------------------------------------------------------------- +// Uses a heuristic to determine if the given exception record is likely to be the exception +// raised by the last invocation of RaiseException on this thread. The current heuristic compares +// ExceptionCode, ExceptionFlags, and all ExceptionInformation. +//----------------------------------------------------------------------------- +BOOL CordbUnmanagedThread::IsExceptionFromLastRaiseException(const EXCEPTION_RECORD* pExceptionRecord) +{ + _ASSERTE(FALSE); // should be unused now + if(!HasRaiseExceptionEntryCtx()) + { + LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - no previous raise context\n")); + return FALSE; + } + + if (pExceptionRecord->ExceptionCode != m_raiseExceptionExceptionCode) + { + LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - exception codes differ 0x%x 0x%x\n", + pExceptionRecord->ExceptionCode, m_raiseExceptionExceptionCode)); + return FALSE; + } + + if (pExceptionRecord->ExceptionFlags != m_raiseExceptionExceptionFlags) + { + LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - exception flags differ 0x%x 0x%x\n", + pExceptionRecord->ExceptionFlags, m_raiseExceptionExceptionFlags)); + return FALSE; + } + + if (pExceptionRecord->NumberParameters != m_raiseExceptionNumberParameters) + { + LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - number parameters differ 0x%x 0x%x\n", + pExceptionRecord->NumberParameters, m_raiseExceptionNumberParameters)); + return FALSE; + } + + for(DWORD i = 0; i < pExceptionRecord->NumberParameters; i++) + { + if(m_raiseExceptionExceptionInformation[i] != pExceptionRecord->ExceptionInformation[i]) + { + LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: not a match - param %d differs 0x%x 0x%x\n", + i, pExceptionRecord->ExceptionInformation[i], m_raiseExceptionExceptionInformation[i])); + return FALSE; + } + } + + LOG((LF_CORDB, LL_INFO1000, "CP::IEFLRE: match\n")); + return TRUE; +} + + +//----------------------------------------------------------------------------- +// Inject an int3 at the given remote address +//----------------------------------------------------------------------------- + +// This flavor is assuming our caller already knows the opcode. +HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress) +{ +#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64) + const BYTE patch = CORDbg_BREAK_INSTRUCTION; + HRESULT hr = pProcess->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &patch); + SIMPLIFYING_ASSUMPTION_SUCCEEDED(hr); +#else + PORTABILITY_ASSERT("NYI: ApplyRemotePatch for this platform"); +#endif + return S_OK; +} + + +// Get the opcode that we're replacing. +HRESULT ApplyRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE * pOpcode) +{ +#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64) + // Read out opcode. 1 byte on x86 + BYTE opcode; + + HRESULT hr = pProcess->SafeReadStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &opcode); + if (FAILED(hr)) + { + return hr; + } + + *pOpcode = (PRD_TYPE) opcode; +#else + PORTABILITY_ASSERT("NYI: ApplyRemotePatch for this platform"); +#endif + ApplyRemotePatch(pProcess, pRemoteAddress); + return S_OK; +} + +//----------------------------------------------------------------------------- +// Remove the int3 from the remote address +//----------------------------------------------------------------------------- +HRESULT RemoveRemotePatch(CordbProcess * pProcess, const void * pRemoteAddress, PRD_TYPE opcode) +{ +#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_AMD64) + // Replace the BP w/ the opcode. + BYTE opcode2 = (BYTE) opcode; + + pProcess->SafeWriteStruct(PTR_TO_CORDB_ADDRESS(pRemoteAddress), &opcode2); + + // This may fail because the module has been unloaded. In which case, the patch is also + // gone so it makes sense to return success. +#else + PORTABILITY_ASSERT("NYI: RemoveRemotePatch for this platform"); +#endif + return S_OK; +} +#endif // FEATURE_INTEROP_DEBUGGING + +//--------------------------------------------------------------------------------------- +// +// Simple helper to return the SP value stored in a DebuggerREGDISPLAY. +// +// Arguments: +// pDRD - the DebuggerREGDISPLAY in question +// +// Return Value: +// the SP value +// + +inline CORDB_ADDRESS GetSPFromDebuggerREGDISPLAY(DebuggerREGDISPLAY* pDRD) +{ + return pDRD->SP; +} + + +HRESULT CordbContext::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugContext) + *pInterface = static_cast<ICorDebugContext*>(this); + else if (id == IID_IUnknown) + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugContext*>(this)); + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + + +/* ------------------------------------------------------------------------- * + * Frame class + * ------------------------------------------------------------------------- */ + + +// This is just used as a proxy object to pass a FramePointer around. +CordbFrame::CordbFrame(CordbProcess * pProcess, FramePointer fp) + : CordbBase(pProcess, 0, enumCordbFrame), + m_fp(fp) +{ + UnsafeNeuterDeadObject(); // mark as neutered. +} + + +CordbFrame::CordbFrame(CordbThread * pThread, + FramePointer fp, + SIZE_T ip, + CordbAppDomain * pCurrentAppDomain) + : CordbBase(pThread->GetProcess(), 0, enumCordbFrame), + m_ip(ip), + m_pThread(pThread), + m_currentAppDomain(pCurrentAppDomain), + m_fp(fp) +{ +#ifdef _DEBUG + // For debugging purposes, track what Continue session these frames were created in. + m_DbgContinueCounter = GetProcess()->m_continueCounter; +#endif + + HRESULT hr = S_OK; + EX_TRY + { + m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this); + } + EX_CATCH_HRESULT(hr); + SetUnrecoverableIfFailed(GetProcess(), hr); +} + + +CordbFrame::~CordbFrame() +{ + _ASSERTE(IsNeutered()); +} + +// Neutered by DerivedClasses +void CordbFrame::Neuter() +{ + CordbBase::Neuter(); +} + + +HRESULT CordbFrame::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugFrame) + *pInterface = static_cast<ICorDebugFrame*>(this); + else if (id == IID_IUnknown) + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugFrame*>(this)); + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +// ---------------------------------------------------------------------------- +// CordbFrame::GetChain +// +// Description: +// Return the owning chain. Since chains have been deprecated in Arrowhead, +// this function returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppChain - out parameter; return the owning chain +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppChain is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered. +// Return E_NOTIMPL if there is no shim. +// Return E_FAIL if failed to find the chain +// + +HRESULT CordbFrame::GetChain(ICorDebugChain **ppChain) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this) + { + ValidateOrThrow(ppChain); + *ppChain = NULL; + + if (GetProcess()->GetShim() != NULL) + { + PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER()); + ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread); + pSSW->GetChainForFrame(static_cast<ICorDebugFrame *>(this), ppChain); + + if (*ppChain == NULL) + hr = E_FAIL; + } + else + { + // This is the Arrowhead case, where ICDChain has been deprecated. + hr = E_NOTIMPL; + } + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + + +// Return the stack range taken up by this frame. +// Note that this is not implemented in the base CordbFrame class. +// Instead, this is implemented by the derived classes. +// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary. +// +// Notes: see code:#GetStackRange +HRESULT CordbFrame::GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this) + { + ValidateOrThrow(pStart); + ValidateOrThrow(pEnd); + + hr = E_NOTIMPL; + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + +// Return the ICorDebugFunction associated with this frame. +// There is one ICorDebugFunction for each EnC version of a method. +HRESULT CordbFrame::GetFunction(ICorDebugFunction **ppFunction) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this) + { + ValidateOrThrow(ppFunction); + + CordbFunction * pFunc = this->GetFunction(); + + if (pFunc == NULL) + { + ThrowHR(CORDBG_E_CODE_NOT_AVAILABLE); + } + + // @dbgtodo LCG methods, IL stubs, dynamic language debugging + // Don't return an ICDFunction if we are dealing with a dynamic method. + // The dynamic debugging feature crew needs to decide exactly what to hand out for dynamic methods. + if (pFunc->GetMetadataToken() == mdMethodDefNil) + { + ThrowHR(CORDBG_E_CODE_NOT_AVAILABLE); + } + + *ppFunction = static_cast<ICorDebugFunction *>(pFunc); + pFunc->ExternalAddRef(); + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + +// Return the token of the ICorDebugFunction associated with this frame. +// There is one ICorDebugFunction for each EnC version of a method. +HRESULT CordbFrame::GetFunctionToken(mdMethodDef *pToken) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this) + { + ValidateOrThrow(pToken); + + CordbFunction * pFunc = GetFunction(); + if (pFunc == NULL) + { + hr = CORDBG_E_CODE_NOT_AVAILABLE; + } + else + { + *pToken = pFunc->GetMetadataToken(); + } + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbFrame::GetCaller +// +// Description: +// Return the caller of this frame. The caller is closer to the root. +// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppFrame - out parameter; return the caller frame +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppFrame is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered. +// Return E_NOTIMPL if there is no shim. +// + +HRESULT CordbFrame::GetCaller(ICorDebugFrame **ppFrame) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this) + { + ValidateOrThrow(ppFrame); + + *ppFrame = NULL; + + if (GetProcess()->GetShim() != NULL) + { + PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER()); + ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread); + pSSW->GetCallerForFrame(this, ppFrame); + } + else + { + *ppFrame = NULL; + hr = E_NOTIMPL; + } + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbFrame::GetCallee +// +// Description: +// Return the callee of this frame. The callee is closer to the leaf. +// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppFrame - out parameter; return the callee frame +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppFrame is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbFrame is neutered. +// Return E_NOTIMPL if there is no shim. +// + +HRESULT CordbFrame::GetCallee(ICorDebugFrame **ppFrame) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this) + { + ValidateOrThrow(ppFrame); + + *ppFrame = NULL; + + if (GetProcess()->GetShim() != NULL) + { + PUBLIC_CALLBACK_IN_THIS_SCOPE0(GetProcess(), GET_PUBLIC_LOCK_HOLDER()); + ShimStackWalk * pSSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread); + pSSW->GetCalleeForFrame(static_cast<ICorDebugFrame *>(this), ppFrame); + } + else + { + *ppFrame = NULL; + hr = E_NOTIMPL; + } + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + +// Create a stepper on the frame. +HRESULT CordbFrame::CreateStepper(ICorDebugStepper **ppStepper) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + VALIDATE_POINTER_TO_OBJECT(ppStepper, ICorDebugStepper **); + + HRESULT hr = S_OK; + EX_TRY + { + RSInitHolder<CordbStepper> pStepper(new CordbStepper(m_pThread, this)); + pStepper.TransferOwnershipExternal(ppStepper); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Given a frame pointer, determine if it is in the stack range owned by the frame. +// +// Arguments: +// fp - frame pointer to check +// +// Return Value: +// whether the specified frame pointer is in the stack range or not +// + +bool CordbFrame::IsContainedInFrame(FramePointer fp) +{ + CORDB_ADDRESS stackStart; + CORDB_ADDRESS stackEnd; + + // get the stack range + HRESULT hr; + hr = GetStackRange(&stackStart, &stackEnd); + _ASSERTE(SUCCEEDED(hr)); + + CORDB_ADDRESS sp = PTR_TO_CORDB_ADDRESS(fp.GetSPValue()); + + if ((stackStart <= sp) && (sp <= stackEnd)) + { + return true; + } + else + { + return false; + } +} + +//--------------------------------------------------------------------------------------- +// +// Given an ICorDebugFrame interface pointer, return a pointer to the base class CordbFrame. +// +// Arguments: +// pFrame - the ICorDebugFrame interface pointer +// +// Return Value: +// the CordbFrame pointer corresponding to the specified interface pointer +// +// Note: +// This is currently only used for continuable exceptions. +// + +// static +CordbFrame* CordbFrame::GetCordbFrameFromInterface(ICorDebugFrame *pFrame) +{ + CordbFrame* pTargetFrame = NULL; + + if (pFrame != NULL) + { + // test for CordbNativeFrame + RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame; + pFrame->QueryInterface(IID_ICorDebugNativeFrame, (void**)&pNativeFrame); + if (pNativeFrame != NULL) + { + pTargetFrame = static_cast<CordbFrame*>(static_cast<CordbNativeFrame*>(pNativeFrame.GetValue())); + } + else + { + // test for CordbJITILFrame + RSExtSmartPtr<ICorDebugILFrame> pILFrame; + pFrame->QueryInterface(IID_ICorDebugILFrame, (void**)&pILFrame); + if (pILFrame != NULL) + { + pTargetFrame = (static_cast<CordbJITILFrame*>(pILFrame.GetValue()))->m_nativeFrame; + } + else + { + // test for CordbInternalFrame + RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame; + pFrame->QueryInterface(IID_ICorDebugInternalFrame, (void**)&pInternalFrame); + if (pInternalFrame != NULL) + { + pTargetFrame = static_cast<CordbFrame*>(static_cast<CordbInternalFrame*>(pInternalFrame.GetValue())); + } + else + { + // when all else fails, this is just a CordbFrame + pTargetFrame = static_cast<CordbFrame*>(pFrame); + } + } + } + } + return pTargetFrame; +} + + +/* ------------------------------------------------------------------------- * + + * Value Enumerator class + * + * Used by CordbJITILFrame for EnumLocalVars & EnumArgs. + * NOTE NOTE NOTE WE ASSUME that the 'frame' argument is actually the + * CordbJITILFrame's native frame member variable. + * ------------------------------------------------------------------------- */ + +CordbValueEnum::CordbValueEnum(CordbNativeFrame *frame, ValueEnumMode mode) : + CordbBase(frame->GetProcess(), 0) +{ + _ASSERTE( frame != NULL ); + _ASSERTE( mode == LOCAL_VARS_ORIGINAL_IL || mode == LOCAL_VARS_REJIT_IL || mode == ARGS); + + m_frame = frame; + m_mode = mode; + m_iCurrent = 0; + m_iMax = 0; +} + +/* + * CordbValueEnum::Init + * + * Initialize a CordbValueEnum object. Must be called after allocating the object and before using it. If Init + * fails, then destroy the object and release the memory. + * + * Parameters: + * none. + * + * Returns: + * HRESULT for success or failure. + * + */ +HRESULT CordbValueEnum::Init() +{ + HRESULT hr = S_OK; + CordbNativeFrame *nil = m_frame; + CordbJITILFrame *jil = nil->m_JITILFrame; + + switch (m_mode) + { + case ARGS: + { + // Get the function signature + CordbFunction *func = m_frame->GetFunction(); + ULONG methodArgCount; + + IfFailRet(func->GetSig(NULL, &methodArgCount, NULL)); + + // Grab the argument count for the size of the enumeration. + m_iMax = methodArgCount; + if (jil->m_fVarArgFnx && !jil->m_sigParserCached.IsNull()) + { + m_iMax = jil->m_allArgsCount; + } + break; + } + case LOCAL_VARS_ORIGINAL_IL: + { + // Get the locals signature. + ULONG localsCount; + IfFailRet(jil->GetOriginalILCode()->GetLocalVarSig(NULL, &localsCount)); + + // Grab the number of locals for the size of the enumeration. + m_iMax = localsCount; + break; + } + case LOCAL_VARS_REJIT_IL: + { + // Get the locals signature. + ULONG localsCount; + CordbReJitILCode* pCode = jil->GetReJitILCode(); + if (pCode == NULL) + { + m_iMax = 0; + } + else + { + IfFailRet(pCode->GetLocalVarSig(NULL, &localsCount)); + + // Grab the number of locals for the size of the enumeration. + m_iMax = localsCount; + } + break; + } + } + // Everything worked okay, so add this object to the neuter list for objects that are tied to the stack trace. + EX_TRY + { + m_frame->m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this); + } + EX_CATCH_HRESULT(hr); + SetUnrecoverableIfFailed(GetProcess(), hr); + + return hr; +} + +CordbValueEnum::~CordbValueEnum() +{ + _ASSERTE(this->IsNeutered()); + _ASSERTE(m_frame == NULL); +} + +void CordbValueEnum::Neuter() +{ + m_frame = NULL; + CordbBase::Neuter(); +} + + + +HRESULT CordbValueEnum::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugEnum) + *pInterface = static_cast<ICorDebugEnum*>(this); + else if (id == IID_ICorDebugValueEnum) + *pInterface = static_cast<ICorDebugValueEnum*>(this); + else if (id == IID_IUnknown) + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugValueEnum*>(this)); + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +HRESULT CordbValueEnum::Skip(ULONG celt) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = E_FAIL; + if ( (m_iCurrent+celt) < m_iMax || + celt == 0) + { + m_iCurrent += celt; + hr = S_OK; + } + + return hr; +} + +HRESULT CordbValueEnum::Reset() +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + m_iCurrent = 0; + return S_OK; +} + +HRESULT CordbValueEnum::Clone(ICorDebugEnum **ppEnum) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **); + + HRESULT hr = S_OK; + EX_TRY + { + *ppEnum = NULL; + RSInitHolder<CordbValueEnum> pCVE(new CordbValueEnum(m_frame, m_mode)); + + // Initialize the new enum + hr = pCVE->Init(); + IfFailThrow(hr); + + pCVE.TransferOwnershipExternal(ppEnum); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +HRESULT CordbValueEnum::GetCount(ULONG *pcelt) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *); + + if( pcelt == NULL) + { + return E_INVALIDARG; + } + + (*pcelt) = m_iMax; + return S_OK; +} + +// +// In the event of failure, the current pointer will be left at +// one element past the troublesome element. Thus, if one were +// to repeatedly ask for one element to iterate through the +// array, you would iterate exactly m_iMax times, regardless +// of individual failures. +HRESULT CordbValueEnum::Next(ULONG celt, ICorDebugValue *values[], ULONG *pceltFetched) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugValue *, + celt, true, true); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *); + + if ((pceltFetched == NULL) && (celt != 1)) + { + return E_INVALIDARG; + } + + if (celt == 0) + { + if (pceltFetched != NULL) + { + *pceltFetched = 0; + } + return S_OK; + } + + HRESULT hr = S_OK; + + int iMax = min( m_iMax, m_iCurrent+celt); + int i; + for (i = m_iCurrent; i< iMax;i++) + { + switch ( m_mode ) + { + case ARGS: + { + hr = m_frame->m_JITILFrame->GetArgument( i, &(values[i-m_iCurrent]) ); + break; + } + case LOCAL_VARS_ORIGINAL_IL: + { + hr = m_frame->m_JITILFrame->GetLocalVariableEx(ILCODE_ORIGINAL_IL, i, &(values[i-m_iCurrent]) ); + break; + } + case LOCAL_VARS_REJIT_IL: + { + hr = m_frame->m_JITILFrame->GetLocalVariableEx(ILCODE_REJIT_IL, i, &(values[i - m_iCurrent])); + break; + } + } + if ( FAILED( hr ) ) + { + break; + } + } + + int count = (i - m_iCurrent); + + if ( FAILED( hr ) ) + { + // + // we failed: +1 pushes us past troublesome element + // + m_iCurrent += 1 + count; + } + else + { + m_iCurrent += count; + } + + if (pceltFetched != NULL) + { + *pceltFetched = count; + } + + if (FAILED(hr)) + { + return hr; + } + + + // + // If we reached the end of the enumeration, but not the end + // of the number of requested items, we return S_FALSE. + // + if (((ULONG)count) < celt) + { + return S_FALSE; + } + + return hr; +} + + +//----------------------------------------------------------------------------- +// CordbInternalFrame +//----------------------------------------------------------------------------- +CordbInternalFrame::CordbInternalFrame(CordbThread * pThread, + FramePointer fp, + CordbAppDomain * pCurrentAppDomain, + const DebuggerIPCE_STRData * pData) + : CordbFrame(pThread, fp, 0, pCurrentAppDomain) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + m_eFrameType = pData->stubFrame.frameType; + m_funcMetadataToken = pData->stubFrame.funcMetadataToken; + m_vmMethodDesc = pData->stubFrame.vmMethodDesc; + + // Some internal frames may not have a Function associated w/ them. + if (!IsNilToken(m_funcMetadataToken)) + { + // Find the module of the function. Note that this module isn't necessarily in the same domain as our frame. + // FuncEval frames can point to methods they are going to invoke in another domain. + CordbModule * pModule = NULL; + pModule = GetProcess()->LookupOrCreateModule(pData->stubFrame.vmDomainFile); + _ASSERTE(pModule != NULL); + + // + if( pModule != NULL ) + { + _ASSERTE( (pModule->GetAppDomain() == pCurrentAppDomain) || (m_eFrameType == STUBFRAME_FUNC_EVAL) ); + + + + mdMethodDef token = pData->stubFrame.funcMetadataToken; + + // @dbgtodo synchronization - push this up. + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + + // CordbInternalFrame could handle a null function. + // But if we fail to lookup, things are not in a good state anyways. + CordbFunction * pFunction = pModule->LookupOrCreateFunctionLatestVersion(token); + m_function.Assign(pFunction); + + } + } +} + +CordbInternalFrame::CordbInternalFrame(CordbThread * pThread, + FramePointer fp, + CordbAppDomain * pCurrentAppDomain, + CorDebugInternalFrameType frameType, + mdMethodDef funcMetadataToken, + CordbFunction * pFunction, + VMPTR_MethodDesc vmMethodDesc) + : CordbFrame(pThread, fp, 0, pCurrentAppDomain) +{ + m_eFrameType = frameType; + m_funcMetadataToken = funcMetadataToken; + m_function.Assign(pFunction); + m_vmMethodDesc = vmMethodDesc; +} + +HRESULT CordbInternalFrame::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugFrame) + { + *pInterface = static_cast<ICorDebugFrame*>(static_cast<ICorDebugInternalFrame*>(this)); + } + else if (id == IID_ICorDebugInternalFrame) + { + *pInterface = static_cast<ICorDebugInternalFrame*>(this); + } + else if (id == IID_ICorDebugInternalFrame2) + { + *pInterface = static_cast<ICorDebugInternalFrame2*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugInternalFrame*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +void CordbInternalFrame::Neuter() +{ + m_function.Clear(); + CordbFrame::Neuter(); +} + + +// ---------------------------------------------------------------------------- +// CordbInternalFrame::GetStackRange +// +// Description: +// Return the stack range owned by this frame. +// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary. +// +// Arguments: +// * pStart - out parameter; return the leaf end of the frame +// * pEnd - out parameter; return the root end of the frame +// +// Return Value: +// Return S_OK on success. +// +// Notes: +// #GetStackRange +// This is a virtual function and so there are multiple implementations for different types of frames. +// It's very important to note that GetStackRange() can work when even after the frame is neutered. +// Debuggers may rely on this to map old frames up to new frames across Continue() calls. +// + +HRESULT CordbInternalFrame::GetStackRange(CORDB_ADDRESS *pStart, + CORDB_ADDRESS *pEnd) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + + // Callers explicit require GetStackRange() to be callable when neutered so that they + // can line up ICorDebugFrame objects across continues. We only return stack ranges + // here and don't access any special data. + OK_IF_NEUTERED(this); + + if (GetProcess()->GetShim() != NULL) + { + CORDB_ADDRESS pFramePointer = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue()); + if (pStart) + { + *pStart = pFramePointer; + } + if (pEnd) + { + *pEnd = pFramePointer; + } + return S_OK; + } + else + { + if (pStart != NULL) + { + *pStart = NULL; + } + if (pEnd != NULL) + { + *pEnd = NULL; + } + return E_NOTIMPL; + } +} + + +// This may return NULL if there's no Method associated w/ this Frame. +// For FuncEval frames, the function returned might also be in a different AppDomain +// than the frame itself. +CordbFunction * CordbInternalFrame::GetFunction() +{ + return m_function; +} + +// Accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata. +// Refer to that function for comments on the return value, the argument, etc. +BOOL CordbInternalFrame::ConvertInternalFrameForILMethodWithoutMetadata( + ICorDebugInternalFrame2 ** ppInternalFrame2) +{ + _ASSERTE(ppInternalFrame2 != NULL); + *ppInternalFrame2 = NULL; + + // The only internal frame conversion we need to perform is from STUBFRAME_JIT_COMPILATION to + // STUBFRAME_LIGTHWEIGHT_FUNCTION. + if (m_eFrameType != STUBFRAME_JIT_COMPILATION) + { + return FALSE; + } + + // Check whether the internal frame has an associated MethodDesc. + // Currently, the only STUBFRAME_JIT_COMPILATION frame with a NULL MethodDesc is ComPrestubMethodFrame, + // which is not exposed in Whidbey. So convert it according to rule #2 below. + if (m_vmMethodDesc.IsNull()) + { + return TRUE; + } + + // Retrieve the type of the method associated with the STUBFRAME_JIT_COMPILATION. + IDacDbiInterface::DynamicMethodType type = GetProcess()->GetDAC()->IsILStubOrLCGMethod(m_vmMethodDesc); + + // Here are the conversion rules: + // 1) For a normal managed method, we don't convert, and we return FALSE. + // 2) For an IL stub, we convert to NULL, and we return TRUE. + // 3) For a dynamic method, we convert to a STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return TRUE. + if (type == IDacDbiInterface::kNone) + { + return FALSE; + } + else if (type == IDacDbiInterface::kILStub) + { + return TRUE; + } + else if (type == IDacDbiInterface::kLCGMethod) + { + // Here we are basically cloning another CordbInternalFrame. + RSInitHolder<CordbInternalFrame> pInternalFrame(new CordbInternalFrame(m_pThread, + m_fp, + m_currentAppDomain, + STUBFRAME_LIGHTWEIGHT_FUNCTION, + m_funcMetadataToken, + m_function.GetValue(), + m_vmMethodDesc)); + pInternalFrame.TransferOwnershipExternal(ppInternalFrame2); + return TRUE; + } + + UNREACHABLE(); +} + + +//--------------------------------------------------------------------------------------- +// +// Returns the address of an internal frame. The address is a stack pointer, even on IA64. +// +// Arguments: +// pAddress - out parameter; return the frame marker address +// +// Return Value: +// S_OK on success. +// E_INVALIDARG if pAddress is NULL. +// + +HRESULT CordbInternalFrame::GetAddress(CORDB_ADDRESS * pAddress) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (pAddress == NULL) + { + return E_INVALIDARG; + } + + *pAddress = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue()); + return S_OK; +} + +//--------------------------------------------------------------------------------------- +// +// Refer to the comment for code:CordbInternalFrame::IsCloserToLeaf +// + +BOOL CordbInternalFrame::IsCloserToLeafWorker(ICorDebugFrame * pFrameToCompare) +{ + // Get the address of the "this" internal frame. + CORDB_ADDRESS thisFrameAddr = PTR_TO_CORDB_ADDRESS(this->GetFramePointer().GetSPValue()); + + // Note that a QI on ICorDebugJITILFrame for ICorDebugNativeFrame will work. + RSExtSmartPtr<ICorDebugNativeFrame> pNativeFrame; + pFrameToCompare->QueryInterface(IID_ICorDebugNativeFrame, (void **)&pNativeFrame); + if (pNativeFrame != NULL) + { + // The frame to compare is a CordbNativeFrame. + CordbNativeFrame * pCNativeFrame = static_cast<CordbNativeFrame *>(pNativeFrame.GetValue()); + + // Compare the address of the "this" internal frame to the SP of the stack frame. + // We can't compare frame pointers because the frame pointer means different things on + // different platforms. + CORDB_ADDRESS stackFrameSP = GetSPFromDebuggerREGDISPLAY(&(pCNativeFrame->m_rd)); + return (thisFrameAddr < stackFrameSP); + } + + RSExtSmartPtr<ICorDebugRuntimeUnwindableFrame> pRUFrame; + pFrameToCompare->QueryInterface(IID_ICorDebugRuntimeUnwindableFrame, (void **)&pRUFrame); + if (pRUFrame != NULL) + { + // The frame to compare is a CordbRuntimeUnwindableFrame. + CordbRuntimeUnwindableFrame * pCRUFrame = + static_cast<CordbRuntimeUnwindableFrame *>(pRUFrame.GetValue()); + + DT_CONTEXT * pResumeContext = const_cast<DT_CONTEXT *>(pCRUFrame->GetContext()); + CORDB_ADDRESS stackFrameSP = PTR_TO_CORDB_ADDRESS(CORDbgGetSP(pResumeContext)); + return (thisFrameAddr < stackFrameSP); + } + + RSExtSmartPtr<ICorDebugInternalFrame> pInternalFrame; + pFrameToCompare->QueryInterface(IID_ICorDebugInternalFrame, (void **)&pInternalFrame); + if (pInternalFrame != NULL) + { + // The frame to compare is a CordbInternalFrame. + CordbInternalFrame * pCInternalFrame = + static_cast<CordbInternalFrame *>(pInternalFrame.GetValue()); + + CORDB_ADDRESS frameAddr = PTR_TO_CORDB_ADDRESS(pCInternalFrame->GetFramePointer().GetSPValue()); + return (thisFrameAddr < frameAddr); + } + + // What does this mean? This is unexpected. + _ASSERTE(!"CIF::ICTLW - Unexpected frame type.\n"); + ThrowHR(E_FAIL); +} + +//--------------------------------------------------------------------------------------- +// +// Checks whether the "this" internal frame is closer to the leaf than the specified ICDFrame. +// If the specified ICDFrame represents a stack frame, then we compare the address of the "this" +// internal frame against the SP of the stack frame. +// +// Arguments: +// pFrameToCompare - the ICDFrame to compare against +// pIsCloser - out parameter; returns TRUE if the "this" internal frame is closer to the leaf +// +// Return Value: +// S_OK on success. +// E_INVALIDARG if pFrameToCompare or pIsCloser is NULL. +// E_FAIL if pFrameToCompare is bogus. +// +// Notes: +// This function doesn't deal with the backing store at all. +// + +HRESULT CordbInternalFrame::IsCloserToLeaf(ICorDebugFrame * pFrameToCompare, + BOOL * pIsCloser) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this); + { + ValidateOrThrow(pFrameToCompare); + ValidateOrThrow(pIsCloser); + + *pIsCloser = IsCloserToLeafWorker(pFrameToCompare); + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + + +CordbRuntimeUnwindableFrame::CordbRuntimeUnwindableFrame(CordbThread * pThread, + FramePointer fp, + CordbAppDomain * pCurrentAppDomain, + DT_CONTEXT * pContext) + : CordbFrame(pThread, fp, 0, pCurrentAppDomain), + m_context(*pContext) +{ +} + +void CordbRuntimeUnwindableFrame::Neuter() +{ + CordbFrame::Neuter(); +} + +HRESULT CordbRuntimeUnwindableFrame::QueryInterface(REFIID id, void ** ppInterface) +{ + if (id == IID_ICorDebugFrame) + { + *ppInterface = static_cast<ICorDebugFrame *>(static_cast<ICorDebugRuntimeUnwindableFrame *>(this)); + } + else if (id == IID_ICorDebugRuntimeUnwindableFrame) + { + *ppInterface = static_cast<ICorDebugRuntimeUnwindableFrame *>(this); + } + else if (id == IID_IUnknown) + { + *ppInterface = static_cast<IUnknown *>(static_cast<ICorDebugRuntimeUnwindableFrame *>(this)); + } + else + { + *ppInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +//--------------------------------------------------------------------------------------- +// +// Returns the CONTEXT corresponding to this CordbRuntimeUnwindableFrame. +// +// Return Value: +// Return a pointer to the CONTEXT. +// + +const DT_CONTEXT * CordbRuntimeUnwindableFrame::GetContext() const +{ + return &m_context; +} + + +// default constructor to make the compiler happy +CordbMiscFrame::CordbMiscFrame() +{ +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + this->parentIP = 0; + this->fpParentOrSelf = LEAF_MOST_FRAME; + this->fIsFilterFunclet = false; +#endif // _WIN64 +} + +// the real constructor which stores the funclet-related information in the CordbMiscFrame +CordbMiscFrame::CordbMiscFrame(DebuggerIPCE_JITFuncData * pJITFuncData) +{ +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + this->parentIP = pJITFuncData->parentNativeOffset; + this->fpParentOrSelf = pJITFuncData->fpParentOrSelf; + this->fIsFilterFunclet = (pJITFuncData->fIsFilterFrame == TRUE); +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM +} + +/* ------------------------------------------------------------------------- * + * Native Frame class + * ------------------------------------------------------------------------- */ + + +CordbNativeFrame::CordbNativeFrame(CordbThread * pThread, + FramePointer fp, + CordbNativeCode * pNativeCode, + SIZE_T ip, + DebuggerREGDISPLAY * pDRD, + TADDR taAmbientESP, + bool fQuicklyUnwound, + CordbAppDomain * pCurrentAppDomain, + CordbMiscFrame * pMisc /*= NULL*/, + DT_CONTEXT * pContext /*= NULL*/) + : CordbFrame(pThread, fp, ip, pCurrentAppDomain), + m_rd(*pDRD), + m_quicklyUnwound(fQuicklyUnwound), + m_JITILFrame(NULL), + m_nativeCode(pNativeCode), // implicit InternalAddRef + m_taAmbientESP(taAmbientESP) +{ + m_misc = *pMisc; + + // Only new CordbNativeFrames created by the new stackwalk contain a CONTEXT. + _ASSERTE(pContext != NULL); + m_context = *pContext; +} + +/* + A list of which resources owned by this object are accounted for. + + RESOLVED: + CordbJITILFrame* m_JITILFrame; // Neutered +*/ + +CordbNativeFrame::~CordbNativeFrame() +{ + _ASSERTE(IsNeutered()); +} + +// Neutered by CordbThread::CleanupStack +void CordbNativeFrame::Neuter() +{ + // Neuter may be called multiple times so be sure to set ptrs to NULL so that we don't + // double release them. + if (IsNeutered()) + { + return; + } + + m_nativeCode.Clear(); + + if (m_JITILFrame != NULL) + { + m_JITILFrame->Neuter(); + m_JITILFrame.Clear(); + } + + CordbFrame::Neuter(); +} + +// CordbNativeFrame::QueryInterface +// +// Description +// interface query for this COM object +// +// NOTE: the COM object associated with this CordbNativeFrame may consist of +// two C++ objects (the CordbNativeFrame and the CordbJITILFrame). +// +// Parameters +// id the GUID associated with the requested interface +// pInterface [out] the interface pointer +// +// Returns +// HRESULT +// S_OK If this CordbJITILFrame supports the interface +// E_NOINTERFACE If this object does not support the interface +// +// Exceptions +// None +// +// +HRESULT CordbNativeFrame::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugFrame) + { + *pInterface = static_cast<ICorDebugFrame*>(static_cast<ICorDebugNativeFrame*>(this)); + } + else if (id == IID_ICorDebugNativeFrame) + { + *pInterface = static_cast<ICorDebugNativeFrame*>(this); + } + else if (id == IID_ICorDebugNativeFrame2) + { + *pInterface = static_cast<ICorDebugNativeFrame2*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugNativeFrame*>(this)); + } + else + { + // might be searching for an IL Frame. delegate that search to the + // JITILFrame + if (m_JITILFrame != NULL) + { + return m_JITILFrame->QueryInterfaceInternal(id, pInterface); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + } + + ExternalAddRef(); + return S_OK; +} + +// Return the CordbNativeCode object associated with this native frame. +// This is just a wrapper around the real helper. +HRESULT CordbNativeFrame::GetCode(ICorDebugCode **ppCode) +{ + PUBLIC_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **); + FAIL_IF_NEUTERED(this); + + CordbNativeCode * pCode = GetNativeCode(); + *ppCode = static_cast<ICorDebugCode*> (pCode); + pCode->ExternalAddRef(); + + return S_OK;; +} + +//--------------------------------------------------------------------------------------- +// +// Returns the CONTEXT corresponding to this CordbNativeFrame. +// +// Return Value: +// Return a pointer to the CONTEXT. +// + +const DT_CONTEXT * CordbNativeFrame::GetContext() const +{ + return &m_context; +} + +//--------------------------------------------------------------------------------------- +// +// This is an internal helper to get the CordbNativeCode object associated with this native frame. +// +// Return Value: +// the associated CordbNativeCode object +// + +CordbNativeCode * CordbNativeFrame::GetNativeCode() +{ + return this->m_nativeCode; +} + +//--------------------------------------------------------------------------------------- +// +// This is an internal helper to get the CordbFunction object associated with this native frame. +// +// Return Value: +// the associated CordbFunction object +// + +CordbFunction *CordbNativeFrame::GetFunction() +{ + return this->m_nativeCode->GetFunction(); +} + + + +// Return the native offset. +HRESULT CordbNativeFrame::GetIP(ULONG32 *pnOffset) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pnOffset, ULONG32 *); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + *pnOffset = (ULONG32)m_ip; + + return S_OK; +} + +ULONG32 CordbNativeFrame::GetIPOffset() +{ + return (ULONG32)m_ip; +} + +TADDR CordbNativeFrame::GetReturnRegisterValue() +{ +#if defined(DBG_TARGET_X86) + return (TADDR)m_context.Eax; +#elif defined(DBG_TARGET_AMD64) + return (TADDR)m_context.Rax; +#elif defined(DBG_TARGET_ARM) + return (TADDR)m_context.R0; +#elif defined(DBG_TARGET_ARM64) + return (TADDR)m_context.X0; +#else + _ASSERTE(!"nyi for platform"); + return 0; +#endif +} + +// Determine if we can set IP at this point. The specified offset is the native offset. +HRESULT CordbNativeFrame::CanSetIP(ULONG32 nOffset) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + if (!IsLeafFrame()) + { + ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME); + } + + hr = m_pThread->SetIP(SetIP_fCanSetIPOnly, + m_nativeCode, + nOffset, + SetIP_fNative ); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +// Try to set the IP to the specified offset. The specified offset is the native offset. +HRESULT CordbNativeFrame::SetIP(ULONG32 nOffset) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + if (!IsLeafFrame()) + { + ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME); + } + + hr = m_pThread->SetIP(SetIP_fSetIP, + m_nativeCode, + nOffset, + SetIP_fNative ); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + + +// Given a (register,offset) description of a stack location, compute +// the real memory address for it. +// This will also handle ambient SP values (which are encoded with regNum == REGNUM_AMBIENT_SP). +CORDB_ADDRESS CordbNativeFrame::GetLSStackAddress( + ICorDebugInfo::RegNum regNum, + signed offset) +{ + UINT_PTR *pRegAddr; + + CORDB_ADDRESS pRemoteValue; + + if (regNum != DBG_TARGET_REGNUM_AMBIENT_SP) + { + // Even if we're inside a funclet, variables (in both x64 and ARM) are still + // relative to the frame pointer or stack pointer, which are accurate in the + // funclet, after the funclet prolog; the frame pointer is re-established in the + // funclet prolog using the PSP. Thus, we just look up the frame pointer in the + // current native frame. + + pRegAddr = this->GetAddressOfRegister( + ConvertRegNumToCorDebugRegister(regNum)); + + // This should never be null as long as regNum is a member of the RegNum enum. + // If it is, an AV dereferencing a null-pointer in retail builds, or an assert in debug + // builds is exactly the behavior we want. + PREFIX_ASSUME(pRegAddr != NULL); + + pRemoteValue = PTR_TO_CORDB_ADDRESS(*pRegAddr + offset); + } + else + { + // Use the ambient ESP. At this point we're decoding an ambient-sp var, so + // we should definitely have an ambient-sp. If this is null, then the jit + // likely gave us an inconsistent data. + TADDR taAmbient = this->GetAmbientESP(); + _ASSERTE(taAmbient != NULL); + + pRemoteValue = PTR_TO_CORDB_ADDRESS(taAmbient + offset); + } + + return pRemoteValue; +} + + +// ---------------------------------------------------------------------------- +// CordbNativeFrame::GetStackRange +// +// Description: +// Return the stack range owned by this native frame. +// The start of the stack range is the leafmost boundary, and the end is the rootmost boundary. +// +// Arguments: +// * pStart - out parameter; return the leaf end of the frame +// * pEnd - out parameter; return the root end of the frame +// +// Return Value: +// Return S_OK on success. +// +// Notes: see code:#GetStackRange + +HRESULT CordbNativeFrame::GetStackRange(CORDB_ADDRESS *pStart, + CORDB_ADDRESS *pEnd) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + + // Callers explicit require GetStackRange() to be callable when neutered so that they + // can line up ICorDebugFrame objects across continues. We only return stack ranges + // here and don't access any special data. + OK_IF_NEUTERED(this); + + if (GetProcess()->GetShim() != NULL) + { + if (pStart) + { + // From register set. + *pStart = GetSPFromDebuggerREGDISPLAY(&m_rd); + } + + if (pEnd) + { + // The rootmost boundary is the frame pointer. + // <NOTE> + // This is not true on AMD64, on which we use the stack pointer as the frame pointer. + // </NOTE> + *pEnd = PTR_TO_CORDB_ADDRESS(GetFramePointer().GetSPValue()); + } + return S_OK; + } + else + { + if (pStart != NULL) + { + *pStart = NULL; + } + if (pEnd != NULL) + { + *pEnd = NULL; + } + return E_NOTIMPL; + } +} + +// Return the register set of the native frame. +HRESULT CordbNativeFrame::GetRegisterSet(ICorDebugRegisterSet **ppRegisters) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppRegisters, ICorDebugRegisterSet **); + + HRESULT hr = S_OK; + EX_TRY + { + // allocate a new CordbRegisterSet object + RSInitHolder<CordbRegisterSet> pRegisterSet(new CordbRegisterSet(&m_rd, + m_pThread, + IsLeafFrame(), + m_quicklyUnwound)); + + pRegisterSet.TransferOwnershipExternal(ppRegisters); + } + EX_CATCH_HRESULT(hr); + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Checks whether the frame is a child frame or not. +// +// Arguments: +// pIsChild - out parameter; returns whether the frame is a child frame +// +// Return Value: +// S_OK on success. +// E_INVALIDARG if the out parmater is NULL. +// + +HRESULT CordbNativeFrame::IsChild(BOOL * pIsChild) +{ + HRESULT hr = S_OK; + PUBLIC_REENTRANT_API_BEGIN(this) + { + if (pIsChild == NULL) + { + ThrowHR(E_INVALIDARG); + } + else + { + *pIsChild = ((this->IsFunclet() && !this->IsFilterFunclet()) ? TRUE : FALSE); + } + } + PUBLIC_REENTRANT_API_END(hr); + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Given an ICDNativeFrame2, check whether it is the parent frame of the current frame. +// +// Arguments: +// pPotentialParentFrame - the ICDNativeFrame2 to check +// pIsParent - out paramter; returns whether the specified frame is indeed the parent frame +// +// Return Value: +// S_OK on success. +// CORDBG_E_NOT_CHILD_FRAME if the current frame is not a child frame. +// E_INVALIDARG if either of the incoming argument is NULL. +// E_FAIL on other failures. +// + +HRESULT CordbNativeFrame::IsMatchingParentFrame(ICorDebugNativeFrame2 * pPotentialParentFrame, + BOOL * pIsParent) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pPotentialParentFrame, ICorDebugNativeFrame2 *); + + HRESULT hr = S_OK; + EX_TRY + { + if ((pPotentialParentFrame == NULL) || (pIsParent == NULL)) + { + ThrowHR(E_INVALIDARG); + } + + *pIsParent = FALSE; + + if (!this->IsFunclet()) + { + ThrowHR(CORDBG_E_NOT_CHILD_FRAME); + } + +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + CordbNativeFrame * pFrameToCheck = static_cast<CordbNativeFrame *>(pPotentialParentFrame); + if (pFrameToCheck->IsFunclet()) + { + *pIsParent = FALSE; + } + else + { + FramePointer fpParent = this->m_misc.fpParentOrSelf; + FramePointer fpToCheck = pFrameToCheck->m_misc.fpParentOrSelf; + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + *pIsParent = pDAC->IsMatchingParentFrame(fpToCheck, fpParent); + } +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// Return the stack parameter size of the current frame. Since this information is only used on x86, +// we return S_FALSE and a size of 0 on WIN64 platforms. +// +// Arguments: +// pSize - out parameter; return the size of the stack parameter +// +// Return Value: +// S_OK on success. +// S_FALSE on WIN64 platforms. +// E_INVALIDARG if pSize is NULL. +// +// Notes: +// Always return S_FALSE on WIN64. +// + +HRESULT CordbNativeFrame::GetStackParameterSize(ULONG32 * pSize) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + EX_TRY + { + if (pSize == NULL) + { + ThrowHR(E_INVALIDARG); + } + +#if defined(_TARGET_X86_) + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + *pSize = pDAC->GetStackParameterSize(PTR_TO_CORDB_ADDRESS(CORDbgGetIP(&m_context))); +#else // !_TARGET_X86_ + hr = S_FALSE; + *pSize = 0; +#endif // _TARGET_X86_ + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +// +// GetAddressOfRegister returns the address of the given register in the +// frame's current register display (eg, a local address). This is usually used to build a +// ICorDebugValue from. +// +UINT_PTR * CordbNativeFrame::GetAddressOfRegister(CorDebugRegister regNum) const +{ + UINT_PTR* ret = NULL; + + switch (regNum) + { + case REGISTER_STACK_POINTER: + ret = (UINT_PTR*)GetSPAddress(&m_rd); + break; + +#if !defined(DBG_TARGET_AMD64) && !defined(DBG_TARGET_ARM) // @ARMTODO + case REGISTER_FRAME_POINTER: + ret = (UINT_PTR*)GetFPAddress(&m_rd); + break; +#endif + +#if defined(DBG_TARGET_X86) + case REGISTER_X86_EAX: + ret = (UINT_PTR*)&m_rd.Eax; + break; + + case REGISTER_X86_ECX: + ret = (UINT_PTR*)&m_rd.Ecx; + break; + + case REGISTER_X86_EDX: + ret = (UINT_PTR*)&m_rd.Edx; + break; + + case REGISTER_X86_EBX: + ret = (UINT_PTR*)&m_rd.Ebx; + break; + + case REGISTER_X86_ESI: + ret = (UINT_PTR*)&m_rd.Esi; + break; + + case REGISTER_X86_EDI: + ret = (UINT_PTR*)&m_rd.Edi; + break; + +#elif defined(DBG_TARGET_AMD64) + case REGISTER_AMD64_RBP: + ret = (UINT_PTR*)&m_rd.Rbp; + break; + + case REGISTER_AMD64_RAX: + ret = (UINT_PTR*)&m_rd.Rax; + break; + + case REGISTER_AMD64_RCX: + ret = (UINT_PTR*)&m_rd.Rcx; + break; + + case REGISTER_AMD64_RDX: + ret = (UINT_PTR*)&m_rd.Rdx; + break; + + case REGISTER_AMD64_RBX: + ret = (UINT_PTR*)&m_rd.Rbx; + break; + + case REGISTER_AMD64_RSI: + ret = (UINT_PTR*)&m_rd.Rsi; + break; + + case REGISTER_AMD64_RDI: + ret = (UINT_PTR*)&m_rd.Rdi; + break; + + case REGISTER_AMD64_R8: + ret = (UINT_PTR*)&m_rd.R8; + break; + + case REGISTER_AMD64_R9: + ret = (UINT_PTR*)&m_rd.R9; + break; + + case REGISTER_AMD64_R10: + ret = (UINT_PTR*)&m_rd.R10; + break; + + case REGISTER_AMD64_R11: + ret = (UINT_PTR*)&m_rd.R11; + break; + + case REGISTER_AMD64_R12: + ret = (UINT_PTR*)&m_rd.R12; + break; + + case REGISTER_AMD64_R13: + ret = (UINT_PTR*)&m_rd.R13; + break; + + case REGISTER_AMD64_R14: + ret = (UINT_PTR*)&m_rd.R14; + break; + + case REGISTER_AMD64_R15: + ret = (UINT_PTR*)&m_rd.R15; + break; +#elif defined(DBG_TARGET_ARM) + case REGISTER_ARM_R0: + ret = (UINT_PTR*)&m_rd.R0; + break; + + case REGISTER_ARM_R1: + ret = (UINT_PTR*)&m_rd.R1; + break; + + case REGISTER_ARM_R2: + ret = (UINT_PTR*)&m_rd.R2; + break; + + case REGISTER_ARM_R3: + ret = (UINT_PTR*)&m_rd.R3; + break; + + case REGISTER_ARM_R4: + ret = (UINT_PTR*)&m_rd.R4; + break; + + case REGISTER_ARM_R5: + ret = (UINT_PTR*)&m_rd.R5; + break; + + case REGISTER_ARM_R6: + ret = (UINT_PTR*)&m_rd.R6; + break; + + case REGISTER_ARM_R7: + ret = (UINT_PTR*)&m_rd.R7; + break; + + case REGISTER_ARM_R8: + ret = (UINT_PTR*)&m_rd.R8; + break; + + case REGISTER_ARM_R9: + ret = (UINT_PTR*)&m_rd.R9; + break; + + case REGISTER_ARM_R10: + ret = (UINT_PTR*)&m_rd.R10; + break; + + case REGISTER_ARM_R11: + ret = (UINT_PTR*)&m_rd.R11; + break; + + case REGISTER_ARM_R12: + ret = (UINT_PTR*)&m_rd.R12; + break; + + case REGISTER_ARM_LR: + ret = (UINT_PTR*)&m_rd.LR; + break; + + case REGISTER_ARM_PC: + ret = (UINT_PTR*)&m_rd.PC; + break; +#elif defined(DBG_TARGET_ARM64) + case REGISTER_ARM64_X0: + case REGISTER_ARM64_X1: + case REGISTER_ARM64_X2: + case REGISTER_ARM64_X3: + case REGISTER_ARM64_X4: + case REGISTER_ARM64_X5: + case REGISTER_ARM64_X6: + case REGISTER_ARM64_X7: + case REGISTER_ARM64_X8: + case REGISTER_ARM64_X9: + case REGISTER_ARM64_X10: + case REGISTER_ARM64_X11: + case REGISTER_ARM64_X12: + case REGISTER_ARM64_X13: + case REGISTER_ARM64_X14: + case REGISTER_ARM64_X15: + case REGISTER_ARM64_X16: + case REGISTER_ARM64_X17: + case REGISTER_ARM64_X18: + case REGISTER_ARM64_X19: + case REGISTER_ARM64_X20: + case REGISTER_ARM64_X21: + case REGISTER_ARM64_X22: + case REGISTER_ARM64_X23: + case REGISTER_ARM64_X24: + case REGISTER_ARM64_X25: + case REGISTER_ARM64_X26: + case REGISTER_ARM64_X27: + case REGISTER_ARM64_X28: + ret = (UINT_PTR*)&m_rd.X[regNum - REGISTER_ARM64_X0]; + break; + + case REGISTER_ARM64_LR: + ret = (UINT_PTR*)&m_rd.LR; + break; + + case REGISTER_ARM64_PC: + ret = (UINT_PTR*)&m_rd.PC; + break; +#endif + + default: + _ASSERT(!"Invalid register number!"); + } + + return ret; +} + +// +// GetLeftSideAddressOfRegister returns the Left Side address of the given register in the frames current register +// display. +// +CORDB_ADDRESS CordbNativeFrame::GetLeftSideAddressOfRegister(CorDebugRegister regNum) const +{ +#if !defined(USE_REMOTE_REGISTER_ADDRESS) + // Use marker values as the register address. This is to implement the funceval breaking change. + // + if (IsLeafFrame()) + { + return kLeafFrameRegAddr; + } + else + { + return kNonLeafFrameRegAddr; + } + +#else // USE_REMOTE_REGISTER_ADDRESS + void* ret = 0; + + switch (regNum) + { + +#if !defined(DBG_TARGET_AMD64) + case REGISTER_FRAME_POINTER: + ret = m_rd.pFP; + break; +#endif + +#if defined(DBG_TARGET_X86) + case REGISTER_X86_EAX: + ret = m_rd.pEax; + break; + + case REGISTER_X86_ECX: + ret = m_rd.pEcx; + break; + + case REGISTER_X86_EDX: + ret = m_rd.pEdx; + break; + + case REGISTER_X86_EBX: + ret = m_rd.pEbx; + break; + + case REGISTER_X86_ESI: + ret = m_rd.pEsi; + break; + + case REGISTER_X86_EDI: + ret = m_rd.pEdi; + break; + +#elif defined(DBG_TARGET_AMD64) + case REGISTER_AMD64_RBP: + ret = m_rd.pRbp; + break; + + case REGISTER_AMD64_RAX: + ret = m_rd.pRax; + break; + + case REGISTER_AMD64_RCX: + ret = m_rd.pRcx; + break; + + case REGISTER_AMD64_RDX: + ret = m_rd.pRdx; + break; + + case REGISTER_AMD64_RBX: + ret = m_rd.pRbx; + break; + + case REGISTER_AMD64_RSI: + ret = m_rd.pRsi; + break; + + case REGISTER_AMD64_RDI: + ret = m_rd.pRdi; + break; + + case REGISTER_AMD64_R8: + ret = m_rd.pR8; + break; + + case REGISTER_AMD64_R9: + ret = m_rd.pR9; + break; + + case REGISTER_AMD64_R10: + ret = m_rd.pR10; + break; + + case REGISTER_AMD64_R11: + ret = m_rd.pR11; + break; + + case REGISTER_AMD64_R12: + ret = m_rd.pR12; + break; + + case REGISTER_AMD64_R13: + ret = m_rd.pR13; + break; + + case REGISTER_AMD64_R14: + ret = m_rd.pR14; + break; + + case REGISTER_AMD64_R15: + ret = m_rd.pR15; + break; +#endif + default: + _ASSERT(!"Invalid register number!"); + } + + return PTR_TO_CORDB_ADDRESS(ret); +#endif // !USE_REMOTE_REGISTER_ADDRESS +} + + +//--------------------------------------------------------------------------------------- +// +// Given the native variable information of a variable, return its value. +// +// Arguments: +// pNativeVarInfo - the variable information of the variable to be retrieved +// +// Returns: +// Return the specified value. +// Throw on error. +// +// Assumption: +// This function assumes that the value is either in a register or on the stack +// (i.e. VLT_REG or VLT_STK). +// +// Notes: +// Eventually we should make this more general-purpose. +// + +SIZE_T CordbNativeFrame::GetRegisterOrStackValue(const ICorDebugInfo::NativeVarInfo * pNativeVarInfo) +{ + SIZE_T uResult; + + if (pNativeVarInfo->loc.vlType == ICorDebugInfo::VLT_REG) + { + CorDebugRegister reg = ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg); + uResult = *(reinterpret_cast<SIZE_T *>(GetAddressOfRegister(reg))); + } + else if (pNativeVarInfo->loc.vlType == ICorDebugInfo::VLT_STK) + { + CORDB_ADDRESS remoteAddr = GetLSStackAddress(pNativeVarInfo->loc.vlStk.vlsBaseReg, + pNativeVarInfo->loc.vlStk.vlsOffset); + + HRESULT hr = GetProcess()->SafeReadStruct(remoteAddr, &uResult); + IfFailThrow(hr); + } + else + { + ThrowHR(E_FAIL); + } + + return uResult; +} + + +//--------------------------------------------------------------------------------------- +// +// Looks in a register and retrieves the value as a specific type, returning it +// as an ICorDebugValue. +// +// Arguments: +// reg - The register to use. +// cbSigBlob - The number of bytes in the signature given. +// pvSigBlob - A signature stream that describes the type of the value in the register. +// ppValue - OUT: Space to store the resulting ICorDebugValue +// +// Returns: +// S_OK on success, else an error code. +// +HRESULT CordbNativeFrame::GetLocalRegisterValue(CorDebugRegister reg, + ULONG cbSigBlob, + PCCOR_SIGNATURE pvSigBlob, + ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, false); + + CordbType * pType; + + SigParser sigParser(pvSigBlob, cbSigBlob); + + Instantiation emptyInst; + + HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType); + + if (FAILED(hr)) + { + return hr; + } + + return GetLocalRegisterValue(reg, pType, ppValue); +} + +//--------------------------------------------------------------------------------------- +// +// Looks in two registers and retrieves the value as a specific type, returning it +// as an ICorDebugValue. +// +// Arguments: +// highWordReg - The register to use for the high word. +// lowWordReg - The register to use for the low word. +// cbSigBlob - The number of bytes in the signature given. +// pvSigBlob - A signature stream that describes the type of the value in the register. +// ppValue - OUT: Space to store the resulting ICorDebugValue +// +// Returns: +// S_OK on success, else an error code. +// +HRESULT CordbNativeFrame::GetLocalDoubleRegisterValue(CorDebugRegister highWordReg, + CorDebugRegister lowWordReg, + ULONG cbSigBlob, + PCCOR_SIGNATURE pvSigBlob, + ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (cbSigBlob == 0) + { + return E_INVALIDARG; + } + + CordbType * pType; + + SigParser sigParser(pvSigBlob, cbSigBlob); + + Instantiation emptyInst; + + HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType); + + if (FAILED(hr)) + { + return hr; + } + + return GetLocalDoubleRegisterValue(highWordReg, lowWordReg, pType, ppValue); +} + + +//--------------------------------------------------------------------------------------- +// +// Uses an address and retrieves the value as a specific type, returning it +// as an ICorDebugValue. +// +// Arguments: +// address - A local memory address. +// cbSigBlob - The number of bytes in the signature given. +// pvSigBlob - A signature stream that describes the type of the value in the register. +// ppValue - OUT: Space to store the resulting ICorDebugValue +// +// Returns: +// S_OK on success, else an error code. +// +HRESULT CordbNativeFrame::GetLocalMemoryValue(CORDB_ADDRESS address, + ULONG cbSigBlob, + PCCOR_SIGNATURE pvSigBlob, + ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, false); + + CordbType * pType; + + SigParser sigParser(pvSigBlob, cbSigBlob); + + Instantiation emptyInst; + + HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType); + + if (FAILED(hr)) + { + return hr; + } + + return GetLocalMemoryValue(address, pType, ppValue); +} + + +//--------------------------------------------------------------------------------------- +// +// Uses a register and an address, retrieving the value as a specific type, returning it +// as an ICorDebugValue. +// +// Arguments: +// highWordReg - Register to use as the high word. +// lowWordAddress - A local memory address containing the low word. +// cbSigBlob - The number of bytes in the signature given. +// pvSigBlob - A signature stream that describes the type of the value in the register. +// ppValue - OUT: Space to store the resulting ICorDebugValue +// +// Returns: +// S_OK on success, else an error code. +// +HRESULT CordbNativeFrame::GetLocalRegisterMemoryValue(CorDebugRegister highWordReg, + CORDB_ADDRESS lowWordAddress, + ULONG cbSigBlob, + PCCOR_SIGNATURE pvSigBlob, + ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (cbSigBlob == 0) + { + return E_INVALIDARG; + } + + VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, true); + + CordbType * pType; + + SigParser sigParser(pvSigBlob, cbSigBlob); + + Instantiation emptyInst; + + HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType); + + if (FAILED(hr)) + { + return hr; + } + + return GetLocalRegisterMemoryValue(highWordReg, lowWordAddress, pType, ppValue); +} + + +//--------------------------------------------------------------------------------------- +// +// Uses a register and an address, retrieving the value as a specific type, returning it +// as an ICorDebugValue. +// +// Arguments: +// highWordReg - A local memory address to use as the high word. +// lowWordAddress - Register containing the low word. +// cbSigBlob - The number of bytes in the signature given. +// pvSigBlob - A signature stream that describes the type of the value in the register. +// ppValue - OUT: Space to store the resulting ICorDebugValue +// +// Returns: +// S_OK on success, else an error code. +// +HRESULT CordbNativeFrame::GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress, + CorDebugRegister lowWordRegister, + ULONG cbSigBlob, + PCCOR_SIGNATURE pvSigBlob, + ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (cbSigBlob == 0) + { + return E_INVALIDARG; + } + + VALIDATE_POINTER_TO_OBJECT_ARRAY(pvSigBlob, BYTE, cbSigBlob, true, true); + + CordbType * pType; + + SigParser sigParser(pvSigBlob, cbSigBlob); + + Instantiation emptyInst; + + HRESULT hr = CordbType::SigToType(m_JITILFrame->GetModule(), &sigParser, &emptyInst, &pType); + + if (FAILED(hr)) + { + return hr; + } + + return GetLocalMemoryRegisterValue(highWordAddress, lowWordRegister, pType, ppValue); +} + + + +HRESULT CordbNativeFrame::GetLocalRegisterValue(CorDebugRegister reg, + CordbType * pType, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + +#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_WIN64) +#if defined(DBG_TARGET_X86) + if ((reg >= REGISTER_X86_FPSTACK_0) && (reg <= REGISTER_X86_FPSTACK_7)) +#elif defined(DBG_TARGET_AMD64) + if ((reg >= REGISTER_AMD64_XMM0) && (reg <= REGISTER_AMD64_XMM15)) +#endif + { + return GetLocalFloatingPointValue(reg, pType, ppValue); + } +#endif + + // The address of the given register is the address of the value + // in this process. We have no remote address here. + void *pLocalValue = (void*)GetAddressOfRegister(reg); + HRESULT hr = S_OK; + + EX_TRY + { + // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to + // the new instance of CordbValue. + EnregisteredValueHomeHolder pRemoteReg(new RegValueHome(this, reg)); + EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr(); + + ICorDebugValue *pValue; + CordbValue::CreateValueByType(GetCurrentAppDomain(), + pType, + false, + EMPTY_BUFFER, + MemoryRange(pLocalValue, REG_SIZE), + pRegHolder, + &pValue); // throws + + *ppValue = pValue; + } + EX_CATCH_HRESULT(hr); + return hr; +} + +HRESULT CordbNativeFrame::GetLocalDoubleRegisterValue( + CorDebugRegister highWordReg, + CorDebugRegister lowWordReg, + CordbType * pType, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to + // the new instance of CordbValue. + EnregisteredValueHomeHolder pRemoteReg(new RegRegValueHome(this, highWordReg, lowWordReg)); + EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr(); + + CordbValue::CreateValueByType(GetCurrentAppDomain(), + pType, + false, + EMPTY_BUFFER, + MemoryRange(NULL, 0), + pRegHolder, + ppValue); // throws + } + EX_CATCH_HRESULT(hr); + +#ifdef _DEBUG + { + // sanity check object size + if (SUCCEEDED(hr)) + { + ULONG32 objectSize; + hr = (*ppValue)->GetSize(&objectSize); + _ASSERTE(SUCCEEDED(hr)); + // + // nickbe + // 10/31/2002 11:09:42 + // + // This assert assumes that the JIT will only partially enregister + // objects that have a size equal to twice the size of a register. + // + _ASSERTE(objectSize == 2 * sizeof(void*)); + } + } +#endif + return hr; +} + +HRESULT +CordbNativeFrame::GetLocalMemoryValue(CORDB_ADDRESS address, + CordbType * pType, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + _ASSERTE(m_nativeCode->GetFunction() != NULL); + HRESULT hr = S_OK; + + ICorDebugValue *pValue; + EX_TRY + { + CordbValue::CreateValueByType(GetCurrentAppDomain(), + pType, + false, + TargetBuffer(address, CordbValue::GetSizeForType(pType, kUnboxed)), + MemoryRange(NULL, 0), + NULL, + &pValue); // throws + } + EX_CATCH_HRESULT(hr); + + if (SUCCEEDED(hr)) + *ppValue = pValue; + + return hr; +} + +HRESULT +CordbNativeFrame::GetLocalByRefMemoryValue(CORDB_ADDRESS address, + CordbType * pType, + ICorDebugValue **ppValue) +{ + INTERNAL_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + LPVOID actualAddress = NULL; + HRESULT hr = GetProcess()->SafeReadStruct(address, &actualAddress); + if (FAILED(hr)) + { + return hr; + } + + return GetLocalMemoryValue(PTR_TO_CORDB_ADDRESS(actualAddress), pType, ppValue); +} + +HRESULT +CordbNativeFrame::GetLocalRegisterMemoryValue(CorDebugRegister highWordReg, + CORDB_ADDRESS lowWordAddress, + CordbType * pType, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to + // the new instance of CordbValue. + EnregisteredValueHomeHolder pRemoteReg(new RegMemValueHome(this, + highWordReg, + lowWordAddress)); + EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr(); + + CordbValue::CreateValueByType(GetCurrentAppDomain(), + pType, + false, + EMPTY_BUFFER, + MemoryRange(NULL, 0), + pRegHolder, + ppValue); // throws + } + EX_CATCH_HRESULT(hr); + +#ifdef _DEBUG + { + if (SUCCEEDED(hr)) + { + ULONG32 objectSize; + hr = (*ppValue)->GetSize(&objectSize); + _ASSERTE(SUCCEEDED(hr)); + // See the comment in CordbNativeFrame::GetLocalDoubleRegisterValue + // for more information on this assertion + _ASSERTE(objectSize == 2 * sizeof(void*)); + } + } +#endif + return hr; +} + +HRESULT +CordbNativeFrame::GetLocalMemoryRegisterValue(CORDB_ADDRESS highWordAddress, + CorDebugRegister lowWordRegister, + CordbType * pType, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to + // the new instance of CordbValue. + EnregisteredValueHomeHolder pRemoteReg(new MemRegValueHome(this, + lowWordRegister, + highWordAddress)); + EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr(); + + CordbValue::CreateValueByType(GetCurrentAppDomain(), + pType, + false, + EMPTY_BUFFER, + MemoryRange(NULL, 0), + pRegHolder, + ppValue); // throws + } + EX_CATCH_HRESULT(hr); + +#ifdef _DEBUG + { + if (SUCCEEDED(hr)) + { + ULONG32 objectSize; + hr = (*ppValue)->GetSize(&objectSize); + _ASSERTE(SUCCEEDED(hr)); + // See the comment in CordbNativeFrame::GetLocalDoubleRegisterValue + // for more information on this assertion + _ASSERTE(objectSize == 2 * sizeof(void*)); + } + } +#endif + return hr; +} + +#if !defined(DBG_TARGET_ARM) // @ARMTODO +HRESULT CordbNativeFrame::GetLocalFloatingPointValue(DWORD index, + CordbType * pType, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + HRESULT hr = S_OK; + + CorElementType et = pType->m_elementType; + + if ((et != ELEMENT_TYPE_R4) && + (et != ELEMENT_TYPE_R8)) + return E_INVALIDARG; + +#if defined(DBG_TARGET_AMD64) + if (!((index >= REGISTER_AMD64_XMM0) && + (index <= REGISTER_AMD64_XMM15))) + return E_INVALIDARG; + index -= REGISTER_AMD64_XMM0; +#else + if (!((index >= REGISTER_X86_FPSTACK_0) && + (index <= REGISTER_X86_FPSTACK_7))) + return E_INVALIDARG; + index -= REGISTER_X86_FPSTACK_0; +#endif + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + + // Make sure the thread's floating point stack state is loaded + // over from the left side. + // + CordbThread *pThread = m_pThread; + + EX_TRY + { + if (!pThread->m_fFloatStateValid) + { + pThread->LoadFloatState(); + } + } + EX_CATCH_HRESULT(hr); + if (SUCCEEDED(hr)) + { +#if !defined(DBG_TARGET_WIN64) + // This is needed on x86 because we are dealing with a stack. + index = pThread->m_floatStackTop - index; +#endif + + if (index >= (sizeof(pThread->m_floatValues) / + sizeof(pThread->m_floatValues[0]))) + return E_INVALIDARG; + +#ifdef DBG_TARGET_X86 + // A workaround (sort of) to get around the difference in format between + // a float value and a double value. We can't simply cast a double pointer to + // a float pointer. Instead, we have to cast the double itself to a float. + if (pType->m_elementType == ELEMENT_TYPE_R4) + *(float *)&(pThread->m_floatValues[index]) = (float)pThread->m_floatValues[index]; +#endif + + ICorDebugValue* pValue; + + EX_TRY + { + // Provide the register info as we create the value. CreateValueByType will transfer ownership of this to + // the new instance of CordbValue. + EnregisteredValueHomeHolder pRemoteReg(new FloatRegValueHome(this, index)); + EnregisteredValueHomeHolder * pRegHolder = pRemoteReg.GetAddr(); + + CordbValue::CreateValueByType(GetCurrentAppDomain(), + pType, + false, + EMPTY_BUFFER, + MemoryRange(&(pThread->m_floatValues[index]), sizeof(double)), + pRegHolder, + &pValue); // throws + + *ppValue = pValue; + } + EX_CATCH_HRESULT(hr); + + } + + return hr; +} +#endif // !DBG_TARGET_ARM @ARMTODO + +//--------------------------------------------------------------------------------------- +// +// Quick accessor to tell if we're the leaf frame. +// +// Return Value: +// whether we are the leaf frame or not +// + +bool CordbNativeFrame::IsLeafFrame() const +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + // Should only be called by non-neutered stuff. + // Also, since we're not neutered, we know we have a Thread object, and we know it's state is current. + _ASSERTE(!this->IsNeutered()); + + // If the thread's state is sleeping, then there's no frame below us, but we're actually + // not the leaf frame. + // @todo- consider having Sleep / Wait / Join be an ICDInternalFrame. + _ASSERTE(m_pThread != NULL); // not neutered, so should have a thread + if (m_pThread->IsThreadWaitingOrSleeping()) + { + return false; + } + + if (!m_optfIsLeafFrame.HasValue()) + { + if (GetProcess()->GetShim() != NULL) + { + // In V2, the definition of "leaf frame" is the leaf frame in the leaf chain in the stackwalk. + PRIVATE_SHIM_CALLBACK_IN_THIS_SCOPE0(GetProcess()); + ShimStackWalk * pSW = GetProcess()->GetShim()->LookupOrCreateShimStackWalk(m_pThread); + + // check if there is any chain + if (pSW->GetChainCount() > 0) + { + // check if the leaf chain has any frame + if (pSW->GetChain(0)->GetLastFrameIndex() > 0) + { + CordbFrame * pCFrame = GetCordbFrameFromInterface(pSW->GetFrame(0)); + CordbNativeFrame * pNFrame = pCFrame->GetAsNativeFrame(); + if (pNFrame != NULL) + { + // check if the leaf frame in the leaf chain is "this" + if (CompareControlRegisters(GetContext(), pNFrame->GetContext())) + { + m_optfIsLeafFrame = TRUE; + } + } + } + } + + if (!m_optfIsLeafFrame.HasValue()) + { + m_optfIsLeafFrame = FALSE; + } + } + else + { + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + m_optfIsLeafFrame = (pDAC->IsLeafFrame(m_pThread->m_vmThreadToken, &m_context) == TRUE); + } + } + return m_optfIsLeafFrame.GetValue(); +} + +//--------------------------------------------------------------------------------------- +// +// Get the offset used to determine if a variable is live in a particular method frame. +// +// Return Value: +// the offset used for inspection purposes +// +// Notes: +// On WIN64, variables used in funclets are always homed on the stack. Morever, the variable lifetime +// information only covers the parent method. The idea is that the variables which are live in a funclet +// will be the variables which are live in the parent method at the offset at which the exception occurs. +// Thus, to determine if a variable is live in a funclet frame, we need to use the offset of the parent +// method frame at which the exception occurs. +// + +SIZE_T CordbNativeFrame::GetInspectionIP() +{ +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + // On 64-bit, if this is a funclet, then return the offset of the parent method frame at which + // the exception occurs. Otherwise just return the normal offset. + return (IsFunclet() ? GetParentIP() : m_ip); +#else + // Always return the normal offset on all other platforms. + return m_ip; +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM +} + +//--------------------------------------------------------------------------------------- +// +// Return whether this is a funclet method frame. +// +// Return Value: +// whether this is a funclet method frame. +// + +bool CordbNativeFrame::IsFunclet() +{ +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + return (m_misc.parentIP != NULL); +#else // !DBG_TARGET_WIN64 && !DBG_TARGET_ARM + return false; +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM +} + +//--------------------------------------------------------------------------------------- +// +// Return whether this is a filter funclet method frame. +// +// Return Value: +// whether this is a filter funclet method frame. +// + +bool CordbNativeFrame::IsFilterFunclet() +{ +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + return (IsFunclet() && m_misc.fIsFilterFunclet); +#else // !DBG_TARGET_WIN64 && !DBG_TARGET_ARM + return false; +#endif // DBG_TARGET_WIN64 +} + + +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) +//--------------------------------------------------------------------------------------- +// +// Return the offset of the parent method frame at which the exception occurs. +// +// Return Value: +// the offset of the parent method frame at which the exception occurs +// + +SIZE_T CordbNativeFrame::GetParentIP() +{ + return m_misc.parentIP; +} +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM + +// Accessor for the shim private hook code:CordbThread::ConvertFrameForILMethodWithoutMetadata. +// Refer to that function for comments on the return value, the argument, etc. +BOOL CordbNativeFrame::ConvertNativeFrameForILMethodWithoutMetadata( + ICorDebugInternalFrame2 ** ppInternalFrame2) +{ + _ASSERTE(ppInternalFrame2 != NULL); + *ppInternalFrame2 = NULL; + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + IDacDbiInterface::DynamicMethodType type = + pDAC->IsILStubOrLCGMethod(GetNativeCode()->GetVMNativeCodeMethodDescToken()); + + // Here are the conversion rules: + // 1) For a normal managed method, we don't convert, and we return FALSE. + // 2) For an IL stub, we convert to NULL, and we return TRUE. + // 3) For a dynamic method, we convert to a STUBFRAME_LIGHTWEIGHT_FUNCTION, and we return TRUE. + if (type == IDacDbiInterface::kNone) + { + return FALSE; + } + else if (type == IDacDbiInterface::kILStub) + { + return TRUE; + } + else if (type == IDacDbiInterface::kLCGMethod) + { + RSInitHolder<CordbInternalFrame> pInternalFrame( + new CordbInternalFrame(m_pThread, + m_fp, + m_currentAppDomain, + STUBFRAME_LIGHTWEIGHT_FUNCTION, + GetNativeCode()->GetMetadataToken(), + GetNativeCode()->GetFunction(), + GetNativeCode()->GetVMNativeCodeMethodDescToken())); + + pInternalFrame.TransferOwnershipExternal(ppInternalFrame2); + return TRUE; + } + + UNREACHABLE(); +} + +/* ------------------------------------------------------------------------- * + * JIT-IL Frame class + * ------------------------------------------------------------------------- */ + +CordbJITILFrame::CordbJITILFrame(CordbNativeFrame * pNativeFrame, + CordbILCode * pCode, + UINT_PTR ip, + CorDebugMappingResult mapping, + GENERICS_TYPE_TOKEN exactGenericArgsToken, + DWORD dwExactGenericArgsTokenIndex, + bool fVarArgFnx, + CordbReJitILCode * pRejitCode) + : CordbBase(pNativeFrame->GetProcess(), 0, enumCordbJITILFrame), + m_nativeFrame(pNativeFrame), + m_ilCode(pCode), + m_ip(ip), + m_mapping(mapping), + m_fVarArgFnx(fVarArgFnx), + m_allArgsCount(0), + m_rgbSigParserBuf(NULL), + m_FirstArgAddr(NULL), + m_rgNVI(NULL), + m_genericArgs(), + m_genericArgsLoaded(false), + m_frameParamsToken(exactGenericArgsToken), + m_dwFrameParamsTokenIndex(dwExactGenericArgsTokenIndex), + m_pReJitCode(pRejitCode) +{ + // We'll initialize the SigParser in CordbJITILFrame::Init(). + m_sigParserCached = SigParser(NULL, 0); + _ASSERTE(m_sigParserCached.IsNull()); + + HRESULT hr = S_OK; + EX_TRY + { + m_nativeFrame->m_pThread->GetRefreshStackNeuterList()->Add(GetProcess(), this); + } + EX_CATCH_HRESULT(hr); + SetUnrecoverableIfFailed(GetProcess(), hr); +} + +//--------------------------------------------------------------------------------------- +// +// Initialize a CordbJITILFrame object. Must be called after allocating the object and before using it. +// If Init fails, then destroy the object and release the memory. +// +// Return Value: +// HRESULT for the operation +// +// Notes: +// This is a nop if the function is not a vararg function. +// + +HRESULT CordbJITILFrame::Init() +{ + // ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + HRESULT hr = S_OK; + + + EX_TRY + { + _ASSERTE(m_ilCode != NULL); + + if (m_fVarArgFnx) + { + // First, we need to find the VASigCookie. Use the native var info to do so. + const ICorDebugInfo::NativeVarInfo * pNativeVarInfo = NULL; + CordbNativeFrame * pNativeFrame = this->m_nativeFrame; + + pNativeFrame->m_nativeCode->LoadNativeInfo(); + hr = pNativeFrame->m_nativeCode->ILVariableToNative((DWORD)ICorDebugInfo::VARARGS_HND_ILNUM, + pNativeFrame->GetInspectionIP(), + &pNativeVarInfo); + IfFailThrow(hr); + + // Check for the case where the VASigCookie isn't pushed on the stack yet. + // This should only be a problem with optimized code. + if (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK) + { + ThrowHR(E_FAIL); + } + + // Retrieve the target address. + CORDB_ADDRESS pRemoteValue = pNativeFrame->GetLSStackAddress( + pNativeVarInfo->loc.vlStk.vlsBaseReg, + pNativeVarInfo->loc.vlStk.vlsOffset); + + CORDB_ADDRESS argBase; + // Now is the time to ask DacDbi to retrieve the information based on the VASigCookie. + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + TargetBuffer sigTargetBuf = pDAC->GetVarArgSig(pRemoteValue, &argBase); + + // make sure we are not leaking any memory + _ASSERTE(m_rgbSigParserBuf == NULL); + + m_rgbSigParserBuf = new BYTE[sigTargetBuf.cbSize]; + GetProcess()->SafeReadBuffer(sigTargetBuf, m_rgbSigParserBuf); + m_sigParserCached = SigParser(m_rgbSigParserBuf, sigTargetBuf.cbSize); + + // Note that we should never mutate the SigParser. + // Instead, make a copy and work with the copy instead. + if (!m_sigParserCached.IsNull()) + { + SigParser sigParser = m_sigParserCached; + + // get the actual count of arguments, including the var args + IfFailThrow(sigParser.SkipMethodHeaderSignature(&m_allArgsCount)); + + BOOL methodIsStatic; + + m_ilCode->GetSig(NULL, NULL, &methodIsStatic); // throws + + if (!methodIsStatic) + { + m_allArgsCount++; // skip the "this" object + } + + // initialize the variable lifetime information + m_rgNVI = new ICorDebugInfo::NativeVarInfo[m_allArgsCount]; // throws + + _ASSERTE(ICorDebugInfo::VLT_COUNT <= ICorDebugInfo::VLT_INVALID); + + for (ULONG i = 0; i < m_allArgsCount; i++) + { + m_rgNVI[i].loc.vlType = ICorDebugInfo::VLT_INVALID; + } + } + + // GetVarArgSig gets the address of the beginning of the arguments pushed for this frame. + // We'll need the address of the first argument, which will depend on its size and the + // calling convention, so we'll commpute that now that we have the SigParser. + CordbType * pArgType; + IfFailThrow(GetArgumentType(0, &pArgType)); + ULONG32 argSize = 0; + IfFailThrow(pArgType->GetUnboxedObjectSize(&argSize)); +#if defined(_TARGET_X86_) // (STACK_GROWS_DOWN_ON_ARGS_WALK) + m_FirstArgAddr = argBase - argSize; +#else // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK) + AlignAddressForType(pArgType, argBase); + m_FirstArgAddr = argBase; +#endif // !_TARGET_X86_ (STACK_GROWS_UP_ON_ARGS_WALK) + } + + // The stackwalking code can't always successfully retrieve the generics type token. + // For example, on 64-bit, the JIT only encodes the generics type token location if + // a method has catch clause for a generic exception (e.g. "catch(MyException<string> e)"). + if ((m_dwFrameParamsTokenIndex != (DWORD)ICorDebugInfo::MAX_ILNUM) && (m_frameParamsToken == NULL)) + { + // All variables are unavailable in the prolog and the epilog. + // This includes the generics type token. Failing to get the token just means that + // we won't have full generics information. This should not be a disastrous failure. + // + // Currently, on X64, the JIT is reporting that the variables are live even in the epilog. + // That's why we need this check here. I need to follow up on this. + if ((m_mapping != MAPPING_PROLOG) && (m_mapping != MAPPING_EPILOG)) + { + // Find the generics type token using the variable lifetime information. + const ICorDebugInfo::NativeVarInfo * pNativeVarInfo = NULL; + CordbNativeFrame * pNativeFrame = this->m_nativeFrame; + + pNativeFrame->m_nativeCode->LoadNativeInfo(); + HRESULT hrTmp = pNativeFrame->m_nativeCode->ILVariableToNative(m_dwFrameParamsTokenIndex, + pNativeFrame->GetInspectionIP(), + &pNativeVarInfo); + + // It's not a disaster if we can't find the generics token, so don't throw an exception here. + // In fact, it's fairly common in retail code. Even if we can't find the generics token, + // we may still be able to look up the generics type information later by using the MethodDesc, + // the "this" object, etc. If not, we'll at least get the representative type information + // (e.g. Foo<T> instead of Foo<string>). + if (SUCCEEDED(hrTmp)) + { + _ASSERTE(pNativeVarInfo != NULL); + + // The generics type token should be stored either in a register or on the stack. + SIZE_T uRawToken = pNativeFrame->GetRegisterOrStackValue(pNativeVarInfo); + + // Ask DAC to resolve the token for us. We really don't want to deal with all the logic here. + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + // On a minidump, we'll throw if we're missing the memory. + ALLOW_DATATARGET_MISSING_MEMORY( + m_frameParamsToken = pDAC->ResolveExactGenericArgsToken(m_dwFrameParamsTokenIndex, uRawToken); + ); + } + } + } + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +/* + A list of which resources owned by this object are accounted for. + + UNKNOWN: + CordbNativeFrame* m_nativeFrame; + CordbILCode * m_ilCode; + CorDebugMappingResult m_mapping; + CORDB_ADDRESS m_FirstArgAddr; + ICorDebugInfo::NativeVarInfo * m_rgNVI; // Deleted in neuter + CordbClass **m_genericArgs; +*/ + +CordbJITILFrame::~CordbJITILFrame() +{ + _ASSERTE(IsNeutered()); +} + +// Neutered by CordbNativeFrame +void CordbJITILFrame::Neuter() +{ + // Since neutering here calls Release directly, we don't want to double-release + // if neuter is called multiple times. + if (IsNeutered()) + { + return; + } + + // Frames include pointers across to other types that specify the + // representation instantiation - reduce the reference counts on these.... + for (unsigned int i = 0; i < m_genericArgs.m_cInst; i++) + { + m_genericArgs.m_ppInst[i]->Release(); + } + + if (m_rgNVI != NULL) + { + delete [] m_rgNVI; + m_rgNVI = NULL; + } + + if (m_rgbSigParserBuf != NULL) + { + delete [] m_rgbSigParserBuf; + m_rgbSigParserBuf = NULL; + } + + m_pReJitCode.Clear(); + + // If this class ever inherits from the CordbFrame we'll need a call + // to CordbFrame::Neuter() here instead of to CordbBase::Neuter(); + CordbBase::Neuter(); +} + +//--------------------------------------------------------------------------------------- +// +// Load the generic type and method arguments and store them into the frame if possible. +// +// Return Value: +// HRESULT for the operation +// + +void CordbJITILFrame::LoadGenericArgs() +{ + THROW_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); // + + // The case where there are no type parameters, or the case where we've + // already feched the realInst, is easy. + if (m_genericArgsLoaded) + { + return; + } + + _ASSERTE(m_nativeFrame->m_nativeCode != NULL); + + if (!m_nativeFrame->m_nativeCode->IsInstantiatedGeneric()) + { + m_genericArgs = Instantiation(0, NULL,0); + m_genericArgsLoaded = true; + return; + } + + // Find the exact generic arguments for a frame that is executing + // a generic method. The left-side will fetch these from arguments + // given on the stack and/or from the IP. + + + IDacDbiInterface * pDAC = GetProcess()->GetDAC(); + + UINT32 cGenericClassTypeParams = 0; + DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> rgGenericTypeParams; + + pDAC->GetMethodDescParams(GetCurrentAppDomain()->GetADToken(), + m_nativeFrame->GetNativeCode()->GetVMNativeCodeMethodDescToken(), + m_frameParamsToken, + &cGenericClassTypeParams, + &rgGenericTypeParams); + + UINT32 cTotalGenericTypeParams = rgGenericTypeParams.Count(); + + // @dbgtodo reliability - This holder doesn't actually work in this case because it just deletes + // each element on error. The RS classes are all expected to be neutered before the destructor is called. + NewArrayHolder<CordbType *> ppGenericArgs(new CordbType *[cTotalGenericTypeParams]); + + for (UINT32 i = 0; i < cTotalGenericTypeParams;i++) + { + // creates a CordbType object for the generic argument + HRESULT hr = CordbType::TypeDataToType(GetCurrentAppDomain(), + &(rgGenericTypeParams[i]), + &ppGenericArgs[i]); + IfFailThrow(hr); + + // We add a ref as the instantiation will be stored away in the + // ref-counted data structure associated with the JITILFrame + ppGenericArgs[i]->AddRef(); + } + + // initialize the generics information + m_genericArgs = Instantiation(cTotalGenericTypeParams, ppGenericArgs, cGenericClassTypeParams); + m_genericArgsLoaded = true; + + ppGenericArgs.SuppressRelease(); +} + + +// +// CordbJITILFrame::QueryInterface +// +// Description +// Interface query for this COM object +// +// NOTE: the COM object associated with this CordbJITILFrame may consist of two +// C++ objects (a CordbJITILFrame and its associated CordbNativeFrame) +// +// Parameters +// id the GUID associated with the requested interface +// pInterface [out] the interface pointer +// +// Returns +// HRESULT +// S_OK If this CordbJITILFrame supports the interface +// E_NOINTERFACE If this object does not support the interface +// +// Exceptions +// None +// +HRESULT CordbJITILFrame::QueryInterface(REFIID id, void **pInterface) +{ + if (NULL != m_nativeFrame) + { + // If the native frame does not support the requested interface, then + // the native fram is responsible for delegating the query back to this + // object through QueryInterfaceInternal(...) + return m_nativeFrame->QueryInterface(id, pInterface); + } + + // no native frame. Check for interfaces common to CordbNativeFrame and + // CordbJITILFrame + if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugILFrame*>(this)); + } + else if (id == IID_ICorDebugFrame) + { + *pInterface = static_cast<ICorDebugFrame*>(this); + } + else + { + // didn't find an interface yet. Since there's no native frame + // associated with this IL frame, go ahead and check for the IL frame + return this->QueryInterfaceInternal(id, pInterface); + } + + ExternalAddRef(); + return S_OK; +} + +// +// CordbJITILFrame::QueryInterfaceInternal +// +// Description +// Interface query for interfaces implemented ONLY by CordbJITILFrame (as +// opposed to interfaces implemented by both CordbNativeFrame and +// CordbJITILFrame) +// +// Parameters +// id the GUID associated with the requested interface +// pInterface [out] the interface pointer +// NOTE: id must not be IUnknown or ICorDebugFrame +// NOTE: if this object is in "forward compatibility mode", passing in +// IID_ICorDebugILFrame2 for the id will result in a failure (returns +// E_NOINTERFACE) +// +// Returns +// HRESULT +// S_OK If this CordbJITILFrame supports the interface +// E_NOINTERFACE If this object does not support the interface +// +// Exceptions +// None +// +HRESULT +CordbJITILFrame::QueryInterfaceInternal(REFIID id, void** pInterface) +{ + _ASSERTE(IID_ICorDebugFrame != id); + _ASSERTE(IID_IUnknown != id); + + // don't query for IUnknown or ICorDebugFrame! Someone else should have + // already taken care of that. + if (id == IID_ICorDebugILFrame) + { + *pInterface = static_cast<ICorDebugILFrame*>(this); + } + else if (id == IID_ICorDebugILFrame2) + { + *pInterface = static_cast<ICorDebugILFrame2*>(this); + } + else if (id == IID_ICorDebugILFrame3) + { + *pInterface = static_cast<ICorDebugILFrame3*>(this); + } + else if (id == IID_ICorDebugILFrame4) + { + *pInterface = static_cast<ICorDebugILFrame4*>(this); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +//--------------------------------------------------------------------------------------- +// +// Get an enumerator for the generic type and method arguments on this frame. +// +// Arguments: +// ppTypeParameterEnum - out parameter; return the enumerator +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbJITILFrame::EnumerateTypeParameters(ICorDebugTypeEnum **ppTypeParameterEnum) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppTypeParameterEnum, ICorDebugTypeEnum **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + (*ppTypeParameterEnum) = NULL; + + HRESULT hr = S_OK; + EX_TRY + { + + // load the generic arguments, which may be cached + LoadGenericArgs(); + + // create the enumerator + RSInitHolder<CordbTypeEnum> pEnum( + CordbTypeEnum::Build(GetCurrentAppDomain(), m_nativeFrame->m_pThread->GetRefreshStackNeuterList(), m_genericArgs.m_cInst, m_genericArgs.m_ppInst)); + if ( pEnum == NULL ) + { + ThrowOutOfMemory(); + } + + pEnum.TransferOwnershipExternal(ppTypeParameterEnum); + } + EX_CATCH_HRESULT(hr); + return hr; +} + + +// ---------------------------------------------------------------------------- +// CordbJITILFrame::GetChain +// +// Description: +// Return the owning chain. Since chains have been deprecated in Arrowhead, +// this function returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppChain - out parameter; return the owning chain +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppChain is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered. +// Return E_NOTIMPL if there is no shim. +// + +HRESULT CordbJITILFrame::GetChain(ICorDebugChain **ppChain) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppChain, ICorDebugChain **); + + HRESULT hr = S_OK; + EX_TRY + { + hr = m_nativeFrame->GetChain(ppChain); + // Since we are returning anyway, let's not throw even if the call fails. + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// Return the IL code blob associated with this IL frame. +// Each IL frame corresponds to exactly one IL code blob. +HRESULT CordbJITILFrame::GetCode(ICorDebugCode **ppCode) +{ + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppCode, ICorDebugCode **); + + *ppCode = static_cast<ICorDebugCode*> (m_ilCode); + m_ilCode->ExternalAddRef(); + + return S_OK;; +} + +// Return the function associated with this IL frame. +// Each IL frame corresponds to exactly one function. +HRESULT CordbJITILFrame::GetFunction(ICorDebugFunction **ppFunction) +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this) + { + ValidateOrThrow(ppFunction); + + CordbFunction * pFunc = m_nativeFrame->GetFunction(); + *ppFunction = static_cast<ICorDebugFunction *>(pFunc); + pFunc->ExternalAddRef(); + } + PUBLIC_API_END(hr); + return hr; +} + +// Return the token of the function associated with this IL frame. +// Each IL frame corresponds to exactly one function. +HRESULT CordbJITILFrame::GetFunctionToken(mdMethodDef *pToken) +{ + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pToken, mdMethodDef *); + + *pToken = m_nativeFrame->m_nativeCode->GetMetadataToken(); + + return S_OK; +} + +// ---------------------------------------------------------------------------- +// CordJITILFrame::GetStackRange +// +// Description: +// Get the stack range owned by the associated native frame. +// IL frames and native frames are 1:1 for normal jitted managed methods. +// Dynamic methods are an exception. +// +// Arguments: +// * pStart - out parameter; return the leaf end of the frame +// * pEnd - out parameter; return the root end of the frame +// +// Return Value: +// Return S_OK on success. +// +// Notes: see code:#GetStackRange + +HRESULT CordbJITILFrame::GetStackRange(CORDB_ADDRESS *pStart, CORDB_ADDRESS *pEnd) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + + // The access of m_nativeFrame is not safe here. It's a weak reference. + OK_IF_NEUTERED(this); + + HRESULT hr = S_OK; + EX_TRY + { + hr = m_nativeFrame->GetStackRange(pStart, pEnd); + // Since we are returning anyway, let's not throw even if the call fails. + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbJITILFrame::GetCaller +// +// Description: +// Delegate to the associated native frame to return the caller, which is closer to the root. +// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppFrame - out parameter; return the caller frame +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppFrame is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered. +// Return E_NOTIMPL if there is no shim. +// + +HRESULT CordbJITILFrame::GetCaller(ICorDebugFrame **ppFrame) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **); + + HRESULT hr = S_OK; + EX_TRY + { + hr = m_nativeFrame->GetCaller(ppFrame); + // Since we are returning anyway, let's not throw even if the call fails. + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// ---------------------------------------------------------------------------- +// CordbJITILFrame::GetCallee +// +// Description: +// Delegate to the associated native frame to return the callee, which is closer to the leaf. +// This function has been deprecated in Arrowhead, and so it returns E_NOTIMPL unless there is a shim. +// +// Arguments: +// * ppFrame - out parameter; return the callee frame +// +// Return Value: +// Return S_OK on success. +// Return E_INVALIDARG if ppFrame is NULL. +// Return CORDBG_E_OBJECT_NEUTERED if the CordbJITILFrame is neutered. +// Return E_NOTIMPL if there is no shim. +// + +HRESULT CordbJITILFrame::GetCallee(ICorDebugFrame **ppFrame) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppFrame, ICorDebugFrame **); + + HRESULT hr = S_OK; + EX_TRY + { + hr = m_nativeFrame->GetCallee(ppFrame); + // Since we are returning anyway, let's not throw even if the call fails. + } + EX_CATCH_HRESULT(hr); + return hr; +} + +// Create a stepper on the frame. +HRESULT CordbJITILFrame::CreateStepper(ICorDebugStepper **ppStepper) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // by default, a stepper operates on the IL level, using IL offsets + return m_nativeFrame->CreateStepper(ppStepper); +} + +// Return the IL offset and the mapping result. +HRESULT CordbJITILFrame::GetIP(ULONG32 *pnOffset, + CorDebugMappingResult *pMappingResult) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pnOffset, ULONG32 *); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pMappingResult, CorDebugMappingResult *); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + *pnOffset = (ULONG32)m_ip; + if (pMappingResult) + *pMappingResult = m_mapping; + + return S_OK; +} + +// Determine if we can set IP at this point. The specified offset is the IL offset. +HRESULT CordbJITILFrame::CanSetIP(ULONG32 nOffset) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + // Check to see that this is a leaf frame + if (!m_nativeFrame->IsLeafFrame()) + { + ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME); + } + + // delegate to the associated native frame + CordbNativeCode * pNativeCode = m_nativeFrame->m_nativeCode; + hr = m_nativeFrame->m_pThread->SetIP(SetIP_fCanSetIPOnly, // specify that this is for checking only + pNativeCode, + nOffset, + SetIP_fIL ); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +// Try to set the IP to the specified offset. The specified offset is the IL offset. +HRESULT CordbJITILFrame::SetIP(ULONG32 nOffset) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + EX_TRY + { + // Check to see that this is a leaf frame + if (!m_nativeFrame->IsLeafFrame()) + { + ThrowHR(CORDBG_E_SET_IP_NOT_ALLOWED_ON_NONLEAF_FRAME); + } + + // delegate to the native frame + CordbNativeCode * pNativeCode = m_nativeFrame->m_nativeCode; + hr = m_nativeFrame->m_pThread->SetIP(SetIP_fSetIP, // specify that this is a real SetIP operation + pNativeCode, + nOffset, + SetIP_fIL ); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// This routine creates backing native info for a local variable, returning an ICorDebugInfo +// object for the local variable when successful. +// +// Arguments: +// dwIndex - Index of the local variable to create native info for. +// ppNativeInfo - OUT: Space for storing the resulting pointer to native variable info. +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbJITILFrame::FabricateNativeInfo(DWORD dwIndex, + const ICorDebugInfo::NativeVarInfo ** ppNativeInfo) +{ + HRESULT hr = S_OK; + EX_TRY + { + THROW_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(this->GetProcess()); + _ASSERTE(m_fVarArgFnx); + + // This array should have been populated in CordbJITILFrame::Init(). + _ASSERTE(m_rgNVI != NULL); + + // check if we have already fabricated all the information + if (m_rgNVI[dwIndex].loc.vlType != ICorDebugInfo::VLT_INVALID) + { + (*ppNativeInfo) = &m_rgNVI[dwIndex]; + } + else + { + // We'll initialize everything at once + ULONG cbArchitectureMin; + + // m_FirstArgAddr will already be aligned on platforms that require alignment + CORDB_ADDRESS rpCur = m_FirstArgAddr; + +#if defined(DBG_TARGET_X86) || defined(DBG_TARGET_ARM) + cbArchitectureMin = 4; +#elif defined(DBG_TARGET_WIN64) + cbArchitectureMin = 8; +#else + cbArchitectureMin = 8; //REVISIT_TODO not sure if this is correct + PORTABILITY_ASSERT("What is the architecture-dependent minimum word size?"); +#endif // DBG_TARGET_X86 + + // make a copy of the cached SigParser + SigParser sigParser = m_sigParserCached; + + IfFailThrow(sigParser.SkipMethodHeaderSignature(NULL)); + + ULONG32 cbType; + + CordbType * pArgType; + + // make sure all the generic type and method arguments are loaded + LoadGenericArgs(); + + // get a CordbType object for the generic argument + IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), &pArgType)); + + IfFailThrow(pArgType->GetUnboxedObjectSize(&cbType)); + +#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK + // The the rpCur pointer starts off in the right spot for the + // first argument, but thereafter we have to decrement it + // before getting the variable's location from it. So increment + // it here to be consistent later. + rpCur += max(cbType, cbArchitectureMin); +#endif + + // Grab the IL code's function's method signature so we can see if it's static. + BOOL fMethodIsStatic; + + m_ilCode->GetSig(NULL, NULL, &fMethodIsStatic); // throws + + ULONG i; + + if (fMethodIsStatic) + { + i = 0; + } + else + { + i = 1; + } + + for ( ; i < m_allArgsCount; i++) + { + m_rgNVI[i].startOffset = 0; + m_rgNVI[i].endOffset = 0xFFffFFff; + m_rgNVI[i].varNumber = i; + m_rgNVI[i].loc.vlType = ICorDebugInfo::VLT_FIXED_VA; + + LoadGenericArgs(); + + IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), &pArgType)); + + IfFailThrow(pArgType->GetUnboxedObjectSize(&cbType)); + +#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK + rpCur -= max(cbType, cbArchitectureMin); + m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset = + (unsigned)(m_FirstArgAddr - rpCur); + + // Since the JIT adds in the size of this field, we do too to + // be consistent. + m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset += sizeof(((CORINFO_VarArgInfo*)0)->argBytes); +#else // STACK_GROWS_UP_ON_ARGS_WALK + m_rgNVI[i].loc.vlFixedVarArg.vlfvOffset = + (unsigned)(rpCur - m_FirstArgAddr); + rpCur += max(cbType, cbArchitectureMin); + AlignAddressForType(pArgType, rpCur); +#endif + + IfFailThrow(sigParser.SkipExactlyOne()); + } // for ( ; i M m_allArgsCount; i++) + + (*ppNativeInfo) = &m_rgNVI[dwIndex]; + } // else (m_rgNVI[dwIndex].loc.vlType == ICorDebugInfo::VLT_INVALID) + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +HRESULT CordbJITILFrame::ILVariableToNative(DWORD dwVarNumber, + const ICorDebugInfo::NativeVarInfo **ppNativeInfo) +{ + FAIL_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); // + + _ASSERTE(m_nativeFrame->m_nativeCode->IsNativeCodeValid()); + // We keep the fixed argument native var infos in the + // CordbFunction, which only is an issue for var args info: + if (!m_fVarArgFnx || //not a var args function + (dwVarNumber < m_nativeFrame->m_nativeCode->GetFixedArgCount()) || // var args,fixed arg + // note that this include the implicit 'this' for nonstatic fnxs + (dwVarNumber >= m_allArgsCount) ||// var args, local variable + (m_sigParserCached.IsNull())) //we don't have any VA info + { + // If we're in a var args fnx, but we're actually looking + // for a local variable, then we want to use the variable + // index as the function sees it - fixed (but not var) + // args are added to local var number to get native info + // We are really trying to find a variable by it's number, + // but "special" variables have a negative number which we + // don't use. We "number" them conceptually between the + // arguments and locals: + // + // arguments special locals + // ----------------------------------------- + // Actual numbers: 1 2 3 . . . 4 5 6 7 + // Logical numbers: 0 1 2 3 4 5 6 7 8 + // + // We have two different counts for the number of arguments: the fixedArgCount + // gives the actual number of arguments and the allArgsCount is the number of + // of fixed arguments plus the number of var args. + // + // Thus, to get the correct actual number for locals we have to compute it as + // logicalNumber - allArgsCount + fixedArgCount + + if (m_fVarArgFnx && (dwVarNumber >= m_allArgsCount) && !m_sigParserCached.IsNull()) + { + dwVarNumber -= m_allArgsCount; + dwVarNumber += m_nativeFrame->m_nativeCode->GetFixedArgCount(); + } + + return m_nativeFrame->m_nativeCode->ILVariableToNative(dwVarNumber, + m_nativeFrame->GetInspectionIP(), + ppNativeInfo); + } + + return FabricateNativeInfo(dwVarNumber,ppNativeInfo); +} + +//--------------------------------------------------------------------------------------- +// +// This routine get the type of a particular argument. +// +// Arguments: +// dwIndex - Index of the argument. +// ppResultType - OUT: Space for storing the type of the argument. +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbJITILFrame::GetArgumentType(DWORD dwIndex, + CordbType ** ppResultType) +{ + HRESULT hr = S_OK; + THROW_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); + + LoadGenericArgs(); + + if (m_fVarArgFnx && !m_sigParserCached.IsNull()) + { + SigParser sigParser = m_sigParserCached; + + IfFailThrow(sigParser.SkipMethodHeaderSignature(NULL)); + + // Grab the IL code's function's method signature so we can see if it's static. + BOOL fMethodIsStatic; + + m_ilCode->GetSig(NULL, NULL, &fMethodIsStatic); // throws + if (!fMethodIsStatic) + { + if (dwIndex == 0) + { + // Return the signature for the 'this' pointer for the + // class this method is in. + + IfFailThrow(m_ilCode->GetClass()->GetThisType(&(this->m_genericArgs), ppResultType)); + return hr; + } + else + { + dwIndex--; + } + } + for (ULONG i = 0; i < dwIndex; i++) + { + IfFailThrow(sigParser.SkipExactlyOne()); + } + + IfFailThrow(sigParser.SkipFunkyAndCustomModifiers()); + + IfFailThrow(sigParser.SkipAnyVASentinel()); + + IfFailThrow(CordbType::SigToType(GetModule(), &sigParser, &(this->m_genericArgs), ppResultType)); + } + else // (!m_fVarArgFnx || m_sigParserCached.IsNull()) + { + m_nativeFrame->m_nativeCode->GetArgumentType(dwIndex, &(this->m_genericArgs), ppResultType); + } + + return hr; +} + +// +// GetNativeVariable uses the JIT variable information to delegate to +// the native frame when the value is really created. +// +HRESULT CordbJITILFrame::GetNativeVariable(CordbType *type, + const ICorDebugInfo::NativeVarInfo *pNativeVarInfo, + ICorDebugValue **ppValue) +{ + INTERNAL_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + HRESULT hr = S_OK; + +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + if (m_nativeFrame->IsFunclet()) + { + if ( (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK) && + (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK2) && + (pNativeVarInfo->loc.vlType != ICorDebugInfo::VLT_STK_BYREF) ) + { + _ASSERTE(!"CordbJITILFrame::GetNativeVariable()" + " - Variables used in funclets should always be homed on the stack.\n"); + return E_FAIL; + } + } +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM + + switch (pNativeVarInfo->loc.vlType) + { + case ICorDebugInfo::VLT_REG: + hr = m_nativeFrame->GetLocalRegisterValue( + ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg), + type, ppValue); + break; + + case ICorDebugInfo::VLT_REG_BYREF: + { + CORDB_ADDRESS pRemoteByRefAddr = PTR_TO_CORDB_ADDRESS( + *( m_nativeFrame->GetAddressOfRegister(ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlReg.vlrReg))) ); + + hr = m_nativeFrame->GetLocalMemoryValue(pRemoteByRefAddr, + type, + ppValue); + } + break; + +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + case ICorDebugInfo::VLT_REG_FP: +#if defined(DBG_TARGET_ARM) // @ARMTODO + hr = E_NOTIMPL; +#else // DBG_TARGET_ARM @ARMTODO + hr = m_nativeFrame->GetLocalFloatingPointValue(pNativeVarInfo->loc.vlReg.vlrReg + REGISTER_AMD64_XMM0, + type, ppValue); + +#endif // DBG_TARGET_ARM @ARMTODO + break; +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM + + case ICorDebugInfo::VLT_STK_BYREF: + { + CORDB_ADDRESS pRemoteByRefAddr = m_nativeFrame->GetLSStackAddress( + pNativeVarInfo->loc.vlStk.vlsBaseReg, pNativeVarInfo->loc.vlStk.vlsOffset) ; + + hr = m_nativeFrame->GetLocalByRefMemoryValue(pRemoteByRefAddr, + type, + ppValue); + } + break; + + case ICorDebugInfo::VLT_STK: + { + CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress( + pNativeVarInfo->loc.vlStk.vlsBaseReg, pNativeVarInfo->loc.vlStk.vlsOffset) ; + + hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue, + type, + ppValue); + } + break; + + case ICorDebugInfo::VLT_REG_REG: + hr = m_nativeFrame->GetLocalDoubleRegisterValue( + ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg2), + ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegReg.vlrrReg1), + type, ppValue); + break; + + case ICorDebugInfo::VLT_REG_STK: + { + CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress( + pNativeVarInfo->loc.vlRegStk.vlrsStk.vlrssBaseReg, pNativeVarInfo->loc.vlRegStk.vlrsStk.vlrssOffset); + + hr = m_nativeFrame->GetLocalMemoryRegisterValue( + pRemoteValue, + ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlRegStk.vlrsReg), + type, ppValue); + } + break; + + case ICorDebugInfo::VLT_STK_REG: + { + CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress( + pNativeVarInfo->loc.vlStkReg.vlsrStk.vlsrsBaseReg, pNativeVarInfo->loc.vlStkReg.vlsrStk.vlsrsOffset); + + hr = m_nativeFrame->GetLocalRegisterMemoryValue( + ConvertRegNumToCorDebugRegister(pNativeVarInfo->loc.vlStkReg.vlsrReg), + pRemoteValue, type, ppValue); + } + break; + + case ICorDebugInfo::VLT_STK2: + { + CORDB_ADDRESS pRemoteValue = m_nativeFrame->GetLSStackAddress( + pNativeVarInfo->loc.vlStk2.vls2BaseReg, pNativeVarInfo->loc.vlStk2.vls2Offset); + + hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue, + type, + ppValue); + } + break; + + case ICorDebugInfo::VLT_FPSTK: +#if defined(DBG_TARGET_ARM) // @ARMTODO + hr = E_NOTIMPL; +#else + /* + @TODO [Microsoft] We have to make this work!!!!!!!!!!!!! + hr = m_nativeFrame->GetLocalFloatingPointValue( + pNativeVarInfo->loc.vlFPstk.vlfReg + REGISTER_X86_FPSTACK_0, + type, ppValue); + */ + hr = CORDBG_E_IL_VAR_NOT_AVAILABLE; +#endif + break; + + case ICorDebugInfo::VLT_FIXED_VA: + if (m_sigParserCached.IsNull()) //no var args info + return CORDBG_E_IL_VAR_NOT_AVAILABLE; + + CORDB_ADDRESS pRemoteValue; + + +#if defined(DBG_TARGET_X86) // STACK_GROWS_DOWN_ON_ARGS_WALK + pRemoteValue = m_FirstArgAddr - pNativeVarInfo->loc.vlFixedVarArg.vlfvOffset; + // Remember to subtract out this amount + pRemoteValue += sizeof(((CORINFO_VarArgInfo*)0)->argBytes); +#else // STACK_GROWS_UP_ON_ARGS_WALK + pRemoteValue = m_FirstArgAddr + pNativeVarInfo->loc.vlFixedVarArg.vlfvOffset; +#endif + + hr = m_nativeFrame->GetLocalMemoryValue(pRemoteValue, + type, + ppValue); + + break; + + + default: + _ASSERTE(!"Invalid locVarType"); + hr = E_FAIL; + break; + } + + return hr; +} + +HRESULT CordbJITILFrame::EnumerateLocalVariables(ICorDebugValueEnum **ppValueEnum) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return EnumerateLocalVariablesEx(ILCODE_ORIGINAL_IL, ppValueEnum); +} + +HRESULT CordbJITILFrame::GetLocalVariable(DWORD dwIndex, + ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + return GetLocalVariableEx(ILCODE_ORIGINAL_IL, dwIndex, ppValue); +} + + +HRESULT CordbJITILFrame::EnumerateArguments(ICorDebugValueEnum **ppValueEnum) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + + EX_TRY + { + RSInitHolder<CordbValueEnum> cdVE(new CordbValueEnum(m_nativeFrame, CordbValueEnum::ARGS)); + + // Initialize the new enum + hr = cdVE->Init(); + IfFailThrow(hr); + + cdVE.TransferOwnershipExternal(ppValueEnum); + + } + EX_CATCH_HRESULT(hr); + return hr; +} + +//--------------------------------------------------------------------------------------- +// +// This routine gets the value of a particular argument +// +// Arguments: +// dwIndex - Index of the argument. +// ppValue - OUT: Space for storing the value of the argument +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbJITILFrame::GetArgument(DWORD dwIndex, ICorDebugValue ** ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + const ICorDebugInfo::NativeVarInfo * pNativeInfo; + + // + // First, make sure that we've got the jitted variable location data + // loaded from the left side. + // + HRESULT hr = S_OK; + EX_TRY + { + m_nativeFrame->m_nativeCode->LoadNativeInfo(); //throws + + hr = ILVariableToNative(dwIndex, &pNativeInfo); + IfFailThrow(hr); + + // Get the type of this argument from the function + CordbType * pType; + + hr = GetArgumentType(dwIndex, &pType); + IfFailThrow(hr); + + hr = GetNativeVariable(pType, pNativeInfo, ppValue); + IfFailThrow(hr); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + + +HRESULT CordbJITILFrame::GetStackDepth(ULONG32 *pDepth) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pDepth, ULONG32 *); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + + /* !!! */ + + return E_NOTIMPL; +} + +HRESULT CordbJITILFrame::GetStackValue(DWORD dwIndex, ICorDebugValue **ppValue) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + /* !!! */ + + return E_NOTIMPL; +} + +//--------------------------------------------------------------------------------------- +// +// Remaps the active frame to the latest EnC version of the function, preserving the +// execution state of the method such as the values of locals. +// Can only be called when the leaf frame is at a remap opportunity. +// +// Arguments: +// nOffset - the IL offset in the new version of the function to remap to +// + +HRESULT CordbJITILFrame::RemapFunction(ULONG32 nOffset) +{ + HRESULT hr = S_OK; + PUBLIC_API_BEGIN(this) + { +#if !defined(EnC_SUPPORTED) + ThrowHR(E_NOTIMPL); + +#else // EnC_SUPPORTED + // Can only be called on leaf frame. + if (!m_nativeFrame->IsLeafFrame()) + { + ThrowHR(E_INVALIDARG); + } + + // mark frames as not fresh, because this frame has been updated. + m_nativeFrame->m_pThread->CleanupStack(); + + // Since we may have overwritten anything (objects, code, etc), we should mark + // everything as needing to be re-cached. + m_nativeFrame->m_pThread->GetProcess()->m_continueCounter++; + + // Tell the left-side to do the remap + hr = m_nativeFrame->m_pThread->SetRemapIP(nOffset); + +#endif // EnC_SUPPORTED + } + PUBLIC_API_END(hr); + + return hr; +} +HRESULT CordbJITILFrame::GetReturnValueForILOffset(ULONG32 ILoffset, ICorDebugValue** ppReturnValue) +{ + HRESULT hr = S_OK; + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + EX_TRY + { + hr = GetReturnValueForILOffsetImpl(ILoffset, ppReturnValue); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + + +HRESULT CordbJITILFrame::BuildInstantiationForCallsite(CordbModule * pModule, NewArrayHolder<CordbType*> &types, Instantiation &inst, Instantiation *currentInstantiation, mdToken targetClass, SigParser genericSig) +{ + // This function builds an Instantiation object (and backing "types" array) for a given + // class and method signature. + HRESULT hr = S_OK; + RSExtSmartPtr<IMetaDataImport2> pImport2; + IfFailRet(pModule->GetMetaDataImporter()->QueryInterface(IID_IMetaDataImport2, (void**)&pImport2)); + + // If the targetClass is a TypeSpec that means its first element is GENERICINST. + // We only need to build types for the Instantiation if targetClass is a TypeSpec. + ULONG classGenerics = 0; + SigParser typeSig; + if (TypeFromToken(targetClass) == mdtTypeSpec) + { + // Our goal with this is to full "classGenerics" with the number of + // generics, and move "typeSig" to the start of the first generic type. + PCCOR_SIGNATURE sig = 0; + ULONG sigCount = 0; + + IfFailRet(pImport2->GetTypeSpecFromToken(targetClass, &sig, &sigCount)); + + typeSig = SigParser(sig, sigCount); + CorElementType elemType; + IfFailRet(typeSig.GetElemType(&elemType)); + + if (elemType != ELEMENT_TYPE_GENERICINST) + return META_E_BAD_SIGNATURE; + + IfFailRet(typeSig.GetElemType(&elemType)); + if (elemType != ELEMENT_TYPE_VALUETYPE && elemType != ELEMENT_TYPE_CLASS) + return META_E_BAD_SIGNATURE; + + IfFailRet(typeSig.GetToken(NULL)); + IfFailRet(typeSig.GetData(&classGenerics)); + } + + // Similarly for method generics. Simply fill "methodGenerics" with the number + // of generics, and move "genericSig" to the start of the first generic param. + ULONG methodGenerics = 0; + if (!genericSig.IsNull()) + { + ULONG callingConv = 0; + IfFailRet(genericSig.GetCallingConvInfo(&callingConv)); + if (callingConv == IMAGE_CEE_CS_CALLCONV_GENERICINST) + IfFailRet(genericSig.GetData(&methodGenerics)); + } + + + // Now build "types" and "inst". + CordbType *pType = 0; + types = new CordbType*[methodGenerics+classGenerics]; + ULONG i = 0; + for (;i < classGenerics; ++i) + { + CorElementType et; + IfFailRet(typeSig.PeekElemType(&et)); + if ((et == ELEMENT_TYPE_VAR || et == ELEMENT_TYPE_MVAR) && currentInstantiation->m_cInst == 0) + return E_FAIL; + + CordbType::SigToType(pModule, &typeSig, currentInstantiation, &pType); + types[i] = pType; + typeSig.SkipExactlyOne(); + } + + for (; i < methodGenerics+classGenerics; ++i) + { + CorElementType et; + IfFailRet(genericSig.PeekElemType(&et)); + if ((et == ELEMENT_TYPE_VAR || et == ELEMENT_TYPE_MVAR) && currentInstantiation->m_cInst == 0) + return E_FAIL; + + CordbType::SigToType(pModule, &genericSig, currentInstantiation, &pType); + types[i] = pType; + genericSig.SkipExactlyOne(); + } + + inst = Instantiation(methodGenerics+classGenerics, types, classGenerics); + return S_OK; +} + +HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDebugValue** ppReturnValue) +{ + if (ppReturnValue == NULL) + return E_INVALIDARG; + + if (!m_genericArgsLoaded) + LoadGenericArgs(); + + // First verify that we're stopped at the correct native offset + // by calling ICorDebugCode3::GetReturnValueLiveOffset and + // compare the returned native offset to our current location. + HRESULT hr = S_OK; + CordbNativeCode *pCode = m_nativeFrame->m_nativeCode; + pCode->LoadNativeInfo(); + + ULONG32 count = 0; + IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, 0, &count, NULL)); + + NewArrayHolder<ULONG32> offsets(new ULONG32[count]); + IfFailRet(pCode->GetReturnValueLiveOffsetImpl(&m_genericArgs, ILoffset, count, &count, offsets)); + + bool found = false; + ULONG32 currentOffset = m_nativeFrame->GetIPOffset(); + for (ULONG32 i = 0; i < count; ++i) + if (found = currentOffset == offsets[i]) + break; + + if (!found) + return E_UNEXPECTED; + + // Get the signatures and mdToken for the callee. + SigParser methodSig, genericSig; + mdToken mdFunction = 0, targetClass = 0; + IfFailRet(pCode->GetCallSignature(ILoffset, &targetClass, &mdFunction, methodSig, genericSig)); + IfFailRet(CordbNativeCode::SkipToReturn(methodSig)); + + + + + // Create the Instantiation, type and then return value + NewArrayHolder<CordbType*> types; + Instantiation inst; + CordbType *pType = 0; + IfFailRet(BuildInstantiationForCallsite(GetModule(), types, inst, &m_genericArgs, targetClass, genericSig)); + IfFailRet(CordbType::SigToType(GetModule(), &methodSig, &inst, &pType)); + return GetReturnValueForType(pType, ppReturnValue); +} + + +HRESULT CordbJITILFrame::GetReturnValueForType(CordbType *pType, ICorDebugValue **ppReturnValue) +{ +#if defined(DBG_TARGET_ARM) || defined(DBG_TARGET_ARM64) + return E_NOTIMPL; +#else + +#if defined(DBG_TARGET_X86) + const CorDebugRegister floatRegister = REGISTER_X86_FPSTACK_0; +#elif defined(DBG_TARGET_AMD64) + const CorDebugRegister floatRegister = REGISTER_AMD64_XMM0; +#endif + +#if defined(DBG_TARGET_X86) + const CorDebugRegister ptrRegister = REGISTER_X86_EAX; +#elif defined(DBG_TARGET_AMD64) + const CorDebugRegister ptrRegister = REGISTER_AMD64_RAX; +#endif + + CorElementType corReturnType = pType->GetElementType(); + switch (corReturnType) + { + default: + return m_nativeFrame->GetLocalRegisterValue(ptrRegister, pType, ppReturnValue); + + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_R8: + return m_nativeFrame->GetLocalFloatingPointValue(floatRegister, pType, ppReturnValue); + +#ifdef DBG_TARGET_X86 + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + return m_nativeFrame->GetLocalDoubleRegisterValue(REGISTER_X86_EDX, REGISTER_X86_EAX, pType, ppReturnValue); +#endif + } +#endif +} + +HRESULT CordbJITILFrame::EnumerateLocalVariablesEx(ILCodeKind flags, ICorDebugValueEnum **ppValueEnum) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppValueEnum, ICorDebugValueEnum **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + HRESULT hr = S_OK; + if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL) + return E_INVALIDARG; + + EX_TRY + { + RSInitHolder<CordbValueEnum> cdVE(new CordbValueEnum(m_nativeFrame, + flags == ILCODE_ORIGINAL_IL ? CordbValueEnum::LOCAL_VARS_ORIGINAL_IL : CordbValueEnum::LOCAL_VARS_REJIT_IL)); + + // Initialize the new enum + hr = cdVE->Init(); + IfFailThrow(hr); + + cdVE.TransferOwnershipExternal(ppValueEnum); + } + EX_CATCH_HRESULT(hr); + + return hr; +} +HRESULT CordbJITILFrame::GetLocalVariableEx(ILCodeKind flags, DWORD dwIndex, ICorDebugValue **ppValue) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL) + return E_INVALIDARG; + if (flags == ILCODE_REJIT_IL && m_pReJitCode == NULL) + return E_INVALIDARG; + + const ICorDebugInfo::NativeVarInfo *pNativeInfo; + + // + // First, make sure that we've got the jitted variable location data + // loaded from the left side. + // + + HRESULT hr = S_OK; + EX_TRY + { + m_nativeFrame->m_nativeCode->LoadNativeInfo(); //throws + + ULONG cArgs; + if (m_fVarArgFnx && (!m_sigParserCached.IsNull())) + { + cArgs = m_allArgsCount; + } + else + { + cArgs = m_nativeFrame->m_nativeCode->GetFixedArgCount(); + } + + hr = ILVariableToNative(dwIndex + cArgs, &pNativeInfo); + IfFailThrow(hr); + + LoadGenericArgs(); + + // Get the type of this argument from the function + CordbType *type; + CordbILCode* pActiveCode = m_pReJitCode != NULL ? m_pReJitCode : m_ilCode; + hr = pActiveCode->GetLocalVariableType(dwIndex, &(this->m_genericArgs), &type); + IfFailThrow(hr); + + // if the caller wants the original IL local, it should implicitly map to the same index + // variable in the profiler instrumented code. We can't determine whether the instrumented code + // really adhered to this, but we can check two things: + // a) the requested index was valid in the original signature + // (GetLocalVariableType will return E_INVALIDARG if not) + // b) the type of local in the original signature matches the type of local in the instrumented signature + // (the code below will return CORDBG_E_IL_VAR_NOT_AVAILABLE) + if (flags == ILCODE_ORIGINAL_IL && m_pReJitCode != NULL) + { + CordbType* pOriginalType; + hr = m_ilCode->GetLocalVariableType(dwIndex, &(this->m_genericArgs), &pOriginalType); + IfFailThrow(hr); + if (pOriginalType != type) + { + IfFailThrow(CORDBG_E_IL_VAR_NOT_AVAILABLE); // bad profiler, it shouldn't have changed types + } + } + + + hr = GetNativeVariable(type, pNativeInfo, ppValue); + IfFailThrow(hr); + } + EX_CATCH_HRESULT(hr); + + return hr; +} + +HRESULT CordbJITILFrame::GetCodeEx(ILCodeKind flags, ICorDebugCode **ppCode) +{ + HRESULT hr = S_OK; + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + if (flags != ILCODE_ORIGINAL_IL && flags != ILCODE_REJIT_IL) + return E_INVALIDARG; + + if (flags == ILCODE_ORIGINAL_IL) + { + return GetCode(ppCode); + } + else + { + *ppCode = m_pReJitCode; + if (m_pReJitCode != NULL) + { + m_pReJitCode->ExternalAddRef(); + } + } + return S_OK; +} + +CordbILCode* CordbJITILFrame::GetOriginalILCode() +{ + return m_ilCode; +} + +CordbReJitILCode* CordbJITILFrame::GetReJitILCode() +{ + return m_pReJitCode; +} + +/* ------------------------------------------------------------------------- * + * Eval class + * ------------------------------------------------------------------------- */ + +CordbEval::CordbEval(CordbThread *pThread) + : CordbBase(pThread->GetProcess(), 0, enumCordbEval), + m_thread(pThread), // implicit InternalAddRef + m_function(NULL), + m_complete(false), + m_successful(false), + m_aborted(false), + m_resultAddr(NULL), + m_evalDuringException(false) +{ + m_vmObjectHandle = VMPTR_OBJECTHANDLE::NullPtr(); + m_debuggerEvalKey = LSPTR_DEBUGGEREVAL::NullPtr(); + + m_resultType.elementType = ELEMENT_TYPE_VOID; + m_resultAppDomainToken = VMPTR_AppDomain::NullPtr(); + + CordbAppDomain * pDomain = m_thread->GetAppDomain(); + (void)pDomain; //prevent "unused variable" error from GCC +#ifdef _DEBUG + // Remember what AD we started in so that we can check that we finish there too. + m_DbgAppDomainStarted = pDomain; +#endif + + // Place ourselves on the processes neuter-list. + HRESULT hr = S_OK; + EX_TRY + { + GetProcess()->AddToLeftSideResourceCleanupList(this); + } + EX_CATCH_HRESULT(hr); + SetUnrecoverableIfFailed(GetProcess(), hr); +} + +CordbEval::~CordbEval() +{ + _ASSERTE(IsNeutered()); +} + +// Free the left-side resources for the eval. +void CordbEval::NeuterLeftSideResources() +{ + SendCleanup(); + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + Neuter(); +} + +// Neuter the CordbEval +// +// Assumptions: +// By the time we neuter the eval, it's associated left-side resources +// are already cleaned up (either explicitly from calling code:CordbEval::SendCleanup +// or implicitly from the left-side exiting). +// +// Notes: +// We place ourselves on a neuter list. This gets called when the neuterlist sweeps. +void CordbEval::Neuter() +{ + // By now, we should have freed our target-resources (code:CordbEval::NeuterLeftSideResources + // or code:CordbEval::SendCleanup), unless the target is dead (terminated or about to exit). + BOOL fTargetIsDead = !GetProcess()->IsSafeToSendEvents() || GetProcess()->m_exiting; + (void)fTargetIsDead; //prevent "unused variable" error from GCC + _ASSERTE(fTargetIsDead || (m_debuggerEvalKey == NULL)); + + m_thread.Clear(); + + CordbBase::Neuter(); +} + +HRESULT CordbEval::SendCleanup() +{ + FAIL_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); // + + HRESULT hr = S_OK; + + // Send a message to the left side to release the eval object over + // there if one exists. + if ((m_debuggerEvalKey != NULL) && + GetProcess()->IsSafeToSendEvents()) + { + // Call Abort() before doing new CallFunction() + if (!m_complete) + return CORDBG_E_FUNC_EVAL_NOT_COMPLETE; + + // Release the left side handle to the object + DebuggerIPCEvent event; + + GetProcess()->InitIPCEvent( + &event, + DB_IPCE_FUNC_EVAL_CLEANUP, + true, + m_thread->GetAppDomain()->GetADToken()); + + event.FuncEvalCleanup.debuggerEvalKey = m_debuggerEvalKey; + + hr = GetProcess()->SendIPCEvent(&event, sizeof(DebuggerIPCEvent)); + IfFailRet(hr); + +#if _DEBUG + if (SUCCEEDED(hr)) + _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_CLEANUP_RESULT); +#endif + + // Null out the key so we don't try to do this again. + m_debuggerEvalKey = LSPTR_DEBUGGEREVAL::NullPtr(); + + hr = event.hr; + } + + // Release the cached HandleValue for the result. This may cleanup resources, + // like our object handle to the func-eval result. + m_pHandleValue.Clear(); + + + return hr; +} + +HRESULT CordbEval::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugEval) + { + *pInterface = static_cast<ICorDebugEval*>(this); + } + else if (id == IID_ICorDebugEval2) + { + *pInterface = static_cast<ICorDebugEval2*>(this); + } + else if (id == IID_IUnknown) + { + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugEval*>(this)); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +// +// Gather data about an argument to either CallFunction or NewObject +// and place it into a DebuggerIPCE_FuncEvalArgData struct for passing +// to the Left Side. +// +HRESULT CordbEval::GatherArgInfo(ICorDebugValue *pValue, + DebuggerIPCE_FuncEvalArgData *argData) +{ + FAIL_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); // + + HRESULT hr; + CORDB_ADDRESS addr; + CorElementType ty; + bool needRelease = false; + + pValue->GetType(&ty); + + // Note: if the value passed in is in fact a byref, then we need to dereference it to get to the real thing. Passing + // a byref as a byref to a func eval is never right. + if ((ty == ELEMENT_TYPE_BYREF) || (ty == ELEMENT_TYPE_TYPEDBYREF)) + { + ICorDebugReferenceValue *prv = NULL; + + // The value had better implement ICorDebugReference value. + IfFailRet(pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&prv)); + + // This really should always work for a byref, unless we're out of memory. + hr = prv->Dereference(&pValue); + prv->Release(); + + IfFailRet(hr); + + // Make sure to get the type we were referencing for use below. + pValue->GetType(&ty); + needRelease = true; + } + + // We should never have a byref by this point. + _ASSERTE((ty != ELEMENT_TYPE_BYREF) && (ty != ELEMENT_TYPE_TYPEDBYREF)); + + pValue->GetAddress(&addr); + + argData->argAddr = CORDB_ADDRESS_TO_PTR(addr); + argData->argElementType = ty; + + argData->argIsHandleValue = false; + argData->argIsLiteral = false; + argData->fullArgType = NULL; + argData->fullArgTypeNodeCount = 0; + + // We have to have knowledge of our value implementation here, + // which it would nice if we didn't have to know. + CordbValue *cv = NULL; + + switch(ty) + { + + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_SZARRAY: + { + ICorDebugHandleValue *pHandle = NULL; + pValue->QueryInterface(IID_ICorDebugHandleValue, (void **) &pHandle); + if (pHandle == NULL) + { + // A reference value + cv = static_cast<CordbValue*> (static_cast<CordbReferenceValue*> (pValue)); + argData->argIsHandleValue = !(((CordbReferenceValue *)pValue)->m_valueHome.ObjHandleIsNull()); + + // Is this a literal value? If, we'll copy the data to the + // buffer area so the left side can get it. + CordbReferenceValue *rv; + rv = static_cast<CordbReferenceValue*>(pValue); + argData->argIsLiteral = rv->CopyLiteralData(argData->argLiteralData); + if (rv->GetValueHome()) + { + rv->GetValueHome()->CopyToIPCEType(&(argData->argHome)); + } + } + else + { + argData->argIsHandleValue = true; + argData->argIsLiteral = false; + pHandle->Release(); + argData->argHome.kind = RAK_NONE; + } + } + break; + + case ELEMENT_TYPE_VALUETYPE: // OK: this E_T_VALUETYPE comes ICorDebugValue::GetType + + // A value class object + cv = static_cast<CordbValue*> (static_cast<CordbVCObjectValue*>(static_cast<ICorDebugObjectValue*> (pValue))); + + // The EE does not guarantee to have exact type information + // available for all struct types, so we indicate the type by using a + // DebuggerIPCE_TypeArgData serialization of a type. + // + // At the moment the LHS only cares about this data + // when boxing the "this" pointer. + { + CordbVCObjectValue * pVCObjVal = + static_cast<CordbVCObjectValue *>(static_cast<ICorDebugObjectValue*> (pValue)); + + unsigned int fullArgTypeNodeCount = 0; + cv->m_type->CountTypeDataNodes(&fullArgTypeNodeCount); + + _ASSERTE(fullArgTypeNodeCount > 0); + unsigned int bufferSize = sizeof(DebuggerIPCE_TypeArgData) * fullArgTypeNodeCount; + DebuggerIPCE_TypeArgData *bufferFrom = (DebuggerIPCE_TypeArgData *) _alloca(bufferSize); + + DebuggerIPCE_TypeArgData *curr = bufferFrom; + CordbType::GatherTypeData(cv->m_type, &curr); + + void *buffer = NULL; + IfFailRet(m_thread->GetProcess()->GetAndWriteRemoteBuffer(m_thread->GetAppDomain(), bufferSize, bufferFrom, &buffer)); + + argData->fullArgType = buffer; + argData->fullArgTypeNodeCount = fullArgTypeNodeCount; + // Is it enregistered? + if ((addr == NULL) && (pVCObjVal->GetValueHome() != NULL)) + { + pVCObjVal->GetValueHome()->CopyToIPCEType(&(argData->argHome)); + } + + } + break; + + default: + + // A generic value + cv = static_cast<CordbValue*> (static_cast<CordbGenericValue*> (pValue)); + + // Is this a literal value? If, we'll copy the data to the + // buffer area so the left side can get it. + CordbGenericValue *gv = (CordbGenericValue*)pValue; + argData->argIsLiteral = gv->CopyLiteralData(argData->argLiteralData); + // Is it enregistered? + if ((addr == NULL) && (gv->GetValueHome() != NULL)) + { + gv->GetValueHome()->CopyToIPCEType(&(argData->argHome)); + } + break; + } + + + // Release pValue if we got it via a dereference from above. + if (needRelease) + pValue->Release(); + + return S_OK; +} + + +HRESULT CordbEval::SendFuncEval(unsigned int genericArgsCount, + ICorDebugType *genericArgs[], + void *argData1, unsigned int argData1Size, + void *argData2, unsigned int argData2Size, + DebuggerIPCEvent * event) +{ + FAIL_IF_NEUTERED(this); + INTERNAL_SYNC_API_ENTRY(GetProcess()); // + unsigned int genericArgsNodeCount = 0; + + DebuggerIPCE_TypeArgData *tyargData = NULL; + CordbType::CountTypeDataNodesForInstantiation(genericArgsCount,genericArgs,&genericArgsNodeCount); + + unsigned int tyargDataSize = sizeof(DebuggerIPCE_TypeArgData) * genericArgsNodeCount; + + if (genericArgsNodeCount > 0) + { + tyargData = new (nothrow) DebuggerIPCE_TypeArgData[genericArgsNodeCount]; + if (tyargData == NULL) + { + return E_OUTOFMEMORY; + } + + DebuggerIPCE_TypeArgData *curr_tyargData = tyargData; + CordbType::GatherTypeDataForInstantiation(genericArgsCount, genericArgs, &curr_tyargData); + + } + event->FuncEval.genericArgsNodeCount = genericArgsNodeCount; + + + // Are we doing an eval during an exception? If so, we need to remember + // that over here and also tell the Left Side. + event->FuncEval.evalDuringException = m_thread->HasException(); + m_evalDuringException = !!event->FuncEval.evalDuringException; + m_vmThreadOldExceptionHandle = m_thread->GetThreadExceptionRawObjectHandle(); + + // Corresponding Release() on DB_IPCE_FUNC_EVAL_COMPLETE. + // If a func eval is aborted, the LHS may not complete the abort + // immediately and hence we cant do a SendCleanup(). Hence, we maintain + // an extra ref-count to determine when this can be done. + AddRef(); + + HRESULT hr = m_thread->GetProcess()->SendIPCEvent(event, sizeof(DebuggerIPCEvent)); + + // If the send failed, return that failure. + if (FAILED(hr)) + goto LExit; + + _ASSERTE(event->type == DB_IPCE_FUNC_EVAL_SETUP_RESULT); + + hr = event->hr; + + // Memory has been allocated to hold info about each argument on + // the left side now, so copy the argument data over to the left + // side. No need to send another event, since the left side won't + // take any more action on this evaluation until the process is + // continued anyway. + // + // The type arguments come first, followed by up to two blobs of data + // for other arguments. + if (SUCCEEDED(hr)) + { + EX_TRY + { + CORDB_ADDRESS argdata = event->FuncEvalSetupComplete.argDataArea; + + if ((tyargData != NULL) && (tyargDataSize != 0)) + { + + TargetBuffer tb(argdata, tyargDataSize); + m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) tyargData); // throws + + argdata += tyargDataSize; + } + + if ((argData1 != NULL) && (argData1Size != 0)) + { + TargetBuffer tb(argdata, argData1Size); + m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) argData1); // throws + + argdata += argData1Size; + } + + if ((argData2 != NULL) && (argData2Size != 0)) + { + TargetBuffer tb(argdata, argData2Size); + m_thread->GetProcess()->SafeWriteBuffer(tb, (const BYTE*) argData2); // throws + + argdata += argData2Size; + } + } + EX_CATCH_HRESULT(hr); + } + +LExit: + if (tyargData) + { + delete [] tyargData; + } + + // Save the key to the eval on the left side for future reference. + if (SUCCEEDED(hr)) + { + m_debuggerEvalKey = event->FuncEvalSetupComplete.debuggerEvalKey; + m_thread->GetProcess()->IncrementOutstandingEvalCount(); + } + else + { + // We dont expect to receive a DB_IPCE_FUNC_EVAL_COMPLETE, so just release here + Release(); + } + + return hr; +} + + +// Get the AppDomain that an object lives in. +// This does not adjust any reference counts. +// Returns NULL if we can't determine the appdomain, or if the value is known to be agile. +CordbAppDomain * GetAppDomainFromValue(ICorDebugValue * pValue) +{ + // Unfortunately, there's no direct way to cast from an ICDValue to a CordbValue. + // So we need to QI for the culprit interfaces and check specifically. + + { + RSExtSmartPtr<ICorDebugHandleValue> handleP; + pValue->QueryInterface(IID_ICorDebugHandleValue, (void**)&handleP); + if (handleP != NULL) + { + CordbHandleValue * chp = static_cast<CordbHandleValue *> (handleP.GetValue()); + return chp->GetAppDomain(); + } + } + + { + RSExtSmartPtr<ICorDebugReferenceValue> refP; + pValue->QueryInterface(IID_ICorDebugReferenceValue, (void**)&refP); + if (refP != NULL) + { + CordbReferenceValue * crp = static_cast<CordbReferenceValue *> (refP.GetValue()); + return crp->GetAppDomain(); + } + } + + { + RSExtSmartPtr<ICorDebugObjectValue> objP; + pValue->QueryInterface(IID_ICorDebugObjectValue, (void**)&objP); + if (objP != NULL) + { + CordbVCObjectValue * crp = static_cast<CordbVCObjectValue*> (objP.GetValue()); + return crp->GetAppDomain(); + } + } + + // Assume nothing else has AD affinity. + return NULL; +} + +HRESULT CordbEval::CallFunction(ICorDebugFunction *pFunction, + ULONG32 nArgs, + ICorDebugValue *pArgs[]) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + if (GetProcess()->GetShim() == NULL) + { + return E_NOTIMPL; + } + return CallParameterizedFunction(pFunction,0,NULL,nArgs,pArgs); +} + +//----------------------------------------------------------------------------- +// See if we can convert general Func-eval failure HRs (which are usually based on EE-invariants that +// may be meaningless to the user) into a more specific user-friendly hr. +// Doing the conversions here in the RS (instead of in the LS) makes it more clear that these +// HRs definitely map to concepts described by the ICorDebugAPI instead of EE-invariants. +// It also lets us clearly prioritize the HRs in case of ambiguity. +//----------------------------------------------------------------------------- +HRESULT CordbEval::FilterHR(HRESULT hr) +{ + // Currently, we only make CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT more specific. + // If it's not that HR, then shortcut our work. + if (hr != CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT) + { + return hr; + } + + // In the case of conflicting HRs (if the func-eval fails for multiple reasons), + // we'll try to give priority to the more general HR. + // This communicates the quickest action for the user to be able to get to a + // func-eval friendly spot. It also means less churn in the hrs we return + // because specific hrs are more likely to change than general ones. + + // If we got CORDBG_E_ILLEGAL_AT_GC_UNSAFE_POINT, check the common reasons. + // We'll use the Right-Side's intimate knowledge of the Left-Side to guess _why_ + // it's a GC-unsafe spot, and then we'll communicate that back w/ a more meaningful HR. + // If GC safe-spots change, then these errors should be updated. + + + // + // Most likely is if we're in native code. Check that first. + // + // In V2, we do this check by checking if the leaf chain is native. Since we have no chain in Arrowhead, + // we can't do this check. Instead, we check whether the active frame is NULL or not. If it's NULL, + // then we are stopped in native code. + // + HRESULT hrTemp = S_OK; + if (GetProcess()->GetShim() != NULL) + { + // the V2 case + RSExtSmartPtr<ICorDebugChain> pChain; + hrTemp = m_thread->GetActiveChain(&pChain); + if (FAILED(hrTemp)) + { + // just return the original HR if this call fails + return hr; + } + + // pChain should never be NULL here, since we should have at least one thread start chain even if + // there is no managed code on the stack, but let's just be extra careful here. + if (pChain == NULL) + { + return hr; + } + + BOOL fManagedChain; + hrTemp = pChain->IsManaged(&fManagedChain); + if (FAILED(hrTemp)) + { + // just return the original HR if this call fails + return hr; + } + + if (fManagedChain == FALSE) + { + return CORDBG_E_ILLEGAL_IN_NATIVE_CODE; + } + } + + RSExtSmartPtr<ICorDebugFrame> pIFrame; + hrTemp = m_thread->GetActiveFrame(&pIFrame); + if (FAILED(hrTemp)) + { + // just return the original HR if this call fails + return hr; + } + + CordbFrame * pFrame = NULL; + pFrame = CordbFrame::GetCordbFrameFromInterface(pIFrame); + + if (GetProcess()->GetShim() == NULL) + { + // the Arrowhead case + if (pFrame == NULL) + { + return CORDBG_E_ILLEGAL_IN_NATIVE_CODE; + } + } + + // Next, check if we're in optimized code. + // Optimized code doesn't directly mean that func-evals are illegal; but it greatly + // increases the odds of being at a GC-unsafe point. + // We give this failure higher precedence than the "Is in prolog" failure. + + if (pFrame != NULL) + { + CordbNativeFrame * pNativeFrame = pFrame->GetAsNativeFrame(); + if (pNativeFrame != NULL) + { + CordbNativeCode * pCode = pNativeFrame->GetNativeCode(); + if (pCode != NULL) + { + DWORD flags; + hrTemp = pCode->GetModule()->GetJITCompilerFlags(&flags); + + if (SUCCEEDED(hrTemp)) + { + if ((flags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION) + { + return CORDBG_E_ILLEGAL_IN_OPTIMIZED_CODE; + } + + } // GetCompilerFlags + } // Code + + CordbJITILFrame * pILFrame = pNativeFrame->m_JITILFrame; + if (pILFrame != NULL) + { + if (pILFrame->m_mapping == MAPPING_PROLOG) + { + return CORDBG_E_ILLEGAL_IN_PROLOG; + } + } + } // Native Frame + } + + // No filtering. + return hr; + +} + +//--------------------------------------------------------------------------------------- +// +// This routine calls a function with the given set of type arguments and actual arguments. +// This is the jumping off point for func-eval. +// +// Arguments: +// pFunction - The function to call. +// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs +// rgpTypeArgs - An array of pointers to types. +// nArgs - The number of arguments for the method in rgpArgs +// rgpArgs - An array of pointers to values for the arguments to the method. +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbEval::CallParameterizedFunction(ICorDebugFunction *pFunction, + ULONG32 nTypeArgs, + ICorDebugType * rgpTypeArgs[], + ULONG32 nArgs, + ICorDebugValue * rgpArgs[]) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + VALIDATE_POINTER_TO_OBJECT(pFunction, ICorDebugFunction *); + + if (nArgs > 0) + { + VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpArgs, ICorDebugValue *, nArgs, true, true); + } + + HRESULT hr = E_FAIL; + + { + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // The LS will assume that all of the ICorDebugValues and ICorDebugTypes are in + // the same appdomain as the function. Verify this. + CordbAppDomain * pMethodAppDomain = (static_cast<CordbFunction *> (pFunction))->GetAppDomain(); + + if (!DoAppDomainsMatch(pMethodAppDomain, nTypeArgs, rgpTypeArgs, nArgs, rgpArgs)) + { + return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH); + } + + // Callers are free to reuse an ICorDebugEval object for multiple + // evals. Since we create a Left Side eval representation each + // time, we need to be sure to clean it up now that we know we're + // done with it. + hr = SendCleanup(); + + if (FAILED(hr)) + { + return hr; + } + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + + // Must be locked to get a cookie + RsPtrHolder<CordbEval> hFuncEval(this); + + if (hFuncEval.Ptr().IsNull()) + { + return E_OUTOFMEMORY; + } + lockHolder.Release(); // release to send an IPC event. + + // Remember the function that we're evaluating. + m_function = static_cast<CordbFunction *>(pFunction); + m_evalType = DB_IPCE_FET_NORMAL; + + + // Arrange the arguments into a form that the left side can deal + // with. We do this before starting the func eval setup to ensure + // that we can complete this step before mutating the left + // side. + DebuggerIPCE_FuncEvalArgData * pArgData = NULL; + + if (nArgs > 0) + { + // We need to make the same type of array that the left side + // holds. + pArgData = new (nothrow) DebuggerIPCE_FuncEvalArgData[nArgs]; + + if (pArgData == NULL) + { + return E_OUTOFMEMORY; + } + + // For each argument, convert its home into something the left + // side can understand. + for (unsigned int i = 0; i < nArgs; i++) + { + hr = GatherArgInfo(rgpArgs[i], &(pArgData[i])); + + if (FAILED(hr)) + { + delete [] pArgData; + return hr; + } + } + } + + // Send over to the left side and get it to setup this eval. + DebuggerIPCEvent event; + m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken()); + + event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken; + event.FuncEval.funcEvalType = m_evalType; + event.FuncEval.funcMetadataToken = m_function->GetMetadataToken(); + event.FuncEval.vmDomainFile = m_function->GetModule()->GetRuntimeDomainFile(); + event.FuncEval.funcEvalKey = hFuncEval.Ptr(); + event.FuncEval.argCount = nArgs; + event.FuncEval.genericArgsCount = nTypeArgs; + + + + hr = SendFuncEval(nTypeArgs, + rgpTypeArgs, + reinterpret_cast<void *>(pArgData), + sizeof(DebuggerIPCE_FuncEvalArgData) * nArgs, + NULL, + 0, + &event); + + // Cleanup + + if (pArgData) + { + delete [] pArgData; + } + + if (SUCCEEDED(hr)) + { + hFuncEval.SuppressRelease(); // Now LS owns. + } + } + + // Convert from LS EE-centric failure code to something more friendly to end-users. + // Success HRs will not be converted. + hr = FilterHR(hr); + + // Return any failure the Left Side may have told us about. + return hr; +} + +BOOL CordbEval::DoAppDomainsMatch( CordbAppDomain * pAppDomain, + ULONG32 nTypes, + ICorDebugType *pTypes[], + ULONG32 nValues, + ICorDebugValue *pValues[] ) +{ + _ASSERTE( !(pTypes == NULL && nTypes != 0) ); + _ASSERTE( !(pValues == NULL && nValues != 0) ); + + // Make sure each value is in the appdomain. + for(unsigned int i = 0; i < nValues; i++) + { + // Assuming that only Ref Values have AD affinity + CordbAppDomain * pValueAppDomain = GetAppDomainFromValue( pValues[i] ); + + if ((pValueAppDomain != NULL) && (pValueAppDomain != pAppDomain)) + { + LOG((LF_CORDB,LL_INFO1000, "CordbEval::DADM - AD mismatch. appDomain=0x%08x, param #%d=0x%08x, must fail.\n", + pAppDomain, i, pValueAppDomain)); + return FALSE; + } + } + + for(unsigned int i = 0; i < nTypes; i++ ) + { + CordbType* t = static_cast<CordbType*>( pTypes[i] ); + CordbAppDomain * pTypeAppDomain = t->GetAppDomain(); + + if( pTypeAppDomain != NULL && pTypeAppDomain != pAppDomain ) + { + LOG((LF_CORDB,LL_INFO1000, "CordbEval::DADM - AD mismatch. appDomain=0x%08x, type param #%d=0x%08x, must fail.\n", + pAppDomain, i, pTypeAppDomain)); + return FALSE; + } + } + + return TRUE; +} + +HRESULT CordbEval::NewObject(ICorDebugFunction *pConstructor, + ULONG32 nArgs, + ICorDebugValue *pArgs[]) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + return NewParameterizedObject(pConstructor,0,NULL,nArgs,pArgs); +} + +//--------------------------------------------------------------------------------------- +// +// This routine calls a constructor with the given set of type arguments and actual arguments. +// This is the jumping off point for func-evaling "new". +// +// Arguments: +// pConstructor - The function to call. +// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs +// rgpTypeArgs - An array of pointers to types. +// nArgs - The number of arguments for the method in rgpArgs +// rgpArgs - An array of pointers to values for the arguments to the method. +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbEval::NewParameterizedObject(ICorDebugFunction * pConstructor, + ULONG32 nTypeArgs, + ICorDebugType * rgpTypeArgs[], + ULONG32 nArgs, + ICorDebugValue * rgpArgs[]) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pConstructor, ICorDebugFunction *); + VALIDATE_POINTER_TO_OBJECT_ARRAY(rgpArgs, ICorDebugValue *, nArgs, true, true); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // The LS will assume that all of the ICorDebugValues and ICorDebugTypes are in + // the same appdomain as the constructor. Verify this. + CordbAppDomain * pConstructorAppDomain = (static_cast<CordbFunction *> (pConstructor))->GetAppDomain(); + + if (!DoAppDomainsMatch(pConstructorAppDomain, nTypeArgs, rgpTypeArgs, nArgs, rgpArgs)) + { + return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH); + } + + // Callers are free to reuse an ICorDebugEval object for multiple + // evals. Since we create a Left Side eval representation each + // time, we need to be sure to clean it up now that we know we're + // done with it. + HRESULT hr = SendCleanup(); + + if (FAILED(hr)) + { + return hr; + } + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + RsPtrHolder<CordbEval> hFuncEval(this); + + if (hFuncEval.Ptr().IsNull()) + { + return E_OUTOFMEMORY; + } + lockHolder.Release(); + + // Remember the function that we're evaluating. + m_function = static_cast<CordbFunction *>(pConstructor); + m_evalType = DB_IPCE_FET_NEW_OBJECT; + + // Arrange the arguments into a form that the left side can deal + // with. We do this before starting the func eval setup to ensure + // that we can complete this step before mutating up the left + // side. + DebuggerIPCE_FuncEvalArgData * pArgData = NULL; + + if (nArgs > 0) + { + // We need to make the same type of array that the left side + // holds. + pArgData = new (nothrow) DebuggerIPCE_FuncEvalArgData[nArgs]; + + if (pArgData == NULL) + { + return E_OUTOFMEMORY; + } + + // For each argument, convert its home into something the left + // side can understand. + for (unsigned int i = 0; i < nArgs; i++) + { + hr = GatherArgInfo(rgpArgs[i], &(pArgData[i])); + + if (FAILED(hr)) + { + return hr; + } + } + } + + // Send over to the left side and get it to setup this eval. + DebuggerIPCEvent event; + + m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken()); + + event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken; + event.FuncEval.funcEvalType = m_evalType; + event.FuncEval.funcMetadataToken = m_function->GetMetadataToken(); + event.FuncEval.vmDomainFile = m_function->GetModule()->GetRuntimeDomainFile(); + event.FuncEval.funcEvalKey = hFuncEval.Ptr(); + event.FuncEval.argCount = nArgs; + event.FuncEval.genericArgsCount = nTypeArgs; + + hr = SendFuncEval(nTypeArgs, + rgpTypeArgs, + reinterpret_cast<void *>(pArgData), + sizeof(DebuggerIPCE_FuncEvalArgData) * nArgs, + NULL, + 0, + &event); + + // Cleanup + + if (pArgData) + { + delete [] pArgData; + } + + if (SUCCEEDED(hr)) + { + hFuncEval.SuppressRelease(); // Now LS owns. + } + + + // Return any failure the Left Side may have told us about. + return hr; +} + +HRESULT CordbEval::NewObjectNoConstructor(ICorDebugClass *pClass) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + return NewParameterizedObjectNoConstructor(pClass,0,NULL); +} + +//--------------------------------------------------------------------------------------- +// +// This routine creates an object of a certain type, but does not call the constructor +// for the type on the object. +// +// Arguments: +// pClass - the type of the object to create. +// nTypeArgs - The number of type-arguments for the method in rgpTypeArgs +// rgpTypeArgs - An array of pointers to types. +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbEval::NewParameterizedObjectNoConstructor(ICorDebugClass * pClass, + ULONG32 nTypeArgs, + ICorDebugType * rgpTypeArgs[]) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pClass, ICorDebugClass *); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // The LS will assume that all of the ICorDebugTypes are in + // the same appdomain as the class. Verify this. + CordbAppDomain * pClassAppDomain = (static_cast<CordbClass *> (pClass))->GetAppDomain(); + + if (!DoAppDomainsMatch(pClassAppDomain, nTypeArgs, rgpTypeArgs, 0, NULL)) + { + return ErrWrapper(CORDBG_E_APPDOMAIN_MISMATCH); + } + + // Callers are free to reuse an ICorDebugEval object for multiple + // evals. Since we create a Left Side eval representation each + // time, we need to be sure to clean it up now that we know we're + // done with it. + HRESULT hr = SendCleanup(); + + if (FAILED(hr)) + { + return hr; + } + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + RsPtrHolder<CordbEval> hFuncEval(this); + lockHolder.Release(); // release to send an IPC event. + + if (hFuncEval.Ptr().IsNull()) + { + return E_OUTOFMEMORY; + } + + // Remember the function that we're evaluating. + m_class = (CordbClass*)pClass; + m_evalType = DB_IPCE_FET_NEW_OBJECT_NC; + + // Send over to the left side and get it to setup this eval. + DebuggerIPCEvent event; + + m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken()); + + event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken; + event.FuncEval.funcEvalType = m_evalType; + event.FuncEval.funcMetadataToken = mdMethodDefNil; + event.FuncEval.funcClassMetadataToken = (mdTypeDef)m_class->m_id; + event.FuncEval.vmDomainFile = m_class->GetModule()->GetRuntimeDomainFile(); + event.FuncEval.funcEvalKey = hFuncEval.Ptr(); + event.FuncEval.argCount = 0; + event.FuncEval.genericArgsCount = nTypeArgs; + + hr = SendFuncEval(nTypeArgs, rgpTypeArgs, NULL, 0, NULL, 0, &event); + + if (SUCCEEDED(hr)) + { + hFuncEval.SuppressRelease(); // Now LS owns. + } + + // Return any failure the Left Side may have told us about. + return hr; +} + +/* + * + * NewString + * + * This routine is the interface function for ICorDebugEval::NewString + * + * Parameters: + * string - the string to create - must be null-terminated + * + * Return Value: + * HRESULT from the helper routines on RS and LS. + * + */ +HRESULT CordbEval::NewString(LPCWSTR string) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + return NewStringWithLength(string, (UINT)wcslen(string)); +} + +//--------------------------------------------------------------------------------------- +// +// This routine is the interface function for ICorDebugEval::NewStringWithLength. +// +// Arguments: +// wszString - the string to create +// iLength - the number of characters that you want to create. Can include embedded nulls. +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbEval::NewStringWithLength(LPCWSTR wszString, UINT iLength) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(wszString, LPCWSTR); // Gotta have a string... + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + // Callers are free to reuse an ICorDebugEval object for multiple + // evals. Since we create a Left Side eval representation each + // time, we need to be sure to clean it up now that we know we're + // done with it. + HRESULT hr = SendCleanup(); + + if (FAILED(hr)) + { + return hr; + } + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + RsPtrHolder<CordbEval> hFuncEval(this); + lockHolder.Release(); // release to send an IPC event. + + if (hFuncEval.Ptr().IsNull()) + { + return E_OUTOFMEMORY; + } + + + // Length of the string? Don't account for null as COMString::NewString is length-based + SIZE_T cbString = iLength * sizeof(WCHAR); + + // Remember that we're doing a func eval for a new string. + m_function = NULL; + m_evalType = DB_IPCE_FET_NEW_STRING; + + // Send over to the left side and get it to setup this eval. + DebuggerIPCEvent event; + + m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken()); + + event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken; + event.FuncEval.funcEvalType = m_evalType; + event.FuncEval.funcEvalKey = hFuncEval.Ptr(); + event.FuncEval.stringSize = cbString; + + // Note: no function or module here... + event.FuncEval.funcMetadataToken = mdMethodDefNil; + event.FuncEval.funcClassMetadataToken = mdTypeDefNil; + event.FuncEval.vmDomainFile = VMPTR_DomainFile::NullPtr(); + event.FuncEval.argCount = 0; + event.FuncEval.genericArgsCount = 0; + event.FuncEval.genericArgsNodeCount = 0; + + hr = SendFuncEval(0, NULL, (void *)wszString, (unsigned int)cbString, NULL, 0, &event); + + if (SUCCEEDED(hr)) + { + hFuncEval.SuppressRelease(); // Now LS owns. + } + + // Return any failure the Left Side may have told us about. + return hr; +} + +HRESULT CordbEval::NewArray(CorElementType elementType, + ICorDebugClass *pElementClass, + ULONG32 rank, + ULONG32 dims[], + ULONG32 lowBounds[]) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pElementClass, ICorDebugClass *); + + // If you want a class, you gotta pass a class. + if ((elementType == ELEMENT_TYPE_CLASS) && (pElementClass == NULL)) + return E_INVALIDARG; + + // If you want an array of objects, then why pass a class? + if ((elementType == ELEMENT_TYPE_OBJECT) && (pElementClass != NULL)) + return E_INVALIDARG; + + // Arg check... + if (elementType == ELEMENT_TYPE_VOID) + return E_INVALIDARG; + + CordbType *typ; + HRESULT hr = S_OK; + hr = CordbType::MkUnparameterizedType(m_thread->GetAppDomain(), elementType, (CordbClass *) pElementClass, &typ); + + if (FAILED(hr)) + return hr; + + return NewParameterizedArray(typ, rank,dims,lowBounds); + +} + + +//--------------------------------------------------------------------------------------- +// +// This routine sets up a func-eval to create a new array of the given type. +// +// Arguments: +// pElementType - The type of each element of the array. +// rank - Rank of the array. +// rgDimensions - Array of dimensions for the array. +// rmLowBounds - Array of lower bounds on the array. +// +// Return Value: +// HRESULT for the operation +// +HRESULT CordbEval::NewParameterizedArray(ICorDebugType * pElementType, + ULONG32 rank, + ULONG32 rgDimensions[], + ULONG32 rgLowBounds[]) +{ + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pElementType, ICorDebugType *); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + + // Callers are free to reuse an ICorDebugEval object for multiple evals. Since we create a Left Side eval + // representation each time, we need to be sure to clean it up now that we know we're done with it. + HRESULT hr = SendCleanup(); + + if (FAILED(hr)) + { + return hr; + } + + // Arg check... + if ((rank == 0) || (rgDimensions == NULL)) + { + return E_INVALIDARG; + } + + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + RsPtrHolder<CordbEval> hFuncEval(this); + lockHolder.Release(); // release to send an IPC event. + + if (hFuncEval.Ptr().IsNull()) + { + return E_OUTOFMEMORY; + } + + + // Remember that we're doing a func eval for a new string. + m_function = NULL; + m_evalType = DB_IPCE_FET_NEW_ARRAY; + + // Send over to the left side and get it to setup this eval. + DebuggerIPCEvent event; + + m_thread->GetProcess()->InitIPCEvent(&event, DB_IPCE_FUNC_EVAL, true, m_thread->GetAppDomain()->GetADToken()); + + event.FuncEval.vmThreadToken = m_thread->m_vmThreadToken; + event.FuncEval.funcEvalType = m_evalType; + event.FuncEval.funcEvalKey = hFuncEval.Ptr(); + + event.FuncEval.arrayRank = rank; + + // Note: no function or module here... + event.FuncEval.funcMetadataToken = mdMethodDefNil; + event.FuncEval.funcClassMetadataToken = mdTypeDefNil; + event.FuncEval.vmDomainFile = VMPTR_DomainFile::NullPtr(); + event.FuncEval.argCount = 0; + event.FuncEval.genericArgsCount = 1; + + // Prefast overflow sanity check. + S_UINT32 allocSize = S_UINT32(rank) * S_UINT32(sizeof(SIZE_T)); + + if (allocSize.IsOverflow()) + { + return E_INVALIDARG; + } + + // Just in case sizeof(SIZE_T) != sizeof(ULONG32) + SIZE_T * rgDimensionsSizeT = reinterpret_cast<SIZE_T *>(_alloca(allocSize.Value())); + + for (unsigned int i = 0; i < rank; i++) + { + rgDimensionsSizeT[i] = rgDimensions[i]; + } + + ICorDebugType * rgpGenericArgs[1]; + + rgpGenericArgs[0] = pElementType; + + // @dbgtodo funceval : lower bounds were ignored in V1 - fix this. + hr = SendFuncEval(1, + rgpGenericArgs, + reinterpret_cast<void *>(rgDimensionsSizeT), + rank * sizeof(SIZE_T), + NULL, // (void*)lowBounds, + 0, // ((lowBounds == NULL) ? 0 : rank * sizeof(SIZE_T)), + &event); + + if (SUCCEEDED(hr)) + { + hFuncEval.SuppressRelease(); // Now LS owns. + } + + // Return any failure the Left Side may have told us about. + return hr; +} + +HRESULT CordbEval::IsActive(BOOL *pbActive) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(pbActive, BOOL *); + + *pbActive = (m_complete == true); + return S_OK; +} + +/* + * This routine submits an abort request to the LS. + * + * Parameters: + * None. + * + * Returns: + * The HRESULT as returned by the LS. + * + */ + +HRESULT +CordbEval::Abort( + void + ) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + ATT_ALLOW_LIVE_DO_STOPGO(GetProcess()); + + + // + // No need to abort if its already completed. + // + if (m_complete) + { + return S_OK; + } + + + // + // Can't abort if its never even been started. + // + if (m_debuggerEvalKey == NULL) + { + return E_INVALIDARG; + } + + CORDBRequireProcessStateOK(m_thread->GetProcess()); + + // + // Send over to the left side to get the eval aborted. + // + DebuggerIPCEvent event; + + m_thread->GetProcess()->InitIPCEvent(&event, + DB_IPCE_FUNC_EVAL_ABORT, + true, + m_thread->GetAppDomain()->GetADToken() + ); + + event.FuncEvalAbort.debuggerEvalKey = m_debuggerEvalKey; + + HRESULT hr = m_thread->GetProcess()->SendIPCEvent(&event, + sizeof(DebuggerIPCEvent) + ); + + + // + // If the send failed, return that failure. + // + if (FAILED(hr)) + { + return hr; + } + + _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_ABORT_RESULT); + + // + // Since we may have + // overwritten anything (objects, code, etc), we should mark + // everything as needing to be re-cached. + // + m_thread->GetProcess()->m_continueCounter++; + + hr = event.hr; + + return hr; +} + +HRESULT CordbEval::GetResult(ICorDebugValue **ppResult) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppResult, ICorDebugValue **); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + *ppResult = NULL; + + // Is the evaluation complete? + if (!m_complete) + { + return CORDBG_E_FUNC_EVAL_NOT_COMPLETE; + } + + if (m_aborted) + { + return CORDBG_S_FUNC_EVAL_ABORTED; + } + + // Does the evaluation have a result? + if (m_resultType.elementType == ELEMENT_TYPE_VOID) + { + return CORDBG_S_FUNC_EVAL_HAS_NO_RESULT; + } + + HRESULT hr = S_OK; + EX_TRY + { + // Make a ICorDebugValue out of the result. + CordbAppDomain * pAppDomain; + + if (!m_resultAppDomainToken.IsNull()) + { + // @dbgtodo funceval - push this up + RSLockHolder lockHolder(GetProcess()->GetProcessLock()); + + pAppDomain = m_thread->GetProcess()->LookupOrCreateAppDomain(m_resultAppDomainToken); + } + else + { + pAppDomain = m_thread->GetAppDomain(); + } + PREFIX_ASSUME(pAppDomain != NULL); + + CordbType * pType = NULL; + hr = CordbType::TypeDataToType(pAppDomain, &m_resultType, &pType); + IfFailThrow(hr); + + bool resultInHandle = + ((m_resultType.elementType == ELEMENT_TYPE_CLASS) || + (m_resultType.elementType == ELEMENT_TYPE_SZARRAY) || + (m_resultType.elementType == ELEMENT_TYPE_OBJECT) || + (m_resultType.elementType == ELEMENT_TYPE_ARRAY) || + (m_resultType.elementType == ELEMENT_TYPE_STRING)); + + if (resultInHandle) + { + // if object handle is null here, something has gone wrong!!! + _ASSERTE(!m_vmObjectHandle.IsNull()); + + if (m_pHandleValue == NULL) + { + // Create CordbHandleValue for result + RSInitHolder<CordbHandleValue> pHandleValue(new CordbHandleValue(pAppDomain, pType, HANDLE_STRONG)); + + // Initialize the handle value object. The HandleValue will now + // own the m_objectHandle. + hr = pHandleValue->Init(m_vmObjectHandle); + + if (!SUCCEEDED(hr)) + { + // Neuter the new object we've been working on. This will + // call Dispose(), and that will go back to the left side + // and free the handle that we got above. + pHandleValue->NeuterLeftSideResources(); + + // + + // Do not delete chv here. The neuter list still has a reference to it, and it will be cleaned up automatically. + ThrowHR(hr); + } + m_pHandleValue.Assign(pHandleValue); + pHandleValue.ClearAndMarkDontNeuter(); + } + + // This AddRef is for caller to release + // + *ppResult = m_pHandleValue; + m_pHandleValue->ExternalAddRef(); + } + else if (CorIsPrimitiveType(m_resultType.elementType) && (m_resultType.elementType != ELEMENT_TYPE_STRING)) + { + // create a CordbGenericValue flagged as a literal + hr = CordbEval::CreatePrimitiveLiteral(pType, ppResult); + } + else + { + TargetBuffer remoteValue(m_resultAddr, CordbValue::GetSizeForType(pType, kBoxed)); + // Now that we have the module, go ahead and create the result. + + CordbValue::CreateValueByType(pAppDomain, + pType, + true, + remoteValue, + MemoryRange(NULL, 0), + NULL, + ppResult); // throws + } + + } + EX_CATCH_HRESULT(hr); + return hr; +} + +HRESULT CordbEval::GetThread(ICorDebugThread **ppThread) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + VALIDATE_POINTER_TO_OBJECT(ppThread, ICorDebugThread **); + + *ppThread = static_cast<ICorDebugThread*> (m_thread); + m_thread->ExternalAddRef(); + + return S_OK; +} + +// Create a RS literal for primitive type funceval result. In case the result is used as an argument for +// another funceval, we need to make sure that we're not relying on the LS value, which will be freed and +// thus unavailable. +// Arguments: +// input: pType - CordbType instance representing the type of the primitive value +// output: ppValue - ICorDebugValue representing the result as a literal CordbGenericValue +// Return Value: +// hr: may fail for OOM, ReadProcessMemory failures +HRESULT CordbEval::CreatePrimitiveLiteral(CordbType * pType, + ICorDebugValue ** ppValue) +{ + CordbGenericValue * gv = NULL; + HRESULT hr = S_OK; + EX_TRY + { + // Create a generic value. + gv = new CordbGenericValue(pType); + + // initialize the local value + int size = CordbValue::GetSizeForType(pType, kBoxed); + if (size > 8) + { + ThrowHR(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + } + TargetBuffer remoteValue(m_resultAddr, size); + BYTE localBuffer[8] = {0}; + + GetProcess()->SafeReadBuffer (remoteValue, localBuffer); + gv->SetValue(localBuffer); + + // Do not delete gv here even if the initialization fails. + // The neuter list still has a reference to it, and it will be cleaned up automatically. + gv->ExternalAddRef(); + *ppValue = (ICorDebugValue*)(ICorDebugGenericValue*)gv; + } + EX_CATCH_HRESULT(hr); + return hr; +} + +HRESULT CordbEval::CreateValue(CorElementType elementType, + ICorDebugClass *pElementClass, + ICorDebugValue **ppValue) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + CordbType *typ; + + // @todo: only primitive values right now. + if (((elementType < ELEMENT_TYPE_BOOLEAN) || + (elementType > ELEMENT_TYPE_R8)) && + !(elementType == ELEMENT_TYPE_CLASS)) + return E_INVALIDARG; + + HRESULT hr = S_OK; + + // MkUnparameterizedType now works if you give it ELEMENT_TYPE_CLASS and + // a null pElementClass - it returns the type for ELEMENT_TYPE_OBJECT. + + hr = CordbType::MkUnparameterizedType(m_thread->GetAppDomain(), elementType, (CordbClass *) pElementClass, &typ); + + if (FAILED(hr)) + return hr; + + return CreateValueForType(typ, ppValue); +} + +// create an ICDValue to represent a value for a funceval +// Arguments: +// input: pIType - the type for the new value +// output: ppValue - the new ICDValue. If there is a failure of some sort, this will be NULL +// ReturnValue: S_OK on success (ppValue should contain a non-NULL address) +// E_OUTOFMEMORY, if we can't allocate space for the new ICDValue +// Notes: We can also get read process memory errors or E_INVALIDARG if errors occur during initialization, +// but in that case, we don't return the hresult. Instead, we just never update ppValue, so it will still be +// NULL on exit. +HRESULT CordbEval::CreateValueForType(ICorDebugType * pIType, + ICorDebugValue ** ppValue) +{ + HRESULT hr = S_OK; + + PUBLIC_REENTRANT_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + ATT_REQUIRE_STOPPED_MAY_FAIL(GetProcess()); + + VALIDATE_POINTER_TO_OBJECT(ppValue, ICorDebugValue **); + VALIDATE_POINTER_TO_OBJECT(pIType, ICorDebugType*); + + *ppValue = NULL; + CordbType *pType = static_cast<CordbType *> (pIType); + + CorElementType elementType = pType->m_elementType; + // We don't support IntPtr and UIntPtr types as arguments here, but we do support these types as results + // (see code:CordbEval::CreatePrimitiveLiteral) and we have changed the LS to support them as well + if (((elementType < ELEMENT_TYPE_BOOLEAN) || + (elementType > ELEMENT_TYPE_R8)) && + !((elementType == ELEMENT_TYPE_CLASS) || (elementType == ELEMENT_TYPE_OBJECT))) + return E_INVALIDARG; + + // Note: ELEMENT_TYPE_OBJECT is what we'll get for the null reference case, so allow that. + if ((elementType == ELEMENT_TYPE_CLASS) || (elementType == ELEMENT_TYPE_OBJECT)) + { + EX_TRY + { + // create a reference value + CordbReferenceValue *rv = new CordbReferenceValue(pType); + + if (SUCCEEDED(rv->InitRef(MemoryRange(NULL,0)))) + { + // Do not delete rv here even if the initialization fails. + // The neuter list still has a reference to it, and it will be cleaned up automatically. + rv->ExternalAddRef(); + *ppValue = (ICorDebugValue*)(ICorDebugReferenceValue*)rv; + } + } + EX_CATCH_HRESULT(hr); + } + else + { + CordbGenericValue * gv = NULL; + EX_TRY + { + // Create a generic value. + gv = new CordbGenericValue(pType); + + gv->Init(MemoryRange(NULL,0)); + // Do not delete gv here even if the initialization fails. + // The neuter list still has a reference to it, and it will be cleaned up automatically. + gv->ExternalAddRef(); + *ppValue = (ICorDebugValue*)(ICorDebugGenericValue*)gv; + } + EX_CATCH_HRESULT(hr); + } + + return hr; +} // CordbEval::CreateValueForType + + +/* ------------------------------------------------------------------------- * + * CordbEval2 + * + * Extentions to the CordbEval class for Whidbey + * + * ------------------------------------------------------------------------- */ + + +/* + * This routine submits a rude abort request to the LS. + * + * Parameters: + * None. + * + * Returns: + * The HRESULT as returned by the LS. + * + */ + +HRESULT +CordbEval::RudeAbort( + void + ) +{ + PUBLIC_API_ENTRY(this); + FAIL_IF_NEUTERED(this); + + + ATT_ALLOW_LIVE_DO_STOPGO(GetProcess()); + + // + // No need to abort if its already completed. + // + if (m_complete) + { + return S_OK; + } + + // + // Can't abort if its never even been started. + // + if (m_debuggerEvalKey == NULL) + { + return E_INVALIDARG; + } + + CORDBRequireProcessStateOK(m_thread->GetProcess()); + + // + // Send over to the left side to get the eval aborted. + // + DebuggerIPCEvent event; + + m_thread->GetProcess()->InitIPCEvent(&event, + DB_IPCE_FUNC_EVAL_RUDE_ABORT, + true, + m_thread->GetAppDomain()->GetADToken() + ); + + event.FuncEvalRudeAbort.debuggerEvalKey = m_debuggerEvalKey; + + HRESULT hr = m_thread->GetProcess()->SendIPCEvent(&event, + sizeof(DebuggerIPCEvent) + ); + + // + // If the send failed, return that failure. + // + if (FAILED(hr)) + { + return hr; + } + + _ASSERTE(event.type == DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT); + + // + // Since we may have + // overwritten anything (objects, code, etc), we should mark + // everything as needing to be re-cached. + // + m_thread->GetProcess()->m_continueCounter++; + + hr = event.hr; + + return hr; +} + + + + +/* ------------------------------------------------------------------------- * + * CodeParameter Enumerator class + * ------------------------------------------------------------------------- */ + +CordbCodeEnum::CordbCodeEnum(unsigned int cCodes, RSSmartPtr<CordbCode> * ppCodes) : + CordbBase(NULL, 0) +{ + // Because the array is of smart-ptrs, the elements are already reffed + // We now take ownership of the array itself too. + m_ppCodes = ppCodes; + + m_iCurrent = 0; + m_iMax = cCodes; +} + + +CordbCodeEnum::~CordbCodeEnum() +{ + // This will invoke the SmartPtr dtors on each element and call release. + delete [] m_ppCodes; +} + +HRESULT CordbCodeEnum::QueryInterface(REFIID id, void **pInterface) +{ + if (id == IID_ICorDebugEnum) + *pInterface = static_cast<ICorDebugEnum*>(this); + else if (id == IID_ICorDebugCodeEnum) + *pInterface = static_cast<ICorDebugCodeEnum*>(this); + else if (id == IID_IUnknown) + *pInterface = static_cast<IUnknown*>(static_cast<ICorDebugCodeEnum*>(this)); + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + ExternalAddRef(); + return S_OK; +} + +HRESULT CordbCodeEnum::Skip(ULONG celt) +{ + HRESULT hr = E_FAIL; + if ( (m_iCurrent+celt) < m_iMax || + celt == 0) + { + m_iCurrent += celt; + hr = S_OK; + } + + return hr; +} + +HRESULT CordbCodeEnum::Reset() +{ + m_iCurrent = 0; + return S_OK; +} + +HRESULT CordbCodeEnum::Clone(ICorDebugEnum **ppEnum) +{ + VALIDATE_POINTER_TO_OBJECT(ppEnum, ICorDebugEnum **); + (*ppEnum) = NULL; + + HRESULT hr = S_OK; + + // Create a new copy of the array because the CordbCodeEnum will + // take ownership of it. + RSSmartPtr<CordbCode> * ppCodes = new (nothrow) RSSmartPtr<CordbCode> [m_iMax]; + if (ppCodes == NULL) + { + return E_OUTOFMEMORY; + } + for(UINT i = 0; i < m_iMax; i++) + { + ppCodes[i].Assign(m_ppCodes[i]); + } + + + CordbCodeEnum *pCVE = new (nothrow) CordbCodeEnum( m_iMax, ppCodes); + if ( pCVE == NULL ) + { + delete [] ppCodes; + hr = E_OUTOFMEMORY; + goto LExit; + } + + pCVE->ExternalAddRef(); + (*ppEnum) = (ICorDebugEnum*)pCVE; + +LExit: + return hr; +} + +HRESULT CordbCodeEnum::GetCount(ULONG *pcelt) +{ + VALIDATE_POINTER_TO_OBJECT(pcelt, ULONG *); + + if( pcelt == NULL) + return E_INVALIDARG; + + (*pcelt) = m_iMax; + return S_OK; +} + +// +// In the event of failure, the current pointer will be left at +// one element past the troublesome element. Thus, if one were +// to repeatedly ask for one element to iterate through the +// array, you would iterate exactly m_iMax times, regardless +// of individual failures. +HRESULT CordbCodeEnum::Next(ULONG celt, ICorDebugCode *values[], ULONG *pceltFetched) +{ + VALIDATE_POINTER_TO_OBJECT_ARRAY(values, ICorDebugClass *, + celt, true, true); + VALIDATE_POINTER_TO_OBJECT_OR_NULL(pceltFetched, ULONG *); + + if ((pceltFetched == NULL) && (celt != 1)) + { + return E_INVALIDARG; + } + + if (celt == 0) + { + if (pceltFetched != NULL) + { + *pceltFetched = 0; + } + return S_OK; + } + + HRESULT hr = S_OK; + + int iMax = min( m_iMax, m_iCurrent+celt); + int i; + + for (i = m_iCurrent; i < iMax; i++) + { + values[i-m_iCurrent] = m_ppCodes[i]; + values[i-m_iCurrent]->AddRef(); + } + + int count = (i - m_iCurrent); + + if ( FAILED( hr ) ) + { //we failed: +1 pushes us past troublesome element + m_iCurrent += 1 + count; + } + else + { + m_iCurrent += count; + } + + if (pceltFetched != NULL) + { + *pceltFetched = count; + } + + // + // If we reached the end of the enumeration, but not the end + // of the number of requested items, we return S_FALSE. + // + if (((ULONG)count) < celt) + { + return S_FALSE; + } + + return hr; +} + |