summaryrefslogtreecommitdiff
path: root/src/debug/ee/controller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/debug/ee/controller.cpp')
-rw-r--r--src/debug/ee/controller.cpp8892
1 files changed, 8892 insertions, 0 deletions
diff --git a/src/debug/ee/controller.cpp b/src/debug/ee/controller.cpp
new file mode 100644
index 0000000000..7f4d44568d
--- /dev/null
+++ b/src/debug/ee/controller.cpp
@@ -0,0 +1,8892 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// ==++==
+//
+
+//
+// ==--==
+// ****************************************************************************
+// File: controller.cpp
+//
+
+//
+// controller.cpp: Debugger execution control routines
+//
+// ****************************************************************************
+// Putting code & #includes, #defines, etc, before the stdafx.h will
+// cause the code,etc, to be silently ignored
+#include "stdafx.h"
+#include "openum.h"
+#include "../inc/common.h"
+#include "eeconfig.h"
+
+#include "../../vm/methoditer.h"
+
+const char *GetTType( TraceType tt);
+
+#define IsSingleStep(exception) (exception == EXCEPTION_SINGLE_STEP)
+
+
+
+
+
+// -------------------------------------------------------------------------
+// DebuggerController routines
+// -------------------------------------------------------------------------
+
+SPTR_IMPL_INIT(DebuggerPatchTable, DebuggerController, g_patches, NULL);
+SVAL_IMPL_INIT(BOOL, DebuggerController, g_patchTableValid, FALSE);
+
+#if !defined(DACCESS_COMPILE)
+
+DebuggerController *DebuggerController::g_controllers = NULL;
+DebuggerControllerPage *DebuggerController::g_protections = NULL;
+CrstStatic DebuggerController::g_criticalSection;
+int DebuggerController::g_cTotalMethodEnter = 0;
+
+
+// Is this patch at a position at which it's safe to take a stack?
+bool DebuggerControllerPatch::IsSafeForStackTrace()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ TraceType tt = this->trace.GetTraceType();
+ Module *module = this->key.module;
+ BOOL managed = this->IsManagedPatch();
+
+ // Patches placed by MgrPush can come at lots of illegal spots. Can't take a stack trace here.
+ if ((module == NULL) && managed && (tt == TRACE_MGR_PUSH))
+ {
+ return false;
+ }
+
+ // Consider everything else legal.
+ // This is a little shady for TRACE_FRAME_PUSH. But TraceFrame() needs a stackInfo
+ // to get a RegDisplay (though almost nobody uses it, so perhaps it could be removed).
+ return true;
+
+}
+
+#ifndef _TARGET_ARM_
+// returns a pointer to the shared buffer. each call will AddRef() the object
+// before returning it so callers only need to Release() when they're finished with it.
+SharedPatchBypassBuffer* DebuggerControllerPatch::GetOrCreateSharedPatchBypassBuffer()
+{
+ CONTRACTL
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_pSharedPatchBypassBuffer == NULL)
+ {
+ m_pSharedPatchBypassBuffer = new (interopsafeEXEC) SharedPatchBypassBuffer();
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ TRACE_ALLOC(m_pSharedPatchBypassBuffer);
+ }
+
+ m_pSharedPatchBypassBuffer->AddRef();
+
+ return m_pSharedPatchBypassBuffer;
+}
+#endif // _TARGET_ARM_
+
+// @todo - remove all this splicing trash
+// This Sort/Splice stuff just reorders the patches within a particular chain such
+// that when we iterate through by calling GetPatch() and GetNextPatch(DebuggerControllerPatch),
+// we'll get patches in increasing order of DebuggerControllerTypes.
+// Practically, this means that calling GetPatch() will return EnC patches before stepping patches.
+//
+#if 1
+void DebuggerPatchTable::SortPatchIntoPatchList(DebuggerControllerPatch **ppPatch)
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "DPT::SPIPL called.\n"));
+#ifdef _DEBUG
+ DebuggerControllerPatch *patchFirst
+ = (DebuggerControllerPatch *) Find(Hash((*ppPatch)), Key((*ppPatch)));
+ _ASSERTE(patchFirst == (*ppPatch));
+ _ASSERTE((*ppPatch)->controller->GetDCType() != DEBUGGER_CONTROLLER_STATIC);
+#endif //_DEBUG
+ DebuggerControllerPatch *patchNext = GetNextPatch((*ppPatch));
+LOG((LF_CORDB, LL_EVERYTHING, "DPT::SPIPL GetNextPatch passed\n"));
+ //List contains one, (sorted) element
+ if (patchNext == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x is a sorted singleton\n", (*ppPatch)));
+ return;
+ }
+
+ // If we decide to reorder the list, we'll need to keep the element
+ // indexed by the hash function as the (sorted)first item. Everything else
+ // chains off this element, can can thus stay put.
+ // Thus, either the element we just added is already sorted, or else we'll
+ // have to move it elsewhere in the list, meaning that we'll have to swap
+ // the second item & the new item, so that the index points to the proper
+ // first item in the list.
+
+ //use Cur ptr for case where patch gets appended to list
+ DebuggerControllerPatch *patchCur = patchNext;
+
+ while (patchNext != NULL &&
+ ((*ppPatch)->controller->GetDCType() >
+ patchNext->controller->GetDCType()) )
+ {
+ patchCur = patchNext;
+ patchNext = GetNextPatch(patchNext);
+ }
+
+ if (patchNext == GetNextPatch((*ppPatch)))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x is already sorted\n", (*ppPatch)));
+ return; //already sorted
+ }
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x will be moved \n", (*ppPatch)));
+
+ //remove it from the list
+ SpliceOutOfList((*ppPatch));
+
+ // the kinda neat thing is: since we put it originally at the front of the list,
+ // and it's not in order, then it must be behind another element of this list,
+ // so we don't have to write any 'SpliceInFrontOf' code.
+
+ _ASSERTE(patchCur != NULL);
+ SpliceInBackOf((*ppPatch), patchCur);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPT::SPIPL: Patch 0x%x is now sorted\n", (*ppPatch)));
+}
+
+// This can leave the list empty, so don't do this unless you put
+// the patch back somewhere else.
+void DebuggerPatchTable::SpliceOutOfList(DebuggerControllerPatch *patch)
+{
+ // We need to get iHash, the index of the ptr within
+ // m_piBuckets, ie it's entry in the hashtable.
+ ULONG iHash = Hash(patch) % m_iBuckets;
+ ULONG iElement = m_piBuckets[iHash];
+ DebuggerControllerPatch *patchFirst
+ = (DebuggerControllerPatch *) EntryPtr(iElement);
+
+ // Fix up pointers to chain
+ if (patchFirst == patch)
+ {
+ // The first patch shouldn't have anything behind it.
+ _ASSERTE(patch->entry.iPrev == DPT_INVALID_SLOT);
+
+ if (patch->entry.iNext != DPT_INVALID_SLOT)
+ {
+ m_piBuckets[iHash] = patch->entry.iNext;
+ }
+ else
+ {
+ m_piBuckets[iHash] = DPT_INVALID_SLOT;
+ }
+ }
+
+ if (patch->entry.iNext != DPT_INVALID_SLOT)
+ {
+ EntryPtr(patch->entry.iNext)->iPrev = patch->entry.iPrev;
+ }
+
+ if (patch->entry.iPrev != DPT_INVALID_SLOT)
+ {
+ EntryPtr(patch->entry.iNext)->iNext = patch->entry.iNext;
+ }
+
+ patch->entry.iNext = DPT_INVALID_SLOT;
+ patch->entry.iPrev = DPT_INVALID_SLOT;
+}
+
+void DebuggerPatchTable::SpliceInBackOf(DebuggerControllerPatch *patchAppend,
+ DebuggerControllerPatch *patchEnd)
+{
+ ULONG iAppend = ItemIndex((HASHENTRY*)patchAppend);
+ ULONG iEnd = ItemIndex((HASHENTRY*)patchEnd);
+
+ patchAppend->entry.iPrev = iEnd;
+ patchAppend->entry.iNext = patchEnd->entry.iNext;
+
+ if (patchAppend->entry.iNext != DPT_INVALID_SLOT)
+ EntryPtr(patchAppend->entry.iNext)->iPrev = iAppend;
+
+ patchEnd->entry.iNext = iAppend;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Stack safety rules.
+// In general, we're safe to crawl whenever we're in preemptive mode.
+// We're also must be safe at any spot the thread could get synchronized,
+// because that means that the thread will be stopped to let the debugger shell
+// inspect it and that can definitely take stack traces.
+// Basically the only unsafe spot is in the middle of goofy stub with some
+// partially constructed frame while in coop mode.
+//-----------------------------------------------------------------------------
+
+// Safe if we're at certain types of patches.
+// See Patch::IsSafeForStackTrace for details.
+StackTraceTicket::StackTraceTicket(DebuggerControllerPatch * patch)
+{
+ _ASSERTE(patch != NULL);
+ _ASSERTE(patch->IsSafeForStackTrace());
+}
+
+// Safe if there was already another stack trace at this spot. (Grandfather clause)
+// This is commonly used for StepOut, which takes runs stacktraces to crawl up
+// the stack to find a place to patch.
+StackTraceTicket::StackTraceTicket(ControllerStackInfo * info)
+{
+ _ASSERTE(info != NULL);
+
+ // Ensure that the other stack info object actually executed (and thus was
+ // actually valid).
+ _ASSERTE(info->m_dbgExecuted);
+}
+
+// Safe b/c the context shows we're in native managed code.
+// This must be safe because we could always set a managed breakpoint by native
+// offset and thus synchronize the shell at this spot. So this is
+// a specific example of the Synchronized case. The fact that we don't actually
+// synchronize doesn't make us any less safe.
+StackTraceTicket::StackTraceTicket(const BYTE * ip)
+{
+ _ASSERTE(g_pEEInterface->IsManagedNativeCode(ip));
+}
+
+// Safe it we're at a Synchronized point point.
+StackTraceTicket::StackTraceTicket(Thread * pThread)
+{
+ _ASSERTE(pThread != NULL);
+
+ // If we're synchronized, the debugger should be stopped.
+ // That means all threads are synced and must be safe to take a stacktrace.
+ // Thus we don't even need to do a thread-specific check.
+ _ASSERTE(g_pDebugger->IsStopped());
+}
+
+// DebuggerUserBreakpoint has a special case of safety. See that ctor for details.
+StackTraceTicket::StackTraceTicket(DebuggerUserBreakpoint * p)
+{
+ _ASSERTE(p != NULL);
+}
+
+//void ControllerStackInfo::GetStackInfo(): GetStackInfo
+// is invoked by the user to trigger the stack walk. This will
+// cause the stack walk detailed in the class description to happen.
+// Thread* thread: The thread to do the stack walk on.
+// void* targetFP: Can be either NULL (meaning that the bottommost
+// frame is the target), or an frame pointer, meaning that the
+// caller wants information about a specific frame.
+// CONTEXT* pContext: A pointer to a CONTEXT structure. Can be null,
+// we use our temp context.
+// bool suppressUMChainFromComPlusMethodFrameGeneric - A ridiculous flag that is trying to narrowly
+// target a fix for issue 650903.
+// StackTraceTicket - ticket to ensure that we actually have permission for this stacktrace
+void ControllerStackInfo::GetStackInfo(
+ StackTraceTicket ticket,
+ Thread *thread,
+ FramePointer targetFP,
+ CONTEXT *pContext,
+ bool suppressUMChainFromComPlusMethodFrameGeneric
+ )
+{
+ _ASSERTE(thread != NULL);
+
+ BOOL contextValid = (pContext != NULL);
+ if (!contextValid)
+ {
+ // We're assuming the thread is protected w/ a frame (which includes the redirection
+ // case). The stackwalker will use that protection to prime the context.
+ pContext = &this->m_tempContext;
+ }
+ else
+ {
+ // If we provided an explicit context for this thread, it better not be redirected.
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+ }
+
+ // Mark this stackwalk as valid so that it can in turn be used to grandfather
+ // in other stackwalks.
+ INDEBUG(m_dbgExecuted = true);
+
+ m_activeFound = false;
+ m_returnFound = false;
+ m_bottomFP = LEAF_MOST_FRAME;
+ m_targetFP = targetFP;
+ m_targetFrameFound = (m_targetFP == LEAF_MOST_FRAME);
+ m_specialChainReason = CHAIN_NONE;
+ m_suppressUMChainFromComPlusMethodFrameGeneric = suppressUMChainFromComPlusMethodFrameGeneric;
+
+ int result = DebuggerWalkStack(thread,
+ LEAF_MOST_FRAME,
+ pContext,
+ contextValid,
+ WalkStack,
+ (void *) this,
+ FALSE);
+
+ _ASSERTE(m_activeFound); // All threads have at least one unmanaged frame
+
+ if (result == SWA_DONE)
+ {
+ _ASSERTE(!m_returnFound);
+ m_returnFrame = m_activeFrame;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function "undoes" an unwind, i.e. it takes the active frame (the current frame)
+// and sets it to be the return frame (the caller frame). Currently it is only used by
+// the stepper to step out of an LCG method. See DebuggerStepper::DetectHandleLCGMethods()
+// for more information.
+//
+// Assumptions:
+// The current frame is valid on entry.
+//
+// Notes:
+// After this function returns, the active frame on this instance of ControllerStackInfo will no longer be valid.
+//
+// This function is specifically for DebuggerStepper::DetectHandleLCGMethods(). Using it in other scencarios may
+// require additional changes.
+//
+
+void ControllerStackInfo::SetReturnFrameWithActiveFrame()
+{
+ // Copy the active frame into the return frame.
+ m_returnFound = true;
+ m_returnFrame = m_activeFrame;
+
+ // Invalidate the active frame.
+ m_activeFound = false;
+ memset(&(m_activeFrame), 0, sizeof(m_activeFrame));
+ m_activeFrame.fp = LEAF_MOST_FRAME;
+}
+
+// Fill in a controller-stack info.
+StackWalkAction ControllerStackInfo::WalkStack(FrameInfo *pInfo, void *data)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(!pInfo->HasStubFrame()); // we didn't ask for stub frames.
+
+ ControllerStackInfo *i = (ControllerStackInfo *) data;
+
+ //save this info away for later use
+ if (i->m_bottomFP == LEAF_MOST_FRAME)
+ i->m_bottomFP = pInfo->fp;
+
+ // This is part of the targetted fix for issue 650903. (See the other
+ // parts in in code:TrackUMChain and code:DebuggerStepper::TrapStepOut.)
+ // pInfo->fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric has been
+ // set by TrackUMChain to help us remember that the current frame we're looking at is
+ // ComPlusMethodFrameGeneric (we can't rely on looking at pInfo->frame to check
+ // this), and i->m_suppressUMChainFromComPlusMethodFrameGeneric has been set by the
+ // dude initiating this walk to remind us that our goal in life is to do a Step Out
+ // during managed-only debugging. These two things together tell us we should ignore
+ // this frame, rather than erroneously identifying it as the target frame.
+#ifdef FEATURE_COMINTEROP
+ if(i->m_suppressUMChainFromComPlusMethodFrameGeneric &&
+ (pInfo->chainReason == CHAIN_ENTER_UNMANAGED) &&
+ (pInfo->fIgnoreThisFrameIfSuppressingUMChainFromComPlusMethodFrameGeneric))
+ {
+ return SWA_CONTINUE;
+ }
+#endif // FEATURE_COMINTEROP
+
+ //have we reached the correct frame yet?
+ if (!i->m_targetFrameFound &&
+ IsEqualOrCloserToLeaf(i->m_targetFP, pInfo->fp))
+ {
+ i->m_targetFrameFound = true;
+ }
+
+ if (i->m_targetFrameFound )
+ {
+ // Ignore Enter-managed chains.
+ if (pInfo->chainReason == CHAIN_ENTER_MANAGED)
+ {
+ return SWA_CONTINUE;
+ }
+
+ if (i->m_activeFound )
+ {
+ // We care if the current frame is unmanaged (in case a managed stepper is initiated
+ // on a thread currently in unmanaged code). But since we can't step-out to UM frames,
+ // we can just skip them in the stack walk.
+ if (!pInfo->managed)
+ {
+ return SWA_CONTINUE;
+ }
+
+ if (pInfo->chainReason == CHAIN_CLASS_INIT)
+ i->m_specialChainReason = pInfo->chainReason;
+
+ if (pInfo->fp != i->m_activeFrame.fp) // avoid dups
+ {
+ i->m_returnFrame = *pInfo;
+
+#if defined(WIN64EXCEPTIONS)
+ CopyREGDISPLAY(&(i->m_returnFrame.registers), &(pInfo->registers));
+#endif // WIN64EXCEPTIONS
+
+ i->m_returnFound = true;
+
+ return SWA_ABORT;
+ }
+ }
+ else
+ {
+ i->m_activeFrame = *pInfo;
+
+#if defined(WIN64EXCEPTIONS)
+ CopyREGDISPLAY(&(i->m_activeFrame.registers), &(pInfo->registers));
+#endif // WIN64EXCEPTIONS
+
+ i->m_activeFound = true;
+
+ return SWA_CONTINUE;
+ }
+ }
+
+ return SWA_CONTINUE;
+}
+
+
+//
+// Note that patches may be reallocated - do not keep a pointer to a patch.
+//
+DebuggerControllerPatch *DebuggerPatchTable::AddPatchForMethodDef(DebuggerController *controller,
+ Module *module,
+ mdMethodDef md,
+ size_t offset,
+ DebuggerPatchKind kind,
+ FramePointer fp,
+ AppDomain *pAppDomain,
+ SIZE_T masterEnCVersion,
+ DebuggerJitInfo *dji)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG( (LF_CORDB,LL_INFO10000,"DCP:AddPatchForMethodDef unbound "
+ "relative in methodDef 0x%x with dji 0x%x "
+ "controller:0x%x AD:0x%x\n", md,
+ dji, controller, pAppDomain));
+
+ DebuggerFunctionKey key;
+
+ key.module = module;
+ key.md = md;
+
+ // Get a new uninitialized patch object
+ DebuggerControllerPatch *patch =
+ (DebuggerControllerPatch *) Add(HashKey(&key));
+ if (patch == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+#ifndef _TARGET_ARM_
+ patch->Initialize();
+#endif
+
+ //initialize the patch data structure.
+ InitializePRD(&(patch->opcode));
+ patch->controller = controller;
+ patch->key.module = module;
+ patch->key.md = md;
+ patch->offset = offset;
+ patch->offsetIsIL = (kind == PATCH_KIND_IL_MASTER);
+ patch->address = NULL;
+ patch->fp = fp;
+ patch->trace.Bad_SetTraceType(DPT_DEFAULT_TRACE_TYPE); // TRACE_OTHER
+ patch->refCount = 1; // AddRef()
+ patch->fSaveOpcode = false;
+ patch->pAppDomain = pAppDomain;
+ patch->pid = m_pid++;
+
+ if (kind == PATCH_KIND_IL_MASTER)
+ {
+ _ASSERTE(dji == NULL);
+ patch->encVersion = masterEnCVersion;
+ }
+ else
+ {
+ patch->dji = dji;
+ }
+ patch->kind = kind;
+
+ if (dji)
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForMethodDef w/ version 0x%04x, "
+ "pid:0x%x\n", dji->m_encVersion, patch->pid));
+ else if (kind == PATCH_KIND_IL_MASTER)
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForMethodDef w/ version 0x%04x, "
+ "pid:0x%x\n", masterEnCVersion,patch->pid));
+ else
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForMethodDef w/ no dji or dmi, pid:0x%x\n",patch->pid));
+
+
+ // This patch is not yet bound or activated
+ _ASSERTE( !patch->IsBound() );
+ _ASSERTE( !patch->IsActivated() );
+
+ // The only kind of patch with IL offset is the IL master patch.
+ _ASSERTE(patch->IsILMasterPatch() || patch->offsetIsIL == FALSE);
+ return patch;
+}
+
+// Create and bind a patch to the specified address
+// The caller should immediately activate the patch since we typically expect bound patches
+// will always be activated.
+DebuggerControllerPatch *DebuggerPatchTable::AddPatchForAddress(DebuggerController *controller,
+ MethodDesc *fd,
+ size_t offset,
+ DebuggerPatchKind kind,
+ CORDB_ADDRESS_TYPE *address,
+ FramePointer fp,
+ AppDomain *pAppDomain,
+ DebuggerJitInfo *dji,
+ SIZE_T pid,
+ TraceType traceType)
+
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+
+ _ASSERTE(kind == PATCH_KIND_NATIVE_MANAGED || kind == PATCH_KIND_NATIVE_UNMANAGED);
+ LOG((LF_CORDB,LL_INFO10000,"DCP:AddPatchForAddress bound "
+ "absolute to 0x%x with dji 0x%x (mdDef:0x%x) "
+ "controller:0x%x AD:0x%x\n",
+ address, dji, (fd!=NULL?fd->GetMemberDef():0), controller,
+ pAppDomain));
+
+ // get new uninitialized patch object
+ DebuggerControllerPatch *patch =
+ (DebuggerControllerPatch *) Add(HashAddress(address));
+
+ if (patch == NULL)
+ {
+ ThrowOutOfMemory();
+ }
+#ifndef _TARGET_ARM_
+ patch->Initialize();
+#endif
+
+ // initialize the patch data structure
+ InitializePRD(&(patch->opcode));
+ patch->controller = controller;
+
+ if (fd == NULL)
+ {
+ patch->key.module = NULL;
+ patch->key.md = mdTokenNil;
+ }
+ else
+ {
+ patch->key.module = g_pEEInterface->MethodDescGetModule(fd);
+ patch->key.md = fd->GetMemberDef();
+ }
+ patch->offset = offset;
+ patch->offsetIsIL = FALSE;
+ patch->address = address;
+ patch->fp = fp;
+ patch->trace.Bad_SetTraceType(traceType);
+ patch->refCount = 1; // AddRef()
+ patch->fSaveOpcode = false;
+ patch->pAppDomain = pAppDomain;
+ if (pid == DCP_PID_INVALID)
+ patch->pid = m_pid++;
+ else
+ patch->pid = pid;
+
+ patch->dji = dji;
+ patch->kind = kind;
+
+ if (dji == NULL)
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForAddress w/ version with no dji, pid:0x%x\n", patch->pid));
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"AddPatchForAddress w/ version 0x%04x, "
+ "pid:0x%x\n", dji->m_methodInfo->GetCurrentEnCVersion(), patch->pid));
+
+ _ASSERTE( fd==NULL || fd == dji->m_fd );
+ }
+
+ SortPatchIntoPatchList(&patch);
+
+ // This patch is bound but not yet activated
+ _ASSERTE( patch->IsBound() );
+ _ASSERTE( !patch->IsActivated() );
+
+ // The only kind of patch with IL offset is the IL master patch.
+ _ASSERTE(patch->IsILMasterPatch() || patch->offsetIsIL == FALSE);
+ return patch;
+}
+
+// Set the native address for this patch.
+void DebuggerPatchTable::BindPatch(DebuggerControllerPatch *patch, CORDB_ADDRESS_TYPE *address)
+{
+ _ASSERTE(patch != NULL);
+ _ASSERTE(address != NULL);
+ _ASSERTE( !patch->IsILMasterPatch() );
+ _ASSERTE(!patch->IsBound() );
+
+ //Since the actual patch doesn't move, we don't have to worry about
+ //zeroing out the opcode field (see lenghty comment above)
+ // Since the patch is double-hashed based off Address, if we change the address,
+ // we must remove and reinsert the patch.
+ CHashTable::Delete(HashKey(&patch->key), ItemIndex((HASHENTRY*)patch));
+
+ patch->address = address;
+
+ CHashTable::Add(HashAddress(address), ItemIndex((HASHENTRY*)patch));
+
+ SortPatchIntoPatchList(&patch);
+
+ _ASSERTE(patch->IsBound() );
+ _ASSERTE(!patch->IsActivated() );
+}
+
+// Disassociate a patch from a specific code address.
+void DebuggerPatchTable::UnbindPatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(patch != NULL);
+ _ASSERTE(patch->kind != PATCH_KIND_IL_MASTER);
+ _ASSERTE(patch->IsBound() );
+ _ASSERTE(!patch->IsActivated() );
+
+ //<REVISIT_TODO>@todo We're hosed if the patch hasn't been primed with
+ // this info & we can't get it...</REVISIT_TODO>
+ if (patch->key.module == NULL ||
+ patch->key.md == mdTokenNil)
+ {
+ MethodDesc *fd = g_pEEInterface->GetNativeCodeMethodDesc(
+ dac_cast<PCODE>(patch->address));
+ _ASSERTE( fd != NULL );
+ patch->key.module = g_pEEInterface->MethodDescGetModule(fd);
+ patch->key.md = fd->GetMemberDef();
+ }
+
+ // Update it's index entry in the table to use it's unbound key
+ // Since the patch is double-hashed based off Address, if we change the address,
+ // we must remove and reinsert the patch.
+ CHashTable::Delete( HashAddress(patch->address),
+ ItemIndex((HASHENTRY*)patch));
+
+ patch->address = NULL; // we're no longer bound to this address
+
+ CHashTable::Add( HashKey(&patch->key),
+ ItemIndex((HASHENTRY*)patch));
+
+ _ASSERTE(!patch->IsBound() );
+
+}
+
+void DebuggerPatchTable::RemovePatch(DebuggerControllerPatch *patch)
+{
+ // Since we're deleting this patch, it must not be activated (i.e. it must not have a stored opcode)
+ _ASSERTE( !patch->IsActivated() );
+#ifndef _TARGET_ARM_
+ patch->DoCleanup();
+#endif
+
+ //
+ // Because of the implementation of CHashTable, we can safely
+ // delete elements while iterating through the table. This
+ // behavior is relied upon - do not change to a different
+ // implementation without considering this fact.
+ //
+ Delete(Hash(patch), (HASHENTRY *) patch);
+
+}
+
+DebuggerControllerPatch *DebuggerPatchTable::GetNextPatch(DebuggerControllerPatch *prev)
+{
+ ULONG iNext;
+ HASHENTRY *psEntry;
+
+ // Start at the next entry in the chain.
+ // @todo - note that: EntryPtr(ItemIndex(x)) == x
+ iNext = EntryPtr(ItemIndex((HASHENTRY*)prev))->iNext;
+
+ // Search until we hit the end.
+ while (iNext != UINT32_MAX)
+ {
+ // Compare the keys.
+ psEntry = EntryPtr(iNext);
+
+ // Careful here... we can hash the entries in this table
+ // by two types of keys. In this type of search, the type
+ // of the second key (psEntry) does not necessarily
+ // indicate the type of the first key (prev), so we have
+ // to check for sure.
+ DebuggerControllerPatch *pc2 = (DebuggerControllerPatch*)psEntry;
+
+ if (((pc2->address == NULL) && (prev->address == NULL)) ||
+ ((pc2->address != NULL) && (prev->address != NULL)))
+ if (!Cmp(Key(prev), psEntry))
+ return pc2;
+
+ // Advance to the next item in the chain.
+ iNext = psEntry->iNext;
+ }
+
+ return NULL;
+}
+
+#ifdef _DEBUG_PATCH_TABLE
+ // DEBUG An internal debugging routine, it iterates
+ // through the hashtable, stopping at every
+ // single entry, no matter what it's state. For this to
+ // compile, you're going to have to add friend status
+ // of this class to CHashTableAndData in
+ // to $\Com99\Src\inc\UtilCode.h
+void DebuggerPatchTable::CheckPatchTable()
+{
+ if (NULL != m_pcEntries)
+ {
+ DebuggerControllerPatch *dcp;
+ int i = 0;
+ while (i++ <m_iEntries)
+ {
+ dcp = (DebuggerControllerPatch*)&(((DebuggerControllerPatch *)m_pcEntries)[i]);
+ if (dcp->opcode != 0 )
+ {
+ LOG((LF_CORDB,LL_INFO1000, "dcp->addr:0x%8x "
+ "mdMD:0x%8x, offset:0x%x, native:%d\n",
+ dcp->address, dcp->key.md, dcp->offset,
+ dcp->IsNativePatch()));
+ }
+ }
+ }
+}
+
+#endif // _DEBUG_PATCH_TABLE
+
+// Count how many patches are in the table.
+// Use for asserts
+int DebuggerPatchTable::GetNumberOfPatches()
+{
+ int total = 0;
+
+ if (NULL != m_pcEntries)
+ {
+ DebuggerControllerPatch *dcp;
+ ULONG i = 0;
+
+ while (i++ <m_iEntries)
+ {
+ dcp = (DebuggerControllerPatch*)&(((DebuggerControllerPatch *)m_pcEntries)[i]);
+
+ if (dcp->IsActivated() || !dcp->IsFree())
+ total++;
+ }
+ }
+ return total;
+}
+
+#if defined(_DEBUG)
+//-----------------------------------------------------------------------------
+// Debug check that we only have 1 thread-starter per thread.
+// pNew - the new DTS. We'll make sure there's not already a DTS on this thread.
+//-----------------------------------------------------------------------------
+void DebuggerController::EnsureUniqueThreadStarter(DebuggerThreadStarter * pNew)
+{
+ // This lock should be safe to take since our base class ctor takes it.
+ ControllerLockHolder lockController;
+ DebuggerController * pExisting = g_controllers;
+ while(pExisting != NULL)
+ {
+ if (pExisting->GetDCType() == DEBUGGER_CONTROLLER_THREAD_STARTER)
+ {
+ if (pExisting != pNew)
+ {
+ // If we have 2 thread starters, they'd better be on different threads.
+ _ASSERTE((pExisting->GetThread() != pNew->GetThread()));
+ }
+ }
+ pExisting = pExisting->m_next;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// If we have a thread-starter on the given EE thread, make sure it's cancel.
+// Thread-Starters normally delete themselves when they fire. But if the EE
+// destroys the thread before it fires, then we'd still have an active DTS.
+//-----------------------------------------------------------------------------
+void DebuggerController::CancelOutstandingThreadStarter(Thread * pThread)
+{
+ _ASSERTE(pThread != NULL);
+ LOG((LF_CORDB, LL_EVERYTHING, "DC:CancelOutstandingThreadStarter - checking on thread =0x%p\n", pThread));
+
+ ControllerLockHolder lockController;
+ DebuggerController * p = g_controllers;
+ while(p != NULL)
+ {
+ if (p->GetDCType() == DEBUGGER_CONTROLLER_THREAD_STARTER)
+ {
+ if (p->GetThread() == pThread)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DC:CancelOutstandingThreadStarter, pThread=0x%p, Found=0x%p\n", p));
+
+ // There's only 1 DTS per thread, so once we find it, we can quit.
+ p->Delete();
+ p = NULL;
+ break;
+ }
+ }
+ p = p->m_next;
+ }
+ // The common case is that our DTS hit its patch and did a SendEvent (and
+ // deleted itself). So usually we'll get through the whole list w/o deleting anything.
+
+}
+
+//void DebuggerController::Initialize() Sets up the static
+// variables for the static DebuggerController class.
+// How: Sets g_runningOnWin95, initializes the critical section
+HRESULT DebuggerController::Initialize()
+{
+ CONTRACT(HRESULT)
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ // This can be called in an "early attach" case, so DebuggerIsInvolved()
+ // will be b/c we don't realize the debugger's attaching to us.
+ //PRECONDITION(DebuggerIsInvolved());
+ POSTCONDITION(CheckPointer(g_patches));
+ POSTCONDITION(RETVAL == S_OK);
+ }
+ CONTRACT_END;
+
+ if (g_patches == NULL)
+ {
+ ZeroMemory(&g_criticalSection, sizeof(g_criticalSection)); // Init() expects zero-init memory.
+
+ // NOTE: CRST_UNSAFE_ANYMODE prevents a GC mode switch when entering this crst.
+ // If you remove this flag, we will switch to preemptive mode when entering
+ // g_criticalSection, which means all functions that enter it will become
+ // GC_TRIGGERS. (This includes all uses of ControllerLockHolder.) So be sure
+ // to update the contracts if you remove this flag.
+ g_criticalSection.Init(CrstDebuggerController,
+ (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD));
+
+ g_patches = new (interopsafe) DebuggerPatchTable();
+ _ASSERTE(g_patches != NULL); // throws on oom
+
+ HRESULT hr = g_patches->Init();
+
+ if (FAILED(hr))
+ {
+ DeleteInteropSafe(g_patches);
+ ThrowHR(hr);
+ }
+
+ g_patchTableValid = TRUE;
+ TRACE_ALLOC(g_patches);
+ }
+
+ _ASSERTE(g_patches != NULL);
+
+ RETURN (S_OK);
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// Constructor for a controller
+//
+// Arguments:
+// pThread - thread that controller has affinity to. NULL if no thread - affinity.
+// pAppdomain - appdomain that controller has affinity to. NULL if no AD affinity.
+//
+//
+// Notes:
+// "Affinity" is per-controller specific. Affinity is generally passed on to
+// any patches the controller creates. So if a controller has affinity to Thread X,
+// then any patches it creates will only fire on Thread-X.
+//
+//---------------------------------------------------------------------------------------
+
+DebuggerController::DebuggerController(Thread * pThread, AppDomain * pAppDomain)
+ : m_pAppDomain(pAppDomain),
+ m_thread(pThread),
+ m_singleStep(false),
+ m_exceptionHook(false),
+ m_traceCall(0),
+ m_traceCallFP(ROOT_MOST_FRAME),
+ m_unwindFP(LEAF_MOST_FRAME),
+ m_eventQueuedCount(0),
+ m_deleted(false),
+ m_fEnableMethodEnter(false)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ CONSTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC: 0x%x m_eventQueuedCount to 0 - DC::DC\n", this));
+ ControllerLockHolder lockController;
+ {
+ m_next = g_controllers;
+ g_controllers = this;
+ }
+}
+
+//---------------------------------------------------------------------------------------
+//
+// Debugger::Controller::DeleteAllControlers - deletes all debugger contollers
+//
+// Arguments:
+// None
+//
+// Return Value:
+// None
+//
+// Notes:
+// This is used at detach time to remove all DebuggerControllers. This will remove all
+// patches and do whatever other cleanup individual DebuggerControllers consider
+// necessary to allow the debugger to detach and the process to run normally.
+//
+
+void DebuggerController::DeleteAllControllers()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder lockController;
+ DebuggerController * pDebuggerController = g_controllers;
+ DebuggerController * pNextDebuggerController = NULL;
+
+ while (pDebuggerController != NULL)
+ {
+ pNextDebuggerController = pDebuggerController->m_next;
+ pDebuggerController->DebuggerDetachClean();
+ pDebuggerController->Delete();
+ pDebuggerController = pNextDebuggerController;
+ }
+}
+
+DebuggerController::~DebuggerController()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ DESTRUCTOR_CHECK;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder lockController;
+
+ _ASSERTE(m_eventQueuedCount == 0);
+
+ DisableAll();
+
+ //
+ // Remove controller from list
+ //
+
+ DebuggerController **c;
+
+ c = &g_controllers;
+ while (*c != this)
+ c = &(*c)->m_next;
+
+ *c = m_next;
+
+}
+
+// void DebuggerController::Delete()
+// What: Marks an instance as deletable. If it's ref count
+// (see Enqueue, Dequeue) is currently zero, it actually gets deleted
+// How: Set m_deleted to true. If m_eventQueuedCount==0, delete this
+void DebuggerController::Delete()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if (m_eventQueuedCount == 0)
+ {
+ LOG((LF_CORDB|LF_ENC, LL_INFO100000, "DC::Delete: actual delete of this:0x%x!\n", this));
+ TRACE_FREE(this);
+ DeleteInteropSafe(this);
+ }
+ else
+ {
+ LOG((LF_CORDB|LF_ENC, LL_INFO100000, "DC::Delete: marked for "
+ "future delete of this:0x%x!\n", this));
+ LOG((LF_CORDB|LF_ENC, LL_INFO10000, "DC:0x%x m_eventQueuedCount at 0x%x\n",
+ this, m_eventQueuedCount));
+ m_deleted = true;
+ }
+}
+
+void DebuggerController::DebuggerDetachClean()
+{
+ //do nothing here
+}
+
+//static
+void DebuggerController::AddRef(DebuggerControllerPatch *patch)
+{
+ patch->refCount++;
+}
+
+//static
+void DebuggerController::Release(DebuggerControllerPatch *patch)
+{
+ patch->refCount--;
+ if (patch->refCount == 0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DCP::R: patch deleted, deactivating\n"));
+ DeactivatePatch(patch);
+ GetPatchTable()->RemovePatch(patch);
+ }
+}
+
+// void DebuggerController::DisableAll() DisableAll removes
+// all control from the controller. This includes all patches & page
+// protection. This will invoke Disable* for unwind,singlestep,
+// exceptionHook, and tracecall. It will also go through the patch table &
+// attempt to remove any and all patches that belong to this controller.
+// If the patch is currently triggering, then a Dispatch* method expects the
+// patch to be there after we return, so we instead simply mark the patch
+// itself as deleted.
+void DebuggerController::DisableAll()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::DisableAll\n"));
+ _ASSERTE(g_patches != NULL);
+
+ ControllerLockHolder ch;
+ {
+ //
+ // Remove controller's patches from list.
+ // Don't do this on shutdown because the shutdown thread may have killed another thread asynchronously
+ // thus leaving the patchtable in an inconsistent state such that we may fail trying to walk it.
+ // Since we're exiting anyways, leaving int3 in the code can't harm anybody.
+ //
+ if (!g_fProcessDetach)
+ {
+ HASHFIND f;
+ for (DebuggerControllerPatch *patch = g_patches->GetFirstPatch(&f);
+ patch != NULL;
+ patch = g_patches->GetNextPatch(&f))
+ {
+ if (patch->controller == this)
+ {
+ Release(patch);
+ }
+ }
+ }
+
+ if (m_singleStep)
+ DisableSingleStep();
+ if (m_exceptionHook)
+ DisableExceptionHook();
+ if (m_unwindFP != LEAF_MOST_FRAME)
+ DisableUnwind();
+ if (m_traceCall)
+ DisableTraceCall();
+ if (m_fEnableMethodEnter)
+ DisableMethodEnter();
+ }
+}
+
+// void DebuggerController::Enqueue() What: Does
+// reference counting so we don't toast a
+// DebuggerController while it's in a Dispatch queue.
+// Why: In DispatchPatchOrSingleStep, we can't hold locks when going
+// into PreEmptiveGC mode b/c we'll create a deadlock.
+// So we have to UnLock() prior to
+// EnablePreEmptiveGC(). But somebody else can show up and delete the
+// DebuggerControllers since we no longer have the lock. So we have to
+// do this reference counting thing to make sure that the controllers
+// don't get toasted as we're trying to invoke SendEvent on them. We have to
+// reaquire the lock before invoking Dequeue because Dequeue may
+// result in the controller being deleted, which would change the global
+// controller list.
+// How: InterlockIncrement( m_eventQueuedCount )
+void DebuggerController::Enqueue()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ m_eventQueuedCount++;
+ LOG((LF_CORDB, LL_INFO10000, "DC::Enq DC:0x%x m_eventQueuedCount at 0x%x\n",
+ this, m_eventQueuedCount));
+}
+
+// void DebuggerController::Dequeue() What: Does
+// reference counting so we don't toast a
+// DebuggerController while it's in a Dispatch queue.
+// How: InterlockDecrement( m_eventQueuedCount ), delete this if
+// m_eventQueuedCount == 0 AND m_deleted has been set to true
+void DebuggerController::Dequeue()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::Deq DC:0x%x m_eventQueuedCount at 0x%x\n",
+ this, m_eventQueuedCount));
+ if (--m_eventQueuedCount == 0)
+ {
+ if (m_deleted)
+ {
+ TRACE_FREE(this);
+ DeleteInteropSafe(this);
+ }
+ }
+}
+
+
+// bool DebuggerController::BindPatch() If the method has
+// been JITted and isn't hashed by address already, then hash
+// it into the hashtable by address and not DebuggerFunctionKey.
+// If the patch->address field is nonzero, we're done.
+// Otherwise ask g_pEEInterface to FindLoadedMethodRefOrDef, then
+// GetFunctionAddress of the method, if the method is in IL,
+// MapILOffsetToNative. If everything else went Ok, we can now invoke
+// g_patches->BindPatch.
+// Returns: false if we know that we can't bind the patch immediately.
+// true if we either can bind the patch right now, or can't right now,
+// but might be able to in the future (eg, the method hasn't been JITted)
+
+// Have following outcomes:
+// 1) Succeeded in binding the patch to a raw address. patch->address is set.
+// (Note we still must apply the patch to put the int 3 in.)
+// returns true, *pFail = false
+//
+// 2) Fails to bind, but a future attempt may succeed. Obvious ex, for an IL-only
+// patch on an unjitted method.
+// returns false, *pFail = false
+//
+// 3) Fails to bind because something's wrong. Ex: bad IL offset, no DJI to do a
+// mapping with. Future calls will fail too.
+// returns false, *pFail = true
+bool DebuggerController::BindPatch(DebuggerControllerPatch *patch,
+ MethodDesc *fd,
+ CORDB_ADDRESS_TYPE *startAddr)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(patch != NULL);
+ _ASSERTE(!patch->IsILMasterPatch());
+ _ASSERTE(fd != NULL);
+
+ //
+ // Translate patch to address, if it hasn't been already.
+ //
+
+ if (patch->address != NULL)
+ {
+ return true;
+ }
+
+ if (startAddr == NULL)
+ {
+ if (patch->HasDJI() && patch->GetDJI()->m_jitComplete)
+ {
+ startAddr = (CORDB_ADDRESS_TYPE *) CORDB_ADDRESS_TO_PTR(patch->GetDJI()->m_addrOfCode);
+ _ASSERTE(startAddr != NULL);
+ }
+ if (startAddr == NULL)
+ {
+ // Should not be trying to place patches on MethodDecs's for stubs.
+ // These stubs will never get jitted.
+ CONSISTENCY_CHECK_MSGF(!fd->IsWrapperStub(), ("Can't place patch at stub md %p, %s::%s",
+ fd, fd->m_pszDebugClassName, fd->m_pszDebugMethodName));
+
+ startAddr = (CORDB_ADDRESS_TYPE *)g_pEEInterface->GetFunctionAddress(fd);
+ //
+ // Code is not available yet to patch. The prestub should
+ // notify us when it is executed.
+ //
+ if (startAddr == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::BP:Patch at 0x%x not bindable yet.\n", patch->offset));
+
+ return false;
+ }
+ }
+ }
+
+ _ASSERTE(!g_pEEInterface->IsStub((const BYTE *)startAddr));
+
+ // If we've jitted, map to a native offset.
+ DebuggerJitInfo *info = g_pDebugger->GetJitInfo(fd, (const BYTE *)startAddr);
+
+#ifdef LOGGING
+ if (info == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::BindPa: For startAddr 0x%x, didn't find a DJI\n", startAddr));
+ }
+#endif //LOGGING
+ if (info != NULL)
+ {
+ // There is a strange case with prejitted code and unjitted trace patches. We can enter this function
+ // with no DebuggerJitInfo created, then have the call just above this actually create the
+ // DebuggerJitInfo, which causes JitComplete to be called, which causes all patches to be bound! If this
+ // happens, then we don't need to continue here (its already been done recursivley) and we don't need to
+ // re-active the patch, so we return false from right here. We can check this by seeing if we suddently
+ // have the address in the patch set.
+ if (patch->address != NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::BindPa: patch bound recursivley by GetJitInfo, bailing...\n"));
+ return false;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DC::BindPa: For startAddr 0x%x, got DJI "
+ "0x%x, from 0x%x size: 0x%x\n", startAddr, info, info->m_addrOfCode, info->m_sizeOfCode));
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::BP:Trying to bind patch in %s::%s version %d\n",
+ fd->m_pszDebugClassName, fd->m_pszDebugMethodName, info ? info->m_encVersion : (SIZE_T)-1));
+
+ _ASSERTE(g_patches != NULL);
+
+ CORDB_ADDRESS_TYPE *addr = (CORDB_ADDRESS_TYPE *)
+ CodeRegionInfo::GetCodeRegionInfo(NULL, NULL, startAddr).OffsetToAddress(patch->offset);
+ g_patches->BindPatch(patch, addr);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::BP:Binding patch at 0x%x(off:%x)\n", addr, patch->offset));
+
+ return true;
+}
+
+// bool DebuggerController::ApplyPatch() applies
+// the patch described to the code, and
+// remembers the replaced opcode. Note that the same address
+// cannot be patched twice at the same time.
+// Grabs the opcode & stores in patch, then sets a break
+// instruction for either native or IL.
+// VirtualProtect & some macros. Returns false if anything
+// went bad.
+// DebuggerControllerPatch *patch: The patch, indicates where
+// to set the INT3 instruction
+// Returns: true if the user break instruction was successfully
+// placed into the code-stream, false otherwise
+bool DebuggerController::ApplyPatch(DebuggerControllerPatch *patch)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::ApplyPatch at addr 0x%p\n",
+ patch->address));
+
+ // If we try to apply an already applied patch, we'll overide our saved opcode
+ // with the break opcode and end up getting a break in out patch bypass buffer.
+ _ASSERTE(!patch->IsActivated() );
+ _ASSERTE(patch->IsBound());
+
+ // Note we may be patching at certain "blessed" points in mscorwks.
+ // This is very dangerous b/c we can't be sure patch->Address is blessed or not.
+
+
+ //
+ // Apply the patch.
+ //
+ _ASSERTE(!(g_pConfig->GetGCStressLevel() & (EEConfig::GCSTRESS_INSTR_JIT|EEConfig::GCSTRESS_INSTR_NGEN))
+ && "Debugger does not work with GCSTRESS 4");
+
+ if (patch->IsNativePatch())
+ {
+ if (patch->fSaveOpcode)
+ {
+ // We only used SaveOpcode for when we've moved code, so
+ // the patch should already be there.
+ patch->opcode = patch->opcodeSaved;
+ _ASSERTE( AddressIsBreakpoint(patch->address) );
+ return true;
+ }
+
+#if _DEBUG
+ VerifyExecutableAddress((BYTE*)patch->address);
+#endif
+
+ LPVOID baseAddress = (LPVOID)(patch->address);
+
+ DWORD oldProt;
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+
+ patch->opcode = CORDbgGetInstruction(patch->address);
+
+ CORDbgInsertBreakpoint((CORDB_ADDRESS_TYPE *)patch->address);
+ LOG((LF_CORDB, LL_EVERYTHING, "Breakpoint was inserted at %p for opcode %x\n", patch->address, patch->opcode));
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+// TODO: : determine if this is needed for AMD64
+#if defined(_TARGET_X86_) //REVISIT_TODO what is this?!
+ else
+ {
+ DWORD oldProt;
+
+ //
+ // !!! IL patch logic assumes reference insruction encoding
+ //
+ if (!VirtualProtect((void *) patch->address, 2,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ patch->opcode =
+ (unsigned int) *(unsigned short*)(patch->address+1);
+
+ _ASSERTE(patch->opcode != CEE_BREAK);
+
+ *(unsigned short *) (patch->address+1) = CEE_BREAK;
+
+ if (!VirtualProtect((void *) patch->address, 2, oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+#endif //_TARGET_X86_
+
+ return true;
+}
+
+// bool DebuggerController::UnapplyPatch()
+// UnapplyPatch removes the patch described by the patch.
+// (CopyOpcodeFromAddrToPatch, in reverse.)
+// Looks a lot like CopyOpcodeFromAddrToPatch, except that we use a macro to
+// copy the instruction back to the code-stream & immediately set the
+// opcode field to 0 so ReadMemory,WriteMemory will work right.
+// Note that it's very important to zero out the opcode field, as it
+// is used by the right side to determine if a patch is
+// valid or not.
+// NO LOCKING
+// DebuggerControllerPatch * patch: Patch to remove
+// Returns: true if the patch was unapplied, false otherwise
+bool DebuggerController::UnapplyPatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(patch->address != NULL);
+ _ASSERTE(patch->IsActivated() );
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::UP unapply patch at addr 0x%p\n",
+ patch->address));
+
+ if (patch->IsNativePatch())
+ {
+ if (patch->fSaveOpcode)
+ {
+ // We're doing this for MoveCode, and we don't want to
+ // overwrite something if we don't get moved far enough.
+ patch->opcodeSaved = patch->opcode;
+ InitializePRD(&(patch->opcode));
+ _ASSERTE( !patch->IsActivated() );
+ return true;
+ }
+
+ LPVOID baseAddress = (LPVOID)(patch->address);
+
+ DWORD oldProt;
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ //
+ // We may be trying to remove a patch from memory
+ // which has been unmapped. We can ignore the
+ // error in this case.
+ //
+ InitializePRD(&(patch->opcode));
+ return false;
+ }
+
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patch->address, patch->opcode);
+
+ //VERY IMPORTANT to zero out opcode, else we might mistake
+ //this patch for an active on on ReadMem/WriteMem (see
+ //header file comment)
+ InitializePRD(&(patch->opcode));
+
+ if (!VirtualProtect(baseAddress,
+ CORDbg_BREAK_INSTRUCTION_SIZE,
+ oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+ else
+ {
+ DWORD oldProt;
+
+ if (!VirtualProtect((void *) patch->address, 2,
+ PAGE_EXECUTE_READWRITE, &oldProt))
+ {
+ //
+ // We may be trying to remove a patch from memory
+ // which has been unmapped. We can ignore the
+ // error in this case.
+ //
+ InitializePRD(&(patch->opcode));
+ return false;
+ }
+
+ //
+ // !!! IL patch logic assumes reference encoding
+ //
+// TODO: : determine if this is needed for AMD64
+#if defined(_TARGET_X86_)
+ _ASSERTE(*(unsigned short*)(patch->address+1) == CEE_BREAK);
+
+ *(unsigned short *) (patch->address+1)
+ = (unsigned short) patch->opcode;
+#endif //this makes no sense on anything but X86
+ //VERY IMPORTANT to zero out opcode, else we might mistake
+ //this patch for an active on on ReadMem/WriteMem (see
+ //header file comment
+ InitializePRD(&(patch->opcode));
+
+ if (!VirtualProtect((void *) patch->address, 2, oldProt, &oldProt))
+ {
+ _ASSERTE(!"VirtualProtect of code page failed");
+ return false;
+ }
+ }
+
+ _ASSERTE( !patch->IsActivated() );
+ _ASSERTE( patch->IsBound() );
+ return true;
+}
+
+// void DebuggerController::UnapplyPatchAt()
+// NO LOCKING
+// UnapplyPatchAt removes the patch from a copy of the patched code.
+// Like UnapplyPatch, except that we don't bother checking
+// memory permissions, but instead replace the breakpoint instruction
+// with the opcode at an arbitrary memory address.
+void DebuggerController::UnapplyPatchAt(DebuggerControllerPatch *patch,
+ CORDB_ADDRESS_TYPE *address)
+{
+ _ASSERTE(patch->IsBound() );
+
+ if (patch->IsNativePatch())
+ {
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)address, patch->opcode);
+ //note that we don't have to zero out opcode field
+ //since we're unapplying at something other than
+ //the original spot. We assert this is true:
+ _ASSERTE( patch->address != address );
+ }
+ else
+ {
+ //
+ // !!! IL patch logic assumes reference encoding
+ //
+// TODO: : determine if this is needed for AMD64
+#ifdef _TARGET_X86_
+ _ASSERTE(*(unsigned short*)(address+1) == CEE_BREAK);
+
+ *(unsigned short *) (address+1)
+ = (unsigned short) patch->opcode;
+ _ASSERTE( patch->address != address );
+#endif // this makes no sense on anything but X86
+ }
+}
+
+// bool DebuggerController::IsPatched() Is there a patch at addr?
+// How: if fNative && the instruction at addr is the break
+// instruction for this platform.
+bool DebuggerController::IsPatched(CORDB_ADDRESS_TYPE *address, BOOL native)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ if (native)
+ {
+ return AddressIsBreakpoint(address);
+ }
+ else
+ return false;
+}
+
+// DWORD DebuggerController::GetPatchedOpcode() Gets the opcode
+// at addr, 'looking underneath' any patches if needed.
+// GetPatchedInstruction is a function for the EE to call to "see through"
+// a patch to the opcodes which was patched.
+// How: Lock() grab opcode directly unless there's a patch, in
+// which case grab it out of the patch table.
+// BYTE * address: The address that we want to 'see through'
+// Returns: DWORD value, that is the opcode that should really be there,
+// if we hadn't placed a patch there. If we haven't placed a patch
+// there, then we'll see the actual opcode at that address.
+PRD_TYPE DebuggerController::GetPatchedOpcode(CORDB_ADDRESS_TYPE *address)
+{
+ _ASSERTE(g_patches != NULL);
+
+ PRD_TYPE opcode;
+ ZeroMemory(&opcode, sizeof(opcode));
+
+ ControllerLockHolder lockController;
+
+ //
+ // Look for a patch at the address
+ //
+
+ DebuggerControllerPatch *patch = g_patches->GetPatch((CORDB_ADDRESS_TYPE *)address);
+
+ if (patch != NULL)
+ {
+ // Since we got the patch at this address, is must by definition be bound to that address
+ _ASSERTE( patch->IsBound() );
+ _ASSERTE( patch->address == address );
+ // If we're going to be returning it's opcode, then the patch must also be activated
+ _ASSERTE( patch->IsActivated() );
+ opcode = patch->opcode;
+ }
+ else
+ {
+ //
+ // Patch was not found - it either is not our patch, or it has
+ // just been removed. In either case, just return the current
+ // opcode.
+ //
+
+ if (g_pEEInterface->IsManagedNativeCode((const BYTE *)address))
+ {
+ opcode = CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)address);
+ }
+// <REVISIT_TODO>
+// TODO: : determine if this is needed for AMD64
+// </REVISIT_TODO>
+#ifdef _TARGET_X86_ //what is this?!
+ else
+ {
+ //
+ // !!! IL patch logic assumes reference encoding
+ //
+
+ opcode = *(unsigned short*)(address+1);
+ }
+#endif //_TARGET_X86_
+
+ }
+
+ return opcode;
+}
+
+// Holding the controller lock, this will check if an address is patched,
+// and if so will then set the PRT_TYPE out parameter to the unpatched value.
+BOOL DebuggerController::CheckGetPatchedOpcode(CORDB_ADDRESS_TYPE *address,
+ /*OUT*/ PRD_TYPE *pOpcode)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE; // take Controller lock.
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_patches != NULL);
+
+ BOOL res;
+
+ ControllerLockHolder lockController;
+
+ //
+ // Look for a patch at the address
+ //
+
+ if (IsAddressPatched(address))
+ {
+ *pOpcode = GetPatchedOpcode(address);
+ res = TRUE;
+ }
+ else
+ {
+ InitializePRD(pOpcode);
+ res = FALSE;
+ }
+
+
+ return res;
+}
+
+// void DebuggerController::ActivatePatch() Place a breakpoint
+// so that threads will trip over this patch.
+// If there any patches at the address already, then copy
+// their opcode into this one & return. Otherwise,
+// call ApplyPatch(patch). There is an implicit list of patches at this
+// address by virtue of the fact that we can iterate through all the
+// patches in the patch with the same address.
+// DebuggerControllerPatch *patch: The patch to activate
+/* static */ void DebuggerController::ActivatePatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(g_patches != NULL);
+ _ASSERTE(patch != NULL);
+ _ASSERTE(patch->IsBound() );
+ _ASSERTE(!patch->IsActivated() );
+
+ bool fApply = true;
+
+ //
+ // See if we already have an active patch at this address.
+ //
+ for (DebuggerControllerPatch *p = g_patches->GetPatch(patch->address);
+ p != NULL;
+ p = g_patches->GetNextPatch(p))
+ {
+ if (p != patch)
+ {
+ // If we're going to skip activating 'patch' because 'p' already exists at the same address
+ // then 'p' must be activated. We expect that all bound patches are activated.
+ _ASSERTE( p->IsActivated() );
+ patch->opcode = p->opcode;
+ fApply = false;
+ break;
+ }
+ }
+
+ //
+ // This is the only patch at this address - apply the patch
+ // to the code.
+ //
+ if (fApply)
+ {
+ ApplyPatch(patch);
+ }
+
+ _ASSERTE(patch->IsActivated() );
+}
+
+// void DebuggerController::DeactivatePatch() Make sure that a
+// patch won't be hit.
+// How: If this patch is the last one at this address, then
+// UnapplyPatch. The caller should then invoke RemovePatch to remove the
+// patch from the patch table.
+// DebuggerControllerPatch *patch: Patch to deactivate
+void DebuggerController::DeactivatePatch(DebuggerControllerPatch *patch)
+{
+ _ASSERTE(g_patches != NULL);
+
+ if( !patch->IsBound() ) {
+ // patch is not bound, nothing to do
+ return;
+ }
+
+ // We expect that all bound patches are also activated.
+ // One exception to this is if the shutdown thread killed another thread right after
+ // if deactivated a patch but before it got to remove it.
+ _ASSERTE(patch->IsActivated() );
+
+ bool fUnapply = true;
+
+ //
+ // See if we already have an active patch at this address.
+ //
+ for (DebuggerControllerPatch *p = g_patches->GetPatch(patch->address);
+ p != NULL;
+ p = g_patches->GetNextPatch(p))
+ {
+ if (p != patch)
+ {
+ // There is another patch at this address, so don't remove it
+ // However, clear the patch data so that we no longer consider this particular patch activated
+ fUnapply = false;
+ InitializePRD(&(patch->opcode));
+ break;
+ }
+ }
+
+ if (fUnapply)
+ {
+ UnapplyPatch(patch);
+ }
+
+ _ASSERTE(!patch->IsActivated() );
+
+ //
+ // Patch must now be removed from the table.
+ //
+}
+
+// AddILMasterPatch: record a patch on IL code but do not bind it or activate it. The master b.p.
+// is associated with a module/token pair. It is used later
+// (e.g. in MapAndBindFunctionPatches) to create one or more "slave"
+// breakpoints which are associated with particular MethodDescs/JitInfos.
+//
+// Rationale: For generic code a single IL patch (e.g a breakpoint)
+// may give rise to several patches, one for each JITting of
+// the IL (i.e. generic code may be JITted multiple times for
+// different instantiations).
+//
+// So we keep one patch which describes
+// the breakpoint but which is never actually bound or activated.
+// This is then used to apply new "slave" patches to all copies of
+// JITted code associated with the method.
+//
+// <REVISIT_TODO>In theory we could bind and apply the master patch when the
+// code is known not to be generic (as used to happen to all breakpoint
+// patches in V1). However this seems like a premature
+// optimization.</REVISIT_TODO>
+DebuggerControllerPatch *DebuggerController::AddILMasterPatch(Module *module,
+ mdMethodDef md,
+ SIZE_T offset,
+ SIZE_T encVersion)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_patches != NULL);
+
+ ControllerLockHolder ch;
+
+
+ DebuggerControllerPatch *patch = g_patches->AddPatchForMethodDef(this,
+ module,
+ md,
+ offset,
+ PATCH_KIND_IL_MASTER,
+ LEAF_MOST_FRAME,
+ NULL,
+ encVersion,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::AP: Added IL master patch 0x%x for md 0x%x at offset %d encVersion %d\n", patch, md, offset, encVersion));
+
+ return patch;
+}
+
+// See notes above on AddILMasterPatch
+BOOL DebuggerController::AddBindAndActivateILSlavePatch(DebuggerControllerPatch *master,
+ DebuggerJitInfo *dji)
+{
+ _ASSERTE(g_patches != NULL);
+ _ASSERTE(master->IsILMasterPatch());
+ _ASSERTE(dji != NULL);
+
+ // Do not dereference the "master" pointer in the loop! The loop may add more patches,
+ // causing the patch table to grow and move.
+ BOOL result = FALSE;
+ SIZE_T masterILOffset = master->offset;
+
+ // Loop through all the native offsets mapped to the given IL offset. On x86 the mapping
+ // should be 1:1. On WIN64, because there are funclets, we have have an 1:N mapping.
+ DebuggerJitInfo::ILToNativeOffsetIterator it;
+ for (dji->InitILToNativeOffsetIterator(it, masterILOffset); !it.IsAtEnd(); it.Next())
+ {
+ BOOL fExact;
+ SIZE_T offsetNative = it.Current(&fExact);
+
+ // We special case offset 0, which is when a breakpoint is set
+ // at the beginning of a method that hasn't been jitted yet. In
+ // that case it's possible that offset 0 has been optimized out,
+ // but we still want to set the closest breakpoint to that.
+ if (!fExact && (masterILOffset != 0))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::BP:Failed to bind patch at IL offset 0x%p in %s::%s\n",
+ masterILOffset, dji->m_fd->m_pszDebugClassName, dji->m_fd->m_pszDebugMethodName));
+
+ continue;
+ }
+ else
+ {
+ result = TRUE;
+ }
+
+ INDEBUG(BOOL fOk = )
+ AddBindAndActivatePatchForMethodDesc(dji->m_fd, dji,
+ offsetNative, PATCH_KIND_IL_SLAVE,
+ LEAF_MOST_FRAME, m_pAppDomain);
+ _ASSERTE(fOk);
+ }
+
+ // As long as we have successfully bound at least one patch, we consider the operation successful.
+ return result;
+}
+
+
+
+// This routine places a patch that is conceptually a patch on the IL code.
+// The IL code may be jitted multiple times, e.g. due to generics.
+// This routine ensures that both present and subsequent JITtings of code will
+// also be patched.
+//
+// This routine will return FALSE only if we will _never_ be able to
+// place the patch in any native code corresponding to the given offset.
+// Otherwise it will:
+// (a) record a "master" patch
+// (b) apply as many slave patches as it can to existing copies of code
+// that have debugging information
+BOOL DebuggerController::AddILPatch(AppDomain * pAppDomain, Module *module,
+ mdMethodDef md,
+ SIZE_T encVersion, // what encVersion does this apply to?
+ SIZE_T offset)
+{
+ _ASSERTE(g_patches != NULL);
+ _ASSERTE(md != NULL);
+ _ASSERTE(module != NULL);
+
+ BOOL fOk = FALSE;
+
+ DebuggerMethodInfo *dmi = g_pDebugger->GetOrCreateMethodInfo(module, md); // throws
+ if (dmi == NULL)
+ {
+ return false;
+ }
+
+ EX_TRY
+ {
+ // OK, we either have (a) no code at all or (b) we have both JIT information and code
+ //.
+ // Either way, lay down the MasterPatch.
+ //
+ // MapAndBindFunctionPatches will take care of any instantiations that haven't
+ // finished JITting, by making a copy of the master breakpoint.
+ DebuggerControllerPatch *master = AddILMasterPatch(module, md, offset, encVersion);
+
+ // We have to keep the index here instead of the pointer. The loop below adds more patches,
+ // which may cause the patch table to grow and move.
+ ULONG masterIndex = g_patches->GetItemIndex((HASHENTRY*)master);
+
+ // Iterate through every existing NativeCodeBlob (with the same EnC version).
+ // This includes generics + prejitted code.
+ DebuggerMethodInfo::DJIIterator it;
+ dmi->IterateAllDJIs(pAppDomain, NULL /* module filter */, &it);
+
+ if (it.IsAtEnd())
+ {
+ // It is okay if we don't have any DJIs yet. It just means that the method hasn't been jitted.
+ fOk = TRUE;
+ }
+ else
+ {
+ // On the other hand, if the method has been jitted, then we expect to be able to bind at least
+ // one breakpoint. The exception is when we have multiple EnC versions of the method, in which
+ // case it is ok if we don't bind any breakpoint. One scenario is when a method has been updated
+ // via EnC but it's not yet jitted. We need to allow a debugger to put a breakpoint on the new
+ // version of the method, but the new version won't have a DJI yet.
+ BOOL fVersionMatch = FALSE;
+ while(!it.IsAtEnd())
+ {
+ DebuggerJitInfo *dji = it.Current();
+ _ASSERTE(dji->m_jitComplete);
+ if (dji->m_encVersion == encVersion)
+ {
+ fVersionMatch = TRUE;
+
+ master = (DebuggerControllerPatch *)g_patches->GetEntryPtr(masterIndex);
+
+ // <REVISIT_TODO> If we're missing JIT info for any then
+ // we won't have applied the bp to every instantiation. That should probably be reported
+ // as a new kind of condition to the debugger, i.e. report "bp only partially applied". It would be
+ // a shame to completely fail just because on instantiation is missing debug info: e.g. just because
+ // one component hasn't been prejitted with debugging information.</REVISIT_TODO>
+ fOk = (AddBindAndActivateILSlavePatch(master, dji) || fOk);
+ }
+ it.Next();
+ }
+
+ // This is the exceptional case referred to in the comment above. If we fail to put a breakpoint
+ // because we don't have a matching version of the method, we need to return TRUE.
+ if (fVersionMatch == FALSE)
+ {
+ fOk = TRUE;
+ }
+ }
+ }
+ EX_CATCH
+ {
+ fOk = FALSE;
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+ return fOk;
+}
+
+// Add a patch at native-offset 0 in the latest version of the method.
+// This is used by step-in.
+// Calls to new methods always go to the latest version, so EnC is not an issue here.
+// The method may be not yet jitted. Or it may be prejitted.
+void DebuggerController::AddPatchToStartOfLatestMethod(MethodDesc * fd)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ PRECONDITION(CheckPointer(fd));
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(g_patches != NULL);
+ DebuggerController::AddBindAndActivatePatchForMethodDesc(fd, NULL, 0, PATCH_KIND_NATIVE_MANAGED, LEAF_MOST_FRAME, NULL);
+ return;
+}
+
+
+// Place patch in method at native offset.
+BOOL DebuggerController::AddBindAndActivateNativeManagedPatch(MethodDesc * fd,
+ DebuggerJitInfo *dji,
+ SIZE_T offsetNative,
+ FramePointer fp,
+ AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ PRECONDITION(CheckPointer(fd));
+ PRECONDITION(fd->IsDynamicMethod() || (dji != NULL));
+ }
+ CONTRACTL_END;
+
+ // For non-dynamic methods, we always expect to have a DJI, but just in case, we don't want the assert to AV.
+ _ASSERTE((dji == NULL) || (fd == dji->m_fd));
+ _ASSERTE(g_patches != NULL);
+ return DebuggerController::AddBindAndActivatePatchForMethodDesc(fd, dji, offsetNative, PATCH_KIND_NATIVE_MANAGED, fp, pAppDomain);
+}
+
+
+BOOL DebuggerController::AddBindAndActivatePatchForMethodDesc(MethodDesc *fd,
+ DebuggerJitInfo *dji,
+ SIZE_T offset,
+ DebuggerPatchKind kind,
+ FramePointer fp,
+ AppDomain *pAppDomain)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_ANY; // don't really care what mode we're in.
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+
+ BOOL ok = FALSE;
+ ControllerLockHolder ch;
+
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000,"DC::AP: Add to %s::%s, at offs 0x%x "
+ "fp:0x%x AD:0x%x\n", fd->m_pszDebugClassName,
+ fd->m_pszDebugMethodName,
+ offset, fp.GetSPValue(), pAppDomain));
+
+ DebuggerControllerPatch *patch = g_patches->AddPatchForMethodDef(
+ this,
+ g_pEEInterface->MethodDescGetModule(fd),
+ fd->GetMemberDef(),
+ offset,
+ kind,
+ fp,
+ pAppDomain,
+ NULL,
+ dji);
+
+ if (DebuggerController::BindPatch(patch, fd, NULL))
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO1000,"BindPatch went fine, doing ActivatePatch\n"));
+ DebuggerController::ActivatePatch(patch);
+ ok = TRUE;
+ }
+
+ return ok;
+}
+
+
+// This version is particularly useful b/c it doesn't assume that the
+// patch is inside a managed method.
+DebuggerControllerPatch *DebuggerController::AddAndActivateNativePatchForAddress(CORDB_ADDRESS_TYPE *address,
+ FramePointer fp,
+ bool managed,
+ TraceType traceType)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+
+ PRECONDITION(g_patches != NULL);
+ }
+ CONTRACTL_END;
+
+
+ ControllerLockHolder ch;
+
+ DebuggerControllerPatch *patch
+ = g_patches->AddPatchForAddress(this,
+ NULL,
+ 0,
+ (managed? PATCH_KIND_NATIVE_MANAGED : PATCH_KIND_NATIVE_UNMANAGED),
+ address,
+ fp,
+ NULL,
+ NULL,
+ DebuggerPatchTable::DCP_PID_INVALID,
+ traceType);
+
+ ActivatePatch(patch);
+
+ return patch;
+}
+
+void DebuggerController::RemovePatchesFromModule(Module *pModule, AppDomain *pAppDomain )
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO100000, "DPT::CPFM mod:0x%p (%S)\n",
+ pModule, pModule->GetDebugName()));
+
+ // First find all patches of interest
+ DebuggerController::ControllerLockHolder ch;
+ HASHFIND f;
+ for (DebuggerControllerPatch *patch = g_patches->GetFirstPatch(&f);
+ patch != NULL;
+ patch = g_patches->GetNextPatch(&f))
+ {
+ // Skip patches not in the specified domain
+ if ((pAppDomain != NULL) && (patch->pAppDomain != pAppDomain))
+ continue;
+
+ BOOL fRemovePatch = FALSE;
+
+ // Remove both native and IL patches the belong to this module
+ if (patch->HasDJI())
+ {
+ DebuggerJitInfo * dji = patch->GetDJI();
+
+ _ASSERTE(patch->key.module == dji->m_fd->GetModule());
+
+ // It is not necessary to check for m_fd->GetModule() here. It will
+ // be covered by other module unload notifications issued for the appdomain.
+ if ( dji->m_pLoaderModule == pModule )
+ fRemovePatch = TRUE;
+ }
+ else
+ if (patch->key.module == pModule)
+ {
+ fRemovePatch = TRUE;
+ }
+
+ if (fRemovePatch)
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "Removing patch 0x%p\n",
+ patch));
+ // we shouldn't be both hitting this patch AND
+ // unloading the module it belongs to.
+ _ASSERTE(!patch->IsTriggering());
+ Release( patch );
+ }
+ }
+}
+
+#ifdef _DEBUG
+bool DebuggerController::ModuleHasPatches( Module* pModule )
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ if( g_patches == NULL )
+ {
+ // Patch table hasn't been initialized
+ return false;
+ }
+
+ // First find all patches of interest
+ HASHFIND f;
+ for (DebuggerControllerPatch *patch = g_patches->GetFirstPatch(&f);
+ patch != NULL;
+ patch = g_patches->GetNextPatch(&f))
+ {
+ //
+ // This mirrors logic in code:DebuggerController::RemovePatchesFromModule
+ //
+
+ if (patch->HasDJI())
+ {
+ DebuggerJitInfo * dji = patch->GetDJI();
+
+ _ASSERTE(patch->key.module == dji->m_fd->GetModule());
+
+ // It may be sufficient to just check m_pLoaderModule here. Since this is used for debug-only
+ // check, we will check for m_fd->GetModule() as well to catch more potential problems.
+ if ( (dji->m_pLoaderModule == pModule) || (dji->m_fd->GetModule() == pModule) )
+ {
+ return true;
+ }
+ }
+
+ if (patch->key.module == pModule)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // _DEBUG
+
+//
+// Returns true if the given address is in an internal helper
+// function, false if its not.
+//
+// This is a temporary workaround function to avoid having us stop in
+// unmanaged code belonging to the Runtime during a StepIn operation.
+//
+static bool _AddrIsJITHelper(PCODE addr)
+{
+#if !defined(_WIN64) && !defined(FEATURE_PAL)
+ // Is the address in the runtime dll (clr.dll or coreclr.dll) at all? (All helpers are in
+ // that dll)
+ if (g_runtimeLoadedBaseAddress <= addr &&
+ addr < g_runtimeLoadedBaseAddress + g_runtimeVirtualSize)
+ {
+ for (int i = 0; i < CORINFO_HELP_COUNT; i++)
+ {
+ if (hlpFuncTable[i].pfnHelper == (void*)addr)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "_ANIM: address of helper function found: 0x%08x\n",
+ addr));
+ return true;
+ }
+ }
+
+ for (unsigned d = 0; d < DYNAMIC_CORINFO_HELP_COUNT; d++)
+ {
+ if (hlpDynamicFuncTable[d].pfnHelper == (void*)addr)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "_ANIM: address of helper function found: 0x%08x\n",
+ addr));
+ return true;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "_ANIM: address within runtime dll, but not a helper function "
+ "0x%08x\n", addr));
+ }
+#else // !defined(_WIN64) && !defined(FEATURE_PAL)
+ // TODO: Figure out what we want to do here
+#endif // !defined(_WIN64) && !defined(FEATURE_PAL)
+
+ return false;
+}
+
+// bool DebuggerController::PatchTrace() What: Invoke
+// AddPatch depending on the type of the given TraceDestination.
+// How: Invokes AddPatch based on the trace type: TRACE_OTHER will
+// return false, the others will obtain args for a call to an AddPatch
+// method & return true.
+//
+// Return true if we set a patch, else false
+bool DebuggerController::PatchTrace(TraceDestination *trace,
+ FramePointer fp,
+ bool fStopInUnmanaged)
+{
+ CONTRACTL
+ {
+ THROWS; // Because AddPatch may throw on oom. We may want to convert this to nothrow and return false.
+ MODE_ANY;
+ DISABLED(GC_TRIGGERS); // @todo - what should this be?
+
+ PRECONDITION(ThisMaybeHelperThread());
+ }
+ CONTRACTL_END;
+ DebuggerControllerPatch *dcp = NULL;
+
+ switch (trace->GetTraceType())
+ {
+ case TRACE_ENTRY_STUB: // fall through
+ case TRACE_UNMANAGED:
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::PT: Setting unmanaged trace patch at 0x%p(%p)\n",
+ trace->GetAddress(), fp.GetSPValue()));
+
+ if (fStopInUnmanaged && !_AddrIsJITHelper(trace->GetAddress()))
+ {
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)trace->GetAddress(),
+ fp,
+ FALSE,
+ trace->GetTraceType());
+ return true;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::PT: decided to NOT "
+ "place a patch in unmanaged code\n"));
+ return false;
+ }
+
+ case TRACE_MANAGED:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting managed trace patch at 0x%p(%p)\n", trace->GetAddress(), fp.GetSPValue()));
+
+ MethodDesc *fd;
+ fd = g_pEEInterface->GetNativeCodeMethodDesc(trace->GetAddress());
+ _ASSERTE(fd);
+
+ DebuggerJitInfo *dji;
+ dji = g_pDebugger->GetJitInfoFromAddr(trace->GetAddress());
+ //_ASSERTE(dji); //we'd like to assert this, but attach won't work
+
+ AddBindAndActivateNativeManagedPatch(fd,
+ dji,
+ CodeRegionInfo::GetCodeRegionInfo(dji, fd).AddressToOffset((const BYTE *)trace->GetAddress()),
+ fp,
+ NULL);
+ return true;
+
+ case TRACE_UNJITTED_METHOD:
+ // trace->address is actually a MethodDesc* of the method that we'll
+ // soon JIT, so put a relative bp at offset zero in.
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting unjitted method patch in MethodDesc 0x%p %s\n", trace->GetMethodDesc(), trace->GetMethodDesc() ? trace->GetMethodDesc()->m_pszDebugMethodName : ""));
+
+ // Note: we have to make sure to bind here. If this function is prejitted, this may be our only chance to get a
+ // DebuggerJITInfo and thereby cause a JITComplete callback.
+ AddPatchToStartOfLatestMethod(trace->GetMethodDesc());
+ return true;
+
+ case TRACE_FRAME_PUSH:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting frame patch at 0x%p(%p)\n", trace->GetAddress(), fp.GetSPValue()));
+
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)trace->GetAddress(),
+ fp,
+ TRUE,
+ TRACE_FRAME_PUSH);
+ return true;
+
+ case TRACE_MGR_PUSH:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Setting frame patch (TRACE_MGR_PUSH) at 0x%p(%p)\n",
+ trace->GetAddress(), fp.GetSPValue()));
+
+ dcp = AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)trace->GetAddress(),
+ LEAF_MOST_FRAME, // But Mgr_push can't have fp affinity!
+ TRUE,
+ DPT_DEFAULT_TRACE_TYPE); // TRACE_OTHER
+ // Now copy over the trace field since TriggerPatch will expect this
+ // to be set for this case.
+ if (dcp != NULL)
+ {
+ dcp->trace = *trace;
+ }
+
+ return true;
+
+ case TRACE_OTHER:
+ LOG((LF_CORDB, LL_INFO10000,
+ "Can't set a trace patch for TRACE_OTHER...\n"));
+ return false;
+
+ default:
+ _ASSERTE(0);
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Checks if the patch matches the context + thread.
+// Multiple patches can exist at a single address, so given a patch at the
+// Context's current address, this does additional patch-affinity checks like
+// thread, AppDomain, and frame-pointer.
+// thread - thread executing the given context that hit the patch
+// context - context of the thread that hit the patch
+// patch - candidate patch that we're looking for a match.
+// Returns:
+// True if the patch matches.
+// False
+//-----------------------------------------------------------------------------
+bool DebuggerController::MatchPatch(Thread *thread,
+ CONTEXT *context,
+ DebuggerControllerPatch *patch)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::MP: EIP:0x%p\n", GetIP(context)));
+
+ // Caller should have already matched our addresses.
+ if (patch->address != dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(context)))
+ {
+ return false;
+ }
+
+ // <BUGNUM>RAID 67173 -</BUGNUM> we'll make sure that intermediate patches have NULL
+ // pAppDomain so that we don't end up running to completion when
+ // the appdomain switches halfway through a step.
+ if (patch->pAppDomain != NULL)
+ {
+ AppDomain *pAppDomainCur = thread->GetDomain();
+
+ if (pAppDomainCur != patch->pAppDomain)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::MP: patches didn't match b/c of "
+ "appdomains!\n"));
+ return false;
+ }
+ }
+
+ if (patch->controller->m_thread != NULL && patch->controller->m_thread != thread)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::MP: patches didn't match b/c threads\n"));
+ return false;
+ }
+
+ if (patch->fp != LEAF_MOST_FRAME)
+ {
+ // If we specified a Frame-pointer, than it should have been safe to take a stack trace.
+
+ ControllerStackInfo info;
+ StackTraceTicket ticket(patch);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, context);
+
+ // !!! This check should really be != , but there is some ambiguity about which frame is the parent frame
+ // in the destination returned from Frame::TraceFrame, so this allows some slop there.
+
+ if (info.HasReturnFrame() && IsCloserToLeaf(info.m_returnFrame.fp, patch->fp))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Patch hit but frame not matched at %p (current=%p, patch=%p)\n",
+ patch->address, info.m_returnFrame.fp.GetSPValue(), patch->fp.GetSPValue()));
+
+ return false;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "DC::MP: Returning true"));
+
+ return true;
+}
+
+DebuggerPatchSkip *DebuggerController::ActivatePatchSkip(Thread *thread,
+ const BYTE *PC,
+ BOOL fForEnC)
+{
+#ifdef _DEBUG
+ BOOL shouldBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_ActivatePatchSkip);
+ if (shouldBreak > 0) {
+ _ASSERTE(!"ActivatePatchSkip");
+ }
+#endif
+
+ LOG((LF_CORDB,LL_INFO10000, "DC::APS thread=0x%p pc=0x%p fForEnc=%d\n",
+ thread, PC, fForEnC));
+ _ASSERTE(g_patches != NULL);
+
+ // Previously, we assumed that if we got to this point & the patch
+ // was still there that we'd have to skip the patch. SetIP changes
+ // this like so:
+ // A breakpoint is set, and hit (but not removed), and all the
+ // EE threads come to a skreeching halt. The Debugger RC thread
+ // continues along, and is told to SetIP of the thread that hit
+ // the BP to whatever. Eventually the RC thread is told to continue,
+ // and at that point the EE thread is released, finishes DispatchPatchOrSingleStep,
+ // and shows up here.
+ // At that point, if the thread's current PC is
+ // different from the patch PC, then SetIP must have moved it elsewhere
+ // & we shouldn't do this patch skip (which will put us back to where
+ // we were, which is clearly wrong). If the PC _is_ the same, then
+ // the thread hasn't been moved, the patch is still in the code stream,
+ // and we want to do the patch skip thing in order to execute this
+ // instruction w/o removing it from the code stream.
+
+ DebuggerControllerPatch *patch = g_patches->GetPatch((CORDB_ADDRESS_TYPE *)PC);
+ DebuggerPatchSkip *skip = NULL;
+
+ if (patch != NULL && patch->IsNativePatch())
+ {
+ //
+ // We adjust the thread's PC to someplace where we write
+ // the next instruction, then
+ // we single step over that, then we set the PC back here so
+ // we don't let other threads race past here while we're stepping
+ // this one.
+ //
+ // !!! check result
+ LOG((LF_CORDB,LL_INFO10000, "DC::APS: About to skip from PC=0x%p\n", PC));
+ skip = new (interopsafe) DebuggerPatchSkip(thread, patch, thread->GetDomain());
+ TRACE_ALLOC(skip);
+ }
+
+ return skip;
+}
+
+DPOSS_ACTION DebuggerController::ScanForTriggers(CORDB_ADDRESS_TYPE *address,
+ Thread *thread,
+ CONTEXT *context,
+ DebuggerControllerQueue *pDcq,
+ SCAN_TRIGGER stWhat,
+ TP_RESULT *pTpr)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ // @todo - should this throw or not?
+ NOTHROW;
+
+ // call Triggers which may invoke GC stuff... See comment in DispatchNativeException for why it's disabled.
+ DISABLED(GC_TRIGGERS);
+ PRECONDITION(!ThisIsHelperThreadWorker());
+
+ PRECONDITION(CheckPointer(address));
+ PRECONDITION(CheckPointer(thread));
+ PRECONDITION(CheckPointer(context));
+ PRECONDITION(CheckPointer(pDcq));
+ PRECONDITION(CheckPointer(pTpr));
+
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(HasLock());
+
+ CONTRACT_VIOLATION(ThrowsViolation);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: starting scan for addr:0x%p"
+ " thread:0x%x\n", address, thread));
+
+ _ASSERTE( pTpr != NULL );
+ DebuggerControllerPatch *patch = NULL;
+
+ if (g_patches != NULL)
+ patch = g_patches->GetPatch(address);
+
+ ULONG iEvent = UINT32_MAX;
+ ULONG iEventNext = UINT32_MAX;
+ BOOL fDone = FALSE;
+
+ // This is a debugger exception if there's a patch here, or
+ // we're here for something like a single step.
+ DPOSS_ACTION used = DPOSS_INVALID;
+ if ((patch != NULL) || !IsPatched(address, TRUE))
+ {
+ // we are sure that we care for this exception but not sure
+ // if we will send event to the RS
+ used = DPOSS_USED_WITH_NO_EVENT;
+ }
+ else
+ {
+ // initialize it to don't care for now
+ used = DPOSS_DONT_CARE;
+ }
+
+ TP_RESULT tpr = TPR_IGNORE;
+
+ while (stWhat & ST_PATCH &&
+ patch != NULL &&
+ !fDone)
+ {
+ _ASSERTE(IsInUsedAction(used) == true);
+
+ DebuggerControllerPatch *patchNext
+ = g_patches->GetNextPatch(patch);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: patch 0x%x, patchNext 0x%x\n", patch, patchNext));
+
+ // Annoyingly, TriggerPatch may add patches, which may cause
+ // the patch table to move, which may, in turn, invalidate
+ // the patch (and patchNext) pointers. Store indeces, instead.
+ iEvent = g_patches->GetItemIndex( (HASHENTRY *)patch );
+
+ if (patchNext != NULL)
+ {
+ iEventNext = g_patches->GetItemIndex((HASHENTRY *)patchNext);
+ }
+
+ if (MatchPatch(thread, context, patch))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: patch matched\n"));
+ AddRef(patch);
+
+ // We are hitting a patch at a virtual trace call target, so let's trigger trace call here.
+ if (patch->trace.GetTraceType() == TRACE_ENTRY_STUB)
+ {
+ patch->controller->TriggerTraceCall(thread, dac_cast<PTR_CBYTE>(::GetIP(context)));
+ tpr = TPR_IGNORE;
+ }
+ else
+ {
+ // Mark if we're at an unsafe place.
+ AtSafePlaceHolder unsafePlaceHolder(thread);
+
+ tpr = patch->controller->TriggerPatch(patch,
+ thread,
+ TY_NORMAL);
+ }
+
+ // Any patch may potentially send an event.
+ // (Whereas some single-steps are "internal-only" and can
+ // never send an event- such as a single step over an exception that
+ // lands us in la-la land.)
+ used = DPOSS_USED_WITH_EVENT;
+
+ if (tpr == TPR_TRIGGER ||
+ tpr == TPR_TRIGGER_ONLY_THIS ||
+ tpr == TPR_TRIGGER_ONLY_THIS_AND_LOOP)
+ {
+ // Make sure we've still got a valid pointer.
+ patch = (DebuggerControllerPatch *)
+ DebuggerController::g_patches->GetEntryPtr( iEvent );
+
+ pDcq->dcqEnqueue(patch->controller, TRUE); // <REVISIT_TODO>@todo Return value</REVISIT_TODO>
+ }
+
+ // Make sure we've got a valid pointer in case TriggerPatch
+ // returned false but still caused the table to move.
+ patch = (DebuggerControllerPatch *)
+ g_patches->GetEntryPtr( iEvent );
+
+ // A patch can be deleted as a result of it's being triggered.
+ // The actual deletion of the patch is delayed until after the
+ // the end of the trigger.
+ // Moreover, "patchNext" could have been deleted as a result of DisableAll()
+ // being called in TriggerPatch(). Thus, we should update our patchNext
+ // pointer now. We were just lucky before, because the now-deprecated
+ // "deleted" flag didn't get set when we iterate the patches in DisableAll().
+ patchNext = g_patches->GetNextPatch(patch);
+ if (patchNext != NULL)
+ iEventNext = g_patches->GetItemIndex((HASHENTRY *)patchNext);
+
+ // Note that Release() actually removes the patch if its ref count
+ // reaches 0 after the release.
+ Release(patch);
+ }
+
+ if (tpr == TPR_IGNORE_AND_STOP ||
+ tpr == TPR_TRIGGER_ONLY_THIS ||
+ tpr == TPR_TRIGGER_ONLY_THIS_AND_LOOP)
+ {
+#ifdef _DEBUG
+ if (tpr == TPR_TRIGGER_ONLY_THIS ||
+ tpr == TPR_TRIGGER_ONLY_THIS_AND_LOOP)
+ _ASSERTE(pDcq->dcqGetCount() == 1);
+#endif //_DEBUG
+
+ fDone = TRUE;
+ }
+ else if (patchNext != NULL)
+ {
+ patch = (DebuggerControllerPatch *)
+ g_patches->GetEntryPtr(iEventNext);
+ }
+ else
+ {
+ patch = NULL;
+ }
+ }
+
+ if (stWhat & ST_SINGLE_STEP &&
+ tpr != TPR_TRIGGER_ONLY_THIS)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT: Trigger controllers with single step\n"));
+
+ //
+ // Now, go ahead & trigger all controllers with
+ // single step events
+ //
+
+ DebuggerController *p;
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_thread == thread && p->m_singleStep)
+ {
+ if (used == DPOSS_DONT_CARE)
+ {
+ // Debugger does care for this exception.
+ used = DPOSS_USED_WITH_NO_EVENT;
+ }
+
+ if (p->TriggerSingleStep(thread, (const BYTE *)address))
+ {
+ // by now, we should already know that we care for this exception.
+ _ASSERTE(IsInUsedAction(used) == true);
+
+ // now we are sure that we will send event to the RS
+ used = DPOSS_USED_WITH_EVENT;
+ pDcq->dcqEnqueue(p, FALSE); // <REVISIT_TODO>@todo Return value</REVISIT_TODO>
+
+ }
+ }
+
+ p = pNext;
+ }
+
+ UnapplyTraceFlag(thread);
+
+ //
+ // See if we have any steppers still active for this thread, if so
+ // re-apply the trace flag.
+ //
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ if (p->m_thread == thread && p->m_singleStep)
+ {
+ ApplyTraceFlag(thread);
+ break;
+ }
+
+ p = p->m_next;
+ }
+ }
+
+ // Significant speed increase from single dereference, I bet :)
+ (*pTpr) = tpr;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::SFT returning 0x%x as used\n",used));
+ return used;
+}
+
+#ifdef EnC_SUPPORTED
+DebuggerControllerPatch *DebuggerController::IsXXXPatched(const BYTE *PC,
+ DEBUGGER_CONTROLLER_TYPE dct)
+{
+ _ASSERTE(g_patches != NULL);
+
+ DebuggerControllerPatch *patch = g_patches->GetPatch((CORDB_ADDRESS_TYPE *)PC);
+
+ while(patch != NULL &&
+ (int)patch->controller->GetDCType() <= (int)dct)
+ {
+ if (patch->IsNativePatch() &&
+ patch->controller->GetDCType()==dct)
+ {
+ return patch;
+ }
+ patch = g_patches->GetNextPatch(patch);
+ }
+
+ return NULL;
+}
+
+// This function will check for an EnC patch at the given address and return
+// it if one is there, otherwise it will return NULL.
+DebuggerControllerPatch *DebuggerController::GetEnCPatch(const BYTE *address)
+{
+ _ASSERTE(address);
+
+ if( g_pEEInterface->IsManagedNativeCode(address) )
+ {
+ DebuggerJitInfo *dji = g_pDebugger->GetJitInfoFromAddr((TADDR) address);
+ if (dji == NULL)
+ return NULL;
+
+ // we can have two types of patches - one in code where the IL has been updated to trigger
+ // the switch and the other in the code we've switched to in order to trigger FunctionRemapComplete
+ // callback. If version == default then can't be the latter, but otherwise if haven't handled the
+ // remap for this function yet is certainly the latter.
+ if (! dji->m_encBreakpointsApplied &&
+ (dji->m_encVersion == CorDB_DEFAULT_ENC_FUNCTION_VERSION))
+ {
+ return NULL;
+ }
+ }
+ return IsXXXPatched(address, DEBUGGER_CONTROLLER_ENC);
+}
+#endif //EnC_SUPPORTED
+
+// DebuggerController::DispatchPatchOrSingleStep - Ask any patches that are active at a given
+// address if they want to do anything about the exception that's occurred there. How: For the given
+// address, go through the list of patches & see if any of them are interested (by invoking their
+// DebuggerController's TriggerPatch). Put any DCs that are interested into a queue and then calls
+// SendEvent on each.
+// Note that control will not return from this function in the case of EnC remap
+DPOSS_ACTION DebuggerController::DispatchPatchOrSingleStep(Thread *thread, CONTEXT *context, CORDB_ADDRESS_TYPE *address, SCAN_TRIGGER which)
+{
+ CONTRACT(DPOSS_ACTION)
+ {
+ // @todo - should this throw or not?
+ NOTHROW;
+ DISABLED(GC_TRIGGERS); // Only GC triggers if we send an event. See Comment in DispatchNativeException
+ PRECONDITION(!ThisIsHelperThreadWorker());
+
+ PRECONDITION(CheckPointer(thread));
+ PRECONDITION(CheckPointer(context));
+ PRECONDITION(CheckPointer(address));
+ PRECONDITION(!HasLock());
+
+ POSTCONDITION(!HasLock()); // make sure we're not leaking the controller lock
+ }
+ CONTRACT_END;
+
+ CONTRACT_VIOLATION(ThrowsViolation);
+
+ LOG((LF_CORDB|LF_ENC,LL_INFO1000,"DC:DPOSS at 0x%x trigger:0x%x\n", address, which));
+
+ // We should only have an exception if some managed thread was running.
+ // Thus we should never be here when we're stopped.
+ // @todo - this assert fires! Is that an issue, or is it invalid?
+ //_ASSERTE(!g_pDebugger->IsStopped());
+ DPOSS_ACTION used = DPOSS_DONT_CARE;
+
+ DebuggerControllerQueue dcq;
+ if (!g_patchTableValid)
+ {
+
+ LOG((LF_CORDB|LF_ENC, LL_INFO1000, "DC::DPOSS returning, no patch table.\n"));
+ RETURN (used);
+ }
+ _ASSERTE(g_patches != NULL);
+
+ CrstHolderWithState lockController(&g_criticalSection);
+
+#ifdef EnC_SUPPORTED
+ DebuggerControllerPatch *dcpEnCOriginal = NULL;
+
+ // If this sequence point has an EnC patch, we want to process it ahead of any others. If the
+ // debugger wants to remap the function at this point, then we'll call ResumeInUpdatedFunction and
+ // not return, otherwise we will just continue with regular patch-handling logic
+ dcpEnCOriginal = GetEnCPatch(dac_cast<PTR_CBYTE>(GetIP(context)));
+
+ if (dcpEnCOriginal)
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS EnC short-circuit\n"));
+ TP_RESULT tpres =
+ dcpEnCOriginal->controller->TriggerPatch(dcpEnCOriginal,
+ thread,
+ TY_SHORT_CIRCUIT);
+
+ // We will only come back here on a RemapOppporunity that wasn't taken, or on a RemapComplete.
+ // If we processed a RemapComplete (which returns TPR_IGNORE_AND_STOP), then don't want to handle
+ // additional breakpoints on the current line because we've already effectively executed to that point
+ // and would have hit them already. If they are new, we also don't want to hit them because eg. if are
+ // sitting on line 10 and add a breakpoint at line 10 and step,
+ // don't expect to stop at line 10, expect to go to line 11.
+ //
+ // Special case is if an EnC remap breakpoint exists in the function. This could only happen if the function was
+ // updated between the RemapOpportunity and the RemapComplete. In that case we want to not skip the patches
+ // and fall through to handle the remap breakpoint.
+
+ if (tpres == TPR_IGNORE_AND_STOP)
+ {
+ // It was a RemapComplete, so fall through. Set dcpEnCOriginal to NULL to indicate that any
+ // EnC patch still there should be treated as a new patch. Any RemapComplete patch will have been
+ // already removed by patch processing.
+ dcpEnCOriginal = NULL;
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS done EnC short-circuit, exiting\n"));
+ used = DPOSS_USED_WITH_EVENT; // indicate that we handled a patch
+ goto Exit;
+ }
+
+ _ASSERTE(tpres==TPR_IGNORE);
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS done EnC short-circuit, ignoring\n"));
+ // if we got here, then the EnC remap opportunity was not taken, so just continue on.
+ }
+#endif // EnC_SUPPORTED
+
+ TP_RESULT tpr;
+
+ used = ScanForTriggers((CORDB_ADDRESS_TYPE *)address, thread, context, &dcq, which, &tpr);
+
+ LOG((LF_CORDB|LF_ENC, LL_EVERYTHING, "DC::DPOSS ScanForTriggers called and returned.\n"));
+
+
+ // If we setip, then that will change the address in the context.
+ // Remeber the old address so that we can compare it to the context's ip and see if it changed.
+ // If it did change, then don't dispatch our current event.
+ TADDR originalAddress = (TADDR) address;
+
+#ifdef _DEBUG
+ // If we do a SetIP after this point, the value of address will be garbage. Set it to a distictive pattern now, so
+ // we don't accidentally use what will (98% of the time) appear to be a valid value.
+ address = (CORDB_ADDRESS_TYPE *)(UINT_PTR)0xAABBCCFF;
+#endif //_DEBUG
+
+ if (dcq.dcqGetCount()> 0)
+ {
+ lockController.Release();
+
+ // Mark if we're at an unsafe place.
+ bool atSafePlace = g_pDebugger->IsThreadAtSafePlace(thread);
+ if (!atSafePlace)
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+
+ DWORD dwEvent = 0xFFFFFFFF;
+ DWORD dwNumberEvents = 0;
+ BOOL reabort = FALSE;
+
+ SENDIPCEVENT_BEGIN(g_pDebugger, thread);
+
+ // Now that we've resumed from blocking, check if somebody did a SetIp on us.
+ bool fIpChanged = (originalAddress != GetIP(context));
+
+ // Send the events outside of the controller lock
+ bool anyEventsSent = false;
+
+ dwNumberEvents = dcq.dcqGetCount();
+ dwEvent = 0;
+
+ while (dwEvent < dwNumberEvents)
+ {
+ DebuggerController *event = dcq.dcqGetElement(dwEvent);
+
+ if (!event->m_deleted)
+ {
+#ifdef DEBUGGING_SUPPORTED
+ if (thread->GetDomain()->IsDebuggerAttached())
+ {
+ if (event->SendEvent(thread, fIpChanged))
+ {
+ anyEventsSent = true;
+ }
+ }
+#endif //DEBUGGING_SUPPORTED
+ }
+
+ dwEvent++;
+ }
+
+ // Trap all threads if necessary, but only if we actually sent a event up (i.e., all the queued events weren't
+ // deleted before we got a chance to get the EventSending lock.)
+ if (anyEventsSent)
+ {
+ LOG((LF_CORDB|LF_ENC, LL_EVERYTHING, "DC::DPOSS We sent an event\n"));
+ g_pDebugger->SyncAllThreads(SENDIPCEVENT_PtrDbgLockHolder);
+ LOG((LF_CORDB,LL_INFO1000, "SAT called!\n"));
+ }
+
+
+ // If we need to to a re-abort (see below), then save the current IP in the thread's context before we block and
+ // possibly let another func eval get setup.
+ reabort = thread->m_StateNC & Thread::TSNC_DebuggerReAbort;
+ SENDIPCEVENT_END;
+
+ if (!atSafePlace)
+ g_pDebugger->DecThreadsAtUnsafePlaces();
+
+ lockController.Acquire();
+
+ // Dequeue the events while we have the controller lock.
+ dwEvent = 0;
+ while (dwEvent < dwNumberEvents)
+ {
+ dcq.dcqDequeue();
+ dwEvent++;
+ }
+ // If a func eval completed with a ThreadAbortException, go ahead and setup the thread to re-abort itself now
+ // that we're continuing the thread. Note: we make sure that the thread's IP hasn't changed between now and when
+ // we blocked above. While blocked above, the debugger has a chance to setup another func eval on this
+ // thread. If that happens, we don't want to setup the reabort just yet.
+ if (reabort)
+ {
+ if ((UINT_PTR)GetEEFuncEntryPoint(::FuncEvalHijack) != (UINT_PTR)GetIP(context))
+ {
+ HRESULT hr;
+ hr = g_pDebugger->FuncEvalSetupReAbort(thread, Thread::TAR_Thread);
+ _ASSERTE(SUCCEEDED(hr));
+ }
+ }
+ }
+
+#if defined EnC_SUPPORTED
+Exit:
+#endif
+
+ // Note: if the thread filter context is NULL, then SetIP would have failed & thus we should do the
+ // patch skip thing.
+ // @todo - do we need to get the context again here?
+ CONTEXT *pCtx = GetManagedLiveCtx(thread);
+
+#ifdef EnC_SUPPORTED
+ DebuggerControllerPatch *dcpEnCCurrent = GetEnCPatch(dac_cast<PTR_CBYTE>((GetIP(context))));
+
+ // we have a new patch if the original was null and the current is non-null. Otherwise we have an old
+ // patch. We want to skip old patches, but handle new patches.
+ if (dcpEnCOriginal == NULL && dcpEnCCurrent != NULL)
+ {
+ LOG((LF_CORDB|LF_ENC,LL_INFO10000, "DC::DPOSS EnC post-processing\n"));
+ dcpEnCCurrent->controller->TriggerPatch( dcpEnCCurrent,
+ thread,
+ TY_SHORT_CIRCUIT);
+ used = DPOSS_USED_WITH_EVENT; // indicate that we handled a patch
+ }
+#endif
+
+ ActivatePatchSkip(thread, dac_cast<PTR_CBYTE>(GetIP(pCtx)), FALSE);
+
+ lockController.Release();
+
+
+ // We pulse the GC mode here too cooperate w/ a thread trying to suspend the runtime. If we didn't pulse
+ // the GC, the odds of catching this thread in interuptable code may be very small (since this filter
+ // could be very large compared to the managed code this thread is running).
+ // Only do this if the exception was actually for the debugger. (We don't want to toggle the GC mode on every
+ // random exception). We can't do this while holding any debugger locks.
+ if (used == DPOSS_USED_WITH_EVENT)
+ {
+ bool atSafePlace = g_pDebugger->IsThreadAtSafePlace(thread);
+ if (!atSafePlace)
+ {
+ g_pDebugger->IncThreadsAtUnsafePlaces();
+ }
+
+ // Always pulse the GC mode. This will allow an async break to complete even if we have a patch
+ // at an unsafe place.
+ // If we are at an unsafe place, then we can't do a GC.
+ thread->PulseGCMode();
+
+ if (!atSafePlace)
+ {
+ g_pDebugger->DecThreadsAtUnsafePlaces();
+ }
+
+ }
+
+ RETURN used;
+}
+
+bool DebuggerController::IsSingleStepEnabled()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_singleStep;
+}
+
+void DebuggerController::EnableSingleStep()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifdef _DEBUG
+ // Some controllers don't need to set the SS to do their job, and if they are setting it, it's likely an issue.
+ // So we assert here to catch them red-handed. This assert can always be updated to accomodate changes
+ // in a controller's behavior.
+
+ switch(GetDCType())
+ {
+ case DEBUGGER_CONTROLLER_THREAD_STARTER:
+ case DEBUGGER_CONTROLLER_BREAKPOINT:
+ case DEBUGGER_CONTROLLER_USER_BREAKPOINT:
+ case DEBUGGER_CONTROLLER_FUNC_EVAL_COMPLETE:
+ CONSISTENCY_CHECK_MSGF(false, ("Controller pThis=%p shouldn't be setting ss flag.", this));
+ break;
+ default: // MingW compilers require all enum cases to be handled in switch statement.
+ break;
+ }
+#endif
+
+ EnableSingleStep(m_thread);
+ m_singleStep = true;
+}
+
+#ifdef EnC_SUPPORTED
+// Note that this doesn't tell us if Single Stepping is currently enabled
+// at the hardware level (ie, for x86, if (context->EFlags & 0x100), but
+// rather, if we WANT single stepping enabled (pThread->m_State &Thread::TS_DebuggerIsStepping)
+// This gets called from exactly one place - ActivatePatchSkipForEnC
+BOOL DebuggerController::IsSingleStepEnabled(Thread *pThread)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // This should be an atomic operation, do we
+ // don't need to lock it.
+ if(pThread->m_StateNC & Thread::TSNC_DebuggerIsStepping)
+ {
+ _ASSERTE(pThread->m_StateNC & Thread::TSNC_DebuggerIsStepping);
+
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+#endif //EnC_SUPPORTED
+
+void DebuggerController::EnableSingleStep(Thread *pThread)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::EnableSingleStep\n"));
+
+ _ASSERTE(pThread != NULL);
+
+ ControllerLockHolder lockController;
+
+ ApplyTraceFlag(pThread);
+}
+
+// Disable Single stepping for this controller.
+// If none of the controllers on this thread want single-stepping, then also
+// ensure that it's disabled on the hardware level.
+void DebuggerController::DisableSingleStep()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_thread != NULL);
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::DisableSingleStep\n"));
+
+ ControllerLockHolder lockController;
+ {
+ DebuggerController *p = g_controllers;
+
+ m_singleStep = false;
+
+ while (p != NULL)
+ {
+ if (p->m_thread == m_thread
+ && p->m_singleStep)
+ break;
+
+ p = p->m_next;
+ }
+
+ if (p == NULL)
+ {
+ UnapplyTraceFlag(m_thread);
+ }
+ }
+}
+
+
+//
+// ApplyTraceFlag sets the trace flag (i.e., turns on single-stepping)
+// for a thread.
+//
+void DebuggerController::ApplyTraceFlag(Thread *thread)
+{
+ LOG((LF_CORDB,LL_INFO1000, "DC::ApplyTraceFlag thread:0x%x [0x%0x]\n", thread, Debugger::GetThreadIdHelper(thread)));
+
+ CONTEXT *context;
+ if(thread->GetInteropDebuggingHijacked())
+ {
+ context = GetManagedLiveCtx(thread);
+ }
+ else
+ {
+ context = GetManagedStoppedCtx(thread);
+ }
+ CONSISTENCY_CHECK_MSGF(context != NULL, ("Can't apply ss flag to thread 0x%p b/c it's not in a safe place.\n", thread));
+ PREFIX_ASSUME(context != NULL);
+
+
+ g_pEEInterface->MarkThreadForDebugStepping(thread, true);
+ LOG((LF_CORDB,LL_INFO1000, "DC::ApplyTraceFlag marked thread for debug stepping\n"));
+
+ SetSSFlag(reinterpret_cast<DT_CONTEXT *>(context) ARM_ARG(thread));
+ LOG((LF_CORDB,LL_INFO1000, "DC::ApplyTraceFlag Leaving, baby!\n"));
+}
+
+//
+// UnapplyTraceFlag sets the trace flag for a thread.
+// Removes the hardware trace flag on this thread.
+//
+
+void DebuggerController::UnapplyTraceFlag(Thread *thread)
+{
+ LOG((LF_CORDB,LL_INFO1000, "DC::UnapplyTraceFlag thread:0x%x\n", thread));
+
+
+ // Either this is the helper thread, or we're manipulating our own context.
+ _ASSERTE(
+ ThisIsHelperThreadWorker() ||
+ (thread == ::GetThread())
+ );
+
+ CONTEXT *context = GetManagedStoppedCtx(thread);
+
+ // If there's no context available, then the thread shouldn't have the single-step flag
+ // enabled and there's nothing for us to do.
+ if (context == NULL)
+ {
+ // In theory, I wouldn't expect us to ever get here.
+ // Even if we are here, our single-step flag should already be deactivated,
+ // so there should be nothing to do. However, we still assert b/c we want to know how
+ // we'd actually hit this.
+ // @todo - is there a path if TriggerUnwind() calls DisableAll(). But why would
+ CONSISTENCY_CHECK_MSGF(false, ("How did we get here?. thread=%p\n", thread));
+ LOG((LF_CORDB,LL_INFO1000, "DC::UnapplyTraceFlag couldn't get context.\n"));
+ return;
+ }
+
+ // Always need to unmark for stepping
+ g_pEEInterface->MarkThreadForDebugStepping(thread, false);
+ UnsetSSFlag(reinterpret_cast<DT_CONTEXT *>(context) ARM_ARG(thread));
+}
+
+void DebuggerController::EnableExceptionHook()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_thread != NULL);
+
+ ControllerLockHolder lockController;
+
+ m_exceptionHook = true;
+}
+
+void DebuggerController::DisableExceptionHook()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(m_thread != NULL);
+
+ ControllerLockHolder lockController;
+ m_exceptionHook = false;
+}
+
+
+// void DebuggerController::DispatchExceptionHook() Called before
+// the switch statement in DispatchNativeException (therefore
+// when any exception occurs), this allows patches to do something before the
+// regular DispatchX methods.
+// How: Iterate through list of controllers. If m_exceptionHook
+// is set & m_thread is either thread or NULL, then invoke TriggerExceptionHook()
+BOOL DebuggerController::DispatchExceptionHook(Thread *thread,
+ CONTEXT *context,
+ EXCEPTION_RECORD *pException)
+{
+ // ExceptionHook has restrictive contract b/c it could come from anywhere.
+ // This can only modify controller's internal state. Can't send managed debug events.
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ GC_NOTRIGGER;
+ NOTHROW;
+ MODE_ANY;
+
+ // Filter context not set yet b/c we can only set it in COOP, and this may be in preemptive.
+ PRECONDITION(thread == ::GetThread());
+ PRECONDITION((g_pEEInterface->GetThreadFilterContext(thread) == NULL));
+ PRECONDITION(CheckPointer(pException));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DispatchExceptionHook\n"));
+
+ if (!g_patchTableValid)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DEH returning, no patch table.\n"));
+ return (TRUE);
+ }
+
+
+ _ASSERTE(g_patches != NULL);
+
+ ControllerLockHolder lockController;
+
+ TP_RESULT tpr = TPR_IGNORE;
+ DebuggerController *p;
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_exceptionHook
+ && (p->m_thread == NULL || p->m_thread == thread) &&
+ tpr != TPR_IGNORE_AND_STOP)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DEH calling TEH...\n"));
+ tpr = p->TriggerExceptionHook(thread, context , pException);
+ LOG((LF_CORDB, LL_INFO1000, "DC::DEH ... returned.\n"));
+
+ if (tpr == TPR_IGNORE_AND_STOP)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: leaving early!\n"));
+ break;
+ }
+ }
+
+ p = pNext;
+ }
+
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DEH: returning 0x%x!\n", tpr));
+
+ return (tpr != TPR_IGNORE_AND_STOP);
+}
+
+//
+// EnableUnwind enables an unwind event to be called when the stack is unwound
+// (via an exception) to or past the given pointer.
+//
+
+void DebuggerController::EnableUnwind(FramePointer fp)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+ LOG((LF_CORDB,LL_EVERYTHING,"DC:EU EnableUnwind at 0x%x\n", fp.GetSPValue()));
+
+ ControllerLockHolder lockController;
+ m_unwindFP = fp;
+}
+
+FramePointer DebuggerController::GetUnwind()
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return m_unwindFP;
+}
+
+//
+// DisableUnwind disables the unwind event for the controller.
+//
+
+void DebuggerController::DisableUnwind()
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ CAN_TAKE_LOCK;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::DU\n"));
+
+ ControllerLockHolder lockController;
+
+ m_unwindFP = LEAF_MOST_FRAME;
+}
+
+//
+// DispatchUnwind is called when an unwind happens.
+// the event to the appropriate controllers.
+// - handlerFP is the frame pointer that the handler will be invoked at.
+// - DJI is EnC-aware method that the handler is in.
+// - newOffset is the
+//
+bool DebuggerController::DispatchUnwind(Thread *thread,
+ MethodDesc *fd, DebuggerJitInfo * pDJI,
+ SIZE_T newOffset,
+ FramePointer handlerFP,
+ CorDebugStepReason unwindReason)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER; // don't send IPC events
+ MODE_COOPERATIVE; // TriggerUnwind always is coop
+
+ PRECONDITION(!IsDbgHelperSpecialThread());
+ }
+ CONTRACTL_END;
+
+
+ CONTRACT_VIOLATION(ThrowsViolation); // trigger unwind throws
+
+ _ASSERTE(unwindReason == STEP_EXCEPTION_FILTER || unwindReason == STEP_EXCEPTION_HANDLER);
+
+ bool used = false;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC: Dispatch Unwind\n"));
+
+ ControllerLockHolder lockController;
+ {
+ DebuggerController *p;
+
+ p = g_controllers;
+
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_thread == thread && p->m_unwindFP != LEAF_MOST_FRAME)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Dispatch Unwind: Found candidate\n"));
+
+
+ // Assumptions here:
+ // Function with handlers are -ALWAYS- EBP-frame based (JIT assumption)
+ //
+ // newFrame is the EBP for the handler
+ // p->m_unwindFP points to the stack slot with the return address of the function.
+ //
+ // For the interesting case: stepover, we want to know if the handler is in the same function
+ // as the stepper, if its above it (caller) o under it (callee) in order to know if we want
+ // to patch the handler or not.
+ //
+ // 3 cases:
+ //
+ // a) Handler is in a function under the function where the step happened. It therefore is
+ // a stepover. We don't want to patch this handler. The handler will have an EBP frame.
+ // So it will be at least be 2 DWORDs away from the m_unwindFP of the controller (
+ // 1 DWORD from the pushed return address and 1 DWORD for the push EBP).
+ //
+ // b) Handler is in the same function as the stepper. We want to patch the handler. In this
+ // case handlerFP will be the same as p->m_unwindFP-sizeof(void*). Why? p->m_unwindFP
+ // stores a pointer to the return address of the function. As a function with a handler
+ // is always EBP frame based it will have the following code in the prolog:
+ //
+ // push ebp <- ( sub esp, 4 ; mov [esp], ebp )
+ // mov esp, ebp
+ //
+ // Therefore EBP will be equal to &CallerReturnAddress-4.
+ //
+ // c) Handler is above the function where the stepper is. We want to patch the handler. handlerFP
+ // will be always greater than the pointer to the return address of the function where the
+ // stepper is.
+ //
+ //
+ //
+
+ if (IsEqualOrCloserToRoot(handlerFP, p->m_unwindFP))
+ {
+ used = true;
+
+ //
+ // Assume that this isn't going to block us at all --
+ // other threads may be waiting to patch or unpatch something,
+ // or to dispatch.
+ //
+ LOG((LF_CORDB, LL_INFO10000,
+ "Unwind trigger at offset 0x%p; handlerFP: 0x%p unwindReason: 0x%x.\n",
+ newOffset, handlerFP.GetSPValue(), unwindReason));
+
+ p->TriggerUnwind(thread,
+ fd, pDJI,
+ newOffset,
+ handlerFP,
+ unwindReason);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "Unwind trigger at offset 0x%p; handlerFP: 0x%p unwindReason: 0x%x.\n",
+ newOffset, handlerFP.GetSPValue(), unwindReason));
+ }
+ }
+
+ p = pNext;
+ }
+ }
+
+ return used;
+}
+
+//
+// EnableTraceCall enables a call event on the controller
+// maxFrame is the leaf-most frame that we want notifications for.
+// For step-in stuff, this will always be LEAF_MOST_FRAME.
+// for step-out, this will be the current frame because we don't
+// care if the current frame calls back into managed code when we're
+// only interested in our parent frames.
+//
+
+void DebuggerController::EnableTraceCall(FramePointer maxFrame)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+
+ LOG((LF_CORDB,LL_INFO1000, "DC::ETC maxFrame=0x%x, thread=0x%x\n",
+ maxFrame.GetSPValue(), Debugger::GetThreadIdHelper(m_thread)));
+
+ // JMC stepper should never enabled this. (They should enable ME instead).
+ _ASSERTE((DEBUGGER_CONTROLLER_JMC_STEPPER != this->GetDCType()) || !"JMC stepper shouldn't enable trace-call");
+
+
+ ControllerLockHolder lockController;
+ {
+ if (!m_traceCall)
+ {
+ m_traceCall = true;
+ g_pEEInterface->EnableTraceCall(m_thread);
+ }
+
+ if (IsCloserToLeaf(maxFrame, m_traceCallFP))
+ m_traceCallFP = maxFrame;
+ }
+}
+
+struct PatchTargetVisitorData
+{
+ DebuggerController* controller;
+ FramePointer maxFrame;
+};
+
+VOID DebuggerController::PatchTargetVisitor(TADDR pVirtualTraceCallTarget, VOID* pUserData)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerController* controller = ((PatchTargetVisitorData*) pUserData)->controller;
+ FramePointer maxFrame = ((PatchTargetVisitorData*) pUserData)->maxFrame;
+
+ EX_TRY
+ {
+ CONTRACT_VIOLATION(GCViolation); // PatchTrace throws, which implies GC-triggers
+ TraceDestination trace;
+ trace.InitForUnmanagedStub(pVirtualTraceCallTarget);
+ controller->PatchTrace(&trace, maxFrame, true);
+ }
+ EX_CATCH
+ {
+ // not much we can do here
+ }
+ EX_END_CATCH(SwallowAllExceptions)
+}
+
+//
+// DisableTraceCall disables call events on the controller
+//
+
+void DebuggerController::DisableTraceCall()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ASSERT(m_thread != NULL);
+
+ ControllerLockHolder lockController;
+ {
+ if (m_traceCall)
+ {
+ LOG((LF_CORDB,LL_INFO1000, "DC::DTC thread=0x%x\n",
+ Debugger::GetThreadIdHelper(m_thread)));
+
+ g_pEEInterface->DisableTraceCall(m_thread);
+
+ m_traceCall = false;
+ m_traceCallFP = ROOT_MOST_FRAME;
+ }
+ }
+}
+
+// Get a FramePointer for the leafmost frame on this thread's stacktrace.
+// It's tempting to create this off the head of the Frame chain, but that may
+// include internal EE Frames (like GCRoot frames) which a FrameInfo-stackwalk may skip over.
+// Thus using the Frame chain would err on the side of returning a FramePointer that
+// closer to the leaf.
+FramePointer GetCurrentFramePointerFromStackTraceForTraceCall(Thread * thread)
+{
+ _ASSERTE(thread != NULL);
+
+ // Ensure this is really the same as CSI.
+ ControllerStackInfo info;
+
+ // It's possible this stackwalk may be done at an unsafe time.
+ // this method may trigger a GC, for example, in
+ // FramedMethodFrame::AskStubForUnmanagedCallSite
+ // which will trash the incoming argument array
+ // which is not gc-protected.
+
+ // We could probably imagine a more specialized stackwalk that
+ // avoids these calls and is thus GC_NOTRIGGER.
+ CONTRACT_VIOLATION(GCViolation);
+
+ // This is being run live, so there's no filter available.
+ CONTEXT *context;
+ context = g_pEEInterface->GetThreadFilterContext(thread);
+ _ASSERTE(context == NULL);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // This is actually safe because we're coming from a TraceCall, which
+ // means we're not in the middle of a stub. We don't have some partially
+ // constructed frame, so we can safely traverse the stack.
+ // However, we may still have a problem w/ the GC-violation.
+ StackTraceTicket ticket(StackTraceTicket::SPECIAL_CASE_TICKET);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
+
+ FramePointer fp = info.m_activeFrame.fp;
+
+ return fp;
+}
+//
+// DispatchTraceCall is called when a call is traced in the EE
+// It dispatches the event to the appropriate controllers.
+//
+
+bool DebuggerController::DispatchTraceCall(Thread *thread,
+ const BYTE *ip)
+{
+ CONTRACTL
+ {
+ GC_NOTRIGGER;
+ THROWS;
+ }
+ CONTRACTL_END;
+
+ bool used = false;
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DC::DTC: TraceCall at 0x%x\n", ip));
+
+ ControllerLockHolder lockController;
+ {
+ DebuggerController *p;
+
+ p = g_controllers;
+ while (p != NULL)
+ {
+ DebuggerController *pNext = p->m_next;
+
+ if (p->m_thread == thread && p->m_traceCall)
+ {
+ bool trigger;
+
+ if (p->m_traceCallFP == LEAF_MOST_FRAME)
+ trigger = true;
+ else
+ {
+ // We know we don't have a filter context, so get a frame pointer from our frame chain.
+ FramePointer fpToCheck = GetCurrentFramePointerFromStackTraceForTraceCall(thread);
+
+
+ // <REVISIT_TODO>
+ //
+ // Currently, we never ever put a patch in an IL stub, and as such, if the IL stub
+ // throws an exception after returning from unmanaged code, we would not trigger
+ // a trace call when we call the constructor of the exception. The following is
+ // kind of a workaround to make that working. If we ever make the change to stop in
+ // IL stubs (for example, if we start to share security IL stub), then this can be
+ // removed.
+ //
+ // </REVISIT_TODO>
+
+
+
+ // It's possible this stackwalk may be done at an unsafe time.
+ // this method may trigger a GC, for example, in
+ // FramedMethodFrame::AskStubForUnmanagedCallSite
+ // which will trash the incoming argument array
+ // which is not gc-protected.
+ ControllerStackInfo info;
+ {
+ CONTRACT_VIOLATION(GCViolation);
+#ifdef _DEBUG
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+#endif // _DEBUG
+ _ASSERTE(context == NULL);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // See explanation in GetCurrentFramePointerFromStackTraceForTraceCall.
+ StackTraceTicket ticket(StackTraceTicket::SPECIAL_CASE_TICKET);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
+ }
+
+ if (info.m_activeFrame.chainReason == CHAIN_ENTER_UNMANAGED)
+ {
+ _ASSERTE(info.HasReturnFrame());
+
+ // This check makes sure that we don't do this logic for inlined frames.
+ if (info.m_returnFrame.md->IsILStub())
+ {
+ // Make sure that the frame pointer of the active frame is actually
+ // the address of an exit frame.
+ _ASSERTE( (static_cast<Frame*>(info.m_activeFrame.fp.GetSPValue()))->GetFrameType()
+ == Frame::TYPE_EXIT );
+ _ASSERTE(!info.m_returnFrame.HasChainMarker());
+ fpToCheck = info.m_returnFrame.fp;
+ }
+ }
+
+ // @todo - This comparison seems somewhat nonsensical. We don't have a filter context
+ // in place, so what frame pointer is fpToCheck actually for?
+ trigger = IsEqualOrCloserToRoot(fpToCheck, p->m_traceCallFP);
+ }
+
+ if (trigger)
+ {
+ used = true;
+
+ // This can only update controller's state, can't actually send IPC events.
+ p->TriggerTraceCall(thread, ip);
+ }
+ }
+
+ p = pNext;
+ }
+ }
+
+ return used;
+}
+
+bool DebuggerController::IsMethodEnterEnabled()
+{
+ LIMITED_METHOD_CONTRACT;
+ return m_fEnableMethodEnter;
+}
+
+
+// Notify dispatching logic that this controller wants to get TriggerMethodEnter
+// We keep a count of total controllers waiting for MethodEnter (in g_cTotalMethodEnter).
+// That way we know if any controllers want MethodEnter callbacks. If none do,
+// then we can set the JMC probe flag to false for all modules.
+void DebuggerController::EnableMethodEnter()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder chController;
+ Debugger::DebuggerDataLockHolder chInfo(g_pDebugger);
+
+ // Both JMC + Traditional steppers may use MethodEnter.
+ // For JMC, it's a core part of functionality. For Traditional steppers, we use it as a backstop
+ // in case the stub-managers fail.
+ _ASSERTE(g_cTotalMethodEnter >= 0);
+ if (!m_fEnableMethodEnter)
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::EnableME, this=%p, previously disabled\n", this));
+ m_fEnableMethodEnter = true;
+
+ g_cTotalMethodEnter++;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::EnableME, this=%p, already set\n", this));
+ }
+ g_pDebugger->UpdateAllModuleJMCFlag(g_cTotalMethodEnter != 0); // Needs JitInfo lock
+}
+
+// Notify dispatching logic that this controller doesn't want to get
+// TriggerMethodEnter
+void DebuggerController::DisableMethodEnter()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ ControllerLockHolder chController;
+ Debugger::DebuggerDataLockHolder chInfo(g_pDebugger);
+
+ if (m_fEnableMethodEnter)
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::DisableME, this=%p, previously set\n", this));
+ m_fEnableMethodEnter = false;
+
+ g_cTotalMethodEnter--;
+ _ASSERTE(g_cTotalMethodEnter >= 0);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DC::DisableME, this=%p, already disabled\n", this));
+ }
+
+ g_pDebugger->UpdateAllModuleJMCFlag(g_cTotalMethodEnter != 0); // Needs JitInfo lock
+}
+
+// Loop through controllers and dispatch TriggerMethodEnter
+void DebuggerController::DispatchMethodEnter(void * pIP, FramePointer fp)
+{
+ _ASSERTE(pIP != NULL);
+
+ Thread * pThread = g_pEEInterface->GetThread();
+ _ASSERTE(pThread != NULL);
+
+ // Lookup the DJI for this method & ip.
+ // Since we create DJIs when we jit the code, and this code has been jitted
+ // (that's where the probe's coming from!), we will have a DJI.
+ DebuggerJitInfo * dji = g_pDebugger->GetJitInfoFromAddr((TADDR) pIP);
+
+ // This includes the case where we have a LightWeight codegen method.
+ if (dji == NULL)
+ {
+ return;
+ }
+
+ LOG((LF_CORDB, LL_INFO100000, "DC::DispatchMethodEnter for '%s::%s'\n",
+ dji->m_fd->m_pszDebugClassName,
+ dji->m_fd->m_pszDebugMethodName));
+
+ ControllerLockHolder lockController;
+
+ // For debug check, keep a count to make sure that g_cTotalMethodEnter
+ // is actually the number of controllers w/ MethodEnter enabled.
+ int count = 0;
+
+ DebuggerController *p = g_controllers;
+ while (p != NULL)
+ {
+ if (p->m_fEnableMethodEnter)
+ {
+ if ((p->GetThread() == NULL) || (p->GetThread() == pThread))
+ {
+ ++count;
+ p->TriggerMethodEnter(pThread, dji, (const BYTE *) pIP, fp);
+ }
+ }
+ p = p->m_next;
+ }
+
+ _ASSERTE(g_cTotalMethodEnter == count);
+
+}
+
+//
+// AddProtection adds page protection to (at least) the given range of
+// addresses
+//
+
+void DebuggerController::AddProtection(const BYTE *start, const BYTE *end,
+ bool readable)
+{
+ // !!!
+ _ASSERTE(!"Not implemented yet");
+}
+
+//
+// RemoveProtection removes page protection from the given
+// addresses. The parameters should match an earlier call to
+// AddProtection
+//
+
+void DebuggerController::RemoveProtection(const BYTE *start, const BYTE *end,
+ bool readable)
+{
+ // !!!
+ _ASSERTE(!"Not implemented yet");
+}
+
+
+// Default implementations for FuncEvalEnter & Exit notifications.
+void DebuggerController::TriggerFuncEvalEnter(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::TFEEnter, thead=%p, this=%p\n", thread, this));
+}
+
+void DebuggerController::TriggerFuncEvalExit(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::TFEExit, thead=%p, this=%p\n", thread, this));
+}
+
+// bool DebuggerController::TriggerPatch() What: Tells the
+// static DC whether this patch should be activated now.
+// Returns true if it should be, false otherwise.
+// How: Base class implementation returns false. Others may
+// return true.
+TP_RESULT DebuggerController::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerPatch\n"));
+ return TPR_IGNORE;
+}
+
+bool DebuggerController::TriggerSingleStep(Thread *thread,
+ const BYTE *ip)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerSingleStep\n"));
+ return false;
+}
+
+void DebuggerController::TriggerUnwind(Thread *thread,
+ MethodDesc *fd, DebuggerJitInfo * pDJI, SIZE_T offset,
+ FramePointer fp,
+ CorDebugStepReason unwindReason)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerUnwind\n"));
+}
+
+void DebuggerController::TriggerTraceCall(Thread *thread,
+ const BYTE *ip)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerTraceCall\n"));
+}
+
+TP_RESULT DebuggerController::TriggerExceptionHook(Thread *thread, CONTEXT * pContext,
+ EXCEPTION_RECORD *exception)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default TriggerExceptionHook\n"));
+ return TPR_IGNORE;
+}
+
+void DebuggerController::TriggerMethodEnter(Thread * thread,
+ DebuggerJitInfo * dji,
+ const BYTE * ip,
+ FramePointer fp)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DC::TME in default impl. dji=%p, addr=%p, fp=%p\n",
+ dji, ip, fp.GetSPValue()));
+}
+
+bool DebuggerController::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::TP: in default SendEvent\n"));
+
+ // If any derived class trigger SendEvent, it should also implement SendEvent.
+ _ASSERTE(false || !"Base DebuggerController sending an event?");
+ return false;
+}
+
+
+// Dispacth Func-Eval Enter & Exit notifications.
+void DebuggerController::DispatchFuncEvalEnter(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::DispatchFuncEvalEnter for thread 0x%p\n", thread));
+
+ ControllerLockHolder lockController;
+
+ DebuggerController *p = g_controllers;
+ while (p != NULL)
+ {
+ if ((p->GetThread() == NULL) || (p->GetThread() == thread))
+ {
+ p->TriggerFuncEvalEnter(thread);
+ }
+
+ p = p->m_next;
+ }
+
+
+}
+
+void DebuggerController::DispatchFuncEvalExit(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO100000, "DC::DispatchFuncEvalExit for thread 0x%p\n", thread));
+
+ ControllerLockHolder lockController;
+
+ DebuggerController *p = g_controllers;
+ while (p != NULL)
+ {
+ if ((p->GetThread() == NULL) || (p->GetThread() == thread))
+ {
+ p->TriggerFuncEvalExit(thread);
+ }
+
+ p = p->m_next;
+ }
+
+
+}
+
+
+#ifdef _DEBUG
+// See comment in DispatchNativeException
+void ThisFunctionMayHaveTriggerAGC()
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ GC_TRIGGERS;
+ NOTHROW;
+ }
+ CONTRACTL_END;
+}
+#endif
+
+// bool DebuggerController::DispatchNativeException() Figures out
+// if any debugger controllers will handle the exception.
+// DispatchNativeException should be called by the EE when a native exception
+// occurs. If it returns true, the exception was generated by a Controller and
+// should be ignored.
+// How: Calls DispatchExceptionHook to see if anything is
+// interested in ExceptionHook, then does a switch on dwCode:
+// EXCEPTION_BREAKPOINT means invoke DispatchPatchOrSingleStep(ST_PATCH).
+// EXCEPTION_SINGLE_STEP means DispatchPatchOrSingleStep(ST_SINGLE_STEP).
+// EXCEPTION_ACCESS_VIOLATION means invoke DispatchAccessViolation.
+// Returns true if the exception was actually meant for the debugger,
+// returns false otherwise.
+bool DebuggerController::DispatchNativeException(EXCEPTION_RECORD *pException,
+ CONTEXT *pContext,
+ DWORD dwCode,
+ Thread *pCurThread)
+{
+ CONTRACTL
+ {
+ SO_INTOLERANT;
+ NOTHROW;
+
+ // If this exception is for the debugger, then we may trigger a GC.
+ // But we'll be called on _any_ exception, including ones in a GC-no-triggers region.
+ // Our current contract system doesn't let us specify such conditions on GC_TRIGGERS.
+ // So we disable it now, and if we find out the exception is meant for the debugger,
+ // we'll call ThisFunctionMayHaveTriggerAGC() to ping that we're really a GC_TRIGGERS.
+ DISABLED(GC_TRIGGERS); // Only GC triggers if we send an event,
+ PRECONDITION(!IsDbgHelperSpecialThread());
+
+ // If we're called from preemptive mode, than our caller has protected the stack.
+ // If we're in cooperative mode, then we need to protect the stack before toggling GC modes
+ // (by setting the filter-context)
+ MODE_ANY;
+
+ PRECONDITION(CheckPointer(pException));
+ PRECONDITION(CheckPointer(pContext));
+ PRECONDITION(CheckPointer(pCurThread));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB, LL_EVERYTHING, "DispatchNativeException was called\n"));
+ LOG((LF_CORDB, LL_INFO10000, "Native exception at 0x%p, code=0x%8x, context=0x%p, er=0x%p\n",
+ pException->ExceptionAddress, dwCode, pContext, pException));
+
+
+ bool fDebuggers;
+ BOOL fDispatch;
+ DPOSS_ACTION result = DPOSS_DONT_CARE;
+
+
+ // We have a potentially ugly locking problem here. This notification is called on any exception,
+ // but we have no idea what our locking context is at the time. Thus we may hold locks smaller
+ // than the controller lock.
+ // The debugger logic really only cares about exceptions directly in managed code (eg, hardware exceptions)
+ // or in patch-skippers (since that's a copy of managed code running in a look-aside buffer).
+ // That should exclude all C++ exceptions, which are the common case if Runtime code throws an internal ex.
+ // So we ignore those to avoid the lock violation.
+ if (pException->ExceptionCode == EXCEPTION_MSVC)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Debugger skipping for C++ exception.\n"));
+ return FALSE;
+ }
+
+ // The debugger really only cares about exceptions in managed code. Any exception that occurs
+ // while the thread is redirected (such as EXCEPTION_HIJACK) is not of interest to the debugger.
+ // Allowing this would be problematic because when an exception occurs while the thread is
+ // redirected, we don't know which context (saved redirection context or filter context)
+ // we should be operating on (see code:GetManagedStoppedCtx).
+ if( ISREDIRECTEDTHREAD(pCurThread) )
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Debugger ignoring exception 0x%x on redirected thread.\n", dwCode));
+
+ // We shouldn't be seeing debugging exceptions on a redirected thread. While a thread is
+ // redirected we only call a few internal things (see code:Thread.RedirectedHandledJITCase),
+ // and may call into the host. We can't call normal managed code or anything we'd want to debug.
+ _ASSERTE(dwCode != EXCEPTION_BREAKPOINT);
+ _ASSERTE(dwCode != EXCEPTION_SINGLE_STEP);
+
+ return FALSE;
+ }
+
+ // It's possible we're here without a debugger (since we have to call the
+ // patch skippers). The Debugger may detach anytime,
+ // so remember the attach state now.
+#ifdef _DEBUG
+ bool fWasAttached = false;
+#ifdef DEBUGGING_SUPPORTED
+ fWasAttached = (CORDebuggerAttached() != 0);
+#endif //DEBUGGING_SUPPORTED
+#endif //_DEBUG
+
+ {
+ // If we're in cooperative mode, it's unsafe to do a GC until we've put a filter context in place.
+ GCX_NOTRIGGER();
+
+ // If we know the debugger doesn't care about this exception, bail now.
+ // Usually this is just if there's a debugger attached.
+ // However, if a debugger detached but left outstanding controllers (like patch-skippers),
+ // we still may care.
+ // The only way a controller would get created outside of the helper thread is from
+ // a patch skipper, so we always handle breakpoints.
+ if (!CORDebuggerAttached() && (g_controllers == NULL) && (dwCode != EXCEPTION_BREAKPOINT))
+ {
+ return false;
+ }
+
+ FireEtwDebugExceptionProcessingStart();
+
+ // We should never be here if the debugger was never involved.
+ CONTEXT * pOldContext;
+ pOldContext = pCurThread->GetFilterContext();
+
+ // In most cases it is an error to nest, however in the patch-skipping logic we must
+ // copy an unknown amount of code into another buffer and it occasionally triggers
+ // an AV. This heuristic should filter that case out. See DDB 198093.
+ // Ensure we perform this exception nesting filtering even before the call to
+ // DebuggerController::DispatchExceptionHook, otherwise the nesting will continue when
+ // a contract check is triggered in DispatchExceptionHook and another BP exception is
+ // raised. See Dev11 66058.
+ if ((pOldContext != NULL) && pCurThread->AVInRuntimeImplOkay() &&
+ pException->ExceptionCode == STATUS_ACCESS_VIOLATION)
+ {
+ STRESS_LOG1(LF_CORDB, LL_INFO100, "DC::DNE Nested Access Violation at 0x%p is being ignored\n",
+ pException->ExceptionAddress);
+ return false;
+ }
+ // Otherwise it is an error to nest at all
+ _ASSERTE(pOldContext == NULL);
+
+ fDispatch = DebuggerController::DispatchExceptionHook(pCurThread,
+ pContext,
+ pException);
+
+ {
+ // Must be in cooperative mode to set the filter context. We know there are times we'll be in preemptive mode,
+ // (such as M2U handoff, or potentially patches in the middle of a stub, or various random exceptions)
+
+ // @todo - We need to worry about GC-protecting our stack. If we're in preemptive mode, the caller did it for us.
+ // If we're in cooperative, then we need to set the FilterContext *before* we toggle GC mode (since
+ // the FC protects the stack).
+ // If we're in preemptive, then we need to set the FilterContext *after* we toggle ourselves to Cooperative.
+ // Also note it may not be possible to toggle GC mode at these times (such as in the middle of the stub).
+ //
+ // Part of the problem is that the Filter Context is serving 2 purposes here:
+ // - GC protect the stack. (essential if we're in coop mode).
+ // - provide info to controllers (such as current IP, and a place to set the Single-Step flag).
+ //
+ // This contract violation is mitigated in that we must have had the debugger involved to get to this point.
+ CONTRACT_VIOLATION(ModeViolation);
+ g_pEEInterface->SetThreadFilterContext(pCurThread, pContext);
+ }
+ // Now that we've set the filter context, we can let the GCX_NOTRIGGER expire.
+ // It's still possible that we may be called from a No-trigger region.
+ }
+
+
+ if (fDispatch)
+ {
+ // Disable SingleStep for all controllers on this thread. This requires the filter context set.
+ // This is what would disable the ss-flag when single-stepping over an AV.
+ if (g_patchTableValid && (dwCode != EXCEPTION_SINGLE_STEP))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DNE non-single-step exception; check if any controller has ss turned on\n"));
+
+ ControllerLockHolder lockController;
+ for (DebuggerController* p = g_controllers; p != NULL; p = p->m_next)
+ {
+ if (p->m_singleStep && (p->m_thread == pCurThread))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC::DNE turn off ss for controller 0x%p\n", p));
+ p->DisableSingleStep();
+ }
+ }
+ // implicit controller lock release
+ }
+
+ CORDB_ADDRESS_TYPE * ip = dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(pContext));
+
+ switch (dwCode)
+ {
+ case EXCEPTION_BREAKPOINT:
+ // EIP should be properly set up at this point.
+ result = DebuggerController::DispatchPatchOrSingleStep(pCurThread,
+ pContext,
+ ip,
+ ST_PATCH);
+ LOG((LF_CORDB, LL_EVERYTHING, "DC::DNE DispatchPatch call returned\n"));
+
+ // If we detached, we should remove all our breakpoints. So if we try
+ // to handle this breakpoint, make sure that we're attached.
+ if (IsInUsedAction(result) == true)
+ {
+ _ASSERTE(fWasAttached);
+ }
+ break;
+
+ case EXCEPTION_SINGLE_STEP:
+ LOG((LF_CORDB, LL_EVERYTHING, "DC::DNE SINGLE_STEP Exception\n"));
+
+ result = DebuggerController::DispatchPatchOrSingleStep(pCurThread,
+ pContext,
+ ip,
+ (SCAN_TRIGGER)(ST_PATCH|ST_SINGLE_STEP));
+ // We pass patch | single step since single steps actually
+ // do both (eg, you SS onto a breakpoint).
+ break;
+
+ default:
+ break;
+ } // end switch
+
+ }
+#ifdef _DEBUG
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DC:: DNE step-around fDispatch:0x%x!\n", fDispatch));
+ }
+#endif //_DEBUG
+
+ fDebuggers = (fDispatch?(IsInUsedAction(result)?true:false):true);
+
+ LOG((LF_CORDB, LL_INFO10000, "DC::DNE, returning 0x%x.\n", fDebuggers));
+
+#ifdef _DEBUG
+ if (fDebuggers && (result == DPOSS_USED_WITH_EVENT))
+ {
+ // If the exception belongs to the debugger, then we may have sent an event,
+ // and thus we may have triggered a GC.
+ ThisFunctionMayHaveTriggerAGC();
+ }
+#endif
+
+
+
+ // Must restore the filter context. After the filter context is gone, we're
+ // unprotected again and unsafe for a GC.
+ {
+ CONTRACT_VIOLATION(ModeViolation);
+ g_pEEInterface->SetThreadFilterContext(pCurThread, NULL);
+ }
+
+#ifdef _TARGET_ARM_
+ if (pCurThread->IsSingleStepEnabled())
+ pCurThread->ApplySingleStep(pContext);
+#endif
+
+ FireEtwDebugExceptionProcessingEnd();
+
+ return fDebuggers;
+}
+
+// * -------------------------------------------------------------------------
+// * DebuggerPatchSkip routines
+// * -------------------------------------------------------------------------
+
+DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread,
+ DebuggerControllerPatch *patch,
+ AppDomain *pAppDomain)
+ : DebuggerController(thread, pAppDomain),
+ m_address(patch->address)
+{
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPS::DPS: Patch skip 0x%p\n", patch->address));
+
+ // On ARM the single-step emulation already utilizes a per-thread execution buffer similar to the scheme
+ // below. As a result we can skip most of the instruction parsing logic that's instead internalized into
+ // the single-step emulation itself.
+#ifndef _TARGET_ARM_
+
+ // NOTE: in order to correctly single-step RIP-relative writes on multiple threads we need to set up
+ // a shared buffer with the instruction and a buffer for the RIP-relative value so that all threads
+ // are working on the same copy. as the single-steps complete the modified data in the buffer is
+ // copied back to the real address to ensure proper execution of the program.
+
+ //
+ // Create the shared instruction block. this will also create the shared RIP-relative buffer
+ //
+
+ m_pSharedPatchBypassBuffer = patch->GetOrCreateSharedPatchBypassBuffer();
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+
+ // Copy the instruction block over to the patch skip
+ // WARNING: there used to be an issue here because CopyInstructionBlock copied the breakpoint from the
+ // jitted code stream into the patch buffer. Further below CORDbgSetInstruction would correct the
+ // first instruction. This buffer is shared by all threads so if another thread executed the buffer
+ // between this thread's execution of CopyInstructionBlock and CORDbgSetInstruction the wrong
+ // code would be executed. The bug has been fixed by changing CopyInstructionBlock to only copy
+ // the code bytes after the breakpoint.
+ // You might be tempted to stop copying the code at all, however that wouldn't work well with rejit.
+ // If we skip a breakpoint that is sitting at the beginning of a method, then the profiler rejits that
+ // method causing a jump-stamp to be placed, then we skip the breakpoint again, we need to make sure
+ // the 2nd skip executes the new jump-stamp code and not the original method prologue code. Copying
+ // the code every time ensures that we have the most up-to-date version of the code in the buffer.
+ _ASSERTE( patch->IsBound() );
+ CopyInstructionBlock(patchBypass, (const BYTE *)patch->address);
+
+ // Technically, we could create a patch skipper for an inactive patch, but we rely on the opcode being
+ // set here.
+ _ASSERTE( patch->IsActivated() );
+ CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, patch->opcode);
+
+ LOG((LF_CORDB, LL_EVERYTHING, "SetInstruction was called\n"));
+ //
+ // Look at instruction to get some attributes
+ //
+
+ NativeWalker::DecodeInstructionForPatchSkip(patchBypass, &(m_instrAttrib));
+
+#if defined(_TARGET_AMD64_)
+
+
+ // The code below handles RIP-relative addressing on AMD64. the original implementation made the assumption that
+ // we are only using RIP-relative addressing to access read-only data (see VSW 246145 for more information). this
+ // has since been expanded to handle RIP-relative writes as well.
+ if (m_instrAttrib.m_dwOffsetToDisp != 0)
+ {
+ _ASSERTE(m_instrAttrib.m_cbInstr != 0);
+
+ //
+ // Populate the RIP-relative buffer with the current value if needed
+ //
+
+ BYTE* bufferBypass = m_pSharedPatchBypassBuffer->BypassBuffer;
+
+ // Overwrite the *signed* displacement.
+ int dwOldDisp = *(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]);
+ int dwNewDisp = offsetof(SharedPatchBypassBuffer, BypassBuffer) -
+ (offsetof(SharedPatchBypassBuffer, PatchBypass) + m_instrAttrib.m_cbInstr);
+ *(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]) = dwNewDisp;
+
+ // This could be an LEA, which we'll just have to change into a MOV
+ // and copy the original address
+ if (((patchBypass[0] == 0x4C) || (patchBypass[0] == 0x48)) && (patchBypass[1] == 0x8d))
+ {
+ patchBypass[1] = 0x8b; // MOV reg, mem
+ _ASSERTE((int)sizeof(void*) <= SharedPatchBypassBuffer::cbBufferBypass);
+ *(void**)bufferBypass = (void*)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
+ }
+ else
+ {
+ // Copy the data into our buffer.
+ memcpy(bufferBypass, patch->address + m_instrAttrib.m_cbInstr + dwOldDisp, SharedPatchBypassBuffer::cbBufferBypass);
+
+ if (m_instrAttrib.m_fIsWrite)
+ {
+ // save the actual destination address and size so when we TriggerSingleStep() we can update the value
+ m_pSharedPatchBypassBuffer->RipTargetFixup = (UINT_PTR)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
+ m_pSharedPatchBypassBuffer->RipTargetFixupSize = m_instrAttrib.m_cOperandSize;
+ }
+ }
+ }
+#endif // _TARGET_AMD64_
+
+#endif // !_TARGET_ARM_
+
+ // Signals our thread that the debugger will be manipulating the context
+ // during the patch skip operation. This effectively prevents other threads
+ // from suspending us until we have completed skiping the patch and restored
+ // a good context (See DDB 188816)
+ thread->BeginDebuggerPatchSkip(this);
+
+ //
+ // Set IP of context to point to patch bypass buffer
+ //
+
+ T_CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+ CONTEXT c;
+ if (context == NULL)
+ {
+ // We can't play with our own context!
+#if _DEBUG
+ if (g_pEEInterface->GetThread())
+ {
+ // current thread is mamaged thread
+ _ASSERTE(Debugger::GetThreadIdHelper(thread) != Debugger::GetThreadIdHelper(g_pEEInterface->GetThread()));
+ }
+#endif // _DEBUG
+
+ c.ContextFlags = CONTEXT_CONTROL;
+
+ thread->GetThreadContext(&c);
+ context =(T_CONTEXT *) &c;
+
+ ARM_ONLY(_ASSERTE(!"We should always have a filter context in DebuggerPatchSkip."));
+ }
+
+#ifdef _TARGET_ARM_
+ // Since we emulate all single-stepping on ARM using an instruction buffer and a breakpoint all we have to
+ // do here is initiate a normal single-step except that we pass the instruction to be stepped explicitly
+ // (calling EnableSingleStep() would infer this by looking at the PC in the context, which would pick up
+ // the patch we're trying to skip).
+ //
+ // Ideally we'd refactor the EnableSingleStep to support this alternative calling sequence but since this
+ // involves three levels of methods and is only applicable to ARM we've chosen to replicate the relevant
+ // implementation here instead.
+ {
+ ControllerLockHolder lockController;
+ g_pEEInterface->MarkThreadForDebugStepping(thread, true);
+ WORD opcode2 = 0;
+
+ if (Is32BitInstruction(patch->opcode))
+ {
+ opcode2 = CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)(((DWORD)patch->address) + 2));
+ }
+
+ thread->BypassWithSingleStep((DWORD)patch->address, patch->opcode, opcode2);
+ m_singleStep = true;
+ }
+
+#else // _TARGET_ARM_
+
+#ifdef _TARGET_ARM64_
+ patchBypass = NativeWalker::SetupOrSimulateInstructionForPatchSkip(context, m_pSharedPatchBypassBuffer, (const BYTE *)patch->address, patch->opcode);
+#endif //_TARGET_ARM64_
+
+ //set eip to point to buffer...
+ SetIP(context, (PCODE)patchBypass);
+
+ if (context ==(T_CONTEXT*) &c)
+ thread->SetThreadContext(&c);
+
+
+ LOG((LF_CORDB, LL_INFO10000, "DPS::DPS Bypass at 0x%p for opcode %p \n", patchBypass, patch->opcode));
+
+ //
+ // Turn on single step (if the platform supports it) so we can
+ // fix up state after the instruction is executed.
+ // Also turn on exception hook so we can adjust IP in exceptions
+ //
+
+ EnableSingleStep();
+
+#endif // _TARGET_ARM_
+
+ EnableExceptionHook();
+}
+
+DebuggerPatchSkip::~DebuggerPatchSkip()
+{
+#ifndef _TARGET_ARM_
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ m_pSharedPatchBypassBuffer->Release();
+#endif
+}
+
+void DebuggerPatchSkip::DebuggerDetachClean()
+{
+// Since for ARM SharedPatchBypassBuffer isn't existed, we don't have to anything here.
+#ifndef _TARGET_ARM_
+ // Fix for Bug 1176448
+ // When a debugger is detaching from the debuggee, we need to move the IP if it is pointing
+ // somewhere in PatchBypassBuffer.All managed threads are suspended during detach, so changing
+ // the context without notifications is safe.
+ // Notice:
+ // THIS FIX IS INCOMPLETE!It attempts to update the IP in the cases we can easily detect.However,
+ // if a thread is in pre - emptive mode, and its filter context has been propagated to a VEH
+ // context, then the filter context we get will be NULL and this fix will not work.Our belief is
+ // that this scenario is rare enough that it doesn’t justify the cost and risk associated with a
+ // complete fix, in which we would have to either :
+ // 1. Change the reference counting for DebuggerController and then change the exception handling
+ // logic in the debuggee so that we can handle the debugger event after detach.
+ // 2. Create a "stack walking" implementation for native code and use it to get the current IP and
+ // set the IP to the right place.
+
+ Thread *thread = GetThread();
+ if (thread != NULL)
+ {
+ BYTE *patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+ CONTEXT *context = thread->GetFilterContext();
+ if (patchBypass != NULL &&
+ context != NULL &&
+ (size_t)GetIP(context) >= (size_t)patchBypass &&
+ (size_t)GetIP(context) <= (size_t)(patchBypass + MAX_INSTRUCTION_LENGTH + 1))
+ {
+ SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
+ }
+ }
+#endif
+}
+
+
+//
+// We have to have a whole seperate function for this because you
+// can't use __try in a function that requires object unwinding...
+//
+
+LONG FilterAccessViolation2(LPEXCEPTION_POINTERS ep, PVOID pv)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
+ ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
+}
+
+// This helper is required because the AVInRuntimeImplOkayHolder can not
+// be directly placed inside the scope of a PAL_TRY
+void _CopyInstructionBlockHelper(BYTE* to, const BYTE* from)
+{
+ AVInRuntimeImplOkayHolder AVOkay;
+
+ // This function only copies the portion of the instruction that follows the
+ // breakpoint opcode, not the breakpoint itself
+ to += CORDbg_BREAK_INSTRUCTION_SIZE;
+ from += CORDbg_BREAK_INSTRUCTION_SIZE;
+
+ // If an AV occurs because we walked off a valid page then we need
+ // to be certain that all bytes on the previous page were copied.
+ // We are certain that we copied enough bytes to contain the instruction
+ // because it must have fit within the valid page.
+ for (int i = 0; i < MAX_INSTRUCTION_LENGTH - CORDbg_BREAK_INSTRUCTION_SIZE; i++)
+ {
+ *to++ = *from++;
+ }
+
+}
+
+// WARNING: this function skips copying the first CORDbg_BREAK_INSTRUCTION_SIZE bytes by design
+// See the comment at the callsite in DebuggerPatchSkip::DebuggerPatchSkip for more details on
+// this
+void DebuggerPatchSkip::CopyInstructionBlock(BYTE *to, const BYTE* from)
+{
+ // We wrap the memcpy in an exception handler to handle the
+ // extremely rare case where we're copying an instruction off the
+ // end of a method that is also at the end of a page, and the next
+ // page is unmapped.
+ struct Param
+ {
+ BYTE *to;
+ const BYTE* from;
+ } param;
+ param.to = to;
+ param.from = from;
+ PAL_TRY(Param *, pParam, &param)
+ {
+ _CopyInstructionBlockHelper(pParam->to, pParam->from);
+ }
+ PAL_EXCEPT_FILTER(FilterAccessViolation2)
+ {
+ // The whole point is that if we copy up the the AV, then
+ // that's enough to execute, otherwise we would not have been
+ // able to execute the code anyway. So we just ignore the
+ // exception.
+ LOG((LF_CORDB, LL_INFO10000,
+ "DPS::DPS: AV copying instruction block ignored.\n"));
+ }
+ PAL_ENDTRY
+
+ // We just created a new buffer of code, but the CPU caches code and may
+ // not be aware of our changes. This should force the CPU to dump any cached
+ // instructions it has in this region and load the new ones from memory
+ FlushInstructionCache(GetCurrentProcess(), to + CORDbg_BREAK_INSTRUCTION_SIZE,
+ MAX_INSTRUCTION_LENGTH - CORDbg_BREAK_INSTRUCTION_SIZE);
+}
+
+TP_RESULT DebuggerPatchSkip::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ ARM_ONLY(_ASSERTE(!"Should not have called DebuggerPatchSkip::TriggerPatch."));
+ LOG((LF_CORDB, LL_EVERYTHING, "DPS::TP called\n"));
+
+#if defined(_DEBUG) && !defined(_TARGET_ARM_)
+ CONTEXT *context = GetManagedLiveCtx(thread);
+
+ LOG((LF_CORDB, LL_INFO1000, "DPS::TP: We've patched 0x%x (byPass:0x%x) "
+ "for a skip after an EnC update!\n", GetIP(context),
+ GetBypassAddress()));
+ _ASSERTE(g_patches != NULL);
+
+ // We shouldn't have mucked with EIP, yet.
+ _ASSERTE(dac_cast<PTR_CORDB_ADDRESS_TYPE>(GetIP(context)) == GetBypassAddress());
+
+ //We should be the _only_ patch here
+ MethodDesc *md2 = dac_cast<PTR_MethodDesc>(GetIP(context));
+ DebuggerControllerPatch *patchCheck = g_patches->GetPatch(g_pEEInterface->MethodDescGetModule(md2),md2->GetMemberDef());
+ _ASSERTE(patchCheck == patch);
+ _ASSERTE(patchCheck->controller == patch->controller);
+
+ patchCheck = g_patches->GetNextPatch(patchCheck);
+ _ASSERTE(patchCheck == NULL);
+#endif // _DEBUG
+
+ DisableAll();
+ EnableExceptionHook();
+ EnableSingleStep(); //gets us back to where we want.
+ return TPR_IGNORE; // don't actually want to stop here....
+}
+
+TP_RESULT DebuggerPatchSkip::TriggerExceptionHook(Thread *thread, CONTEXT * context,
+ EXCEPTION_RECORD *exception)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ GC_NOTRIGGER;
+ // Patch skippers only operate on patches set in managed code. But the infrastructure may have
+ // toggled the GC mode underneath us.
+ MODE_ANY;
+
+ PRECONDITION(GetThread() == thread);
+ PRECONDITION(thread != NULL);
+ PRECONDITION(CheckPointer(context));
+ }
+ CONTRACTL_END;
+
+ if (m_pAppDomain != NULL)
+ {
+ AppDomain *pAppDomainCur = thread->GetDomain();
+
+ if (pAppDomainCur != m_pAppDomain)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TEH: Appdomain mismatch - not skiiping!\n"));
+ return TPR_IGNORE;
+ }
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TEH: doing the patch-skip thing\n"));
+
+#if defined(_TARGET_ARM64_)
+
+ if (!IsSingleStep(exception->ExceptionCode))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Exception in patched Bypass instruction .\n"));
+ return (TPR_IGNORE_AND_STOP);
+ }
+
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+ PCODE targetIp;
+ if (m_pSharedPatchBypassBuffer->RipTargetFixup)
+ {
+ targetIp = m_pSharedPatchBypassBuffer->RipTargetFixup;
+ }
+ else
+ {
+ targetIp = (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address));
+ }
+
+ SetIP(context, targetIp);
+ LOG((LF_CORDB, LL_ALWAYS, "Redirecting after Patch to 0x%p\n", GetIP(context)));
+
+#elif defined (_TARGET_ARM_)
+//Do nothing
+#else
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
+
+ if (m_instrAttrib.m_fIsCall && IsSingleStep(exception->ExceptionCode))
+ {
+ // Fixup return address on stack
+#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
+ SIZE_T *sp = (SIZE_T *) GetSP(context);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "Bypass call return address redirected from 0x%p\n", *sp));
+
+ *sp -= patchBypass - (BYTE*)m_address;
+
+ LOG((LF_CORDB, LL_INFO10000, "to 0x%p\n", *sp));
+#else
+ PORTABILITY_ASSERT("DebuggerPatchSkip::TriggerExceptionHook -- return address fixup NYI");
+#endif
+ }
+
+ if (!m_instrAttrib.m_fIsAbsBranch || !IsSingleStep(exception->ExceptionCode))
+ {
+ // Fixup IP
+
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected from 0x%p\n", GetIP(context)));
+
+ if (IsSingleStep(exception->ExceptionCode))
+ {
+#ifndef FEATURE_PAL
+ // Check if the current IP is anywhere near the exception dispatcher logic.
+ // If it is, ignore the exception, as the real exception is coming next.
+ static FARPROC pExcepDispProc = NULL;
+
+ if (!pExcepDispProc)
+ {
+ HMODULE hNtDll = WszGetModuleHandle(W("ntdll.dll"));
+
+ if (hNtDll != NULL)
+ {
+ pExcepDispProc = GetProcAddress(hNtDll, "KiUserExceptionDispatcher");
+
+ if (!pExcepDispProc)
+ pExcepDispProc = (FARPROC)(size_t)(-1);
+ }
+ else
+ pExcepDispProc = (FARPROC)(size_t)(-1);
+ }
+
+ _ASSERTE(pExcepDispProc != NULL);
+
+ if ((size_t)pExcepDispProc != (size_t)(-1))
+ {
+ LPVOID pExcepDispEntryPoint = pExcepDispProc;
+
+ if ((size_t)GetIP(context) > (size_t)pExcepDispEntryPoint &&
+ (size_t)GetIP(context) <= ((size_t)pExcepDispEntryPoint + MAX_INSTRUCTION_LENGTH * 2 + 1))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "Bypass instruction not redirected. Landed in exception dispatcher.\n"));
+
+ return (TPR_IGNORE_AND_STOP);
+ }
+ }
+#endif // FEATURE_PAL
+
+ // If the IP is close to the skip patch start, or if we were skipping over a call, then assume the IP needs
+ // adjusting.
+ if (m_instrAttrib.m_fIsCall ||
+ ((size_t)GetIP(context) > (size_t)patchBypass &&
+ (size_t)GetIP(context) <= (size_t)(patchBypass + MAX_INSTRUCTION_LENGTH + 1)))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because still in skip area.\n"));
+ LOG((LF_CORDB, LL_INFO10000, "m_fIsCall = %d, patchBypass = 0x%x, m_address = 0x%x\n",
+ m_instrAttrib.m_fIsCall, patchBypass, m_address));
+ SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
+ }
+ else
+ {
+ // Otherwise, need to see if the IP is something we recognize (either managed code
+ // or stub code) - if not, we ignore the exception
+ PCODE newIP = GetIP(context);
+ newIP -= PCODE(patchBypass - (BYTE *)m_address);
+ TraceDestination trace;
+
+ if (g_pEEInterface->IsManagedNativeCode(dac_cast<PTR_CBYTE>(newIP)) ||
+ (g_pEEInterface->TraceStub(LPBYTE(newIP), &trace)))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because we landed in managed or stub code\n"));
+ SetIP(context, newIP);
+ }
+
+ // If we have no idea where things have gone, then we assume that the IP needs no adjusting (which
+ // could happen if the instruction we were trying to patch skip caused an AV). In this case we want
+ // to claim it as ours but ignore it and continue execution.
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction not redirected because we're not in managed or stub code.\n"));
+ return (TPR_IGNORE_AND_STOP);
+ }
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Bypass instruction redirected because it wasn't a single step exception.\n"));
+ SetIP(context, (PCODE)((BYTE *)GetIP(context) - (patchBypass - (BYTE *)m_address)));
+ }
+
+ LOG((LF_CORDB, LL_ALWAYS, "to 0x%x\n", GetIP(context)));
+
+ }
+
+#endif
+
+
+ // Signals our thread that the debugger is done manipulating the context
+ // during the patch skip operation. This effectively prevented other threads
+ // from suspending us until we completed skiping the patch and restored
+ // a good context (See DDB 188816)
+ m_thread->EndDebuggerPatchSkip();
+
+ // Don't delete the controller yet if this is a single step exception, as the code will still want to dispatch to
+ // our single step method, and if it doesn't find something to dispatch to we won't continue from the exception.
+ //
+ // (This is kind of broken behavior but is easily worked around here
+ // by this test)
+ if (!IsSingleStep(exception->ExceptionCode))
+ {
+ Delete();
+ }
+
+ DisableExceptionHook();
+
+ return TPR_TRIGGER;
+}
+
+bool DebuggerPatchSkip::TriggerSingleStep(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TSS: basically a no-op\n"));
+
+ if (m_pAppDomain != NULL)
+ {
+ AppDomain *pAppDomainCur = thread->GetDomain();
+
+ if (pAppDomainCur != m_pAppDomain)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TSS: Appdomain mismatch - "
+ "not SingSteping!!\n"));
+ return false;
+ }
+ }
+#if defined(_TARGET_AMD64_)
+ // Dev11 91932: for RIP-relative writes we need to copy the value that was written in our buffer to the actual address
+ _ASSERTE(m_pSharedPatchBypassBuffer);
+ if (m_pSharedPatchBypassBuffer->RipTargetFixup)
+ {
+ _ASSERTE(m_pSharedPatchBypassBuffer->RipTargetFixupSize);
+
+ BYTE* bufferBypass = m_pSharedPatchBypassBuffer->BypassBuffer;
+ BYTE fixupSize = m_pSharedPatchBypassBuffer->RipTargetFixupSize;
+ UINT_PTR targetFixup = m_pSharedPatchBypassBuffer->RipTargetFixup;
+
+ switch (fixupSize)
+ {
+ case 1:
+ *(reinterpret_cast<BYTE*>(targetFixup)) = *(reinterpret_cast<BYTE*>(bufferBypass));
+ break;
+
+ case 2:
+ *(reinterpret_cast<WORD*>(targetFixup)) = *(reinterpret_cast<WORD*>(bufferBypass));
+ break;
+
+ case 4:
+ *(reinterpret_cast<DWORD*>(targetFixup)) = *(reinterpret_cast<DWORD*>(bufferBypass));
+ break;
+
+ case 8:
+ *(reinterpret_cast<ULONGLONG*>(targetFixup)) = *(reinterpret_cast<ULONGLONG*>(bufferBypass));
+ break;
+
+ case 16:
+ memcpy(reinterpret_cast<void*>(targetFixup), bufferBypass, 16);
+ break;
+
+ default:
+ _ASSERTE(!"bad operand size");
+ }
+ }
+#endif
+ LOG((LF_CORDB,LL_INFO10000, "DPS::TSS: triggered, about to delete\n"));
+
+ TRACE_FREE(this);
+ Delete();
+ return false;
+}
+
+// * -------------------------------------------------------------------------
+// * DebuggerBreakpoint routines
+// * -------------------------------------------------------------------------
+// DebuggerBreakpoint::DebuggerBreakpoint() The constructor
+// invokes AddBindAndActivatePatch to set the breakpoint
+DebuggerBreakpoint::DebuggerBreakpoint(Module *module,
+ mdMethodDef md,
+ AppDomain *pAppDomain,
+ SIZE_T offset,
+ bool native,
+ SIZE_T ilEnCVersion, // must give the EnC version for non-native bps
+ MethodDesc *nativeMethodDesc, // use only when m_native
+ DebuggerJitInfo *nativeJITInfo, // optional when m_native, null otherwise
+ BOOL *pSucceed
+ )
+ : DebuggerController(NULL, pAppDomain)
+{
+ _ASSERTE(pSucceed != NULL);
+ _ASSERTE(native == (nativeMethodDesc != NULL));
+ _ASSERTE(native || nativeJITInfo == NULL);
+ _ASSERTE(!nativeJITInfo || nativeJITInfo->m_jitComplete); // this is sent by the left-side, and it couldn't have got the code if the JIT wasn't complete
+
+ if (native)
+ {
+ (*pSucceed) = AddBindAndActivateNativeManagedPatch(nativeMethodDesc, nativeJITInfo, offset, LEAF_MOST_FRAME, pAppDomain);
+ return;
+ }
+ else
+ {
+ (*pSucceed) = AddILPatch(pAppDomain, module, md, ilEnCVersion, offset);
+ }
+}
+
+// TP_RESULT DebuggerBreakpoint::TriggerPatch()
+// What: This patch will always be activated.
+// How: return true.
+TP_RESULT DebuggerBreakpoint::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DB::TP\n"));
+
+ return TPR_TRIGGER;
+}
+
+// void DebuggerBreakpoint::SendEvent() What: Inform
+// the right side that the breakpoint was reached.
+// How: g_pDebugger->SendBreakpoint()
+bool DebuggerBreakpoint::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+
+ LOG((LF_CORDB, LL_INFO10000, "DB::SE: in DebuggerBreakpoint's SendEvent\n"));
+
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+
+ // If we got interupted by SetIp, we just don't send the IPC event. Our triggers are still
+ // active so no harm done.
+ if (!fIpChanged)
+ {
+ g_pDebugger->SendBreakpoint(thread, context, this);
+ return true;
+ }
+
+ // Controller is still alive, will fire if we hit the breakpoint again.
+ return false;
+}
+
+//* -------------------------------------------------------------------------
+// * DebuggerStepper routines
+// * -------------------------------------------------------------------------
+
+DebuggerStepper::DebuggerStepper(Thread *thread,
+ CorDebugUnmappedStop rgfMappingStop,
+ CorDebugIntercept interceptStop,
+ AppDomain *appDomain)
+ : DebuggerController(thread, appDomain),
+ m_stepIn(false),
+ m_reason(STEP_NORMAL),
+ m_fpStepInto(LEAF_MOST_FRAME),
+ m_rgfInterceptStop(interceptStop),
+ m_rgfMappingStop(rgfMappingStop),
+ m_range(NULL),
+ m_rangeCount(0),
+ m_realRangeCount(0),
+ m_fp(LEAF_MOST_FRAME),
+#if defined(WIN64EXCEPTIONS)
+ m_fpParentMethod(LEAF_MOST_FRAME),
+#endif // WIN64EXCEPTIONS
+ m_fpException(LEAF_MOST_FRAME),
+ m_fdException(0),
+ m_cFuncEvalNesting(0)
+{
+#ifdef _DEBUG
+ m_fReadyToSend = false;
+#endif
+}
+
+DebuggerStepper::~DebuggerStepper()
+{
+ if (m_range != NULL)
+ {
+ TRACE_FREE(m_range);
+ DeleteInteropSafe(m_range);
+ }
+}
+
+// bool DebuggerStepper::ShouldContinueStep() Return true if
+// the stepper should not stop at this address. The stepper should not
+// stop here if: here is in the {prolog,epilog,etc};
+// and the stepper is not interested in stopping here.
+// We assume that this is being called in the frame which the stepper steps
+// through. Unless, of course, we're returning from a call, in which
+// case we want to stop in the epilog even if the user didn't say so,
+// to prevent stepping out of multiple frames at once.
+// <REVISIT_TODO>Possible optimization: GetJitInfo, then AddPatch @ end of prolog?</REVISIT_TODO>
+bool DebuggerStepper::ShouldContinueStep( ControllerStackInfo *info,
+ SIZE_T nativeOffset)
+{
+ LOG((LF_CORDB,LL_INFO10000, "DeSt::ShContSt: nativeOffset:0x%p \n", nativeOffset));
+ if (m_rgfMappingStop != STOP_ALL && (m_reason != STEP_EXIT) )
+ {
+
+ DebuggerJitInfo *ji = info->m_activeFrame.GetJitInfoFromFrame();
+
+ if ( ji != NULL )
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DeSt::ShContSt: For code 0x%p, got "
+ "DJI 0x%p, from 0x%p to 0x%p\n",
+ (const BYTE*)GetControlPC(&(info->m_activeFrame.registers)),
+ ji, ji->m_addrOfCode, ji->m_addrOfCode+ji->m_sizeOfCode));
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DeSt::ShCoSt: For code 0x%p, didn't "
+ "get DJI\n",(const BYTE*)GetControlPC(&(info->m_activeFrame.registers))));
+
+ return false; // Haven't a clue if we should continue, so
+ // don't
+ }
+ CorDebugMappingResult map = MAPPING_UNMAPPED_ADDRESS;
+ DWORD whichIDontCare;
+ ji->MapNativeOffsetToIL( nativeOffset, &map, &whichIDontCare);
+ unsigned int interestingMappings =
+ (map & ~(MAPPING_APPROXIMATE | MAPPING_EXACT));
+
+ LOG((LF_CORDB,LL_INFO10000,
+ "DeSt::ShContSt: interestingMappings:0x%x m_rgfMappingStop:%x\n",
+ interestingMappings,m_rgfMappingStop));
+
+ // If we're in a prolog,epilog, then we may want to skip
+ // over it or stop
+ if ( interestingMappings )
+ {
+ if ( interestingMappings & m_rgfMappingStop )
+ return false;
+ else
+ return true;
+ }
+ }
+ return false;
+}
+
+bool DebuggerStepper::IsRangeAppropriate(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: info:0x%x \n", info));
+ if (m_range == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: m_range == NULL, returning FALSE\n"));
+ return false;
+ }
+
+ FrameInfo *realFrame;
+
+#if defined(WIN64EXCEPTIONS)
+ bool fActiveFrameIsFunclet = info->m_activeFrame.IsNonFilterFuncletFrame();
+
+ if (fActiveFrameIsFunclet)
+ {
+ realFrame = &(info->m_returnFrame);
+ }
+ else
+#endif // WIN64EXCEPTIONS
+ {
+ realFrame = &(info->m_activeFrame);
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: info->m_activeFrame.fp:0x%x m_fp:0x%x\n", info->m_activeFrame.fp, m_fp));
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: m_fdException:0x%x realFrame->md:0x%x realFrame->fp:0x%x m_fpException:0x%x\n",
+ m_fdException, realFrame->md, realFrame->fp, m_fpException));
+ if ( (info->m_activeFrame.fp == m_fp) ||
+ ( (m_fdException != NULL) && (realFrame->md == m_fdException) &&
+ IsEqualOrCloserToRoot(realFrame->fp, m_fpException) ) )
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+
+#if defined(WIN64EXCEPTIONS)
+ // There are two scenarios which make this function more complicated on WIN64.
+ // 1) We initiate a step in the parent method or a funclet but end up stepping into another funclet closer to the leaf.
+ // a) start in the parent method
+ // b) start in a funclet
+ // 2) We initiate a step in a funclet but end up stepping out to the parent method or a funclet closer to the root.
+ // a) end up in the parent method
+ // b) end up in a funclet
+ // In both cases the range of the stepper should still be appropriate.
+
+ bool fValidParentMethodFP = (m_fpParentMethod != LEAF_MOST_FRAME);
+
+ if (fActiveFrameIsFunclet)
+ {
+ // Scenario 1a
+ if (m_fp == info->m_returnFrame.fp)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+ // Scenario 1b & 2b have the same condition
+ else if (fValidParentMethodFP && (m_fpParentMethod == info->m_returnFrame.fp))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+ }
+ else
+ {
+ // Scenario 2a
+ if (fValidParentMethodFP && (m_fpParentMethod == info->m_activeFrame.fp))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning TRUE\n"));
+ return true;
+ }
+ }
+#endif // WIN64EXCEPTIONS
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::IRA: returning FALSE\n"));
+ return false;
+}
+
+// bool DebuggerStepper::IsInRange() Given the native offset ip,
+// returns true if ip falls within any of the native offset ranges specified
+// by the array of COR_DEBUG_STEP_RANGEs.
+// Returns true if ip falls within any of the ranges. Returns false
+// if ip doesn't, or if there are no ranges (rangeCount==0). Note that a
+// COR_DEBUG_STEP_RANGE with an endOffset of zero is interpreted as extending
+// from startOffset to the end of the method.
+// SIZE_T ip: Native offset, relative to the beginning of the method.
+// COR_DEBUG_STEP_RANGE *range: An array of ranges, which are themselves
+// native offsets, to compare against ip.
+// SIZE_T rangeCount: Number of elements in range
+bool DebuggerStepper::IsInRange(SIZE_T ip, COR_DEBUG_STEP_RANGE *range, SIZE_T rangeCount,
+ ControllerStackInfo *pInfo)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: off=0x%x\n", ip));
+
+ if (range == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: range == NULL -> not in range\n"));
+ return false;
+ }
+
+ if (pInfo && !IsRangeAppropriate(pInfo))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: no pInfo or range not appropriate -> not in range\n"));
+ return false;
+ }
+
+ COR_DEBUG_STEP_RANGE *r = range;
+ COR_DEBUG_STEP_RANGE *rEnd = r + rangeCount;
+
+ while (r < rEnd)
+ {
+ SIZE_T endOffset = r->endOffset ? r->endOffset : ~0;
+ LOG((LF_CORDB,LL_INFO100000,"DS::IIR: so=0x%x, eo=0x%x\n",
+ r->startOffset, endOffset));
+
+ if (ip >= r->startOffset && ip < endOffset)
+ {
+ LOG((LF_CORDB,LL_INFO1000,"DS::IIR:this:0x%x Found native offset "
+ "0x%x to be in the range"
+ "[0x%x, 0x%x), index 0x%x\n\n", this, ip, r->startOffset,
+ endOffset, ((r-range)/sizeof(COR_DEBUG_STEP_RANGE *)) ));
+ return true;
+ }
+
+ r++;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"DS::IIR: not in range\n"));
+ return false;
+}
+
+// bool DebuggerStepper::DetectHandleInterceptors() Return true if
+// the current execution takes place within an interceptor (that is, either
+// the current frame, or the parent frame is a framed frame whose
+// GetInterception method returns something other than INTERCEPTION_NONE),
+// and this stepper doesn't want to stop in an interceptor, and we successfully
+// set a breakpoint after the top-most interceptor in the stack.
+bool DebuggerStepper::DetectHandleInterceptors(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Start DetectHandleInterceptors\n"));
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: active frame=0x%08x, has return frame=%d, return frame=0x%08x m_reason:%d\n",
+ info->m_activeFrame.frame, info->HasReturnFrame(), info->m_returnFrame.frame, m_reason));
+
+ // If this is a normal step, then we want to continue stepping, even if we
+ // are in an interceptor.
+ if (m_reason == STEP_NORMAL || m_reason == STEP_RETURN || m_reason == STEP_EXCEPTION_HANDLER)
+ {
+ LOG((LF_CORDB,LL_INFO1000,"DS::DHI: Returning false while stepping within function, finally!\n"));
+ return false;
+ }
+
+ bool fAttemptStepOut = false;
+
+ if (m_rgfInterceptStop != INTERCEPT_ALL) // we may have to skip out of one
+ {
+ if (info->m_activeFrame.frame != NULL &&
+ info->m_activeFrame.frame != FRAME_TOP &&
+ info->m_activeFrame.frame->GetInterception() != Frame::INTERCEPTION_NONE)
+ {
+ if (!((CorDebugIntercept)info->m_activeFrame.frame->GetInterception() & Frame::Interception(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Stepping out b/c of excluded frame type:0x%x\n",
+ info->m_returnFrame. frame->GetInterception()));
+
+ fAttemptStepOut = true;
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+
+ if ((m_reason == STEP_EXCEPTION_FILTER) ||
+ (info->HasReturnFrame() &&
+ info->m_returnFrame.frame != NULL &&
+ info->m_returnFrame.frame != FRAME_TOP &&
+ info->m_returnFrame.frame->GetInterception() != Frame::INTERCEPTION_NONE))
+ {
+ if (m_reason == STEP_EXCEPTION_FILTER)
+ {
+ // Exceptions raised inside of the EE by COMPlusThrow, FCThrow, etc will not
+ // insert an ExceptionFrame, and hence info->m_returnFrame.frame->GetInterception()
+ // will not be accurate. Hence we use m_reason instead
+
+ if (!(Frame::INTERCEPTION_EXCEPTION & Frame::Interception(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Stepping out b/c of excluded INTERCEPTION_EXCEPTION\n"));
+ fAttemptStepOut = true;
+ }
+ }
+ else if (!(info->m_returnFrame.frame->GetInterception() & Frame::Interception(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI: Stepping out b/c of excluded return frame type:0x%x\n",
+ info->m_returnFrame.frame->GetInterception()));
+
+ fAttemptStepOut = true;
+ }
+
+ if (!fAttemptStepOut)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+ else if (info->m_specialChainReason != CHAIN_NONE)
+ {
+ if(!(info->m_specialChainReason & CorDebugChainReason(m_rgfInterceptStop)) )
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::DHI: (special) Stepping out b/c of excluded return frame type:0x%x\n",
+ info->m_specialChainReason));
+
+ fAttemptStepOut = true;
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::DHI 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+ else if (info->m_activeFrame.frame == NULL)
+ {
+ // Make sure we are not dealing with a chain here.
+ if (info->m_activeFrame.HasMethodFrame())
+ {
+ // Check whether we are executing in a class constructor.
+ _ASSERTE(info->m_activeFrame.md != NULL);
+ if (info->m_activeFrame.md->IsClassConstructor())
+ {
+ // We are in a class constructor. Check whether we want to stop in it.
+ if (!(CHAIN_CLASS_INIT & CorDebugChainReason(m_rgfInterceptStop)))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::DHI: Stepping out b/c of excluded cctor:0x%x\n",
+ CHAIN_CLASS_INIT));
+
+ fAttemptStepOut = true;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,"DS::DHI 0x%x set to STEP_INTERCEPT\n", this));
+
+ m_reason = STEP_INTERCEPT; //remember why we've stopped
+ }
+ }
+ }
+ }
+ }
+
+ if (fAttemptStepOut)
+ {
+ LOG((LF_CORDB,LL_INFO1000,"DS::DHI: Doing TSO!\n"));
+
+ // TrapStepOut could alter the step reason if we're stepping out of an inteceptor and it looks like we're
+ // running off the top of the program. So hold onto it here, and if our step reason becomes STEP_EXIT, then
+ // reset it to what it was.
+ CorDebugStepReason holdReason = m_reason;
+
+ // @todo - should this be TrapStepNext??? But that may stop in a child...
+ TrapStepOut(info);
+ EnableUnwind(m_fp);
+
+ if (m_reason == STEP_EXIT)
+ {
+ m_reason = holdReason;
+ }
+
+ return true;
+ }
+
+ // We're not in a special area of code, so we don't want to continue unless some other part of the code decides that
+ // we should.
+ LOG((LF_CORDB,LL_INFO1000,"DS::DHI: Returning false, finally!\n"));
+
+ return false;
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// This function checks whether the given IP is in an LCG method. If so, it enables
+// JMC and does a step out. This effectively makes sure that we never stop in an LCG method.
+//
+// There are two common scnearios here:
+// 1) We single-step into an LCG method from a managed method.
+// 2) We single-step off the end of a method called by an LCG method and end up in the calling LCG method.
+//
+// In both cases, we don't want to stop in the LCG method. If the LCG method directly or indirectly calls
+// another user method, we want to stop there. Otherwise, we just want to step out back to the caller of
+// LCG method. In other words, what we want is exactly the JMC behaviour.
+//
+// Arguments:
+// ip - the current IP where the thread is stopped at
+// pMD - This is the MethodDesc for the specified ip. This can be NULL, but if it's not,
+// then it has to match the specified IP.
+// pInfo - the ControllerStackInfo taken at the specified IP (see Notes below)
+//
+// Return Value:
+// Returns TRUE if the specified IP is indeed in an LCG method, in which case this function has already
+// enabled all the traps to catch the thread, including turning on JMC, enabling unwind callback, and
+// putting a patch in the caller.
+//
+// Notes:
+// LCG methods don't show up in stackwalks done by the ControllerStackInfo. So even if the specified IP
+// is in an LCG method, the LCG method won't show up in the call strack. That's why we need to call
+// ControllerStackInfo::SetReturnFrameWithActiveFrame() in this function before calling TrapStepOut().
+// Otherwise TrapStepOut() will put a patch in the caller's caller (if there is one).
+//
+
+BOOL DebuggerStepper::DetectHandleLCGMethods(const PCODE ip, MethodDesc * pMD, ControllerStackInfo * pInfo)
+{
+ // Look up the MethodDesc for the given IP.
+ if (pMD == NULL)
+ {
+ if (g_pEEInterface->IsManagedNativeCode((const BYTE *)ip))
+ {
+ pMD = g_pEEInterface->GetNativeCodeMethodDesc(ip);
+ _ASSERTE(pMD != NULL);
+ }
+ }
+#if defined(_DEBUG)
+ else
+ {
+ // If a MethodDesc is specified, it has to match the given IP.
+ _ASSERTE(pMD == g_pEEInterface->GetNativeCodeMethodDesc(ip));
+ }
+#endif // _DEBUG
+
+ // If the given IP is in unmanaged code, then we won't have a MethodDesc by this point.
+ if (pMD != NULL)
+ {
+ if (pMD->IsLCGMethod())
+ {
+ // Enable all the traps to catch the thread.
+ EnableUnwind(m_fp);
+ EnableJMCBackStop(pMD);
+
+ pInfo->SetReturnFrameWithActiveFrame();
+ TrapStepOut(pInfo);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+// Steppers override these so that they can skip func-evals. Note that steppers can
+// be created & used inside of func-evals (nested-break states).
+// On enter, we check for freezing the stepper.
+void DebuggerStepper::TriggerFuncEvalEnter(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DS::TFEEnter, this=0x%p, old nest=%d\n", this, m_cFuncEvalNesting));
+
+ // Since this is always called on the hijacking thread, we should be thread-safe
+ _ASSERTE(thread == this->GetThread());
+
+ if (IsDead())
+ return;
+
+ m_cFuncEvalNesting++;
+
+ if (m_cFuncEvalNesting == 1)
+ {
+ // We're entering our 1st funceval, so freeze us.
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEEnter - freezing stepper\n"));
+
+ // Freeze the stepper by disabling all triggers
+ m_bvFrozenTriggers = 0;
+
+ //
+ // We dont explicitly disable single-stepping because the OS
+ // gives us a new thread context during an exception. Since
+ // all func-evals are done inside exceptions, we should never
+ // have this problem.
+ //
+ // Note: however, that if func-evals were no longer done in
+ // exceptions, this would have to change.
+ //
+
+
+ if (IsMethodEnterEnabled())
+ {
+ m_bvFrozenTriggers |= kMethodEnter;
+ DisableMethodEnter();
+ }
+
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEEnter - new nest=%d\n", m_cFuncEvalNesting));
+ }
+}
+
+// On Func-EvalExit, we check if the stepper is trying to step-out of a func-eval
+// (in which case we kill it)
+// or if we previously entered this func-eval and should thaw it now.
+void DebuggerStepper::TriggerFuncEvalExit(Thread * thread)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DS::TFEExit, this=0x%p, old nest=%d\n", this, m_cFuncEvalNesting));
+
+ // Since this is always called on the hijacking thread, we should be thread-safe
+ _ASSERTE(thread == this->GetThread());
+
+ if (IsDead())
+ return;
+
+ m_cFuncEvalNesting--;
+
+ if (m_cFuncEvalNesting == -1)
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEExit - disabling stepper\n"));
+
+ // we're exiting the func-eval session we were created in. So we just completely
+ // disable ourselves so that we don't fire anything anymore.
+ // The RS still has to free the stepper though.
+
+ // This prevents us from stepping-out of a func-eval. For traditional steppers,
+ // this is overkill since it won't have any outstanding triggers. (trap-step-out
+ // won't patch if it crosses a func-eval frame).
+ // But JMC-steppers have Method-Enter; and so this is the only place we have to
+ // disable that.
+ DisableAll();
+ }
+ else if (m_cFuncEvalNesting == 0)
+ {
+ // We're back to our starting Func-eval session, we should have been frozen,
+ // so now we thaw.
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEExit - thawing stepper\n"));
+
+ // Thaw the stepper (reenable triggers)
+ if ((m_bvFrozenTriggers & kMethodEnter) != 0)
+ {
+ EnableMethodEnter();
+ }
+ m_bvFrozenTriggers = 0;
+
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DS::TFEExit - new nest=%d\n", m_cFuncEvalNesting));
+ }
+}
+
+
+// Return true iff we set a patch (which implies to caller that we should
+// let controller run free and hit that patch)
+bool DebuggerStepper::TrapStepInto(ControllerStackInfo *info,
+ const BYTE *ip,
+ TraceDestination *pTD)
+{
+ _ASSERTE( pTD != NULL );
+ _ASSERTE(this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER);
+
+ EnableTraceCall(LEAF_MOST_FRAME);
+ if (IsCloserToRoot(info->m_activeFrame.fp, m_fpStepInto))
+ m_fpStepInto = info->m_activeFrame.fp;
+
+ LOG((LF_CORDB, LL_INFO1000, "Ds::TSI this:0x%x m_fpStepInto:0x%x\n",
+ this, m_fpStepInto.GetSPValue()));
+
+ TraceDestination trace;
+
+ // Trace through the stubs.
+ // If we're calling from managed code, this should either succeed
+ // or become an ecall into mscorwks.
+ // @Todo - what about stubs in mscorwks.
+ // @todo - if this fails, we want to provde as much info as possible.
+ if (!g_pEEInterface->TraceStub(ip, &trace)
+ || !g_pEEInterface->FollowTrace(&trace))
+ {
+ return false;
+ }
+
+
+ (*pTD) = trace; //bitwise copy
+
+ // Step-in always operates at the leaf-most frame. Thus the frame pointer for any
+ // patch for step-in should be LEAF_MOST_FRAME, regardless of whatever our current fp
+ // is before the step-in.
+ // Note that step-in may skip 'internal' frames (FrameInfo w/ internal=true) since
+ // such frames may really just be a marker for an internal EE Frame on the stack.
+ // However, step-out uses these frames b/c it may call frame->TraceFrame() on them.
+ return PatchTrace(&trace,
+ LEAF_MOST_FRAME, // step-in is always leaf-most frame.
+ (m_rgfMappingStop&STOP_UNMANAGED)?(true):(false));
+}
+
+// Enable the JMC backstop for stepping on Step-In.
+// This activate the JMC probes, which will provide a safety net
+// to stop a stepper if the StubManagers don't predict the call properly.
+// Ideally, this should never be necessary (because the SMs would do their job).
+void DebuggerStepper::EnableJMCBackStop(MethodDesc * pStartMethod)
+{
+ // JMC steppers should not need the JMC backstop unless a thread inadvertently stops in an LCG method.
+ //_ASSERTE(DEBUGGER_CONTROLLER_JMC_STEPPER != this->GetDCType());
+
+ // Since we should never hit the JMC backstop (since it's really a SM issue), we'll assert if we actually do.
+ // However, there's 1 corner case here. If we trace calls at the start of the method before the JMC-probe,
+ // then we'll still hit the JMC backstop in our own method.
+ // Record that starting method. That way, if we end up hitting our JMC backstop in our own method,
+ // we don't over aggressively fire the assert. (This won't work for recursive cases, but since this is just
+ // changing an assert, we don't care).
+
+#ifdef _DEBUG
+ // May be NULL if we didn't start in a method.
+ m_StepInStartMethod = pStartMethod;
+#endif
+
+ // We don't want traditional steppers to rely on MethodEnter (b/c it's not guaranteed to be correct),
+ // but it may be a useful last resort.
+ this->EnableMethodEnter();
+}
+
+// Return true if the stepper can run free.
+bool DebuggerStepper::TrapStepInHelper(
+ ControllerStackInfo * pInfo,
+ const BYTE * ipCallTarget,
+ const BYTE * ipNext,
+ bool fCallingIntoFunclet)
+{
+ TraceDestination td;
+
+#ifdef _DEBUG
+ // Begin logging the step-in activity in debug builds.
+ StubManager::DbgBeginLog((TADDR) ipNext, (TADDR) ipCallTarget);
+#endif
+
+
+ if (TrapStepInto(pInfo, ipCallTarget, &td))
+ {
+ // If we placed a patch, see if we need to update our step-reason
+ if (td.GetTraceType() == TRACE_MANAGED )
+ {
+ // Possible optimization: Roll all of g_pEEInterface calls into
+ // one function so we don't repeatedly get the CodeMan,etc
+ MethodDesc *md = NULL;
+ _ASSERTE( g_pEEInterface->IsManagedNativeCode((const BYTE *)td.GetAddress()) );
+ md = g_pEEInterface->GetNativeCodeMethodDesc(td.GetAddress());
+
+ if ( g_pEEInterface->GetFunctionAddress(md) == td.GetAddress())
+ {
+
+ LOG((LF_CORDB,LL_INFO1000,"\tDS::TS 0x%x m_reason = STEP_CALL"
+ "@ip0x%x\n", this, (BYTE*)GetControlPC(&(pInfo->m_activeFrame.registers))));
+ m_reason = STEP_CALL;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Didn't step: md:0x%x"
+ "td.type:%s td.address:0x%x, gfa:0x%x\n",
+ md, GetTType(td.GetTraceType()), td.GetAddress(),
+ g_pEEInterface->GetFunctionAddress(md)));
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS else 0x%x m_reason = STEP_CALL\n",
+ this));
+ m_reason = STEP_CALL;
+ }
+
+
+ return true;
+ } // end TrapStepIn
+ else
+ {
+ // If we can't figure out where the stepper should call into (likely because we can't find a stub-manager),
+ // then enable the JMC backstop.
+ EnableJMCBackStop(pInfo->m_activeFrame.md);
+
+ }
+
+ // We ignore ipNext here. Instead we'll return false and let the caller (TrapStep)
+ // set the patch for us.
+ return false;
+}
+
+FORCEINLINE bool IsTailCall(const BYTE * pTargetIP)
+{
+ return TailCallStubManager::IsTailCallStubHelper(reinterpret_cast<PCODE>(pTargetIP));
+}
+
+// bool DebuggerStepper::TrapStep() TrapStep attepts to set a
+// patch at the next IL instruction to be executed. If we're stepping in &
+// the next IL instruction is a call, then this'll set a breakpoint inside
+// the code that will be called.
+// How: There are a number of cases, depending on where the IP
+// currently is:
+// Unmanaged code: EnableTraceCall() & return false - try and get
+// it when it returns.
+// In a frame: if the <p in> param is true, then do an
+// EnableTraceCall(). If the frame isn't the top frame, also do
+// g_pEEInterface->TraceFrame(), g_pEEInterface->FollowTrace, and
+// PatchTrace.
+// Normal managed frame: create a Walker and walk the instructions until either
+// leave the provided range (AddPatch there, return true), or we don't know what the
+// next instruction is (say, after a call, or return, or branch - return false).
+// Returns a boolean indicating if we were able to set a patch successfully
+// in either this method, or (if in == true & the next instruction is a call)
+// inside a callee method.
+// true: Patch successfully placed either in this method or a callee,
+// so the stepping is taken care of.
+// false: Unable to place patch in either this method or any
+// applicable callee methods, so the only option the caller has to put
+// patch to control flow is to call TrapStepOut & try and place a patch
+// on the method that called the current frame's method.
+bool DebuggerStepper::TrapStep(ControllerStackInfo *info, bool in)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS: this:0x%x\n", this));
+ if (!info->m_activeFrame.managed)
+ {
+ //
+ // We're not in managed code. Patch up all paths back in.
+ //
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TS: not in managed code\n"));
+
+ if (in)
+ {
+ EnablePolyTraceCall();
+ }
+
+ return false;
+ }
+
+ if (info->m_activeFrame.frame != NULL)
+ {
+
+ //
+ // We're in some kind of weird frame. Patch further entry to the frame.
+ // or if we can't, patch return from the frame
+ //
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TS: in a weird frame\n"));
+
+ if (in)
+ {
+ EnablePolyTraceCall();
+
+ // Only traditional steppers should patch a frame. JMC steppers will
+ // just rely on TriggerMethodEnter.
+ if (DEBUGGER_CONTROLLER_STEPPER == this->GetDCType())
+ {
+ if (info->m_activeFrame.frame != FRAME_TOP)
+ {
+ TraceDestination trace;
+
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+
+ // This could be anywhere, especially b/c step could be on non-leaf frame.
+ if (g_pEEInterface->TraceFrame(this->GetThread(),
+ info->m_activeFrame.frame,
+ FALSE, &trace,
+ &(info->m_activeFrame.registers))
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, info->m_activeFrame.fp,
+ (m_rgfMappingStop&STOP_UNMANAGED)?
+ (true):(false)))
+
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+#ifdef _TARGET_X86_
+ LOG((LF_CORDB,LL_INFO1000, "GetJitInfo for pc = 0x%x (addr of "
+ "that value:0x%x)\n", (const BYTE*)(GetControlPC(&info->m_activeFrame.registers)),
+ info->m_activeFrame.registers.PCTAddr));
+#endif
+
+ // Note: we used to pass in the IP from the active frame to GetJitInfo, but there seems to be no value in that, and
+ // it was causing problems creating a stepper while sitting in ndirect stubs after we'd returned from the unmanaged
+ // function that had been called.
+ DebuggerJitInfo *ji = info->m_activeFrame.GetJitInfoFromFrame();
+ if( ji != NULL )
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS: For code 0x%p, got DJI 0x%p, "
+ "from 0x%p to 0x%p\n",
+ (const BYTE*)(GetControlPC(&info->m_activeFrame.registers)),
+ ji, ji->m_addrOfCode, ji->m_addrOfCode+ji->m_sizeOfCode));
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS: For code 0x%p, "
+ "didn't get a DJI \n",
+ (const BYTE*)(GetControlPC(&info->m_activeFrame.registers))));
+ }
+
+ //
+ // We're in a normal managed frame - walk the code
+ //
+
+ NativeWalker walker;
+
+ LOG((LF_CORDB,LL_INFO1000, "DS::TS: &info->m_activeFrame.registers 0x%p\n", &info->m_activeFrame.registers));
+
+ // !!! Eventually when using the fjit, we'll want
+ // to walk the IL to get the next location, & then map
+ // it back to native.
+ walker.Init((BYTE*)GetControlPC(&(info->m_activeFrame.registers)), &info->m_activeFrame.registers);
+
+
+ // Is the active frame really the active frame?
+ // What if the thread is stopped at a managed debug event outside of a filter ctx? Eg, stopped
+ // somewhere directly in mscorwks (like sending a LogMsg or ClsLoad event) or even at WaitForSingleObject.
+ // ActiveFrame is either the stepper's initial frame or the frame of a filterctx.
+ bool fIsActivFrameLive = (info->m_activeFrame.fp == info->m_bottomFP);
+
+ // If this thread isn't stopped in managed code, it can't be at the active frame.
+ if (GetManagedStoppedCtx(this->GetThread()) == NULL)
+ {
+ fIsActivFrameLive = false;
+ }
+
+ bool fIsJump = false;
+ bool fCallingIntoFunclet = false;
+
+ // If m_activeFrame is not the actual active frame,
+ // we should skip this first switch - never single step, and
+ // assume our context is bogus.
+ if (fIsActivFrameLive)
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS: immediate?\n"));
+
+ // Note that by definition our walker must always be able to step
+ // through a single instruction, so any return
+ // of NULL IP's from those cases on the first step
+ // means that an exception is going to be generated.
+ //
+ // (On future steps, it can also mean that the destination
+ // simply can't be computed.)
+ WALK_TYPE wt = walker.GetOpcodeWalkType();
+ {
+ switch (wt)
+ {
+ case WALK_RETURN:
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS:Imm:WALK_RETURN\n"));
+
+ // Normally a 'ret' opcode means we're at the end of a function and doing a step-out.
+ // But the jit is free to use a 'ret' opcode to implement various goofy constructs like
+ // managed filters, in which case we may ret to the same function or we may ret to some
+ // internal CLR stub code.
+ // So we'll just ignore this and tell the Stepper to enable every notification it has
+ // and let the thread run free. This will include TrapStepOut() and EnableUnwind()
+ // to catch any potential filters.
+
+
+ // Go ahead and enable the single-step flag too. We know it's safe.
+ // If this lands in random code, then TriggerSingleStep will just ignore it.
+ EnableSingleStep();
+
+ // Don't set step-reason yet. If another trigger gets hit, it will set the reason.
+ return false;
+ }
+
+ case WALK_BRANCH:
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS:Imm:WALK_BRANCH\n"));
+ // A branch can be handled just like a call. If the branch is within the current method, then we just
+ // down to WALK_UNKNOWN, otherwise we handle it just like a call. Note: we need to force in=true
+ // because for a jmp, in or over is the same thing, we're still going there, and the in==true case is
+ // the case we want to use...
+ fIsJump = true;
+
+ // fall through...
+
+ case WALK_CALL:
+ LOG((LF_CORDB,LL_INFO10000, "DC::TS:Imm:WALK_CALL ip=%p nextip=%p\n", walker.GetIP(), walker.GetNextIP()));
+
+ // If we're doing some sort of intra-method jump (usually, to get EIP in a clever way, via the CALL
+ // instruction), then put the bp where we're going, NOT at the instruction following the call
+ if (IsAddrWithinFrame(ji, info->m_activeFrame.md, walker.GetIP(), walker.GetNextIP()))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "Walk call within method!" ));
+ goto LWALK_UNKNOWN;
+ }
+
+ if (walker.GetNextIP() != NULL)
+ {
+#ifdef WIN64EXCEPTIONS
+ // There are 4 places we could be jumping:
+ // 1) to the beginning of the same method (recursive call)
+ // 2) somewhere in the same funclet, that isn't the method start
+ // 3) somewhere in the same method but different funclet
+ // 4) somewhere in a different method
+ //
+ // IsAddrWithinFrame ruled out option 2, IsAddrWithinMethodIncludingFunclet rules out option 4,
+ // and checking the IP against the start address rules out option 1. That leaves option only what we
+ // wanted, option #3
+ fCallingIntoFunclet = IsAddrWithinMethodIncludingFunclet(ji, info->m_activeFrame.md, walker.GetNextIP()) &&
+ ((CORDB_ADDRESS)(SIZE_T)walker.GetNextIP() != ji->m_addrOfCode);
+#endif
+ // At this point, we know that the call/branch target is not in the current method.
+ // So if the current instruction is a jump, this must be a tail call or possibly a jump to the finally.
+ // So, check if the call/branch target is the JIT helper for handling tail calls if we are not calling
+ // into the funclet.
+ if ((fIsJump && !fCallingIntoFunclet) || IsTailCall(walker.GetNextIP()))
+ {
+ // A step-over becomes a step-out for a tail call.
+ if (!in)
+ {
+ TrapStepOut(info);
+ return true;
+ }
+ }
+
+ // To preserve the old behaviour, if this is not a tail call, then we assume we want to
+ // follow the call/jump.
+ if (fIsJump)
+ {
+ in = true;
+ }
+
+
+ // There are two cases where we need to perform a step-in. One, if the step operation is
+ // a step-in. Two, if the target address of the call is in a funclet of the current method.
+ // In this case, we want to step into the funclet even if the step operation is a step-over.
+ if (in || fCallingIntoFunclet)
+ {
+ if (TrapStepInHelper(info, walker.GetNextIP(), walker.GetSkipIP(), fCallingIntoFunclet))
+ {
+ return true;
+ }
+ }
+
+ }
+ if (walker.GetSkipIP() == NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS 0x%x m_reason = STEP_CALL (skip)\n",
+ this));
+ m_reason = STEP_CALL;
+
+ return true;
+ }
+
+
+ LOG((LF_CORDB,LL_INFO100000, "DC::TS:Imm:WALK_CALL Skip instruction\n"));
+ walker.Skip();
+ break;
+
+ case WALK_UNKNOWN:
+ LWALK_UNKNOWN:
+ LOG((LF_CORDB,LL_INFO10000,"DS::TS:WALK_UNKNOWN - curIP:0x%x "
+ "nextIP:0x%x skipIP:0x%x 1st byte of opcode:0x%x\n", (BYTE*)GetControlPC(&(info->m_activeFrame.
+ registers)), walker.GetNextIP(),walker.GetSkipIP(),
+ *(BYTE*)GetControlPC(&(info->m_activeFrame.registers))));
+
+ EnableSingleStep();
+
+ return true;
+
+ default:
+ if (walker.GetNextIP() == NULL)
+ {
+ return true;
+ }
+
+ walker.Next();
+ }
+ }
+ } // if (fIsActivFrameLive)
+
+ //
+ // Use our range, if we're in the original
+ // frame.
+ //
+
+ COR_DEBUG_STEP_RANGE *range;
+ SIZE_T rangeCount;
+
+ if (info->m_activeFrame.fp == m_fp)
+ {
+ range = m_range;
+ rangeCount = m_rangeCount;
+ }
+ else
+ {
+ range = NULL;
+ rangeCount = 0;
+ }
+
+ //
+ // Keep walking until either we're out of range, or
+ // else we can't predict ahead any more.
+ //
+
+ while (TRUE)
+ {
+ const BYTE *ip = walker.GetIP();
+
+ SIZE_T offset = CodeRegionInfo::GetCodeRegionInfo(ji, info->m_activeFrame.md).AddressToOffset(ip);
+
+ LOG((LF_CORDB, LL_INFO1000, "Walking to ip 0x%p (natOff:0x%x)\n",ip,offset));
+
+ if (!IsInRange(offset, range, rangeCount)
+ && !ShouldContinueStep( info, offset ))
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+
+ switch (walker.GetOpcodeWalkType())
+ {
+ case WALK_RETURN:
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_RETURN Adding Patch.\n"));
+
+ // In the loop above, if we're at the return address, we'll check & see
+ // if we're returning to elsewhere within the same method, and if so,
+ // we'll single step rather than TrapStepOut. If we see a return in the
+ // code stream, then we'll set a breakpoint there, so that we can
+ // examine the return address, and decide whether to SS or TSO then
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+
+ case WALK_CALL:
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL.\n"));
+
+ // If we're doing some sort of intra-method jump (usually, to get EIP in a clever way, via the CALL
+ // instruction), then put the bp where we're going, NOT at the instruction following the call
+ if (IsAddrWithinFrame(ji, info->m_activeFrame.md, walker.GetIP(), walker.GetNextIP()))
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL IsAddrWithinFrame, Adding Patch.\n"));
+
+ // How else to detect this?
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ CodeRegionInfo::GetCodeRegionInfo(ji, info->m_activeFrame.md).AddressToOffset(walker.GetNextIP()),
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+
+ if (IsTailCall(walker.GetNextIP()))
+ {
+ if (!in)
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+ }
+
+#ifdef WIN64EXCEPTIONS
+ fCallingIntoFunclet = IsAddrWithinMethodIncludingFunclet(ji, info->m_activeFrame.md, walker.GetNextIP());
+#endif
+ if (in || fCallingIntoFunclet)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL step in is true\n"));
+ if (walker.GetNextIP() == NULL)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL NextIP == NULL\n"));
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB,LL_INFO10000,"DS0x%x m_reason=STEP_CALL 2\n",
+ this));
+ m_reason = STEP_CALL;
+
+ return true;
+ }
+
+ if (TrapStepInHelper(info, walker.GetNextIP(), walker.GetSkipIP(), fCallingIntoFunclet))
+ {
+ return true;
+ }
+
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: WALK_CALL Calling GetSkipIP\n"));
+ if (walker.GetSkipIP() == NULL)
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB,LL_INFO10000,"DS 0x%x m_reason=STEP_CALL4\n",this));
+ m_reason = STEP_CALL;
+
+ return true;
+ }
+
+ walker.Skip();
+ LOG((LF_CORDB, LL_INFO10000, "DS::TS: skipping over call.\n"));
+ break;
+
+ default:
+ if (walker.GetNextIP() == NULL)
+ {
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ ji,
+ offset,
+ info->m_returnFrame.fp,
+ NULL);
+ return true;
+ }
+ walker.Next();
+ break;
+ }
+ }
+ LOG((LF_CORDB,LL_INFO1000,"Ending TrapStep\n"));
+}
+
+bool DebuggerStepper::IsAddrWithinFrame(DebuggerJitInfo *dji,
+ MethodDesc* pMD,
+ const BYTE* currentAddr,
+ const BYTE* targetAddr)
+{
+ _ASSERTE(dji != NULL);
+
+ bool result = IsAddrWithinMethodIncludingFunclet(dji, pMD, targetAddr);
+
+ // We need to check if this is a recursive call. In RTM we should see if this method is really necessary,
+ // since it looks like the X86 JIT doesn't emit intra-method jumps anymore.
+ if (result)
+ {
+ if ((CORDB_ADDRESS)(SIZE_T)targetAddr == dji->m_addrOfCode)
+ {
+ result = false;
+ }
+ }
+
+#if defined(WIN64EXCEPTIONS)
+ // On WIN64, we also check whether the targetAddr and the currentAddr is in the same funclet.
+ _ASSERTE(currentAddr != NULL);
+ if (result)
+ {
+ int currentFuncletIndex = dji->GetFuncletIndex((CORDB_ADDRESS)currentAddr, DebuggerJitInfo::GFIM_BYADDRESS);
+ int targetFuncletIndex = dji->GetFuncletIndex((CORDB_ADDRESS)targetAddr, DebuggerJitInfo::GFIM_BYADDRESS);
+ result = (currentFuncletIndex == targetFuncletIndex);
+ }
+#endif // WIN64EXCEPTIONS
+
+ return result;
+}
+
+// x86 shouldn't need to call this method directly. We should call IsAddrWithinFrame() on x86 instead.
+// That's why I use a name with the word "funclet" in it to scare people off.
+bool DebuggerStepper::IsAddrWithinMethodIncludingFunclet(DebuggerJitInfo *dji,
+ MethodDesc* pMD,
+ const BYTE* targetAddr)
+{
+ _ASSERTE(dji != NULL);
+ return CodeRegionInfo::GetCodeRegionInfo(dji, pMD).IsMethodAddress(targetAddr);
+}
+
+void DebuggerStepper::TrapStepNext(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DS::TrapStepNext, this=%p\n", this));
+ // StepNext for a Normal stepper is just a step-out
+ TrapStepOut(info);
+
+ // @todo -should we also EnableTraceCall??
+}
+
+// Is this frame interesting?
+// For a traditional stepper, all frames are interesting.
+bool DebuggerStepper::IsInterestingFrame(FrameInfo * pFrame)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return true;
+}
+
+// Place a single patch somewhere up the stack to do a step-out
+void DebuggerStepper::TrapStepOut(ControllerStackInfo *info, bool fForceTraditional)
+{
+ ControllerStackInfo returnInfo;
+ DebuggerJitInfo *dji;
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TSO this:0x%p\n", this));
+
+ bool fReturningFromFinallyFunclet = false;
+
+#if defined(WIN64EXCEPTIONS)
+ // When we step out of a funclet, we should do one of two things, depending
+ // on the original stepping intention:
+ // 1) If we originally want to step out, then we should skip the parent method.
+ // 2) If we originally want to step in/over but we step off the end of the funclet,
+ // then we should resume in the parent, if possible.
+ if (info->m_activeFrame.IsNonFilterFuncletFrame())
+ {
+ // There should always be a frame for the parent method.
+ _ASSERTE(info->HasReturnFrame());
+
+#ifdef _TARGET_ARM_
+ while (info->HasReturnFrame() && info->m_activeFrame.md != info->m_returnFrame.md)
+ {
+ StackTraceTicket ticket(info);
+ returnInfo.GetStackInfo(ticket, GetThread(), info->m_returnFrame.fp, NULL);
+ info = &returnInfo;
+ }
+
+ _ASSERTE(info->HasReturnFrame());
+#endif
+
+ _ASSERTE(info->m_activeFrame.md == info->m_returnFrame.md);
+
+ if (m_eMode == cStepOut)
+ {
+ StackTraceTicket ticket(info);
+ returnInfo.GetStackInfo(ticket, GetThread(), info->m_returnFrame.fp, NULL);
+ info = &returnInfo;
+ }
+ else
+ {
+ _ASSERTE(info->m_returnFrame.managed);
+ _ASSERTE(info->m_returnFrame.frame == NULL);
+
+ MethodDesc *md = info->m_returnFrame.md;
+ dji = info->m_returnFrame.GetJitInfoFromFrame();
+
+ // The return value of a catch funclet is the control PC to resume to.
+ // The return value of a finally funclet has no meaning, so we need to check
+ // if the return value is in the main method.
+ LPVOID resumePC = GetRegdisplayReturnValue(&(info->m_activeFrame.registers));
+
+ // For finally funclet, there are two possible situations. Either the finally is
+ // called normally (i.e. no exception), in which case we simply fall through and
+ // let the normal loop do its work below, or the finally is called by the EH
+ // routines, in which case we need the unwind notification.
+ if (IsAddrWithinMethodIncludingFunclet(dji, md, (const BYTE *)resumePC))
+ {
+ SIZE_T reloffset = dji->m_codeRegionInfo.AddressToOffset((BYTE*)resumePC);
+
+ AddBindAndActivateNativeManagedPatch(info->m_returnFrame.md,
+ dji,
+ reloffset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO:normally managed code AddPatch"
+ " in %s::%s, offset 0x%x, m_reason=%d\n",
+ info->m_returnFrame.md->m_pszDebugClassName,
+ info->m_returnFrame.md->m_pszDebugMethodName,
+ reloffset, m_reason));
+
+ // Do not set m_reason to STEP_RETURN here. Logically, the funclet and the parent method are the
+ // same method, so we should not "return" to the parent method.
+ LOG((LF_CORDB, LL_INFO10000,"DS::TSO: done\n"));
+
+ return;
+ }
+ else
+ {
+ // This is the case where we step off the end of a finally funclet.
+ fReturningFromFinallyFunclet = true;
+ }
+ }
+ }
+#endif // WIN64EXCEPTIONS
+
+#ifdef _DEBUG
+ FramePointer dbgLastFP; // for debug, make sure we're making progress through the stack.
+#endif
+
+ while (info->HasReturnFrame())
+ {
+
+#ifdef _DEBUG
+ dbgLastFP = info->m_activeFrame.fp;
+#endif
+
+ // Continue walking up the stack & set a patch upon the next
+ // frame up. We will eventually either hit managed code
+ // (which we can set a definite patch in), or the top of the
+ // stack.
+ StackTraceTicket ticket(info);
+
+ // The last parameter here is part of a really targetted (*cough* dirty) fix to
+ // disable getting an unwanted UMChain to fix issue 650903 (See
+ // code:ControllerStackInfo::WalkStack and code:TrackUMChain for the other
+ // parts.) In the case of managed step out we know that we aren't interested in
+ // unmanaged frames, and generating that unmanaged frame causes the stackwalker
+ // not to report the managed frame that was at the same SP. However the unmanaged
+ // frame might be used in the mixed-mode step out case so I don't suppress it
+ // there.
+ returnInfo.GetStackInfo(ticket, GetThread(), info->m_returnFrame.fp, NULL, !(m_rgfMappingStop & STOP_UNMANAGED));
+ info = &returnInfo;
+
+#ifdef _DEBUG
+ // If this assert fires, then it means that we're not making progress while
+ // tracing up the towards the root of the stack. Likely an issue in the Left-Side's
+ // stackwalker.
+ _ASSERTE(IsCloserToLeaf(dbgLastFP, info->m_activeFrame.fp));
+#endif
+
+#ifdef FEATURE_STUBS_AS_IL
+ if (info->m_activeFrame.md->IsILStub() && info->m_activeFrame.md->AsDynamicMethodDesc()->IsMulticastStub())
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: multicast frame.\n"));
+
+ // User break should always be called from managed code, so it should never actually hit this codepath.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+ // JMC steppers shouldn't be patching stubs.
+ if (DEBUGGER_CONTROLLER_JMC_STEPPER == this->GetDCType())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TSO: JMC stepper skipping frame.\n"));
+ continue;
+ }
+
+ TraceDestination trace;
+
+ EnableTraceCall(info->m_activeFrame.fp);
+
+ PCODE ip = GetControlPC(&(info->m_activeFrame.registers));
+ if (g_pEEInterface->TraceStub((BYTE*)ip, &trace)
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, info->m_activeFrame.fp,
+ true))
+ break;
+ }
+ else
+#endif // FEATURE_STUBS_AS_IL
+ if (info->m_activeFrame.managed)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: return frame is managed.\n"));
+
+ if (info->m_activeFrame.frame == NULL)
+ {
+ // Returning normally to managed code.
+ _ASSERTE(info->m_activeFrame.md != NULL);
+
+ // Polymorphic check to skip over non-interesting frames.
+ if (!fForceTraditional && !this->IsInterestingFrame(&info->m_activeFrame))
+ continue;
+
+ dji = info->m_activeFrame.GetJitInfoFromFrame();
+ _ASSERTE(dji != NULL);
+
+ // Note: we used to pass in the IP from the active frame to GetJitInfo, but there seems to be no value
+ // in that, and it was causing problems creating a stepper while sitting in ndirect stubs after we'd
+ // returned from the unmanaged function that had been called.
+ ULONG reloffset = info->m_activeFrame.relOffset;
+
+ AddBindAndActivateNativeManagedPatch(info->m_activeFrame.md,
+ dji,
+ reloffset,
+ info->m_returnFrame.fp,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO:normally managed code AddPatch"
+ " in %s::%s, offset 0x%x, m_reason=%d\n",
+ info->m_activeFrame.md->m_pszDebugClassName,
+ info->m_activeFrame.md->m_pszDebugMethodName,
+ reloffset, m_reason));
+
+
+ // Do not set m_reason to STEP_RETURN here. Logically, the funclet and the parent method are the
+ // same method, so we should not "return" to the parent method.
+ if (!fReturningFromFinallyFunclet)
+ {
+ m_reason = STEP_RETURN;
+ }
+ break;
+ }
+ else if (info->m_activeFrame.frame == FRAME_TOP)
+ {
+
+ // Trad-stepper's step-out is actually like a step-next when we go off the top.
+ // JMC-steppers do a true-step out. So for JMC-steppers, don't enable trace-call.
+ if (DEBUGGER_CONTROLLER_JMC_STEPPER == this->GetDCType())
+ {
+ LOG((LF_CORDB, LL_EVERYTHING, "DS::TSO: JMC stepper skipping exit-frame case.\n"));
+ break;
+ }
+
+ // User break should always be called from managed code, so it should never actually hit this codepath.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+
+ // We're walking off the top of the stack. Note that if we call managed code again,
+ // this trace-call will cause us our stepper-to fire. So we'll actually do a
+ // step-next; not a true-step out.
+ EnableTraceCall(info->m_activeFrame.fp);
+
+ LOG((LF_CORDB, LL_INFO1000, "DS::TSO: Off top of frame!\n"));
+
+ m_reason = STEP_EXIT; //we're on the way out..
+
+ // <REVISIT_TODO>@todo not that it matters since we don't send a
+ // stepComplete message to the right side.</REVISIT_TODO>
+ break;
+ }
+ else if (info->m_activeFrame.frame->GetFrameType() == Frame::TYPE_FUNC_EVAL)
+ {
+ // Note: we treat walking off the top of the stack and
+ // walking off the top of a func eval the same way,
+ // except that we don't enable trace call since we
+ // know exactly where were going.
+
+ LOG((LF_CORDB, LL_INFO1000,
+ "DS::TSO: Off top of func eval!\n"));
+
+ m_reason = STEP_EXIT;
+ break;
+ }
+ else if (info->m_activeFrame.frame->GetFrameType() == Frame::TYPE_SECURITY &&
+ info->m_activeFrame.frame->GetInterception() == Frame::INTERCEPTION_NONE)
+ {
+ // If we're stepping out of something that was protected by (declarative) security,
+ // the security subsystem may leave a frame on the stack to cache it's computation.
+ // HOWEVER, this isn't a real frame, and so we don't want to stop here. On the other
+ // hand, if we're in the security goop (sec. executes managed code to do stuff), then
+ // we'll want to use the "returning to stub case", below. GetInterception()==NONE
+ // indicates that the frame is just a cache frame:
+ // Skip it and keep on going
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: returning to a non-intercepting frame. Keep unwinding\n"));
+ continue;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: returning to a stub frame.\n"));
+
+ // User break should always be called from managed code, so it should never actually hit this codepath.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+ // JMC steppers shouldn't be patching stubs.
+ if (DEBUGGER_CONTROLLER_JMC_STEPPER == this->GetDCType())
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TSO: JMC stepper skipping frame.\n"));
+ continue;
+ }
+
+ // We're returning to some funky frame.
+ // (E.g. a security frame has called a native method.)
+
+ // Patch the frame from entering other methods. This effectively gives the Step-out
+ // a step-next behavior. For eg, this can be useful for step-out going between multicast delegates.
+ // This step-next could actually land us leaf-more on the callstack than we currently are!
+ // If we were a true-step out, we'd skip this and keep crawling.
+ // up the callstack.
+ //
+ // !!! For now, we assume that the TraceFrame entry
+ // point is smart enough to tell where it is in the
+ // calling sequence. We'll see how this holds up.
+ TraceDestination trace;
+
+ // We don't want notifications of trace-calls leaf-more than our current frame.
+ // For eg, if our current frame calls out to unmanaged code and then back in,
+ // we'll get a TraceCall notification. But since it's leaf-more than our current frame,
+ // we don't care because we just want to step out of our current frame (and everything
+ // our current frame may call).
+ EnableTraceCall(info->m_activeFrame.fp);
+
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+
+ if (g_pEEInterface->TraceFrame(GetThread(),
+ info->m_activeFrame.frame, FALSE,
+ &trace, &(info->m_activeFrame.registers))
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, info->m_activeFrame.fp,
+ true))
+ break;
+
+ // !!! Problem: we don't know which return frame to use -
+ // the TraceFrame patch may be in a frame below the return
+ // frame, or in a frame parallel with it
+ // (e.g. prestub popping itself & then calling.)
+ //
+ // For now, I've tweaked the FP comparison in the
+ // patch dispatching code to allow either case.
+ }
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: return frame is not managed.\n"));
+
+ // Only step out to unmanaged code if we're actually
+ // marked to stop in unamanged code. Otherwise, just loop
+ // to get us past the unmanaged frames.
+ if (m_rgfMappingStop & STOP_UNMANAGED)
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: return to unmanaged code "
+ "m_reason=STEP_RETURN\n"));
+
+ // Do not set m_reason to STEP_RETURN here. Logically, the funclet and the parent method are the
+ // same method, so we should not "return" to the parent method.
+ if (!fReturningFromFinallyFunclet)
+ {
+ m_reason = STEP_RETURN;
+ }
+
+ // We're stepping out into unmanaged code
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TSO: Setting unmanaged trace patch at 0x%x(%x)\n",
+ GetControlPC(&(info->m_activeFrame.registers)),
+ info->m_returnFrame.fp.GetSPValue()));
+
+
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE *)GetControlPC(&(info->m_activeFrame.registers)),
+ info->m_returnFrame.fp,
+ FALSE,
+ TRACE_UNMANAGED);
+
+ break;
+
+ }
+ }
+ }
+
+ // <REVISIT_TODO>If we get here, we may be stepping out of the last frame. Our thread
+ // exit logic should catch this case. (@todo)</REVISIT_TODO>
+ LOG((LF_CORDB, LL_INFO10000,"DS::TSO: done\n"));
+}
+
+
+// void DebuggerStepper::StepOut()
+// Called by Debugger::HandleIPCEvent to setup
+// everything so that the process will step over the range of IL
+// correctly.
+// How: Converts the provided array of ranges from IL ranges to
+// native ranges (if they're not native already), and then calls
+// TrapStep or TrapStepOut, like so:
+// Get the appropriate MethodDesc & JitInfo
+// Iterate through array of IL ranges, use
+// JitInfo::MapILRangeToMapEntryRange to translate IL to native
+// ranges.
+// Set member variables to remember that the DebuggerStepper now uses
+// the ranges: m_range, m_rangeCount, m_stepIn, m_fp
+// If (!TrapStep()) then {m_stepIn = true; TrapStepOut()}
+// EnableUnwind( m_fp );
+void DebuggerStepper::StepOut(FramePointer fp, StackTraceTicket ticket)
+{
+ LOG((LF_CORDB, LL_INFO10000, "Attempting to step out, fp:0x%x this:0x%x"
+ "\n", fp.GetSPValue(), this ));
+
+ Thread *thread = GetThread();
+
+
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+ ControllerStackInfo info;
+
+ // We pass in the ticket b/c this is called both when we're live (via
+ // DebuggerUserBreakpoint) and when we're stopped (via normal StepOut)
+ info.GetStackInfo(ticket, thread, fp, context);
+
+
+ ResetRange();
+
+
+ m_stepIn = FALSE;
+ m_fp = info.m_activeFrame.fp;
+#if defined(WIN64EXCEPTIONS)
+ // We need to remember the parent method frame pointer here so that we will recognize
+ // the range of the stepper as being valid when we return to the parent method.
+ if (info.m_activeFrame.IsNonFilterFuncletFrame())
+ {
+ m_fpParentMethod = info.m_returnFrame.fp;
+ }
+#endif // WIN64EXCEPTIONS
+
+ m_eMode = cStepOut;
+
+ _ASSERTE((fp == LEAF_MOST_FRAME) || (info.m_activeFrame.md != NULL) || (info.m_returnFrame.md != NULL));
+
+ TrapStepOut(&info);
+ EnableUnwind(m_fp);
+}
+
+#define GROW_RANGES_IF_NECESSARY() \
+ if (rTo == rToEnd) \
+ { \
+ ULONG NewSize, OldSize; \
+ if (!ClrSafeInt<ULONG>::multiply(sizeof(COR_DEBUG_STEP_RANGE), (ULONG)(realRangeCount*2), NewSize) || \
+ !ClrSafeInt<ULONG>::multiply(sizeof(COR_DEBUG_STEP_RANGE), (ULONG)realRangeCount, OldSize) || \
+ NewSize < OldSize) \
+ { \
+ DeleteInteropSafe(m_range); \
+ m_range = NULL; \
+ return false; \
+ } \
+ COR_DEBUG_STEP_RANGE *_pTmp = (COR_DEBUG_STEP_RANGE*) \
+ g_pDebugger->GetInteropSafeHeap()->Realloc(m_range, \
+ NewSize, \
+ OldSize); \
+ \
+ if (_pTmp == NULL) \
+ { \
+ DeleteInteropSafe(m_range); \
+ m_range = NULL; \
+ return false; \
+ } \
+ \
+ m_range = _pTmp; \
+ rTo = m_range + realRangeCount; \
+ rToEnd = m_range + (realRangeCount*2); \
+ realRangeCount *= 2; \
+ }
+
+//-----------------------------------------------------------------------------
+// Given a set of IL ranges, convert them to native and cache them.
+// Return true on success, false on error.
+//-----------------------------------------------------------------------------
+bool DebuggerStepper::SetRangesFromIL(DebuggerJitInfo *dji, COR_DEBUG_STEP_RANGE *ranges, SIZE_T rangeCount)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ WRAPPER(THROWS);
+ GC_NOTRIGGER;
+ PRECONDITION(ThisIsHelperThreadWorker()); // Only help initializes a stepper.
+ PRECONDITION(m_range == NULL); // shouldn't be set already.
+ PRECONDITION(CheckPointer(ranges));
+ PRECONDITION(CheckPointer(dji));
+ }
+ CONTRACTL_END;
+
+ // Note: we used to pass in the IP from the active frame to GetJitInfo, but there seems to be no value in that, and
+ // it was causing problems creating a stepper while sitting in ndirect stubs after we'd returned from the unmanaged
+ // function that had been called.
+ MethodDesc *fd = dji->m_fd;
+
+ // The "+1" is for internal use, when we need to
+ // set an intermediate patch in pitched code. Isn't
+ // used unless the method is pitched & a patch is set
+ // inside it. Thus we still pass cRanges as the
+ // range count.
+ m_range = new (interopsafe) COR_DEBUG_STEP_RANGE[rangeCount+1];
+
+ if (m_range == NULL)
+ return false;
+
+ TRACE_ALLOC(m_range);
+
+ SIZE_T realRangeCount = rangeCount;
+
+ if (dji != NULL)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DeSt::St: For code md=0x%x, got DJI 0x%x, from 0x%x to 0x%x\n",
+ fd,
+ dji, dji->m_addrOfCode, (ULONG)dji->m_addrOfCode
+ + (ULONG)dji->m_sizeOfCode));
+
+ //
+ // Map ranges to native offsets for jitted code
+ //
+ COR_DEBUG_STEP_RANGE *r, *rEnd, *rTo, *rToEnd;
+
+ r = ranges;
+ rEnd = r + rangeCount;
+
+ rTo = m_range;
+ rToEnd = rTo + realRangeCount;
+
+ // <NOTE>
+ // rTo may also be incremented in the middle of the loop on WIN64 platforms.
+ // </NOTE>
+ for (/**/; r < rEnd; r++, rTo++)
+ {
+ // If we are already at the end of our allocated array, but there are still
+ // more ranges to copy over, then grow the array.
+ GROW_RANGES_IF_NECESSARY();
+
+ if (r->startOffset == 0 && r->endOffset == (ULONG) ~0)
+ {
+ // {0...-1} means use the entire method as the range
+ // Code dup'd from below case.
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step: Have DJI, special (0,-1) entry\n"));
+ rTo->startOffset = 0;
+ rTo->endOffset = (ULONG32)g_pEEInterface->GetFunctionSize(fd);
+ }
+ else
+ {
+ //
+ // One IL range may consist of multiple
+ // native ranges.
+ //
+
+ DebuggerILToNativeMap *mStart, *mEnd;
+
+ dji->MapILRangeToMapEntryRange(r->startOffset,
+ r->endOffset,
+ &mStart,
+ &mEnd);
+
+ // Either mStart and mEnd are both NULL (we don't have any sequence point),
+ // or they are both non-NULL.
+ _ASSERTE( ((mStart == NULL) && (mEnd == NULL)) ||
+ ((mStart != NULL) && (mEnd != NULL)) );
+
+ if (mStart == NULL)
+ {
+ // <REVISIT_TODO>@todo Won't this result in us stepping across
+ // the entire method?</REVISIT_TODO>
+ rTo->startOffset = 0;
+ rTo->endOffset = 0;
+ }
+ else if (mStart == mEnd)
+ {
+ rTo->startOffset = mStart->nativeStartOffset;
+ rTo->endOffset = mStart->nativeEndOffset;
+ }
+ else
+ {
+ // Account for more than one continuous range here.
+
+ // Move the pointer back to work with the loop increment below.
+ // Don't dereference this pointer now!
+ rTo--;
+
+ for (DebuggerILToNativeMap* pMap = mStart;
+ pMap <= mEnd;
+ pMap = pMap + 1)
+ {
+ if ((pMap == mStart) ||
+ (pMap->nativeStartOffset != (pMap-1)->nativeEndOffset))
+ {
+ rTo++;
+ GROW_RANGES_IF_NECESSARY();
+
+ rTo->startOffset = pMap->nativeStartOffset;
+ rTo->endOffset = pMap->nativeEndOffset;
+ }
+ else
+ {
+ // If we have continuous ranges, then lump them together.
+ _ASSERTE(rTo->endOffset == pMap->nativeStartOffset);
+ rTo->endOffset = pMap->nativeEndOffset;
+ }
+ }
+
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step: nat off:0x%x to 0x%x\n", rTo->startOffset, rTo->endOffset));
+ }
+ }
+ }
+
+ rangeCount = (int)((BYTE*)rTo - (BYTE*)m_range) / sizeof(COR_DEBUG_STEP_RANGE);
+ }
+ else
+ {
+ // Even if we don't have debug info, we'll be able to
+ // step through the method
+ SIZE_T functionSize = g_pEEInterface->GetFunctionSize(fd);
+
+ COR_DEBUG_STEP_RANGE *r = ranges;
+ COR_DEBUG_STEP_RANGE *rEnd = r + rangeCount;
+
+ COR_DEBUG_STEP_RANGE *rTo = m_range;
+
+ for(/**/; r < rEnd; r++, rTo++)
+ {
+ if (r->startOffset == 0 && r->endOffset == (ULONG) ~0)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, (0,-1) special entry\n"));
+ // Code dup'd from above case.
+ // {0...-1} means use the entire method as the range
+ rTo->startOffset = 0;
+ rTo->endOffset = (ULONG32)functionSize;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step:No DJI, regular entry\n"));
+ // We can't just leave ths IL entry - we have to
+ // get rid of it.
+ // This will just be ignored
+ rTo->startOffset = rTo->endOffset = (ULONG32)functionSize;
+ }
+ }
+ }
+
+
+ m_rangeCount = rangeCount;
+ m_realRangeCount = rangeCount;
+
+ return true;
+}
+
+
+// void DebuggerStepper::Step() Tells the stepper to step over
+// the provided ranges.
+// void *fp: frame pointer.
+// bool in: true if we want to step into a function within the range,
+// false if we want to step over functions within the range.
+// COR_DEBUG_STEP_RANGE *ranges: Assumed to be nonNULL, it will
+// always hold at least one element.
+// SIZE_T rangeCount: One less than the true number of elements in
+// the ranges argument.
+// bool rangeIL: true if the ranges are provided in IL (they'll be
+// converted to native before the DebuggerStepper uses them,
+// false if they already are native.
+bool DebuggerStepper::Step(FramePointer fp, bool in,
+ COR_DEBUG_STEP_RANGE *ranges, SIZE_T rangeCount,
+ bool rangeIL)
+{
+ LOG((LF_CORDB, LL_INFO1000, "DeSt:Step this:0x%x ", this));
+ if (rangeCount>0)
+ LOG((LF_CORDB,LL_INFO10000," start,end[0]:(0x%x,0x%x)\n",
+ ranges[0].startOffset, ranges[0].endOffset));
+ else
+ LOG((LF_CORDB,LL_INFO10000," single step\n"));
+
+ Thread *thread = GetThread();
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+
+ // ControllerStackInfo doesn't report IL stubs, so if we are in an IL stub, we need
+ // to handle the single-step specially. There are probably other problems when we stop
+ // in an IL stub. We need to revisit this later.
+ bool fIsILStub = false;
+ if ((context != NULL) &&
+ g_pEEInterface->IsManagedNativeCode(reinterpret_cast<const BYTE *>(GetIP(context))))
+ {
+ MethodDesc * pMD = g_pEEInterface->GetNativeCodeMethodDesc(GetIP(context));
+ if (pMD != NULL)
+ {
+ fIsILStub = pMD->IsILStub();
+ }
+ }
+ LOG((LF_CORDB, LL_INFO10000, "DS::S - fIsILStub = %d\n", fIsILStub));
+
+ ControllerStackInfo info;
+
+
+ StackTraceTicket ticket(thread);
+ info.GetStackInfo(ticket, thread, fp, context);
+
+ _ASSERTE((fp == LEAF_MOST_FRAME) || (info.m_activeFrame.md != NULL) ||
+ (info.m_returnFrame.md != NULL));
+
+ m_stepIn = in;
+
+ DebuggerJitInfo *dji = info.m_activeFrame.GetJitInfoFromFrame();
+
+ if (dji == NULL)
+ {
+ // !!! ERROR range step in frame with no code
+ ranges = NULL;
+ rangeCount = 0;
+ }
+
+
+ if (m_range != NULL)
+ {
+ TRACE_FREE(m_range);
+ DeleteInteropSafe(m_range);
+ m_range = NULL;
+ m_rangeCount = 0;
+ m_realRangeCount = 0;
+ }
+
+ if (rangeCount > 0)
+ {
+ if (rangeIL)
+ {
+ // IL ranges supplied, we need to convert them to native ranges.
+ bool fOk = SetRangesFromIL(dji, ranges, rangeCount);
+ if (!fOk)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ // Native ranges, already supplied. Just copy them over.
+ m_range = new (interopsafe) COR_DEBUG_STEP_RANGE[rangeCount];
+
+ if (m_range == NULL)
+ {
+ return false;
+ }
+
+ memcpy(m_range, ranges, sizeof(COR_DEBUG_STEP_RANGE) * rangeCount);
+ m_realRangeCount = m_rangeCount = rangeCount;
+ }
+ _ASSERTE(m_range != NULL);
+ _ASSERTE(m_rangeCount > 0);
+ _ASSERTE(m_realRangeCount > 0);
+ }
+ else
+ {
+ // !!! ERROR cannot map IL ranges
+ ranges = NULL;
+ rangeCount = 0;
+ }
+
+ if (fIsILStub)
+ {
+ // Don't use the ControllerStackInfo if we are in an IL stub.
+ m_fp = fp;
+ }
+ else
+ {
+ m_fp = info.m_activeFrame.fp;
+#if defined(WIN64EXCEPTIONS)
+ // We need to remember the parent method frame pointer here so that we will recognize
+ // the range of the stepper as being valid when we return to the parent method.
+ if (info.m_activeFrame.IsNonFilterFuncletFrame())
+ {
+ m_fpParentMethod = info.m_returnFrame.fp;
+ }
+#endif // WIN64EXCEPTIONS
+ }
+ m_eMode = m_stepIn ? cStepIn : cStepOver;
+
+ LOG((LF_CORDB,LL_INFO10000,"DS 0x%x STep: STEP_NORMAL\n",this));
+ m_reason = STEP_NORMAL; //assume it'll be a normal step & set it to
+ //something else if we walk over it
+ if (fIsILStub)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS:Step: stepping in an IL stub\n"));
+
+ // Enable the right triggers if the user wants to step in.
+ if (in)
+ {
+ if (this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER)
+ {
+ EnableTraceCall(info.m_activeFrame.fp);
+ }
+ else if (this->GetDCType() == DEBUGGER_CONTROLLER_JMC_STEPPER)
+ {
+ EnableMethodEnter();
+ }
+ }
+
+ // Also perform a step-out in case this IL stub is returning to managed code.
+ // However, we must fix up the ControllerStackInfo first, since it doesn't
+ // report IL stubs. The active frame reported by the ControllerStackInfo is
+ // actually the return frame in this case.
+ info.SetReturnFrameWithActiveFrame();
+ TrapStepOut(&info);
+ }
+ else if (!TrapStep(&info, in))
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS\n"));
+ m_stepIn = true;
+ TrapStepNext(&info);
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"DS:Step: Did TS,TSO\n"));
+
+ EnableUnwind(m_fp);
+
+ return true;
+}
+
+// TP_RESULT DebuggerStepper::TriggerPatch()
+// What: Triggers patch if we're not in a stub, and we're
+// outside of the stepping range. Otherwise sets another patch so as to
+// step out of the stub, or in the next instruction within the range.
+// How: If module==NULL & managed==> we're in a stub:
+// TrapStepOut() and return false. Module==NULL&!managed==> return
+// true. If m_range != NULL & execution is currently in the range,
+// attempt a TrapStep (TrapStepOut otherwise) & return false. Otherwise,
+// return true.
+TP_RESULT DebuggerStepper::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DeSt::TP\n"));
+
+ // If we're frozen, we may hit a patch but we just ignore it
+ if (IsFrozen())
+ {
+ LOG((LF_CORDB, LL_INFO1000000, "DS::TP, ignoring patch at %p during frozen state\n", patch->address));
+ return TPR_IGNORE;
+ }
+
+ Module *module = patch->key.module;
+ BOOL managed = patch->IsManagedPatch();
+ mdMethodDef md = patch->key.md;
+ SIZE_T offset = patch->offset;
+
+ _ASSERTE((this->GetThread() == thread) || !"Stepper should only get patches on its thread");
+
+ // Note we can only run a stack trace if:
+ // - the context is in managed code (eg, not a stub)
+ // - OR we have a frame in place to prime the stackwalk.
+ ControllerStackInfo info;
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // Context should always be from patch.
+ _ASSERTE(context != NULL);
+
+ bool fSafeToDoStackTrace = true;
+
+ // If we're in a stub (module == NULL and still in managed code), then our context is off in lala-land
+ // Then, it's only safe to do a stackwalk if the top frame is protecting us. That's only true for a
+ // frame_push. If we're here on a manager_push, then we don't have any such protection, so don't do the
+ // stackwalk.
+
+ fSafeToDoStackTrace = patch->IsSafeForStackTrace();
+
+
+ if (fSafeToDoStackTrace)
+ {
+ StackTraceTicket ticket(patch);
+ info.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, context);
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TP: this:0x%p in %s::%s (fp:0x%p, "
+ "off:0x%p md:0x%p), \n\texception source:%s::%s (fp:0x%p)\n",
+ this,
+ info.m_activeFrame.md!=NULL?info.m_activeFrame.md->m_pszDebugClassName:"Unknown",
+ info.m_activeFrame.md!=NULL?info.m_activeFrame.md->m_pszDebugMethodName:"Unknown",
+ info.m_activeFrame.fp.GetSPValue(), patch->offset, patch->key.md,
+ m_fdException!=NULL?m_fdException->m_pszDebugClassName:"None",
+ m_fdException!=NULL?m_fdException->m_pszDebugMethodName:"None",
+ m_fpException.GetSPValue()));
+ }
+
+ DisableAll();
+
+ if (DetectHandleLCGMethods(dac_cast<PCODE>(patch->address), NULL, &info))
+ {
+ return TPR_IGNORE;
+ }
+
+ if (module == NULL)
+ {
+ // JMC steppers should not be patching here...
+ _ASSERTE(DEBUGGER_CONTROLLER_JMC_STEPPER != this->GetDCType());
+
+ if (managed)
+ {
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "Frame (stub) patch hit at offset 0x%x\n", offset));
+
+ // This is a stub patch. If it was a TRACE_FRAME_PUSH that
+ // got us here, then the stub's frame is pushed now, so we
+ // tell the frame to apply the real patch. If we got here
+ // via a TRACE_MGR_PUSH, however, then there is no frame
+ // and we tell the stub manager that generated the
+ // TRACE_MGR_PUSH to apply the real patch.
+ TraceDestination trace;
+ bool traceOk;
+ FramePointer frameFP;
+ PTR_BYTE traceManagerRetAddr = NULL;
+
+ if (patch->trace.GetTraceType() == TRACE_MGR_PUSH)
+ {
+ _ASSERTE(context != NULL);
+ CONTRACT_VIOLATION(GCViolation);
+ traceOk = g_pEEInterface->TraceManager(
+ thread,
+ patch->trace.GetStubManager(),
+ &trace,
+ context,
+ &traceManagerRetAddr);
+
+ // We don't hae an active frame here, so patch with a
+ // FP of NULL so anything will match.
+ //
+ // <REVISIT_TODO>@todo: should we take Esp out of the context?</REVISIT_TODO>
+ frameFP = LEAF_MOST_FRAME;
+ }
+ else
+ {
+ _ASSERTE(fSafeToDoStackTrace);
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+ traceOk = g_pEEInterface->TraceFrame(thread,
+ thread->GetFrame(),
+ TRUE,
+ &trace,
+ &(info.m_activeFrame.registers));
+
+ frameFP = info.m_activeFrame.fp;
+ }
+
+ // Enable the JMC backstop for traditional steppers to catch us in case
+ // we didn't predict the call target properly.
+ EnableJMCBackStop(NULL);
+
+
+ if (!traceOk
+ || !g_pEEInterface->FollowTrace(&trace)
+ || !PatchTrace(&trace, frameFP,
+ (m_rgfMappingStop&STOP_UNMANAGED)?
+ (true):(false)))
+ {
+ //
+ // We can't set a patch in the frame -- we need
+ // to trap returning from this frame instead.
+ //
+ // Note: if we're in the TRACE_MGR_PUSH case from
+ // above, then we must place a patch where the
+ // TraceManager function told us to, since we can't
+ // actually unwind from here.
+ //
+ if (patch->trace.GetTraceType() != TRACE_MGR_PUSH)
+ {
+ _ASSERTE(fSafeToDoStackTrace);
+ LOG((LF_CORDB,LL_INFO10000,"TSO for non TRACE_MGR_PUSH case\n"));
+ TrapStepOut(&info);
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "TSO for TRACE_MGR_PUSH case."));
+
+ // We'd better have a valid return address.
+ _ASSERTE(traceManagerRetAddr != NULL);
+
+ if (g_pEEInterface->IsManagedNativeCode(traceManagerRetAddr))
+ {
+ // Grab the jit info for the method.
+ DebuggerJitInfo *dji;
+ dji = g_pDebugger->GetJitInfoFromAddr((TADDR) traceManagerRetAddr);
+
+ MethodDesc * mdNative = (dji == NULL) ?
+ g_pEEInterface->GetNativeCodeMethodDesc(dac_cast<PCODE>(traceManagerRetAddr)) : dji->m_fd;
+ _ASSERTE(mdNative != NULL);
+
+ // Find the method that the return is to.
+ _ASSERTE(g_pEEInterface->GetFunctionAddress(mdNative) != NULL);
+ SIZE_T offsetRet = dac_cast<TADDR>(traceManagerRetAddr -
+ g_pEEInterface->GetFunctionAddress(mdNative));
+
+ // Place the patch.
+ AddBindAndActivateNativeManagedPatch(mdNative,
+ dji,
+ offsetRet,
+ LEAF_MOST_FRAME,
+ NULL);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DS::TP: normally managed code AddPatch"
+ " in %s::%s, offset 0x%x\n",
+ mdNative->m_pszDebugClassName,
+ mdNative->m_pszDebugMethodName,
+ offsetRet));
+ }
+ else
+ {
+ // We're hitting this code path with MC++ assemblies
+ // that have an unmanaged entry point so the stub returns to CallDescrWorker.
+ _ASSERTE(g_pEEInterface->GetNativeCodeMethodDesc(dac_cast<PCODE>(patch->address))->IsILStub());
+ }
+
+ }
+
+ m_reason = STEP_NORMAL; //we tried to do a STEP_CALL, but since it didn't
+ //work, we're doing what amounts to a normal step.
+ LOG((LF_CORDB,LL_INFO10000,"DS 0x%x m_reason = STEP_NORMAL"
+ "(attempted call thru stub manager, SM didn't know where"
+ " we're going, so did a step out to original call\n",this));
+ }
+ else
+ {
+ m_reason = STEP_CALL;
+ }
+
+ EnableTraceCall(LEAF_MOST_FRAME);
+ EnableUnwind(m_fp);
+
+ return TPR_IGNORE;
+ }
+ else
+ {
+ // @todo - when would we hit this codepath?
+ // If we're not in managed, then we should have pushed a frame onto the Thread's frame chain,
+ // and thus we should still safely be able to do a stackwalk here.
+ _ASSERTE(fSafeToDoStackTrace);
+ if (DetectHandleInterceptors(&info) )
+ {
+ return TPR_IGNORE; //don't actually want to stop
+ }
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "Unmanaged step patch hit at 0x%x\n", offset));
+
+ StackTraceTicket ticket(patch);
+ PrepareForSendEvent(ticket);
+ return TPR_TRIGGER;
+ }
+ } // end (module == NULL)
+
+ // If we're inside an interceptor but don't want to be,then we'll set a
+ // patch outside the current function.
+ _ASSERTE(fSafeToDoStackTrace);
+ if (DetectHandleInterceptors(&info) )
+ {
+ return TPR_IGNORE; //don't actually want to stop
+ }
+
+ LOG((LF_CORDB,LL_INFO10000, "DS: m_fp:0x%p, activeFP:0x%p fpExc:0x%p\n",
+ m_fp.GetSPValue(), info.m_activeFrame.fp.GetSPValue(), m_fpException.GetSPValue()));
+
+ if (IsInRange(offset, m_range, m_rangeCount, &info) ||
+ ShouldContinueStep( &info, offset))
+ {
+ LOG((LF_CORDB, LL_INFO10000,
+ "Intermediate step patch hit at 0x%x\n", offset));
+
+ if (!TrapStep(&info, m_stepIn))
+ TrapStepNext(&info);
+
+ EnableUnwind(m_fp);
+ return TPR_IGNORE;
+ }
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Step patch hit at 0x%x\n", offset));
+
+ // For a JMC stepper, we have an additional constraint:
+ // skip non-user code. So if we're still in non-user code, then
+ // we've got to keep going
+ DebuggerMethodInfo * dmi = g_pDebugger->GetOrCreateMethodInfo(module, md);
+
+ if ((dmi != NULL) && DetectHandleNonUserCode(&info, dmi))
+ {
+ return TPR_IGNORE;
+ }
+
+ StackTraceTicket ticket(patch);
+ PrepareForSendEvent(ticket);
+ return TPR_TRIGGER;
+ }
+}
+
+// Return true if this should be skipped.
+// For a non-jmc stepper, we don't care about non-user code, so we
+// don't skip it and so we always return false.
+bool DebuggerStepper::DetectHandleNonUserCode(ControllerStackInfo *info, DebuggerMethodInfo * pInfo)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ return false;
+}
+
+// For regular steppers, trace-call is just a trace-call.
+void DebuggerStepper::EnablePolyTraceCall()
+{
+ this->EnableTraceCall(LEAF_MOST_FRAME);
+}
+
+// Traditional steppers enable MethodEnter as a back-stop for step-in.
+// We hope that the stub-managers will predict the step-in for us,
+// but in case they don't the Method-Enter should catch us.
+// MethodEnter is not fully correct for traditional steppers for a few reasons:
+// - doesn't handle step-in to native
+// - stops us *after* the prolog (a traditional stepper can stop us before the prolog).
+// - only works for methods that have the JMC probe. That can exclude all optimized code.
+void DebuggerStepper::TriggerMethodEnter(Thread * thread,
+ DebuggerJitInfo *dji,
+ const BYTE * ip,
+ FramePointer fp)
+{
+ _ASSERTE(dji != NULL);
+ _ASSERTE(thread != NULL);
+ _ASSERTE(ip != NULL);
+
+
+
+ _ASSERTE(this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER);
+
+ _ASSERTE(!IsFrozen());
+
+ MethodDesc * pDesc = dji->m_fd;
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, desc=%p, addr=%p\n",
+ pDesc, ip));
+
+ // JMC steppers won't stop in Lightweight delegates. Just return & keep executing.
+ if (pDesc->IsNoMetadata())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TME, skipping b/c it's lw-codegen\n"));
+ return;
+ }
+
+ // This is really just a heuristic. We don't want to trigger a JMC probe when we are
+ // executing in an IL stub, or in one of the marshaling methods called by the IL stub.
+ // The problem is that the IL stub can call into arbitrary code, including custom marshalers.
+ // In that case the user has to put a breakpoint to stop in the code.
+ if (g_pEEInterface->DetectHandleILStubs(thread))
+ {
+ return;
+ }
+
+#ifdef _DEBUG
+ // To help trace down if a problem is related to a stubmanager,
+ // we add a knob that lets us skip the MethodEnter checks. This lets tests directly
+ // go against the Stub-managers w/o the MethodEnter check backstops.
+ int fSkip = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_DbgSkipMEOnStep);
+ if (fSkip)
+ {
+ return;
+ }
+
+ // See EnableJMCBackStop() for details here. This check just makes sure that we don't fire
+ // the assert if we end up in the method we started in (which could happen if we trace call
+ // instructions before the JMC probe).
+ // m_StepInStartMethod may be null (if this step-in didn't start from managed code).
+ if ((m_StepInStartMethod != pDesc) &&
+ (!m_StepInStartMethod->IsLCGMethod()))
+ {
+ // Since normal step-in should stop us at the prolog, and TME is after the prolog,
+ // if a stub-manager did successfully find the address, we should get a TriggerPatch first
+ // at native offset 0 (before the prolog) and before we get the TME. That means if
+ // we do get the TME, then there was no stub-manager to find us.
+
+ SString sLog;
+ StubManager::DbgGetLog(&sLog);
+
+ // Assert b/c the Stub-manager should have caught us first.
+ // We don't want people relying on TriggerMethodEnter as the real implementation for Traditional Step-in
+ // (see above for reasons why). However, using TME will provide a bandage for the final retail product
+ // in cases where we are missing a stub-manager.
+ CONSISTENCY_CHECK_MSGF(false, (
+ "\nThe Stubmanagers failed to identify and trace a stub on step-in. The stub-managers for this code-path path need to be fixed.\n"
+ "See http://team/sites/clrdev/Devdocs/StubManagers.rtf for more information on StubManagers.\n"
+ "Stepper this=0x%p, startMethod='%s::%s'\n"
+ "---------------------------------\n"
+ "Stub manager log:\n%S"
+ "\n"
+ "The thread is now in managed method '%s::%s'.\n"
+ "---------------------------------\n",
+ this,
+ ((m_StepInStartMethod == NULL) ? "unknown" : m_StepInStartMethod->m_pszDebugClassName),
+ ((m_StepInStartMethod == NULL) ? "unknown" : m_StepInStartMethod->m_pszDebugMethodName),
+ sLog.GetUnicode(),
+ pDesc->m_pszDebugClassName, pDesc->m_pszDebugMethodName
+ ));
+ }
+#endif
+
+
+
+ // Place a patch to stopus.
+ // Don't bind to a particular AppDomain so that we can do a Cross-Appdomain step.
+ AddBindAndActivateNativeManagedPatch(pDesc,
+ dji,
+ CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ip),
+ fp,
+ NULL // AppDomain
+ );
+
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, after setting patch to stop\n"));
+
+ // Once we resume, we'll go hit that patch (duh, we patched our return address)
+ // Furthermore, we know the step will complete with reason = call, so set that now.
+ m_reason = STEP_CALL;
+}
+
+
+// We may have single-stepped over a return statement to land us up a frame.
+// Or we may have single-stepped through a method.
+// We never single-step into calls (we place a patch at the call destination).
+bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS:TSS this:0x%x, @ ip:0x%x\n", this, ip));
+
+ _ASSERTE(!IsFrozen());
+
+ // User break should only do a step-out and never actually need a singlestep flag.
+ _ASSERTE(GetDCType() != DEBUGGER_CONTROLLER_USER_BREAKPOINT);
+
+ //
+ // there's one weird case here - if the last instruction generated
+ // a hardware exception, we may be in lala land. If so, rely on the unwind
+ // handler to figure out what happened.
+ //
+ // <REVISIT_TODO>@todo this could be wrong when we have the incremental collector going</REVISIT_TODO>
+ //
+
+ if (!g_pEEInterface->IsManagedNativeCode(ip))
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n"));
+ DisableSingleStep();
+ return false;
+ }
+
+ // If we EnC the method, we'll blast the function address,
+ // and so have to get it from teh DJI that we'll have. If
+ // we haven't gotten debugger info about a regular function, then
+ // we'll have to get the info from the EE, which will be valid
+ // since we're standing in the function at this point, and
+ // EnC couldn't have happened yet.
+ MethodDesc *fd = g_pEEInterface->GetNativeCodeMethodDesc((PCODE)ip);
+
+ SIZE_T offset;
+ DebuggerJitInfo *dji = g_pDebugger->GetJitInfoFromAddr((TADDR) ip);
+ offset = CodeRegionInfo::GetCodeRegionInfo(dji, fd).AddressToOffset(ip);
+
+ ControllerStackInfo info;
+
+ // Safe to stackwalk b/c we've already checked that our IP is in crawlable code.
+ StackTraceTicket ticket(ip);
+ info.GetStackInfo(ticket, GetThread(), LEAF_MOST_FRAME, NULL);
+
+ // This is a special case where we return from a managed method back to an IL stub. This can
+ // only happen if there's no more managed method frames closer to the root and we want to perform
+ // a step out, or if we step-next off the end of a method called by an IL stub. In either case,
+ // we'll get a single step in an IL stub, which we want to ignore. We also want to enable trace
+ // call here, just in case this IL stub is about to call the managed target (in the reverse interop case).
+ if (fd->IsILStub())
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n"));
+ if (this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER)
+ {
+ EnableTraceCall(info.m_activeFrame.fp);
+ }
+ else if (this->GetDCType() == DEBUGGER_CONTROLLER_JMC_STEPPER)
+ {
+ EnableMethodEnter();
+ }
+ DisableSingleStep();
+ return false;
+ }
+
+ DisableAll();
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS m_fp:0x%x, activeFP:0x%x fpExc:0x%x\n",
+ m_fp.GetSPValue(), info.m_activeFrame.fp.GetSPValue(), m_fpException.GetSPValue()));
+
+ if (DetectHandleLCGMethods((PCODE)ip, fd, &info))
+ {
+ return false;
+ }
+
+ if (IsInRange(offset, m_range, m_rangeCount, &info) ||
+ ShouldContinueStep( &info, offset))
+ {
+ if (!TrapStep(&info, m_stepIn))
+ TrapStepNext(&info);
+
+ EnableUnwind(m_fp);
+
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: Returning false Case 1!\n"));
+ return false;
+ }
+ else
+ {
+ LOG((LF_CORDB,LL_INFO10000, "DS::TSS: Returning true Case 2 for reason STEP_%02x!\n", m_reason));
+
+ // @todo - when would a single-step (not a patch) land us in user-code?
+ // For a JMC stepper, we have an additional constraint:
+ // skip non-user code. So if we're still in non-user code, then
+ // we've got to keep going
+ DebuggerMethodInfo * dmi = g_pDebugger->GetOrCreateMethodInfo(fd->GetModule(), fd->GetMemberDef());
+
+ if ((dmi != NULL) && DetectHandleNonUserCode(&info, dmi))
+ return false;
+
+ PrepareForSendEvent(ticket);
+ return true;
+ }
+}
+
+void DebuggerStepper::TriggerTraceCall(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DS:TTC this:0x%x, @ ip:0x%x\n",this,ip));
+ TraceDestination trace;
+
+ if (IsFrozen())
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n"));
+ return;
+ }
+
+ // This is really just a heuristic. We don't want to trigger a JMC probe when we are
+ // executing in an IL stub, or in one of the marshaling methods called by the IL stub.
+ // The problem is that the IL stub can call into arbitrary code, including custom marshalers.
+ // In that case the user has to put a breakpoint to stop in the code.
+ if (g_pEEInterface->DetectHandleILStubs(thread))
+ {
+ return;
+ }
+
+ if (g_pEEInterface->TraceStub(ip, &trace)
+ && g_pEEInterface->FollowTrace(&trace)
+ && PatchTrace(&trace, LEAF_MOST_FRAME,
+ (m_rgfMappingStop&STOP_UNMANAGED)?(true):(false)))
+ {
+ // !!! We really want to know ahead of time if PatchTrace will succeed.
+ DisableAll();
+ PatchTrace(&trace, LEAF_MOST_FRAME, (m_rgfMappingStop&STOP_UNMANAGED)?
+ (true):(false));
+
+ // If we're triggering a trace call, and we're following a trace into either managed code or unjitted managed
+ // code, then we need to update our stepper's reason to STEP_CALL to reflect the fact that we're going to land
+ // into a new function because of a call.
+ if ((trace.GetTraceType() == TRACE_UNJITTED_METHOD) || (trace.GetTraceType() == TRACE_MANAGED))
+ {
+ m_reason = STEP_CALL;
+ }
+
+ EnableUnwind(m_fp);
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::TTC potentially a step call!\n"));
+ }
+}
+
+void DebuggerStepper::TriggerUnwind(Thread *thread,
+ MethodDesc *fd, DebuggerJitInfo * pDJI, SIZE_T offset,
+ FramePointer fp,
+ CorDebugStepReason unwindReason)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS; // from GetJitInfo
+ GC_NOTRIGGER; // don't send IPC events
+ MODE_COOPERATIVE; // TriggerUnwind always is coop
+
+ PRECONDITION(!IsDbgHelperSpecialThread());
+ PRECONDITION(fd->IsDynamicMethod() || (pDJI != NULL));
+ }
+ CONTRACTL_END;
+
+ LOG((LF_CORDB,LL_INFO10000,"DS::TU this:0x%p, in %s::%s, offset 0x%p "
+ "frame:0x%p unwindReason:0x%x\n", this, fd->m_pszDebugClassName,
+ fd->m_pszDebugMethodName, offset, fp.GetSPValue(), unwindReason));
+
+ _ASSERTE(unwindReason == STEP_EXCEPTION_FILTER || unwindReason == STEP_EXCEPTION_HANDLER);
+
+ if (IsFrozen())
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DS:TTC exit b/c of Frozen\n"));
+ return;
+ }
+
+ if (IsCloserToRoot(fp, GetUnwind()))
+ {
+ // Handler is in a parent frame . For all steps (in,out,over)
+ // we want to stop in the handler.
+ // This will be like a Step Out, so we don't need any range.
+ ResetRange();
+ }
+ else
+ {
+ // Handler/Filter is in the same frame as the stepper
+ // For a step-in/over, we want to patch the handler/filter.
+ // But for a step-out, we want to just continue executing (and don't change
+ // the step-reason either).
+ if (m_eMode == cStepOut)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::TU Step-out, returning for same-frame case.\n"));
+ return;
+ }
+
+ }
+
+ // Remember the origin of the exception, so that if the step looks like
+ // it's going to complete in a different frame, but the code comes from the
+ // same frame as the one we're in, we won't stop twice in the "same" range
+ m_fpException = fp;
+ m_fdException = fd;
+
+ //
+ // An exception is exiting the step region. Set a patch on
+ // the filter/handler.
+ //
+
+ DisableAll();
+
+ BOOL fOk;
+ fOk = AddBindAndActivateNativeManagedPatch(fd, pDJI, offset, LEAF_MOST_FRAME, NULL);
+
+ // Since we're unwinding to an already executed method, the method should already
+ // be jitted and placing the patch should work.
+ CONSISTENCY_CHECK_MSGF(fOk, ("Failed to place patch at TriggerUnwind.\npThis=0x%p md=0x%p, native offset=0x%x\n", this, fd, offset));
+
+ LOG((LF_CORDB,LL_INFO100000,"Step reason:%s\n", unwindReason==STEP_EXCEPTION_FILTER
+ ? "STEP_EXCEPTION_FILTER":"STEP_EXCEPTION_HANDLER"));
+ m_reason = unwindReason;
+}
+
+
+// Prepare for sending an event.
+// This is called 1:1 w/ SendEvent, but this guy can be called in a GC_TRIGGERABLE context
+// whereas SendEvent is pretty strict.
+// Caller ensures that it's safe to run a stack trace.
+void DebuggerStepper::PrepareForSendEvent(StackTraceTicket ticket)
+{
+#ifdef _DEBUG
+ _ASSERTE(!m_fReadyToSend);
+ m_fReadyToSend = true;
+#endif
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE m_fpStepInto:0x%x\n", m_fpStepInto.GetSPValue()));
+
+ if (m_fpStepInto != LEAF_MOST_FRAME)
+ {
+ ControllerStackInfo csi;
+ csi.GetStackInfo(ticket, GetThread(), LEAF_MOST_FRAME, NULL);
+
+ if (csi.m_targetFrameFound &&
+#if !defined(WIN64EXCEPTIONS)
+ IsCloserToRoot(m_fpStepInto, csi.m_activeFrame.fp)
+#else
+ IsCloserToRoot(m_fpStepInto, (csi.m_activeFrame.IsNonFilterFuncletFrame() ? csi.m_returnFrame.fp : csi.m_activeFrame.fp))
+#endif // WIN64EXCEPTIONS
+ )
+
+ {
+ m_reason = STEP_CALL;
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE this:0x%x STEP_CALL!\n", this));
+ }
+#ifdef _DEBUG
+ else
+ {
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE this:0x%x not a step call!\n", this));
+ }
+#endif
+ }
+
+#ifdef _DEBUG
+ // Steppers should only stop in interesting code.
+ if (this->GetDCType() == DEBUGGER_CONTROLLER_JMC_STEPPER)
+ {
+ // If we're at either a patch or SS, we'll have a context.
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(GetThread());
+ if (context == NULL)
+ {
+ void * pIP = CORDbgGetIP(reinterpret_cast<DT_CONTEXT *>(context));
+
+ DebuggerJitInfo * dji = g_pDebugger->GetJitInfoFromAddr((TADDR) pIP);
+ DebuggerMethodInfo * dmi = NULL;
+ if (dji != NULL)
+ {
+ dmi = dji->m_methodInfo;
+
+ CONSISTENCY_CHECK_MSGF(dmi->IsJMCFunction(), ("JMC stepper %p stopping in non-jmc method, MD=%p, '%s::%s'",
+ this, dji->m_fd, dji->m_fd->m_pszDebugClassName, dji->m_fd->m_pszDebugMethodName));
+
+ }
+
+
+ }
+ }
+
+#endif
+}
+
+bool DebuggerStepper::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ // We practically should never have a step interupted by SetIp.
+ // We'll still go ahead and send the Step-complete event because we've already
+ // deactivated our triggers by now and we haven't placed any new patches to catch us.
+ // We assert here because we don't believe we'll ever be able to hit this scenario.
+ // This is technically an issue, but we consider it benign enough to leave in.
+ _ASSERTE(!fIpChanged || !"Stepper interupted by SetIp");
+
+ LOG((LF_CORDB, LL_INFO10000, "DS::SE m_fpStepInto:0x%x\n", m_fpStepInto.GetSPValue()));
+
+ _ASSERTE(m_fReadyToSend);
+ _ASSERTE(GetThread() == thread);
+
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread);
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // We need to send the stepper and delete the controller because our stepper
+ // no longer has any patches or other triggers that will let it send the step-complete event.
+ g_pDebugger->SendStep(thread, context, this, m_reason);
+
+ this->Delete();
+
+#ifdef _DEBUG
+ // Now that we've sent the event, we can stop recording information.
+ StubManager::DbgFinishLog();
+#endif
+
+ return true;
+}
+
+void DebuggerStepper::ResetRange()
+{
+ if (m_range)
+ {
+ TRACE_FREE(m_range);
+ DeleteInteropSafe(m_range);
+
+ m_range = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Return true if this stepper is alive, but frozen. (we freeze when the stepper
+// enters a nested func-eval).
+//-----------------------------------------------------------------------------
+bool DebuggerStepper::IsFrozen()
+{
+ return (m_cFuncEvalNesting > 0);
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if this stepper is 'dead' - which happens if a non-frozen stepper
+// gets a func-eval exit.
+//-----------------------------------------------------------------------------
+bool DebuggerStepper::IsDead()
+{
+ return (m_cFuncEvalNesting < 0);
+}
+
+// * ------------------------------------------------------------------------
+// * DebuggerJMCStepper routines
+// * ------------------------------------------------------------------------
+DebuggerJMCStepper::DebuggerJMCStepper(Thread *thread,
+ CorDebugUnmappedStop rgfMappingStop,
+ CorDebugIntercept interceptStop,
+ AppDomain *appDomain) :
+ DebuggerStepper(thread, rgfMappingStop, interceptStop, appDomain)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper ctor, this=%p\n", this));
+}
+
+DebuggerJMCStepper::~DebuggerJMCStepper()
+{
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper dtor, this=%p\n", this));
+}
+
+// If we're a JMC stepper, then don't stop in non-user code.
+bool DebuggerJMCStepper::IsInterestingFrame(FrameInfo * pFrame)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ DebuggerMethodInfo *pInfo = pFrame->GetMethodInfoFromFrameOrThrow();
+ _ASSERTE(pInfo != NULL); // throws on failure
+
+ bool fIsUserCode = pInfo->IsJMCFunction();
+
+
+ LOG((LF_CORDB, LL_INFO1000000, "DS::TSO, frame '%s::%s' is '%s' code\n",
+ pFrame->DbgGetClassName(), pFrame->DbgGetMethodName(),
+ fIsUserCode ? "user" : "non-user"));
+
+ return fIsUserCode;
+}
+
+// A JMC stepper's step-next stops at the next thing of code run.
+// This may be a Step-Out, or any User code called before that.
+// A1 -> B1 -> { A2, B2 -> B3 -> A3}
+// So TrapStepNex at end of A2 should land us in A3.
+void DebuggerJMCStepper::TrapStepNext(ControllerStackInfo *info)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TrapStepNext, this=%p\n", this));
+ EnableMethodEnter();
+
+ // This will place a patch up the stack and set m_reason = STEP_RETURN.
+ // If we end up hitting JMC before that patch, we'll hit TriggerMethodEnter
+ // and that will set our reason to STEP_CALL.
+ TrapStepOut(info);
+}
+
+// ip - target address for call instruction
+bool DebuggerJMCStepper::TrapStepInHelper(
+ ControllerStackInfo * pInfo,
+ const BYTE * ipCallTarget,
+ const BYTE * ipNext,
+ bool fCallingIntoFunclet)
+{
+#ifndef WIN64EXCEPTIONS
+ // There are no funclets on x86.
+ _ASSERTE(!fCallingIntoFunclet);
+#endif
+
+ // If we are calling into a funclet, then we can't rely on the JMC probe to stop us because there are no
+ // JMC probes in funclets. Instead, we have to perform a traditional step-in here.
+ if (fCallingIntoFunclet)
+ {
+ TraceDestination td;
+ td.InitForManaged(reinterpret_cast<PCODE>(ipCallTarget));
+ PatchTrace(&td, LEAF_MOST_FRAME, false);
+
+ // If this succeeds, then we still need to put a patch at the return address. This is done below.
+ // If this fails, then we definitely need to put a patch at the return address to trap the thread.
+ // So in either case, we have to execute the rest of this function.
+ }
+
+ MethodDesc * pDesc = pInfo->m_activeFrame.md;
+ DebuggerJitInfo *dji = NULL;
+
+ // We may not have a DJI if we're in an attach case. We should still be able to do a JMC-step in though.
+ // So NULL is ok here.
+ dji = g_pDebugger->GetJitInfo(pDesc, (const BYTE*) ipNext);
+
+
+ // Place patch after call, which is at ipNext. Note we don't need an IL->Native map here
+ // since we disassembled native code to find the ip after the call.
+ SIZE_T offset = CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ipNext);
+
+
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TSIH, at '%s::%s', calling=0x%p, next=0x%p, offset=%d\n",
+ pDesc->m_pszDebugClassName,
+ pDesc->m_pszDebugMethodName,
+ ipCallTarget, ipNext,
+ offset));
+
+ // Place a patch at the native address (inside the managed method).
+ AddBindAndActivateNativeManagedPatch(pInfo->m_activeFrame.md,
+ dji,
+ offset,
+ pInfo->m_returnFrame.fp,
+ NULL);
+
+ EnableMethodEnter();
+
+ // Return true means that we want to let the stepper run free. It will either
+ // hit the patch after the call instruction or it will hit a TriggerMethodEnter.
+ return true;
+}
+
+// For JMC-steppers, we don't enable trace-call; we enable Method-Enter.
+void DebuggerJMCStepper::EnablePolyTraceCall()
+{
+ _ASSERTE(!IsFrozen());
+
+ this->EnableMethodEnter();
+}
+
+// Return true if this is non-user code. This means we've setup the proper patches &
+// triggers, etc and so we expect the controller to just run free.
+// This is called when all other stepping criteria are met and we're about to
+// send a step-complete. For JMC, this is when we see if we're in non-user code
+// and if so, continue stepping instead of send the step complete.
+// Return false if this is user-code.
+bool DebuggerJMCStepper::DetectHandleNonUserCode(ControllerStackInfo *pInfo, DebuggerMethodInfo * dmi)
+{
+ _ASSERTE(dmi != NULL);
+ bool fIsUserCode = dmi->IsJMCFunction();
+
+ if (!fIsUserCode)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "JMC stepper stopped in non-user code, continuing.\n"));
+ // Not-user code, we want to skip through this.
+
+ // We may be here while trying to step-out.
+ // Step-out just means stop at the first interesting frame above us.
+ // So JMC TrapStepOut won't patch a non-user frame.
+ // But if we're skipping over other stuff (prolog, epilog, interceptors,
+ // trace calls), then we may still be in the middle of non-user
+ //_ASSERTE(m_eMode != cStepOut);
+
+ if (m_eMode == cStepOut)
+ {
+ TrapStepOut(pInfo);
+ }
+ else if (m_stepIn)
+ {
+ EnableMethodEnter();
+ TrapStepOut(pInfo);
+ // Run until we hit the next thing of managed code.
+ } else {
+ // Do a traditional step-out since we just want to go up 1 frame.
+ TrapStepOut(pInfo, true); // force trad step out.
+
+
+ // If we're not in the original frame anymore, then
+ // If we did a Step-over at the end of a method, and that did a single-step over the return
+ // then we may already be in our parent frame. In that case, we also want to behave
+ // like a step-in and TriggerMethodEnter.
+ if (this->m_fp != pInfo->m_activeFrame.fp)
+ {
+ // If we're a step-over, then we should only be stopped in a parent frame.
+ _ASSERTE(m_stepIn || IsCloserToLeaf(this->m_fp, pInfo->m_activeFrame.fp));
+ EnableMethodEnter();
+ }
+
+ // Step-over shouldn't stop in a frame below us in the same callstack.
+ // So we do a tradional step-out of our current frame, which guarantees
+ // that. After that, we act just like a step-in.
+ m_stepIn = true;
+ }
+ EnableUnwind(m_fp);
+
+ // Must keep going...
+ return true;
+ }
+
+ return false;
+}
+
+// Dispatched right after the prolog of a JMC function.
+// We may be blocking the GC here, so let's be fast!
+void DebuggerJMCStepper::TriggerMethodEnter(Thread * thread,
+ DebuggerJitInfo *dji,
+ const BYTE * ip,
+ FramePointer fp)
+{
+ _ASSERTE(dji != NULL);
+ _ASSERTE(thread != NULL);
+ _ASSERTE(ip != NULL);
+
+ _ASSERTE(!IsFrozen());
+
+ MethodDesc * pDesc = dji->m_fd;
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, desc=%p, addr=%p\n",
+ pDesc, ip));
+
+ // JMC steppers won't stop in Lightweight delegates. Just return & keep executing.
+ if (pDesc->IsNoMetadata())
+ {
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TME, skipping b/c it's lw-codegen\n"));
+ return;
+ }
+
+ // Is this user code?
+ DebuggerMethodInfo * dmi = dji->m_methodInfo;
+ bool fIsUserCode = dmi->IsJMCFunction();
+
+
+ LOG((LF_CORDB, LL_INFO100000, "DJMCStepper::TME, '%s::%s' is '%s' code\n",
+ pDesc->m_pszDebugClassName,
+ pDesc->m_pszDebugMethodName,
+ fIsUserCode ? "user" : "non-user"
+ ));
+
+ // If this isn't user code, then just return and continue executing.
+ if (!fIsUserCode)
+ return;
+
+ // MethodEnter is only enabled when we want to stop in a JMC function.
+ // And that's where we are now. So patch the ip and resume.
+ // The stepper will hit the patch, and stop.
+
+ // It's a good thing we have the fp passed in, because we have no other
+ // way of getting it. We can't do a stack trace here (the stack trace
+ // would start at the last pushed Frame, which miss a lot of managed
+ // frames).
+
+ // Don't bind to a particular AppDomain so that we can do a Cross-Appdomain step.
+ AddBindAndActivateNativeManagedPatch(pDesc,
+ dji,
+ CodeRegionInfo::GetCodeRegionInfo(dji, pDesc).AddressToOffset(ip),
+ fp,
+ NULL // AppDomain
+ );
+
+ LOG((LF_CORDB, LL_INFO10000, "DJMCStepper::TME, after setting patch to stop\n"));
+
+ // Once we resume, we'll go hit that patch (duh, we patched our return address)
+ // Furthermore, we know the step will complete with reason = call, so set that now.
+ m_reason = STEP_CALL;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Helper to convert form an EE Frame's interception enum to a CorDebugIntercept
+// bitfield.
+// The intercept value in EE Frame's is a 0-based enumeration (not a bitfield).
+// The intercept value for ICorDebug is a bitfied.
+//-----------------------------------------------------------------------------
+CorDebugIntercept ConvertFrameBitsToDbg(Frame::Interception i)
+{
+ _ASSERTE(i >= 0 && i < Frame::INTERCEPTION_COUNT);
+
+ // Since the ee frame is a 0-based enum, we can just use a map.
+ const CorDebugIntercept map[Frame::INTERCEPTION_COUNT] =
+ {
+ // ICorDebug EE Frame
+ INTERCEPT_NONE, // INTERCEPTION_NONE,
+ INTERCEPT_CLASS_INIT, // INTERCEPTION_CLASS_INIT
+ INTERCEPT_EXCEPTION_FILTER, // INTERCEPTION_EXCEPTION
+ INTERCEPT_CONTEXT_POLICY, // INTERCEPTION_CONTEXT
+ INTERCEPT_SECURITY, // INTERCEPTION_SECURITY
+ INTERCEPT_INTERCEPTION, // INTERCEPTION_OTHER
+ };
+
+ return map[i];
+}
+
+//-----------------------------------------------------------------------------
+// This is a helper class to do a stack walk over a certain range and find all the interceptors.
+// This allows a JMC stepper to see if there are any interceptors it wants to skip over (though
+// there's nothing JMC-specific about this).
+// Note that we only want to walk the stack range that the stepper is operating in.
+// That's because we don't care about interceptors that happened _before_ the
+// stepper was created.
+//-----------------------------------------------------------------------------
+class InterceptorStackInfo
+{
+public:
+#ifdef _DEBUG
+ InterceptorStackInfo()
+ {
+ // since this ctor just nulls out fpTop (which is already done in Init), we
+ // only need it in debug.
+ m_fpTop = LEAF_MOST_FRAME;
+ }
+#endif
+
+ // Get a CorDebugIntercept bitfield that contains a bit for each type of interceptor
+ // if that interceptor is present within our stack-range.
+ // Stack range is from leaf-most up to and including fp
+ CorDebugIntercept GetInterceptorsInRange()
+ {
+ _ASSERTE(m_fpTop != LEAF_MOST_FRAME || !"Must call Init first");
+ return (CorDebugIntercept) m_bits;
+ }
+
+ // Prime the stackwalk.
+ void Init(FramePointer fpTop, Thread *thread, CONTEXT *pContext, BOOL contextValid)
+ {
+ _ASSERTE(fpTop != LEAF_MOST_FRAME);
+ _ASSERTE(thread != NULL);
+
+ m_bits = 0;
+ m_fpTop = fpTop;
+
+ LOG((LF_CORDB,LL_EVERYTHING, "ISI::Init - fpTop=%p, thread=%p, pContext=%p, contextValid=%d\n",
+ fpTop.GetSPValue(), thread, pContext, contextValid));
+
+ int result;
+ result = DebuggerWalkStack(
+ thread,
+ LEAF_MOST_FRAME,
+ pContext,
+ contextValid,
+ WalkStack,
+ (void *) this,
+ FALSE
+ );
+ }
+
+
+protected:
+ // This is a bitfield of all the interceptors we encounter in our stack-range
+ int m_bits;
+
+ // This is the top of our stack range.
+ FramePointer m_fpTop;
+
+ static StackWalkAction WalkStack(FrameInfo *pInfo, void *data)
+ {
+ _ASSERTE(pInfo != NULL);
+ _ASSERTE(data != NULL);
+ InterceptorStackInfo * pThis = (InterceptorStackInfo*) data;
+
+ // If there's an interceptor frame here, then set those
+ // bits in our bitfield.
+ Frame::Interception i = Frame::INTERCEPTION_NONE;
+ Frame * pFrame = pInfo->frame;
+ if ((pFrame != NULL) && (pFrame != FRAME_TOP))
+ {
+ i = pFrame->GetInterception();
+ if (i != Frame::INTERCEPTION_NONE)
+ {
+ pThis->m_bits |= (int) ConvertFrameBitsToDbg(i);
+ }
+ }
+ else if (pInfo->HasMethodFrame())
+ {
+ // Check whether we are executing in a class constructor.
+ _ASSERTE(pInfo->md != NULL);
+
+ // Need to be careful about an off-by-one error here! Imagine your stack looks like:
+ // Foo.DoSomething()
+ // Foo..cctor <--- step starts/ends in here
+ // Bar.Bar();
+ //
+ // and your code looks like this:
+ // Foo..cctor()
+ // {
+ // Foo.DoSomething(); <-- JMC step started here
+ // int x = 1; <-- step ends here
+ // }
+ // This stackwalk covers the inclusive range [Foo..cctor, Foo.DoSomething()] so we will see
+ // the static cctor in this walk. However executing inside a static class constructor does not
+ // count as an interceptor. You must start the step outside the static constructor and then call
+ // into it to have an interceptor. Therefore only static constructors that aren't the outermost
+ // frame should be treated as interceptors.
+ if (pInfo->md->IsClassConstructor() && (pInfo->fp != pThis->m_fpTop))
+ {
+ // We called a class constructor, add the appropriate flag
+ pThis->m_bits |= (int) INTERCEPT_CLASS_INIT;
+ }
+ }
+ LOG((LF_CORDB,LL_EVERYTHING,"ISI::WS- Frame=%p, fp=%p, Frame bits=%x, Cor bits=0x%x\n", pInfo->frame, pInfo->fp.GetSPValue(), i, pThis->m_bits));
+
+
+ // We can stop once we hit the top frame.
+ if (pInfo->fp == pThis->m_fpTop)
+ {
+ return SWA_ABORT;
+ }
+ else
+ {
+ return SWA_CONTINUE;
+ }
+ }
+};
+
+
+
+
+// Skip interceptors for JMC steppers.
+// Return true if we patch something (and thus should keep stepping)
+// Return false if we're done.
+bool DebuggerJMCStepper::DetectHandleInterceptors(ControllerStackInfo * info)
+{
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: Start DetectHandleInterceptors\n"));
+
+ // For JMC, we could stop very far way from an interceptor.
+ // So we have to do a stack walk to search for interceptors...
+ // If we find any in our stack range (from m_fp ... current fp), then we just do a trap-step-next.
+
+ // Note that this logic should also work for regular steppers, but we've left that in
+ // as to keep that code-path unchanged.
+
+ // ControllerStackInfo only gives us the bottom 2 frames on the stack, so we ignore it and
+ // have to do our own stack walk.
+
+ // @todo - for us to properly skip filters, we need to make sure that filters show up in our chains.
+
+
+ InterceptorStackInfo info2;
+ CONTEXT *context = g_pEEInterface->GetThreadFilterContext(this->GetThread());
+ CONTEXT tempContext;
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(this->GetThread()));
+
+ if (context == NULL)
+ {
+ info2.Init(this->m_fp, this->GetThread(), &tempContext, FALSE);
+ }
+ else
+ {
+ info2.Init(this->m_fp, this->GetThread(), context, TRUE);
+ }
+
+ // The following casts are safe on WIN64 platforms.
+ int iOnStack = (int) info2.GetInterceptorsInRange();
+ int iSkip = ~((int) m_rgfInterceptStop);
+
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: iOnStack=%x, iSkip=%x\n", iOnStack, iSkip));
+
+ // If the bits on the stack contain any interceptors we want to skip, then we need to keep going.
+ if ((iOnStack & iSkip) != 0)
+ {
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: keep going!\n"));
+ TrapStepNext(info);
+ EnableUnwind(m_fp);
+ return true;
+ }
+
+ LOG((LF_CORDB,LL_INFO10000,"DJMCStepper::DHI: Done!!\n"));
+ return false;
+}
+
+
+// * ------------------------------------------------------------------------
+// * DebuggerThreadStarter routines
+// * ------------------------------------------------------------------------
+
+DebuggerThreadStarter::DebuggerThreadStarter(Thread *thread)
+ : DebuggerController(thread, NULL)
+{
+ LOG((LF_CORDB, LL_INFO1000, "DTS::DTS: this:0x%x Thread:0x%x\n",
+ this, thread));
+
+ // Check to make sure we only have 1 ThreadStarter on a given thread. (Inspired by NDPWhidbey issue 16888)
+#if defined(_DEBUG)
+ EnsureUniqueThreadStarter(this);
+#endif
+}
+
+// TP_RESULT DebuggerThreadStarter::TriggerPatch() If we're in a
+// stub (module==NULL&&managed) then do a PatchTrace up the stack &
+// return false. Otherwise DisableAll & return
+// true
+TP_RESULT DebuggerThreadStarter::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ Module *module = patch->key.module;
+ BOOL managed = patch->IsManagedPatch();
+
+ LOG((LF_CORDB,LL_INFO1000, "DebuggerThreadStarter::TriggerPatch for thread 0x%x\n", Debugger::GetThreadIdHelper(thread)));
+
+ if (module == NULL && managed)
+ {
+ // This is a stub patch. If it was a TRACE_FRAME_PUSH that got us here, then the stub's frame is pushed now, so
+ // we tell the frame to apply the real patch. If we got here via a TRACE_MGR_PUSH, however, then there is no
+ // frame and we go back to the stub manager that generated the stub for where to patch next.
+ TraceDestination trace;
+ bool traceOk;
+ if (patch->trace.GetTraceType() == TRACE_MGR_PUSH)
+ {
+ BYTE *dummy = NULL;
+ CONTEXT *context = GetManagedLiveCtx(thread);
+ CONTRACT_VIOLATION(GCViolation);
+ traceOk = g_pEEInterface->TraceManager(thread, patch->trace.GetStubManager(), &trace, context, &dummy);
+ }
+ else if ((patch->trace.GetTraceType() == TRACE_FRAME_PUSH) && (thread->GetFrame()->IsTransitionToNativeFrame()))
+ {
+ // If we've got a frame that is transitioning to native, there's no reason to try to keep tracing. So we
+ // bail early and save ourselves some effort. This also works around a problem where we deadlock trying to
+ // do too much work to determine the destination of a ComPlusMethodFrame. (See issue 87103.)
+ //
+ // Note: trace call is still enabled, so we can just ignore this patch and wait for trace call to fire
+ // again...
+ return TPR_IGNORE;
+ }
+ else
+ {
+ // It's questionable whether Trace_Frame_Push is actually safe or not.
+ ControllerStackInfo csi;
+ StackTraceTicket ticket(patch);
+ csi.GetStackInfo(ticket, thread, LEAF_MOST_FRAME, NULL);
+
+ CONTRACT_VIOLATION(GCViolation); // TraceFrame GC-triggers
+ traceOk = g_pEEInterface->TraceFrame(thread, thread->GetFrame(), TRUE, &trace, &(csi.m_activeFrame.registers));
+ }
+
+ if (traceOk && g_pEEInterface->FollowTrace(&trace))
+ {
+ PatchTrace(&trace, LEAF_MOST_FRAME, TRUE);
+ }
+
+ return TPR_IGNORE;
+ }
+ else
+ {
+ // We've hit user code; trigger our event.
+ DisableAll();
+
+
+ {
+
+ // Give the helper thread a chance to get ready. The temporary helper can't handle
+ // execution control well, and the RS won't do any execution control until it gets a
+ // create Thread event, which it won't get until here.
+ // So now's our best time to wait for the real helper thread.
+ g_pDebugger->PollWaitingForHelper();
+ }
+
+ return TPR_TRIGGER;
+ }
+}
+
+void DebuggerThreadStarter::TriggerTraceCall(Thread *thread, const BYTE *ip)
+{
+ LOG((LF_CORDB, LL_EVERYTHING, "DTS::TTC called\n"));
+#ifdef DEBUGGING_SUPPORTED
+ if (thread->GetDomain()->IsDebuggerAttached())
+ {
+ TraceDestination trace;
+
+ if (g_pEEInterface->TraceStub(ip, &trace) && g_pEEInterface->FollowTrace(&trace))
+ {
+ PatchTrace(&trace, LEAF_MOST_FRAME, true);
+ }
+ }
+#endif //DEBUGGING_SUPPORTED
+
+}
+
+bool DebuggerThreadStarter::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ // This SendEvent can't be interupted by a SetIp because until the client
+ // gets a ThreadStarter event, it doesn't even know the thread exists, so
+ // it certainly can't change its ip.
+ _ASSERTE(!fIpChanged);
+
+ LOG((LF_CORDB, LL_INFO10000, "DTS::SE: in DebuggerThreadStarter's SendEvent\n"));
+
+ // Send the thread started event.
+ g_pDebugger->ThreadStarted(thread);
+
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ Delete();
+
+ return true;
+}
+
+// * ------------------------------------------------------------------------
+// * DebuggerUserBreakpoint routines
+// * ------------------------------------------------------------------------
+
+bool DebuggerUserBreakpoint::IsFrameInDebuggerNamespace(FrameInfo * pFrame)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ // Steppers ignore internal frames, so should only be called on real frames.
+ _ASSERTE(pFrame->HasMethodFrame());
+
+ // Now get the namespace of the active frame
+ MethodDesc *pMD = pFrame->md;
+
+ if (pMD != NULL)
+ {
+ MethodTable * pMT = pMD->GetMethodTable();
+
+ LPCUTF8 szNamespace = NULL;
+ LPCUTF8 szClassName = pMT->GetFullyQualifiedNameInfo(&szNamespace);
+
+ if (szClassName != NULL && szNamespace != NULL)
+ {
+ MAKE_WIDEPTR_FROMUTF8(wszNamespace, szNamespace); // throw
+ MAKE_WIDEPTR_FROMUTF8(wszClassName, szClassName);
+ if (wcscmp(wszClassName, W("Debugger")) == 0 &&
+ wcscmp(wszNamespace, W("System.Diagnostics")) == 0)
+ {
+ // This will continue stepping
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Helper check if we're directly in a dynamic method (ignoring any chain goo
+// or stuff in the Debugger namespace.
+class IsLeafFrameDynamic
+{
+protected:
+ static StackWalkAction WalkStackWrapper(FrameInfo *pInfo, void *data)
+ {
+ IsLeafFrameDynamic * pThis = reinterpret_cast<IsLeafFrameDynamic*> (data);
+ return pThis->WalkStack(pInfo);
+ }
+
+ StackWalkAction WalkStack(FrameInfo *pInfo)
+ {
+ _ASSERTE(pInfo != NULL);
+
+ // A FrameInfo may have both Method + Chain rolled into one.
+ if (!pInfo->HasMethodFrame() && !pInfo->HasStubFrame())
+ {
+ // We're a chain. Ignore it and keep looking.
+ return SWA_CONTINUE;
+ }
+
+ // So now this is the first non-chain, non-Debugger namespace frame.
+ // LW frames don't have a name, so we check if it's LW first.
+ if (pInfo->eStubFrameType == STUBFRAME_LIGHTWEIGHT_FUNCTION)
+ {
+ m_fInLightWeightMethod = true;
+ return SWA_ABORT;
+ }
+
+ // Ignore Debugger.Break() frames.
+ // All Debugger.Break calls will have this on the stack.
+ if (DebuggerUserBreakpoint::IsFrameInDebuggerNamespace(pInfo))
+ {
+ return SWA_CONTINUE;
+ }
+
+ // We've now determined leafmost thing, so stop stackwalking.
+ _ASSERTE(m_fInLightWeightMethod == false);
+ return SWA_ABORT;
+ }
+
+
+ bool m_fInLightWeightMethod;
+
+ // Need this context to do stack trace.
+ CONTEXT m_tempContext;
+
+public:
+ // On success, copies the leafmost non-chain frameinfo (including stubs) for the current thread into pInfo
+ // and returns true.
+ // On failure, returns false.
+ // Return true on success.
+ bool DoCheck(IN Thread * pThread)
+ {
+ CONTRACTL
+ {
+ GC_TRIGGERS;
+ THROWS;
+ MODE_ANY;
+
+ PRECONDITION(CheckPointer(pThread));
+ }
+ CONTRACTL_END;
+
+ m_fInLightWeightMethod = false;
+
+
+ DebuggerWalkStack(
+ pThread,
+ LEAF_MOST_FRAME,
+ &m_tempContext, false,
+ WalkStackWrapper,
+ (void *) this,
+ TRUE // includes everything
+ );
+
+ // We don't care whether the stackwalk succeeds or not because the
+ // callback sets our status via this field either way, so just return it.
+ return m_fInLightWeightMethod;
+ };
+};
+
+// Handle a Debug.Break() notification.
+// This may create a controller to step-out out the Debug.Break() call (so that
+// we appear stopped at the callsite).
+// If we can't step-out (eg, we're directly in a dynamic method), then send
+// the debug event immediately.
+void DebuggerUserBreakpoint::HandleDebugBreak(Thread * pThread)
+{
+ bool fDoStepOut = true;
+
+ // If the leaf frame is not a LW method, then step-out.
+ IsLeafFrameDynamic info;
+ fDoStepOut = !info.DoCheck(pThread);
+
+ if (fDoStepOut)
+ {
+ // Create a controller that will step out for us.
+ new (interopsafe) DebuggerUserBreakpoint(pThread);
+ }
+ else
+ {
+ // Send debug event immediately.
+ g_pDebugger->SendUserBreakpointAndSynchronize(pThread);
+ }
+}
+
+
+DebuggerUserBreakpoint::DebuggerUserBreakpoint(Thread *thread)
+ : DebuggerStepper(thread, (CorDebugUnmappedStop) (STOP_ALL & ~STOP_UNMANAGED), INTERCEPT_ALL, NULL)
+{
+ // Setup a step out from the current frame (which we know is
+ // unmanaged, actually...)
+
+
+ // This happens to be safe, but it's a very special case (so we have a special case ticket)
+ // This is called while we're live (so no filter context) and from the fcall,
+ // and we pushed a HelperMethodFrame to protect us. We also happen to know that we have
+ // done anything illegal or dangerous since then.
+
+ StackTraceTicket ticket(this);
+ StepOut(LEAF_MOST_FRAME, ticket);
+}
+
+
+// Is this frame interesting?
+// Use this to skip all code in the namespace "Debugger.Diagnostics"
+bool DebuggerUserBreakpoint::IsInterestingFrame(FrameInfo * pFrame)
+{
+ CONTRACTL
+ {
+ THROWS;
+ MODE_ANY;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+ return !IsFrameInDebuggerNamespace(pFrame);
+}
+
+bool DebuggerUserBreakpoint::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+ // See DebuggerStepper::SendEvent for why we assert here.
+ // This is technically an issue, but it's too benign to fix.
+ _ASSERTE(!fIpChanged);
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DUB::SE: in DebuggerUserBreakpoint's SendEvent\n"));
+
+ // Send the user breakpoint event.
+ g_pDebugger->SendRawUserBreakpoint(thread);
+
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ Delete();
+
+ return true;
+}
+
+// * ------------------------------------------------------------------------
+// * DebuggerFuncEvalComplete routines
+// * ------------------------------------------------------------------------
+
+DebuggerFuncEvalComplete::DebuggerFuncEvalComplete(Thread *thread,
+ void *dest)
+ : DebuggerController(thread, NULL)
+{
+#ifdef _TARGET_ARM_
+ m_pDE = reinterpret_cast<DebuggerEvalBreakpointInfoSegment*>(((DWORD)dest) & ~THUMB_CODE)->m_associatedDebuggerEval;
+#else
+ m_pDE = reinterpret_cast<DebuggerEvalBreakpointInfoSegment*>(dest)->m_associatedDebuggerEval;
+#endif
+
+ // Add an unmanaged patch at the destination.
+ AddAndActivateNativePatchForAddress((CORDB_ADDRESS_TYPE*)dest, LEAF_MOST_FRAME, FALSE, TRACE_UNMANAGED);
+}
+
+TP_RESULT DebuggerFuncEvalComplete::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+
+ // It had better be an unmanaged patch...
+ _ASSERTE((patch->key.module == NULL) && !patch->IsManagedPatch());
+
+ // set ThreadFilterContext back here because we need make stack crawlable! In case,
+ // GC got triggered.
+
+ // Restore the thread's context to what it was before we hijacked it for this func eval.
+ CONTEXT *pCtx = GetManagedLiveCtx(thread);
+ CORDbgCopyThreadContext(reinterpret_cast<DT_CONTEXT *>(pCtx),
+ reinterpret_cast<DT_CONTEXT *>(&(m_pDE->m_context)));
+
+ // We've hit our patch, so simply disable all (which removes the
+ // patch) and trigger the event.
+ DisableAll();
+ return TPR_TRIGGER;
+}
+
+bool DebuggerFuncEvalComplete::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ THROWS;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+
+ // This should not ever be interupted by a SetIp.
+ // The BP will be off in random native code for which SetIp would be illegal.
+ // However, func-eval conroller will restore the context from when we're at the patch,
+ // so that will look like the IP changed on us.
+ _ASSERTE(fIpChanged);
+
+ LOG((LF_CORDB, LL_INFO10000, "DFEC::SE: in DebuggerFuncEval's SendEvent\n"));
+
+ _ASSERTE(!ISREDIRECTEDTHREAD(thread));
+
+ // The DebuggerEval is at our faulting address.
+ DebuggerEval *pDE = m_pDE;
+
+ // Send the func eval complete (or exception) event.
+ g_pDebugger->FuncEvalComplete(thread, pDE);
+
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ Delete();
+
+ return true;
+}
+
+#ifdef EnC_SUPPORTED
+
+// * ------------------------------------------------------------------------ *
+// * DebuggerEnCBreakpoint routines
+// * ------------------------------------------------------------------------ *
+
+//---------------------------------------------------------------------------------------
+//
+// DebuggerEnCBreakpoint constructor - creates and activates a new EnC breakpoint
+//
+// Arguments:
+// offset - native offset in the function to place the patch
+// jitInfo - identifies the function in which the breakpoint is being placed
+// fTriggerType - breakpoint type: either REMAP_PENDING or REMAP_COMPLETE
+// pAppDomain - the breakpoint applies to the specified AppDomain only
+//
+
+DebuggerEnCBreakpoint::DebuggerEnCBreakpoint(SIZE_T offset,
+ DebuggerJitInfo *jitInfo,
+ DebuggerEnCBreakpoint::TriggerType fTriggerType,
+ AppDomain *pAppDomain)
+ : DebuggerController(NULL, pAppDomain),
+ m_fTriggerType(fTriggerType),
+ m_jitInfo(jitInfo)
+{
+ _ASSERTE( jitInfo != NULL );
+ // Add and activate the specified patch
+ AddBindAndActivateNativeManagedPatch(jitInfo->m_fd, jitInfo, offset, LEAF_MOST_FRAME, pAppDomain);
+ LOG((LF_ENC,LL_INFO1000, "DEnCBPDEnCBP::adding %S patch!\n",
+ fTriggerType == REMAP_PENDING ? W("remap pending") : W("remap complete")));
+}
+
+
+//---------------------------------------------------------------------------------------
+//
+// DebuggerEnCBreakpoint::TriggerPatch
+// called by the debugging infrastructure when the patch is hit.
+//
+// Arguments:
+// patch - specifies the patch that was hit
+// thread - identifies the thread on which the patch was hit
+// tyWhy - TY_SHORT_CIRCUIT for normal REMAP_PENDING EnC patches
+//
+// Return value:
+// TPR_IGNORE if the debugger chooses not to take a remap opportunity
+// TPR_IGNORE_AND_STOP when a remap-complete event is sent
+// Doesn't return at all if the debugger remaps execution to the new version of the method
+//
+TP_RESULT DebuggerEnCBreakpoint::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ _ASSERTE(HasLock());
+
+ Module *module = patch->key.module;
+ mdMethodDef md = patch->key.md;
+ SIZE_T offset = patch->offset;
+
+ // Map the current native offset back to the IL offset in the old
+ // function. This will be mapped to the new native offset within
+ // ResumeInUpdatedFunction
+ CorDebugMappingResult map;
+ DWORD which;
+ SIZE_T currentIP = (SIZE_T)m_jitInfo->MapNativeOffsetToIL(offset,
+ &map, &which);
+
+ // We only lay DebuggerEnCBreakpoints at sequence points
+ _ASSERTE(map == MAPPING_EXACT);
+
+ LOG((LF_ENC, LL_ALWAYS,
+ "DEnCBP::TP: triggered E&C %S breakpoint: tid=0x%x, module=0x%08x, "
+ "method def=0x%08x, version=%d, native offset=0x%x, IL offset=0x%x\n this=0x%x\n",
+ m_fTriggerType == REMAP_PENDING ? W("ResumePending") : W("ResumeComplete"),
+ thread, module, md, m_jitInfo->m_encVersion, offset, currentIP, this));
+
+ // If this is a REMAP_COMPLETE patch, then dispatch the RemapComplete callback
+ if (m_fTriggerType == REMAP_COMPLETE)
+ {
+ return HandleRemapComplete(patch, thread, tyWhy);
+ }
+
+ // This must be a REMAP_PENDING patch
+ // unless we got here on an explicit short-circuit, don't do any work
+ if (tyWhy != TY_SHORT_CIRCUIT)
+ {
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::TP: not short-circuit ... bailing\n"));
+ return TPR_IGNORE;
+ }
+
+ _ASSERTE(patch->IsManagedPatch());
+
+ // Grab the MethodDesc for this function.
+ _ASSERTE(module != NULL);
+
+ // GENERICS: @todo generics. This should be replaced by a similar loop
+ // over the DJIs for the DMI as in BindPatch up above.
+ MethodDesc *pFD = g_pEEInterface->FindLoadedMethodRefOrDef(module, md);
+
+ _ASSERTE(pFD != NULL);
+
+ LOG((LF_ENC, LL_ALWAYS,
+ "DEnCBP::TP: in %s::%s\n", pFD->m_pszDebugClassName,pFD->m_pszDebugMethodName));
+
+ // Grab the jit info for the original copy of the method, which is
+ // what we are executing right now.
+ DebuggerJitInfo *pJitInfo = m_jitInfo;
+ _ASSERTE(pJitInfo);
+ _ASSERTE(pJitInfo->m_fd == pFD);
+
+ // Grab the context for this thread. This is the context that was
+ // passed to COMPlusFrameHandler.
+ CONTEXT *pContext = GetManagedLiveCtx(thread);
+
+ // We use the module the current function is in.
+ _ASSERTE(module->IsEditAndContinueEnabled());
+ EditAndContinueModule *pModule = (EditAndContinueModule*)module;
+
+ // Release the controller lock for the rest of this method
+ CrstBase::UnsafeCrstInverseHolder inverseLock(&g_criticalSection);
+
+ // resumeIP is the native offset in the new version of the method the debugger wants
+ // to resume to. We'll pass the address of this variable over to the right-side
+ // and if it modifies the contents while we're stopped dispatching the RemapOpportunity,
+ // then we know it wants a remap.
+ // This form of side-channel communication seems like an error-prone workaround. Ideally the
+ // remap IP (if any) would just be returned in a response event.
+ SIZE_T resumeIP = (SIZE_T) -1;
+
+ // Debugging code to enable a break after N RemapOpportunities
+#ifdef _DEBUG
+ static int breakOnRemapOpportunity = -1;
+ if (breakOnRemapOpportunity == -1)
+ breakOnRemapOpportunity = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EnCBreakOnRemapOpportunity);
+
+ static int remapOpportunityCount = 0;
+
+ ++remapOpportunityCount;
+ if (breakOnRemapOpportunity == 1 || breakOnRemapOpportunity == remapOpportunityCount)
+ {
+ _ASSERTE(!"BreakOnRemapOpportunity");
+ }
+#endif
+
+ // Send an event to the RS to call the RemapOpportunity callback, passing the address of resumeIP.
+ // If the debugger responds with a call to RemapFunction, the supplied IP will be copied into resumeIP
+ // and we will know to update the context and resume the function at the new IP. Otherwise we just do
+ // nothing and try again on next RemapFunction breakpoint
+ g_pDebugger->LockAndSendEnCRemapEvent(pJitInfo, currentIP, &resumeIP);
+
+ LOG((LF_ENC, LL_ALWAYS,
+ "DEnCBP::TP: resume IL offset is 0x%x\n", resumeIP));
+
+ // Has the debugger requested a remap?
+ if (resumeIP != (SIZE_T) -1)
+ {
+ // This will jit the function, update the context, and resume execution at the new location.
+ g_pEEInterface->ResumeInUpdatedFunction(pModule,
+ pFD,
+ (void*)pJitInfo,
+ resumeIP,
+ pContext);
+ _ASSERTE(!"Returned from ResumeInUpdatedFunction!");
+ }
+
+ LOG((LF_CORDB, LL_ALWAYS, "DEnCB::TP: We've returned from ResumeInUpd"
+ "atedFunction, we're going to skip the EnC patch ####\n"));
+
+ // We're returning then we'll have to re-get this lock. Be careful that we haven't kept any controller/patches
+ // in the caller. They can move when we unlock, so when we release the lock and reget it here, things might have
+ // changed underneath us.
+ // inverseLock holder will reaquire lock.
+
+ return TPR_IGNORE;
+}
+
+//
+// HandleResumeComplete is called for an EnC patch in the newly updated function
+// so that we can notify the debugger that the remap has completed and they can
+// now remap their steppers or anything else that depends on the new code actually
+// being on the stack. We return TPR_IGNORE_AND_STOP because it's possible that the
+// function was edited after we handled remap complete and want to make sure we
+// start a fresh call to TriggerPatch
+//
+TP_RESULT DebuggerEnCBreakpoint::HandleRemapComplete(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: HandleRemapComplete\n"));
+
+ // Debugging code to enable a break after N RemapCompletes
+#ifdef _DEBUG
+ static int breakOnRemapComplete = -1;
+ if (breakOnRemapComplete == -1)
+ breakOnRemapComplete = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EnCBreakOnRemapComplete);
+
+ static int remapCompleteCount = 0;
+ ++remapCompleteCount;
+ if (breakOnRemapComplete == 1 || breakOnRemapComplete == remapCompleteCount)
+ {
+ _ASSERTE(!"BreakOnRemapComplete");
+ }
+#endif
+ _ASSERTE(HasLock());
+
+
+ bool fApplied = m_jitInfo->m_encBreakpointsApplied;
+ // Need to delete this before unlock below so if any other thread come in after the unlock
+ // they won't handle this patch.
+ Delete();
+
+ // We just deleted ourselves. Can't access anything any instances after this point.
+
+ // if have somehow updated this function before we resume into it then just bail
+ if (fApplied)
+ {
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: function already updated, ignoring\n"));
+ return TPR_IGNORE_AND_STOP;
+ }
+
+ // GENERICS: @todo generics. This should be replaced by a similar loop
+ // over the DJIs for the DMI as in BindPatch up above.
+ MethodDesc *pFD = g_pEEInterface->FindLoadedMethodRefOrDef(patch->key.module, patch->key.md);
+
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: unlocking controller\n"));
+
+ // Unlock the controller lock and dispatch the remap complete event
+ CrstBase::UnsafeCrstInverseHolder inverseLock(&g_criticalSection);
+
+ LOG((LF_ENC, LL_ALWAYS, "DEnCBP::HRC: sending RemapCompleteEvent\n"));
+
+ g_pDebugger->LockAndSendEnCRemapCompleteEvent(pFD);
+
+ // We're returning then we'll have to re-get this lock. Be careful that we haven't kept any controller/patches
+ // in the caller. They can move when we unlock, so when we release the lock and reget it here, things might have
+ // changed underneath us.
+ // inverseLock holder will reacquire.
+
+ return TPR_IGNORE_AND_STOP;
+}
+#endif //EnC_SUPPORTED
+
+// continuable-exceptions
+// * ------------------------------------------------------------------------ *
+// * DebuggerContinuableExceptionBreakpoint routines
+// * ------------------------------------------------------------------------ *
+
+
+//---------------------------------------------------------------------------------------
+//
+// constructor
+//
+// Arguments:
+// pThread - the thread on which we are intercepting an exception
+// nativeOffset - This is the target native offset. It is where we are going to resume execution.
+// jitInfo - the DebuggerJitInfo of the method at which we are intercepting
+// pAppDomain - the AppDomain in which the thread is executing
+//
+
+DebuggerContinuableExceptionBreakpoint::DebuggerContinuableExceptionBreakpoint(Thread *pThread,
+ SIZE_T nativeOffset,
+ DebuggerJitInfo *jitInfo,
+ AppDomain *pAppDomain)
+ : DebuggerController(pThread, pAppDomain)
+{
+ _ASSERTE( jitInfo != NULL );
+ // Add a native patch at the specified native offset, which is where we are going to resume execution.
+ AddBindAndActivateNativeManagedPatch(jitInfo->m_fd, jitInfo, nativeOffset, LEAF_MOST_FRAME, pAppDomain);
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function is called when the patch added in the constructor is hit. At this point,
+// we have already resumed execution, and the exception is no longer in flight.
+//
+// Arguments:
+// patch - the patch added in the constructor; unused
+// thread - the thread in question; unused
+// tyWhy - a flag which is only useful for EnC; unused
+//
+// Return Value:
+// This function always returns TPR_TRIGGER, meaning that it wants to send an event to notify the RS.
+//
+
+TP_RESULT DebuggerContinuableExceptionBreakpoint::TriggerPatch(DebuggerControllerPatch *patch,
+ Thread *thread,
+ TRIGGER_WHY tyWhy)
+{
+ LOG((LF_CORDB, LL_INFO10000, "DCEBP::TP\n"));
+
+ //
+ // Disable the patch
+ //
+ DisableAll();
+
+ // We will send a notification to the RS when the patch is triggered.
+ return TPR_TRIGGER;
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This function is called when we want to notify the RS that an interception is complete.
+// At this point, we have already resumed execution, and the exception is no longer in flight.
+//
+// Arguments:
+// thread - the thread in question
+// fIpChanged - whether the IP has changed by SetIP after the patch is hit but
+// before this function is called
+//
+
+bool DebuggerContinuableExceptionBreakpoint::SendEvent(Thread *thread, bool fIpChanged)
+{
+ CONTRACTL
+ {
+ SO_NOT_MAINLINE;
+ NOTHROW;
+ SENDEVENT_CONTRACT_ITEMS;
+ }
+ CONTRACTL_END;
+
+
+
+ LOG((LF_CORDB, LL_INFO10000,
+ "DCEBP::SE: in DebuggerContinuableExceptionBreakpoint's SendEvent\n"));
+
+ if (!fIpChanged)
+ {
+ g_pDebugger->SendInterceptExceptionComplete(thread);
+ }
+
+ // On WIN64, by the time we get here the DebuggerExState is gone already.
+ // ExceptionTrackers are cleaned up before we resume execution for a handled exception.
+#if !defined(WIN64EXCEPTIONS)
+ thread->GetExceptionState()->GetDebuggerState()->SetDebuggerInterceptContext(NULL);
+#endif // !WIN64EXCEPTIONS
+
+
+ //
+ // We delete this now because its no longer needed. We can call
+ // delete here because the queued count is above 0. This object
+ // will really be deleted when its dequeued shortly after this
+ // call returns.
+ //
+ Delete();
+
+ return true;
+}
+#endif // !DACCESS_COMPILE