Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VM: Include generic instantiations in stack frames #96440

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1e21c17
Including generic instantiations in stack frames
hez2010 Jan 3, 2024
44f2275
Fix another test
hez2010 Jan 3, 2024
250472a
Always keep generic context alive
hez2010 Jan 3, 2024
dbc1445
Only keep generic context when optimization is disabled
hez2010 Jan 3, 2024
a804a43
Add support for exception stacktrace as well
hez2010 Jan 3, 2024
dc44a0d
Update tests
hez2010 Jan 3, 2024
869f439
Try to fix the GC hole
hez2010 Jan 4, 2024
fbe823e
GC hole fixes take 2
hez2010 Jan 4, 2024
2048e41
Protect oRef
hez2010 Jan 4, 2024
9ec2532
Add some TODO comments
hez2010 Jan 4, 2024
504c3fe
Objects are not verifiable
hez2010 Jan 11, 2024
69ca3eb
Prettify stacktrace: strip assembly info for generic arguments
hez2010 Feb 21, 2024
fa307d5
Cleanup
hez2010 Feb 21, 2024
93ee9a7
Fix build failure
hez2010 Feb 22, 2024
60d2c0c
oops
hez2010 Feb 22, 2024
a1598bb
Fix AV
hez2010 Feb 23, 2024
f6e0743
oops
hez2010 Feb 23, 2024
e6cc527
Update tests
hez2010 Feb 23, 2024
a6d49b0
Move exact pFunc init to a later phase
hez2010 Feb 23, 2024
37502b1
Fix tests
hez2010 Feb 23, 2024
c2742cc
Apply NoOptimization to test stackframe
hez2010 Feb 23, 2024
359e396
More tests fixes
hez2010 Feb 23, 2024
07d86bb
Prevent bad MethodDesc being returned back for reflection.
hez2010 Feb 25, 2024
f3118e1
Introduce a switch
hez2010 Feb 25, 2024
70059c5
Update tests
hez2010 Feb 25, 2024
54790f7
Flip the switch
hez2010 Feb 25, 2024
8e9c556
Code cleanup
hez2010 Feb 26, 2024
6cc5a6a
Merge branch 'main' into feature/generic-stacktrace
hez2010 Mar 29, 2024
bd83c8a
Fix unmatched braces
hez2010 Mar 29, 2024
5465ff4
Try fix GC hole
hez2010 Apr 6, 2024
1a6c019
Fix contract
hez2010 Apr 6, 2024
58ec764
Fix debug assert
hez2010 Apr 6, 2024
657e2b3
Don't create new MT
hez2010 Apr 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ internal void InitializeSourceInfo(int iSkip, bool fNeedFileInfo, Exception? exc
if (mh == IntPtr.Zero)
return null;

IRuntimeMethodInfo? mhReal = RuntimeMethodHandle.GetTypicalMethodDefinition(new RuntimeMethodInfoStub(mh, this));

IRuntimeMethodInfo? mhReal = RuntimeMethodHandle.FromIntPtr(mh).GetMethodInfo();
return RuntimeType.GetMethodBase(mhReal);
}

