summaryrefslogtreecommitdiff
path: root/src/debug
diff options
context:
space:
mode:
authorNoah Falk <noahfalk@users.noreply.github.com>2017-10-16 15:18:43 -0700
committerGitHub <noreply@github.com>2017-10-16 15:18:43 -0700
commita3ddbc87e2807e55059ebf487c9b0b46546b141d (patch)
tree84425ea79fb9aaf730a6c373f54933525f00cef4 /src/debug
parentc8963ccdc59662800877ddd9dd4fc4a4ffcae49e (diff)
downloadcoreclr-a3ddbc87e2807e55059ebf487c9b0b46546b141d.tar.gz
coreclr-a3ddbc87e2807e55059ebf487c9b0b46546b141d.tar.bz2
coreclr-a3ddbc87e2807e55059ebf487c9b0b46546b141d.zip
Fix stepping with tiered jitting
Fix #14426. Added a more flexible IL master breakpoint that can: a) bind to native offset 0 of each jitted code body b) use a MethodDescFilter so that only jitted code for one generic instance receives breakpoints The remaining change is simply to switch step-in and trace stepping to use that new breakpoint binding behavior instead of the code version specific binding behavior that was used before. This ensures that even if the code version changes after the trace occurs we will still have a breakpoint on the alternate code versions and complete the stepping operation.
Diffstat (limited to 'src/debug')
-rw-r--r--src/debug/ee/controller.cpp183
-rw-r--r--src/debug/ee/controller.h46
-rw-r--r--src/debug/ee/debugger.cpp11
-rw-r--r--src/debug/ee/debugger.h11
-rw-r--r--src/debug/ee/functioninfo.cpp75
5 files changed, 233 insertions, 93 deletions
diff --git a/src/debug/ee/controller.cpp b/src/debug/ee/controller.cpp
index cba5d88296..55e9936866 100644
--- a/src/debug/ee/controller.cpp
+++ b/src/debug/ee/controller.cpp
@@ -466,7 +466,9 @@ StackWalkAction ControllerStackInfo::WalkStack(FrameInfo *pInfo, void *data)
DebuggerControllerPatch *DebuggerPatchTable::AddPatchForMethodDef(DebuggerController *controller,
Module *module,
mdMethodDef md,
+ MethodDesc* pMethodDescFilter,
size_t offset,
+ BOOL offsetIsIL,
DebuggerPatchKind kind,
FramePointer fp,
AppDomain *pAppDomain,
@@ -481,6 +483,8 @@ DebuggerControllerPatch *DebuggerPatchTable::AddPatchForMethodDef(DebuggerContro
}
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,
@@ -507,8 +511,9 @@ DebuggerControllerPatch *DebuggerPatchTable::AddPatchForMethodDef(DebuggerContro
patch->controller = controller;
patch->key.module = module;
patch->key.md = md;
+ patch->pMethodDescFilter = pMethodDescFilter;
patch->offset = offset;
- patch->offsetIsIL = (kind == PATCH_KIND_IL_MASTER);
+ patch->offsetIsIL = offsetIsIL;
patch->address = NULL;
patch->fp = fp;
patch->trace.Bad_SetTraceType(DPT_DEFAULT_TRACE_TYPE); // TRACE_OTHER
@@ -544,6 +549,17 @@ DebuggerControllerPatch *DebuggerPatchTable::AddPatchForMethodDef(DebuggerContro
// The only kind of patch with IL offset is the IL master patch.
_ASSERTE(patch->IsILMasterPatch() || patch->offsetIsIL == FALSE);
+
+ // The only kind of patch that allows a MethodDescFilter is the IL master patch
+ _ASSERTE(patch->IsILMasterPatch() || patch->pMethodDescFilter == NULL);
+
+ // Zero is the only native offset that we allow to bind across different jitted
+ // code bodies. There isn't any sensible meaning to binding at some other native offset.
+ // Even if all the code bodies had an instruction that started at that offset there is
+ // no guarantee those instructions represent a semantically equivalent point in the
+ // method's execution.
+ _ASSERTE(!(patch->IsILMasterPatch() && !patch->offsetIsIL && patch->offset != 0));
+
return patch;
}
@@ -604,6 +620,7 @@ DebuggerControllerPatch *DebuggerPatchTable::AddPatchForAddress(DebuggerControll
patch->key.module = g_pEEInterface->MethodDescGetModule(fd);
patch->key.md = fd->GetMemberDef();
}
+ patch->pMethodDescFilter = NULL;
patch->offset = offset;
patch->offsetIsIL = FALSE;
patch->address = address;
@@ -1795,7 +1812,9 @@ void DebuggerController::DeactivatePatch(DebuggerControllerPatch *patch)
// optimization.</REVISIT_TODO>
DebuggerControllerPatch *DebuggerController::AddILMasterPatch(Module *module,
mdMethodDef md,
+ MethodDesc *pMethodDescFilter,
SIZE_T offset,
+ BOOL offsetIsIL,
SIZE_T encVersion)
{
CONTRACTL
@@ -1814,7 +1833,9 @@ DebuggerControllerPatch *DebuggerController::AddILMasterPatch(Module *module,
DebuggerControllerPatch *patch = g_patches->AddPatchForMethodDef(this,
module,
md,
+ pMethodDescFilter,
offset,
+ offsetIsIL,
PATCH_KIND_IL_MASTER,
LEAF_MOST_FRAME,
NULL,
@@ -1822,7 +1843,8 @@ DebuggerControllerPatch *DebuggerController::AddILMasterPatch(Module *module,
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));
+ "DC::AP: Added IL master patch 0x%p for mdTok 0x%x, desc 0x%p at %s offset %d encVersion %d\n",
+ patch, md, pMethodDescFilter, offsetIsIL ? "il" : "native", offset, encVersion));
return patch;
}
@@ -1835,40 +1857,56 @@ BOOL DebuggerController::AddBindAndActivateILSlavePatch(DebuggerControllerPatch
_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;
+ BOOL result = FALSE;
- // 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())
+ if (!master->offsetIsIL)
+ {
+ // Zero is the only native offset that we allow to bind across different jitted
+ // code bodies.
+ _ASSERTE(master->offset == 0);
+ INDEBUG(BOOL fOk = )
+ AddBindAndActivatePatchForMethodDesc(dji->m_fd, dji,
+ 0, PATCH_KIND_IL_SLAVE,
+ LEAF_MOST_FRAME, m_pAppDomain);
+ _ASSERTE(fOk);
+ result = TRUE;
+ }
+ else // bind by IL offset
{
- BOOL fExact;
- SIZE_T offsetNative = it.Current(&fExact);
+ // Do not dereference the "master" pointer in the loop! The loop may add more patches,
+ // causing the patch table to grow and move.
+ SIZE_T masterILOffset = master->offset;
- // 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))
+ // 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())
{
- 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));
+ BOOL fExact;
+ SIZE_T offsetNative = it.Current(&fExact);
- continue;
- }
- else
- {
- result = TRUE;
- }
+ // 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));
- INDEBUG(BOOL fOk = )
- AddBindAndActivatePatchForMethodDesc(dji->m_fd, dji,
- offsetNative, PATCH_KIND_IL_SLAVE,
- LEAF_MOST_FRAME, m_pAppDomain);
- _ASSERTE(fOk);
+ 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.
@@ -1890,8 +1928,10 @@ BOOL DebuggerController::AddBindAndActivateILSlavePatch(DebuggerControllerPatch
// that have debugging information
BOOL DebuggerController::AddILPatch(AppDomain * pAppDomain, Module *module,
mdMethodDef md,
+ MethodDesc *pMethodDescFilter,
SIZE_T encVersion, // what encVersion does this apply to?
- SIZE_T offset)
+ SIZE_T offset,
+ BOOL offsetIsIL)
{
_ASSERTE(g_patches != NULL);
_ASSERTE(md != NULL);
@@ -1913,7 +1953,7 @@ BOOL DebuggerController::AddILPatch(AppDomain * pAppDomain, Module *module,
//
// 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);
+ DebuggerControllerPatch *master = AddILMasterPatch(module, md, pMethodDescFilter, offset, offsetIsIL, 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.
@@ -1922,7 +1962,7 @@ BOOL DebuggerController::AddILPatch(AppDomain * pAppDomain, Module *module,
// 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);
+ dmi->IterateAllDJIs(pAppDomain, NULL /* module filter */, pMethodDescFilter, &it);
if (it.IsAtEnd())
{
@@ -1941,7 +1981,8 @@ BOOL DebuggerController::AddILPatch(AppDomain * pAppDomain, Module *module,
{
DebuggerJitInfo *dji = it.Current();
_ASSERTE(dji->m_jitComplete);
- if (dji->m_encVersion == encVersion)
+ if (dji->m_encVersion == encVersion &&
+ (pMethodDescFilter == NULL || pMethodDescFilter == dji->m_fd))
{
fVersionMatch = TRUE;
@@ -1992,7 +2033,10 @@ void DebuggerController::AddPatchToStartOfLatestMethod(MethodDesc * fd)
CONTRACTL_END;
_ASSERTE(g_patches != NULL);
- DebuggerController::AddBindAndActivatePatchForMethodDesc(fd, NULL, 0, PATCH_KIND_NATIVE_MANAGED, LEAF_MOST_FRAME, NULL);
+ Module* pModule = fd->GetModule();
+ mdToken defToken = fd->GetMemberDef();
+ DebuggerMethodInfo* pDMI = g_pDebugger->GetOrCreateMethodInfo(pModule, defToken);
+ DebuggerController::AddILPatch(GetAppDomain(), pModule, defToken, fd, pDMI->GetCurrentEnCVersion(), 0, FALSE);
return;
}
@@ -2023,10 +2067,10 @@ BOOL DebuggerController::AddBindAndActivateNativeManagedPatch(MethodDesc * fd,
return DebuggerController::AddBindAndActivatePatchForMethodDesc(fd, dji, offsetNative, PATCH_KIND_NATIVE_MANAGED, fp, pAppDomain);
}
-
+// Adds a breakpoint at a specific native offset in a particular jitted code version
BOOL DebuggerController::AddBindAndActivatePatchForMethodDesc(MethodDesc *fd,
DebuggerJitInfo *dji,
- SIZE_T offset,
+ SIZE_T nativeOffset,
DebuggerPatchKind kind,
FramePointer fp,
AppDomain *pAppDomain)
@@ -2039,6 +2083,7 @@ BOOL DebuggerController::AddBindAndActivatePatchForMethodDesc(MethodDesc *fd,
MODE_ANY; // don't really care what mode we're in.
PRECONDITION(ThisMaybeHelperThread());
+ PRECONDITION(kind != PATCH_KIND_IL_MASTER);
}
CONTRACTL_END;
@@ -2048,13 +2093,15 @@ BOOL DebuggerController::AddBindAndActivatePatchForMethodDesc(MethodDesc *fd,
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));
+ nativeOffset, fp.GetSPValue(), pAppDomain));
DebuggerControllerPatch *patch = g_patches->AddPatchForMethodDef(
this,
g_pEEInterface->MethodDescGetModule(fd),
fd->GetMemberDef(),
- offset,
+ NULL,
+ nativeOffset,
+ FALSE,
kind,
fp,
pAppDomain,
@@ -2285,6 +2332,7 @@ bool DebuggerController::PatchTrace(TraceDestination *trace,
}
CONTRACTL_END;
DebuggerControllerPatch *dcp = NULL;
+ SIZE_T nativeOffset = 0;
switch (trace->GetTraceType())
{
@@ -2321,11 +2369,25 @@ bool DebuggerController::PatchTrace(TraceDestination *trace,
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);
+ nativeOffset = CodeRegionInfo::GetCodeRegionInfo(dji, fd).AddressToOffset((const BYTE *)trace->GetAddress());
+
+ // Code versioning allows calls to be redirected to alternate code potentially after this trace is complete but before
+ // execution reaches the call target. Rather than bind the breakpoint to a specific jitted code instance that is currently
+ // configured to receive execution we need to prepare for that potential retargetting by binding all jitted code instances.
+ //
+ // Triggering this based of the native offset is a little subtle, but all of the stubmanagers follow a rule that if they
+ // trace across a call boundary into jitted code they either stop at offset zero of the new method, or they continue tracing
+ // out of that jitted code.
+ if (nativeOffset == 0)
+ {
+ AddPatchToStartOfLatestMethod(fd);
+ }
+ else
+ {
+ AddBindAndActivateNativeManagedPatch(fd, dji, nativeOffset, fp, NULL);
+ }
+
+
return true;
case TRACE_UNJITTED_METHOD:
@@ -4893,14 +4955,39 @@ DebuggerBreakpoint::DebuggerBreakpoint(Module *module,
_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)
+ BOOL bindAcrossAllJittedInstances = !native;
+ MethodDesc* pGenericInstanceFilter = NULL;
+#ifdef DEBUG
+ // Normally any breakpoint specified as a native offset only binds in one jitted instance of a method, however
+ // to better test the breakpoint binding logic in debug builds we allow the behavior to change. The test behavior
+ // binds native breakpoints in every code version of the same generic instance. Currently the only way to get more
+ // than one such version is to use tiered compilation, but even with only one version the code path is a little different.
+ //
+ // This covers the same code paths used to add a step-in breakpoint, because step-in needs to handle code version changes
+ // transparently but it is challenging to create a test case that ensures the code version will change exactly during the
+ // tiny window of time that the step-in breakpoint exists.
+ static ConfigDWORD config;
+ if(config.val(CLRConfig::INTERNAL_DbgNativeCodeBpBindsAcrossVersions))
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DB::DB Test hook COMPLUS_DbgNativeCodeBpBindsAcrossVersions is active\n"));
+ if (native && offset == 0 && nativeMethodDesc)
+ {
+ LOG((LF_CORDB, LL_INFO1000, "DB::DB Test hook modification: native breakpoint at offset 0 binding to all code versions\n"));
+ bindAcrossAllJittedInstances = TRUE;
+ pGenericInstanceFilter = nativeMethodDesc;
+ }
+ }
+#endif
+
+ if (!bindAcrossAllJittedInstances)
{
(*pSucceed) = AddBindAndActivateNativeManagedPatch(nativeMethodDesc, nativeJITInfo, offset, LEAF_MOST_FRAME, pAppDomain);
return;
}
else
{
- (*pSucceed) = AddILPatch(pAppDomain, module, md, ilEnCVersion, offset);
+ _ASSERTE(!native || offset == 0);
+ (*pSucceed) = AddILPatch(pAppDomain, module, md, pGenericInstanceFilter, ilEnCVersion, offset, !native);
}
}
@@ -5594,9 +5681,9 @@ bool DebuggerStepper::TrapStepInHelper(
else
{
LOG((LF_CORDB, LL_INFO1000, "Didn't step: md:0x%x"
- "td.type:%s td.address:0x%x, gfa:0x%x\n",
+ "td.type:%s td.address:0x%p, hot code address:0x%p\n",
md, GetTType(td.GetTraceType()), td.GetAddress(),
- g_pEEInterface->GetFunctionAddress(md)));
+ code.getAddrOfHotCode()));
}
}
else
diff --git a/src/debug/ee/controller.h b/src/debug/ee/controller.h
index 95569b55c7..bac635e2f7 100644
--- a/src/debug/ee/controller.h
+++ b/src/debug/ee/controller.h
@@ -276,16 +276,32 @@ struct DebuggerFunctionKey1
typedef DebuggerFunctionKey1 UNALIGNED DebuggerFunctionKey;
-// ILMaster: Breakpoints on IL code may need to be applied to multiple
-// copies of code, because generics mean code gets JITTed multiple times.
-// The "master" is a patch we keep to record the IL offset, and is used to
-// create new "slave"patches.
-
+// IL Master: Breakpoints on IL code may need to be applied to multiple
+// copies of code. Historically generics was the only way IL code was JITTed
+// multiple times but more recently the CodeVersionManager and tiered compilation
+// provide more open-ended mechanisms to have multiple native code bodies derived
+// from a single IL method body.
+// The "master" is a patch we keep to record the IL offset or native offset, and
+// is used to create new "slave"patches. For native offsets only offset 0 is allowed
+// because that is the only one that we think would have a consistent semantic
+// meaning across different code bodies.
+// There can also be multiple IL bodies for the same method given EnC or ReJIT.
+// A given master breakpoint is tightly bound to one particular IL body determined
+// by encVersion. ReJIT + breakpoints isn't currently supported.
+//
+//
+// IL Slave: The slaves created from Master patches. If the master used an IL offset
+// then the slave also initially has an IL offset that will later become a native offset.
+// If the master uses a native offset (0) then the slave will also have a native offset (0).
+// These patches always resolve to addresses in jitted code.
//
-// ILSlave: The slaves created from ILMaster patches. The offset for
-// these is initially an IL offset and later becomes a native offset.
//
-// NativeManaged: A patch we apply to managed code, usually for walkers etc.
+// NativeManaged: A patch we apply to managed code, usually for walkers etc. If this code
+// is jitted then these patches are always bound to one exact jitted code body.
+// If you need to be 100% sure I suggest you do more code review but I believe we also
+// use this for managed code from other code generators such as a stub or statically compiled
+// code that executes in cooperative mode.
+//
//
// NativeUnmanaged: A patch applied to any kind of native code.
@@ -361,6 +377,8 @@ struct DebuggerControllerPatch
PRD_TYPE opcodeSaved;//also a misnomer
BOOL offsetIsIL;
TraceDestination trace;
+ MethodDesc* pMethodDescFilter; // used for IL Master patches that should only bind to jitted
+ // code versions for a single generic instantiation
private:
int refCount;
union
@@ -663,7 +681,9 @@ public:
DebuggerControllerPatch *AddPatchForMethodDef(DebuggerController *controller,
Module *module,
mdMethodDef md,
- size_t offset,
+ MethodDesc *pMethodDescFilter,
+ size_t offset,
+ BOOL offsetIsIL,
DebuggerPatchKind kind,
FramePointer fp,
AppDomain *pAppDomain,
@@ -1170,8 +1190,10 @@ public:
BOOL AddILPatch(AppDomain * pAppDomain, Module *module,
mdMethodDef md,
+ MethodDesc* pMethodFilter,
SIZE_T encVersion, // what encVersion does this apply to?
- SIZE_T offset);
+ SIZE_T offset,
+ BOOL offsetIsIL);
// The next two are very similar. Both work on offsets,
// but one takes a "patch id". I don't think these are really needed: the
@@ -1244,12 +1266,14 @@ public:
DebuggerControllerPatch *AddILMasterPatch(Module *module,
mdMethodDef md,
+ MethodDesc *pMethodDescFilter,
SIZE_T offset,
+ BOOL offsetIsIL,
SIZE_T encVersion);
BOOL AddBindAndActivatePatchForMethodDesc(MethodDesc *fd,
DebuggerJitInfo *dji,
- SIZE_T offset,
+ SIZE_T nativeOffset,
DebuggerPatchKind kind,
FramePointer fp,
AppDomain *pAppDomain);
diff --git a/src/debug/ee/debugger.cpp b/src/debug/ee/debugger.cpp
index 772862d839..ec6cb7a2eb 100644
--- a/src/debug/ee/debugger.cpp
+++ b/src/debug/ee/debugger.cpp
@@ -4973,6 +4973,15 @@ HRESULT Debugger::MapAndBindFunctionPatches(DebuggerJitInfo *djiNew,
continue;
}
+ // If the patch only applies in certain generic instances, don't bind it
+ // elsewhere.
+ if(dcp->pMethodDescFilter != NULL && dcp->pMethodDescFilter != djiNew->m_fd)
+ {
+ LOG((LF_CORDB, LL_INFO10000, "Patch not in this generic instance\n"));
+ continue;
+ }
+
+
// Do not copy over slave breakpoint patches. Instead place a new slave
// based off the master.
if (dcp->IsILSlavePatch())
@@ -9706,7 +9715,7 @@ void Debugger::LoadModuleFinished(Module * pRuntimeModule, AppDomain * pAppDomai
// Found a relevant IL master patch. Now bind all corresponding slave patches
// that belong to this Module
DebuggerMethodInfo::DJIIterator it;
- dmi->IterateAllDJIs(pAppDomain, pRuntimeModule, &it);
+ dmi->IterateAllDJIs(pAppDomain, pRuntimeModule, pMasterPatchCur->pMethodDescFilter, &it);
for (; !it.IsAtEnd(); it.Next())
{
DebuggerJitInfo *dji = it.Current();
diff --git a/src/debug/ee/debugger.h b/src/debug/ee/debugger.h
index 0bcfca1620..725d052a36 100644
--- a/src/debug/ee/debugger.h
+++ b/src/debug/ee/debugger.h
@@ -955,6 +955,7 @@ public:
DebuggerJitInfo* m_pCurrent;
Module* m_pLoaderModuleFilter;
+ MethodDesc* m_pMethodDescFilter;
public:
DJIIterator();
@@ -964,8 +965,10 @@ public:
};
- // Ensure the DJI cache is completely up to date. (This is heavy weight).
- void CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module * pModuleFilter = NULL);
+ // Ensure the DJI cache is completely up to date. (This can be an expensive call, but
+ // much less so if pMethodDescFilter is used).
+ void CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module * pModuleFilter, MethodDesc * pMethodDescFilter);
+
// Ensure the DJI cache is up to date for a particular closed method desc
void CreateDJIsForMethodDesc(MethodDesc * pMethodDesc);
@@ -975,7 +978,9 @@ public:
// You may optionally pass pLoaderModuleFilter to restrict the DJIs iterated to
// exist only on MethodDescs whose loader module matches the filter (pass NULL not
// to filter by loader module).
- void IterateAllDJIs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, DJIIterator * pEnum);
+ // You may optionally pass pMethodDescFilter to restrict the DJIs iterated to only
+ // a single generic instantiation.
+ void IterateAllDJIs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, MethodDesc * pMethodDescFilter, DJIIterator * pEnum);
private:
// The linked list of JIT's of this version of the method. This will ALWAYS
diff --git a/src/debug/ee/functioninfo.cpp b/src/debug/ee/functioninfo.cpp
index 5ed88d4c09..db0cfc86f9 100644
--- a/src/debug/ee/functioninfo.cpp
+++ b/src/debug/ee/functioninfo.cpp
@@ -1820,6 +1820,10 @@ void DebuggerMethodInfo::DJIIterator::Next(BOOL fFirst /*=FALSE*/)
if ((m_pLoaderModuleFilter != NULL) && (m_pLoaderModuleFilter != pLoaderModule))
continue;
+ //Obey the methodDesc filter if it is provided
+ if ((m_pMethodDescFilter != NULL) && (m_pMethodDescFilter != m_pCurrent->m_fd))
+ continue;
+
// Skip modules that are unloaded, but still hanging around. Note that we can't use DebuggerModule for this check
// because of it is deleted pretty early during unloading, and we do not want to recreate it.
if (pLoaderModule->GetLoaderAllocator()->IsUnloaded())
@@ -1929,7 +1933,7 @@ void DebuggerMethodInfo::SetJMCStatus(bool fStatus)
// Get an iterator that will go through ALL native code-blobs (DJI) in the specified
// AppDomain, optionally filtered by loader module (if pLoaderModuleFilter != NULL).
// This is EnC/ Generics / Prejit aware.
-void DebuggerMethodInfo::IterateAllDJIs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, DebuggerMethodInfo::DJIIterator * pEnum)
+void DebuggerMethodInfo::IterateAllDJIs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, MethodDesc * pMethodDescFilter, DebuggerMethodInfo::DJIIterator * pEnum)
{
CONTRACTL
{
@@ -1940,13 +1944,14 @@ void DebuggerMethodInfo::IterateAllDJIs(AppDomain * pAppDomain, Module * pLoader
CONTRACTL_END;
_ASSERTE(pEnum != NULL);
- _ASSERTE(pAppDomain != NULL);
+ _ASSERTE(pAppDomain != NULL || pMethodDescFilter != NULL);
// Esnure we have DJIs for everything.
- CreateDJIsForNativeBlobs(pAppDomain, pLoaderModuleFilter);
+ CreateDJIsForNativeBlobs(pAppDomain, pLoaderModuleFilter, pMethodDescFilter);
pEnum->m_pCurrent = m_latestJitInfo;
pEnum->m_pLoaderModuleFilter = pLoaderModuleFilter;
+ pEnum->m_pMethodDescFilter = pMethodDescFilter;
// Advance to the first DJI that passes the filter
pEnum->Next(TRUE);
@@ -1962,9 +1967,10 @@ void DebuggerMethodInfo::IterateAllDJIs(AppDomain * pAppDomain, Module * pLoader
// loader module matches this one. (This can be different from m_module in the
// case of generics defined in one module and instantiated in another). If
// non-NULL, create DJIs for all modules in pAppDomain.
+// * pMethodDescFilter - If non-NULL, create DJIs only for this single MethodDesc.
//
-void DebuggerMethodInfo::CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module * pLoaderModuleFilter /* = NULL */)
+void DebuggerMethodInfo::CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module * pLoaderModuleFilter, MethodDesc* pMethodDescFilter)
{
CONTRACTL
{
@@ -1976,37 +1982,46 @@ void DebuggerMethodInfo::CreateDJIsForNativeBlobs(AppDomain * pAppDomain, Module
// If we're not stopped and the module we're iterating over allows types to load,
// then it's possible new native blobs are being created underneath us.
- _ASSERTE(g_pDebugger->IsStopped() || ((pLoaderModuleFilter != NULL) && !pLoaderModuleFilter->IsReadyForTypeLoad()));
-
- // @todo - we really only need to do this if the stop-counter goes up (else we know nothing new is added).
- // B/c of generics, it's possible that new instantiations of a method may have been jitted.
- // So just loop through all known instantiations and ensure that we have all the DJIs.
- // Note that this iterator won't show previous EnC versions, but we're already guaranteed to
- // have DJIs for every verision of a method that was EnCed.
- // This also handles the possibility of getting the same methoddesc back from the iterator.
- // It also lets EnC + generics play nice together (including if an generic method was EnC-ed)
- LoadedMethodDescIterator it(pAppDomain, m_module, m_token);
- CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
- while (it.Next(pDomainAssembly.This()))
- {
- MethodDesc * pDesc = it.Current();
- if (!pDesc->HasNativeCode())
+ _ASSERTE(g_pDebugger->IsStopped() ||
+ ((pLoaderModuleFilter != NULL) && !pLoaderModuleFilter->IsReadyForTypeLoad()) ||
+ pMethodDescFilter != NULL);
+
+ if (pMethodDescFilter != NULL)
+ {
+ CreateDJIsForMethodDesc(pMethodDescFilter);
+ }
+ else
+ {
+ // @todo - we really only need to do this if the stop-counter goes up (else we know nothing new is added).
+ // B/c of generics, it's possible that new instantiations of a method may have been jitted.
+ // So just loop through all known instantiations and ensure that we have all the DJIs.
+ // Note that this iterator won't show previous EnC versions, but we're already guaranteed to
+ // have DJIs for every verision of a method that was EnCed.
+ // This also handles the possibility of getting the same methoddesc back from the iterator.
+ // It also lets EnC + generics play nice together (including if an generic method was EnC-ed)
+ LoadedMethodDescIterator it(pAppDomain, m_module, m_token);
+ CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
+ while (it.Next(pDomainAssembly.This()))
{
- continue;
- }
+ MethodDesc * pDesc = it.Current();
+ if (!pDesc->HasNativeCode())
+ {
+ continue;
+ }
- Module * pLoaderModule = pDesc->GetLoaderModule();
+ Module * pLoaderModule = pDesc->GetLoaderModule();
- // Obey the module filter if it's provided
- if ((pLoaderModuleFilter != NULL) && (pLoaderModuleFilter != pLoaderModule))
- continue;
+ // Obey the module filter if it's provided
+ if ((pLoaderModuleFilter != NULL) && (pLoaderModuleFilter != pLoaderModule))
+ continue;
- // Skip modules that are unloaded, but still hanging around. Note that we can't use DebuggerModule for this check
- // because of it is deleted pretty early during unloading, and we do not want to recreate it.
- if (pLoaderModule->GetLoaderAllocator()->IsUnloaded())
- continue;
+ // Skip modules that are unloaded, but still hanging around. Note that we can't use DebuggerModule for this check
+ // because of it is deleted pretty early during unloading, and we do not want to recreate it.
+ if (pLoaderModule->GetLoaderAllocator()->IsUnloaded())
+ continue;
- CreateDJIsForMethodDesc(pDesc);
+ CreateDJIsForMethodDesc(pDesc);
+ }
}
}