Skip to content

Commit cb978a5

Browse files
janvorliJan Vorlicek
andauthored
Fix missing explicit frame pop on arm32 (#101147)
* Fix missing explicit frame pop on arm32 There is an edge case during exception handling on arm32 where an active InlinedCallFrame is not popped from the explicit frame list. That later leads to various kinds of failures / crashes. For example, the on Alpine arm32, the `dotnet help` hangs eating 100% of one CPU core. That happens due to code executing after the exception was handled and its stack overwriting the explicit frame contents. This can only occur when the pinvoke is inlined in a method that calls it inside of a try region with catch in the same method and exception occurs e.g. due to the target native function or the shared library not existing. What happens is that when we pop the explicit frame, we pop frames that are below the SP of the resume location after catch. But the InlinedCallFrame is in this case above that SP, as it was created in the prolog of the method. To fix that, we need to pop that frame too. The fix uses the same condition as the old EH was using. Closes #100536 * Remove forcing crossgen and filtering by target arch for the test * Reflect PR feedback --------- Co-authored-by: Jan Vorlicek <jan.vorlicek@volny,cz>
1 parent ce84f1d commit cb978a5

File tree

3 files changed

+92
-5
lines changed

3 files changed

+92
-5
lines changed

src/coreclr/vm/exceptionhandling.cpp

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ UINT_PTR ExceptionTracker::FinishSecondPass(
867867

868868
void CleanUpForSecondPass(Thread* pThread, bool fIsSO, LPVOID MemoryStackFpForFrameChain, LPVOID MemoryStackFp);
869869

870-
static void PopExplicitFrames(Thread *pThread, void *targetSp)
870+
static void PopExplicitFrames(Thread *pThread, void *targetSp, void *targetCallerSp)
871871
{
872872
Frame* pFrame = pThread->GetFrame();
873873
while (pFrame < targetSp)
@@ -877,6 +877,39 @@ static void PopExplicitFrames(Thread *pThread, void *targetSp)
877877
pFrame = pThread->GetFrame();
878878
}
879879

880+
// Check if the pFrame is an active InlinedCallFrame inside of the target frame. It needs to be popped or inactivated depending
881+
// on the target architecture / ready to run
882+
if ((pFrame < targetCallerSp) && InlinedCallFrame::FrameHasActiveCall(pFrame))
883+
{
884+
InlinedCallFrame* pInlinedCallFrame = (InlinedCallFrame*)pFrame;
885+
// When unwinding an exception in ReadyToRun, the JIT_PInvokeEnd helper which unlinks the ICF from
886+
// the thread will be skipped. This is because unlike jitted code, each pinvoke is wrapped by calls
887+
// to the JIT_PInvokeBegin and JIT_PInvokeEnd helpers, which push and pop the ICF on the thread. The
888+
// ICF is not linked at the method prolog and unlined at the epilog when running R2R code. Since the
889+
// JIT_PInvokeEnd helper will be skipped, we need to unlink the ICF here. If the executing method
890+
// has another pinvoke, it will re-link the ICF again when the JIT_PInvokeBegin helper is called.
891+
TADDR returnAddress = pInlinedCallFrame->m_pCallerReturnAddress;
892+
#ifdef USE_PER_FRAME_PINVOKE_INIT
893+
// If we're setting up the frame for each P/Invoke for the given platform,
894+
// then we do this for all P/Invokes except ones in IL stubs.
895+
// IL stubs link the frame in for the whole stub, so if an exception is thrown during marshalling,
896+
// the ICF will be on the frame chain and inactive.
897+
if (!ExecutionManager::GetCodeMethodDesc(returnAddress)->IsILStub())
898+
#else
899+
// If we aren't setting up the frame for each P/Invoke (instead setting up once per method),
900+
// then ReadyToRun code is the only code using the per-P/Invoke logic.
901+
if (ExecutionManager::IsReadyToRunCode(returnAddress))
902+
#endif
903+
{
904+
pFrame->ExceptionUnwind();
905+
pFrame->Pop(pThread);
906+
}
907+
else
908+
{
909+
pInlinedCallFrame->Reset();
910+
}
911+
}
912+
880913
GCFrame* pGCFrame = pThread->GetGCFrame();
881914
while (pGCFrame && pGCFrame < targetSp)
882915
{
@@ -907,8 +940,8 @@ ProcessCLRExceptionNew(IN PEXCEPTION_RECORD pExceptionRecord,
907940
if ((pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING))
908941
{
909942
GCX_COOP();
910-
PopExplicitFrames(pThread, (void*)GetSP(pContextRecord));
911-
ExInfo::PopExInfos(pThread, (void*)GetSP(pContextRecord));
943+
PopExplicitFrames(pThread, (void*)pDispatcherContext->EstablisherFrame, (void*)GetSP(pDispatcherContext->ContextRecord));
944+
ExInfo::PopExInfos(pThread, (void*)pDispatcherContext->EstablisherFrame);
912945
}
913946
return ExceptionContinueSearch;
914947
}
@@ -7661,6 +7694,7 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio
76617694
HandlerFn* pfnHandler = (HandlerFn*)pHandlerIP;
76627695
exInfo->m_ScannedStackRange.ExtendUpperBound(exInfo->m_frameIter.m_crawl.GetRegisterSet()->SP);
76637696
DWORD_PTR dwResumePC = 0;
7697+
UINT_PTR callerTargetSp = 0;
76647698

76657699
if (pHandlerIP != NULL)
76667700
{
@@ -7694,10 +7728,11 @@ extern "C" void * QCALLTYPE CallCatchFunclet(QCall::ObjectHandleOnStack exceptio
76947728
// Profiler, debugger and ETW events
76957729
exInfo->MakeCallbacksRelatedToHandler(false, pThread, pMD, &exInfo->m_ClauseForCatch, (DWORD_PTR)pHandlerIP, spForDebugger);
76967730
SetIP(pvRegDisplay->pCurrentContext, dwResumePC);
7731+
callerTargetSp = CallerStackFrame::FromRegDisplay(pvRegDisplay).SP;
76977732
}
76987733

76997734
UINT_PTR targetSp = GetSP(pvRegDisplay->pCurrentContext);
7700-
PopExplicitFrames(pThread, (void*)targetSp);
7735+
PopExplicitFrames(pThread, (void*)targetSp, (void*)callerTargetSp);
77017736

77027737
ExInfo* pExInfo = (PTR_ExInfo)pThread->GetExceptionState()->GetCurrentExceptionTracker();
77037738

@@ -7856,7 +7891,8 @@ extern "C" void QCALLTYPE ResumeAtInterceptionLocation(REGDISPLAY* pvRegDisplay)
78567891

78577892
pExInfo->m_ScannedStackRange.ExtendUpperBound(targetSp);
78587893

7859-
PopExplicitFrames(pThread, (void*)targetSp);
7894+
EECodeManager::EnsureCallerContextIsValid(pvRegDisplay);
7895+
PopExplicitFrames(pThread, (void*)targetSp, (void*)CallerStackFrame::FromRegDisplay(pvRegDisplay).SP);
78607896

78617897
// This must be done before we pop the ExInfos.
78627898
BOOL fIntercepted = pThread->GetExceptionState()->GetFlags()->DebuggerInterceptInfo();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
using System;
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
using Xunit;
7+
8+
public class Test100536
9+
{
10+
[DllImport("__test", CallingConvention = CallingConvention.Cdecl, EntryPoint = "Nonexistent")]
11+
private static extern IntPtr Nonexistent();
12+
13+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
14+
private static void GarbleStack()
15+
{
16+
Span<byte> local = stackalloc byte[4096];
17+
}
18+
19+
[MethodImpl(MethodImplOptions.NoInlining)]
20+
private static void Test()
21+
{
22+
try
23+
{
24+
Nonexistent();
25+
}
26+
catch (Exception ex)
27+
{
28+
Console.WriteLine($"Expected exception {ex} caught");
29+
}
30+
}
31+
32+
[Fact]
33+
public static void TestEntryPoint()
34+
{
35+
Test();
36+
GarbleStack();
37+
GC.Collect();
38+
}
39+
}
40+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<CLRTestPriority>1</CLRTestPriority>
4+
</PropertyGroup>
5+
<ItemGroup>
6+
<Compile Include="test100536.cs" />
7+
</ItemGroup>
8+
<ItemGroup>
9+
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
10+
</ItemGroup>
11+
</Project>

0 commit comments

Comments
 (0)