Expand Down
88 changes: 77 additions & 11 deletions src/coreclr/vm/debugdebugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,14 +460,17 @@ FCIMPL4(void, DebugStackTrace::GetStackFramesInternal,
int iNumValidFrames = 0;
for (int i = 0; i < data.cElements; i++)
{
// The managed stacktrace classes always returns typical method definition, so we don't need to bother providing exact instantiation.
// Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(data.pElements[i].pFunc, data.pElements[i].pExactGenericArgsToken, &pExactMethod, &thExactType);
MethodDesc* pFunc = data.pElements[i].pFunc;

// Strip the instantiation to make sure that the reflection never gets a bad method desc back.
noahfalk marked this conversation as resolved.
Show resolved Hide resolved
if (pFunc->HasMethodInstantiation())
pFunc = pFunc->StripMethodInstantiation();
_ASSERTE(pFunc->IsRuntimeMethodHandle());
PTR_VOID pExactGenericArgsToken = data.pElements[i].pExactGenericArgsToken;
if (pFunc->HasClassOrMethodInstantiation() && pFunc->IsSharedByGenericInstantiations())
{
TypeHandle th;
MethodDesc* pConstructedFunc = NULL;
if (pExactGenericArgsToken != NULL && Generics::GetExactInstantiationsOfMethodAndItsClassFromCallInformation(pFunc, pExactGenericArgsToken, &th, &pConstructedFunc))
{
pFunc = pConstructedFunc;
}
}

// Method handle
size_t *pElem = (size_t*)pStackFrameHelper->rgMethodHandle->GetDataPtr();
Expand Down Expand Up @@ -1057,7 +1060,8 @@ StackWalkAction DebugStackTrace::GetStackFramesCallback(CrawlFrame* pCf, VOID* d
dwNativeOffset,
pFunc,
ip,
flags);
flags,
GetExactGenericArgsToken(pFunc, pCf->GetRegisterSet(), pCf->GetCodeInfo(), pCf->GetFrame()));

// We'll init the IL offsets outside the TSL lock.

Expand All @@ -1078,6 +1082,58 @@ StackWalkAction DebugStackTrace::GetStackFramesCallback(CrawlFrame* pCf, VOID* d
}
#endif // !DACCESS_COMPILE

PTR_VOID DebugStackTrace::GetExactGenericArgsToken(PTR_MethodDesc pFunc, PREGDISPLAY pRD, EECodeInfo* pCodeInfo, PTR_Frame pFrame)
{
CONTRACTL
{
NOTHROW;
GC_TRIGGERS;
MODE_COOPERATIVE;
}
CONTRACTL_END;

_ASSERTE(pRD != NULL && pCodeInfo != NULL);

if (!pFunc || !pFunc->IsSharedByGenericInstantiations())
return NULL;

if (pFunc->AcquiresInstMethodTableFromThis())
{
// objects could be in invalid state at this time
// trying to get this here could result in invalid objects
return NULL;
}
else
{
if (pFrame == NULL)
{
return pCodeInfo->GetCodeManager()->GetParamTypeArg(pRD, pCodeInfo);
}
else
{
if (!pFunc->RequiresInstArg())
{
return NULL;
}

#ifdef FEATURE_INTERPRETER
if (pFrame != NULL && pFrame->GetVTablePtr() == InterpreterFrame::GetMethodFrameVPtr())
{
#ifdef DACCESS_COMPILE
// TBD: DACize the interpreter.
return NULL;
#else
return dac_cast<PTR_InterpreterFrame>(pFrame)->GetInterpreter()->GetParamTypeArg();
#endif
}
// Otherwise...
#endif // FEATURE_INTERPRETER

return (dac_cast<PTR_FramedMethodFrame>(pFrame))->GetParamTypeArg();
}
}
}

void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e,
GetStackFramesData *pData,
PTRARRAYREF * pDynamicMethodArray /*= NULL*/
Expand Down Expand Up @@ -1151,22 +1207,30 @@ void DebugStackTrace::GetStackFramesFromException(OBJECTREF * e,
// Currently such methods always return an IP of 0, so they're easy
// to spot.
DWORD dwNativeOffset;
PTR_VOID pExactGenericArgsToken = NULL;

if (cur.ip)
{
EECodeInfo codeInfo(cur.ip);
dwNativeOffset = codeInfo.GetRelOffset();
REGDISPLAY pRD;
CONTEXT ctx = {};
SetIP(&ctx, cur.ip);
SetSP(&ctx, cur.sp);
FillRegDisplay(&pRD, &ctx);
pExactGenericArgsToken = GetExactGenericArgsToken(cur.pFunc, &pRD, &codeInfo, NULL);
}
else
{
dwNativeOffset = 0;
}

pData->pElements[i].InitPass1(
dwNativeOffset,
pMD,
(PCODE)cur.ip,
cur.flags);
cur.flags,
pExactGenericArgsToken);
#ifndef DACCESS_COMPILE
pData->pElements[i].InitPass2();
#endif
Expand All @@ -1187,7 +1251,8 @@ void DebugStackTrace::DebugStackTraceElement::InitPass1(
DWORD dwNativeOffset,
MethodDesc *pFunc,
PCODE ip,
INT flags
INT flags,
PTR_VOID pExactGenericArgsToken
)
{
LIMITED_METHOD_CONTRACT;
Expand All @@ -1200,6 +1265,7 @@ void DebugStackTrace::DebugStackTraceElement::InitPass1(
this->dwOffset = dwNativeOffset;
this->ip = ip;
this->flags = flags;
this->pExactGenericArgsToken = pExactGenericArgsToken;
}

#ifndef DACCESS_COMPILE
Expand Down
5 changes: 4 additions & 1 deletion src/coreclr/vm/debugdebugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,16 @@ class DebugStackTrace
MethodDesc *pFunc;
PCODE ip;
INT flags; // StackStackElementFlags
PTR_VOID pExactGenericArgsToken;

// Initialization done under TSL.
// This is used when first collecting the stack frame data.
void InitPass1(
DWORD dwNativeOffset,
MethodDesc *pFunc,
PCODE ip,
INT flags // StackStackElementFlags
INT flags, // StackStackElementFlags
PTR_VOID pExactGenericArgsToken
);

// Initialization done outside the TSL.
Expand Down Expand Up @@ -157,6 +159,7 @@ class DebugStackTrace
);

static void GetStackFramesFromException(OBJECTREF * e, GetStackFramesData *pData, PTRARRAYREF * pDynamicMethodArray = NULL);
static PTR_VOID GetExactGenericArgsToken(PTR_MethodDesc pFunc, PREGDISPLAY pRD, EECodeInfo* pCodeInfo, PTR_Frame pFrame);

#ifndef DACCESS_COMPILE
// the DAC directly calls GetStackFramesFromException
Expand Down
28 changes: 18 additions & 10 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7684,6 +7684,7 @@ static void getMethodInfoHelper(
(ftn->RequiresInstMethodTableArg() ? CORINFO_GENERICS_CTXT_FROM_METHODTABLE : 0) |
(ftn->RequiresInstMethodDescArg() ? CORINFO_GENERICS_CTXT_FROM_METHODDESC : 0)));


// EEJitManager::ResolveEHClause and CrawlFrame::GetExactGenericInstantiations
// need to be able to get to CORINFO_GENERICS_CTXT_MASK if there are any
// catch clauses like "try {} catch(MyException<T> e) {}".
Expand All @@ -7692,19 +7693,26 @@ static void getMethodInfoHelper(

if (methInfo->options & CORINFO_GENERICS_CTXT_MASK)
{
#if defined(PROFILING_SUPPORTED)
BOOL fProfilerRequiresGenericsContextForEnterLeave = FALSE;
if (ftn->IsJitOptimizationDisabled())
{
BEGIN_PROFILER_CALLBACK(CORProfilerPresent());
if ((&g_profControlBlock)->RequiresGenericsContextForEnterLeave())
{
fProfilerRequiresGenericsContextForEnterLeave = TRUE;
}
END_PROFILER_CALLBACK();
methInfo->options = CorInfoOptions(methInfo->options|CORINFO_GENERICS_CTXT_KEEP_ALIVE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndyAyersMS - Do you know if the JIT is currently preserving the generic context when optimizations are disabled somehow? I expected this would be something we already do but looking around at how CORINFO_GENERICS_CTXT_KEEP_ALIVE is used now that didn't seem to be the case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not.

Generally speaking the generic context is only kept alive if we see the CORINFO_GENERICS_CTXT_KEEP_ALIVE flag from the jithost, or if there is a runtime lookup that uses the context.

}
if (fProfilerRequiresGenericsContextForEnterLeave)
else
hez2010 marked this conversation as resolved.
Show resolved Hide resolved
{
methInfo->options = CorInfoOptions(methInfo->options|CORINFO_GENERICS_CTXT_KEEP_ALIVE);
#if defined(PROFILING_SUPPORTED)
BOOL fProfilerRequiresGenericsContextForEnterLeave = FALSE;
{
BEGIN_PROFILER_CALLBACK(CORProfilerPresent());
if ((&g_profControlBlock)->RequiresGenericsContextForEnterLeave())
{
fProfilerRequiresGenericsContextForEnterLeave = TRUE;
}
END_PROFILER_CALLBACK();
}
if (fProfilerRequiresGenericsContextForEnterLeave)
{
methInfo->options = CorInfoOptions(methInfo->options|CORINFO_GENERICS_CTXT_KEEP_ALIVE);
}
}
#endif // defined(PROFILING_SUPPORTED)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,13 @@ public static IEnumerable<object[]> ToString_TestData()
yield return new object[] { new StackFrame(), "MoveNext at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
yield return new object[] { new StackFrame("FileName", 1, 2), "MoveNext at offset {offset} in file:line:column FileName:1:2" + Environment.NewLine };
yield return new object[] { new StackFrame(int.MaxValue), "<null>" + Environment.NewLine };
yield return new object[] { GenericMethod<string>(), "GenericMethod<T> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
yield return new object[] { GenericMethod<string, int>(), "GenericMethod<T,U> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
#if DEBUG
yield return new object[] { GenericMethod<string>(), "GenericMethod<String> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
yield return new object[] { GenericMethod<string, int>(), "GenericMethod<String,Int32> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
#else
yield return new object[] { GenericMethod<string>(), "GenericMethod<__Canon> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
hez2010 marked this conversation as resolved.
Show resolved Hide resolved
hez2010 marked this conversation as resolved.
Show resolved Hide resolved
yield return new object[] { GenericMethod<string, int>(), "GenericMethod<__Canon,Int32> at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
#endif
yield return new object[] { new ClassWithConstructor().StackFrame, ".ctor at offset {offset} in file:line:column {fileName}:{lineNumber}:{column}" + Environment.NewLine };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,13 @@ public static IEnumerable<object[]> ToString_TestData()
yield return new object[] { NoParameters(), "System.Diagnostics.Tests.StackTraceTests.NoParameters()" };
yield return new object[] { OneParameter(1), "System.Diagnostics.Tests.StackTraceTests.OneParameter(Int32 x)" };
yield return new object[] { TwoParameters(1, null), "System.Diagnostics.Tests.StackTraceTests.TwoParameters(Int32 x, String y)" };
yield return new object[] { Generic<int>(), "System.Diagnostics.Tests.StackTraceTests.Generic[T]()" };
yield return new object[] { Generic<int, string>(), "System.Diagnostics.Tests.StackTraceTests.Generic[T,U]()" };
#if DEBUG
yield return new object[] { Generic<int>(), "System.Diagnostics.Tests.StackTraceTests.Generic[Int32]()" };
yield return new object[] { Generic<int, string>(), "System.Diagnostics.Tests.StackTraceTests.Generic[Int32,String]()" };
#else
yield return new object[] { Generic<int>(), "System.Diagnostics.Tests.StackTraceTests.Generic[Int32]()" };
yield return new object[] { Generic<int, string>(), "System.Diagnostics.Tests.StackTraceTests.Generic[Int32,__Canon]()" };
#endif
yield return new object[] { new ClassWithConstructor().StackTrace, "System.Diagnostics.Tests.StackTraceTests.ClassWithConstructor..ctor()" };

// Methods belonging to the System.Diagnostics namespace are ignored.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ public void StackTraceTest()
{
"System.Tests.EnvironmentStackTrace.StaticFrame(Object obj)",
"System.Tests.EnvironmentStackTrace.TestClass..ctor()",
"System.Tests.EnvironmentStackTrace.GenericFrame[T1,T2](T1 t1, T2 t2)",
#if DEBUG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test will likely fail in native aot outerloop testing.

Implementing this level of detail on native aot side is not possible without a size regression because we simply don't have this information in the file format. It is not an easy fix on native aot side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect such information only being emitted for methods with optimization disabled. So there shouldn't be any size regression for production.

"System.Tests.EnvironmentStackTrace.GenericFrame[DateTime,StringBuilder](DateTime t1, StringBuilder t2)",
#else
"System.Tests.EnvironmentStackTrace.GenericFrame[DateTime,__Canon](DateTime t1, __Canon t2)",
#endif
"System.Tests.EnvironmentStackTrace.TestFrame()"
};

Expand Down
Loading