Skip to content

Commit 12fa864

Browse files
authored
Fix exception handling in the prestub worker (#111937)
* Fix exception handling in prestub worker There is a bug in the new exception handling when ThePreStub is called from CallDescrWorkerInternal and the exception is propagated through that. One of such cases is when a managed class is being initialized during JITting and the constructor is in an assembly that's not found. The bug results in skipping all the native frames upto a managed frame that called that native chain that lead to the exception. In the specific case I've mentioned, a lock in the native code is left in locked state. That later leads to a hang. This was case was observed with Roslyn invoking an analyzer where one of the dependencies was missing. The fix is to ensure that when ThePreStub is called by CallDescrWorkerInternal, the exception is not caught in the PreStubWorker. It is left flowing into the native calling code instead. On Windows, we also need to prevent the ProcessCLRException invocation to call into the managed exception handling code. * Regression test * Fix ExternalMethodFixupWorker and VSD_ResolveWorker too These two need the same treatment when they end up being called from CallDescrWorkerInternal. Add regression tests for those cases too. These two cases don't result in a visible failure, so the regression tests just exercise the specific code paths. * Disable the regression test for NativeAOT / MonoAOT * Reverting change in PrestubWorker_Preemptive This change is not needed, as that function cannot be called via CallDescrWorker. * Few forgotten cleanups * Remove the RequiresProcessIsolation * Disable the regression test for WASM * Attempt to properly disable the test for WASM * Another attempt to prevent running the test on WASM
1 parent 99d6fc0 commit 12fa864

File tree

12 files changed

+270
-62
lines changed

12 files changed

+270
-62
lines changed

src/coreclr/vm/excep.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3053,14 +3053,17 @@ void StackTraceInfo::AppendElement(OBJECTHANDLE hThrowable, UINT_PTR currentIP,
30533053
// This is a workaround to fix the generation of stack traces from exception objects so that
30543054
// they point to the line that actually generated the exception instead of the line
30553055
// following.
3056-
if (pCf->IsIPadjusted())
3056+
if (pCf != NULL)
30573057
{
3058-
stackTraceElem.flags |= STEF_IP_ADJUSTED;
3059-
}
3060-
else if (!pCf->HasFaulted() && stackTraceElem.ip != 0)
3061-
{
3062-
stackTraceElem.ip -= STACKWALK_CONTROLPC_ADJUST_OFFSET;
3063-
stackTraceElem.flags |= STEF_IP_ADJUSTED;
3058+
if (pCf->IsIPadjusted())
3059+
{
3060+
stackTraceElem.flags |= STEF_IP_ADJUSTED;
3061+
}
3062+
else if (!pCf->HasFaulted() && stackTraceElem.ip != 0)
3063+
{
3064+
stackTraceElem.ip -= STACKWALK_CONTROLPC_ADJUST_OFFSET;
3065+
stackTraceElem.flags |= STEF_IP_ADJUSTED;
3066+
}
30643067
}
30653068

30663069
#ifndef TARGET_UNIX // Watson is supported on Windows only

src/coreclr/vm/exceptionhandling.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,12 @@ ProcessCLRExceptionNew(IN PEXCEPTION_RECORD pExceptionRecord,
932932

933933
Thread* pThread = GetThread();
934934

935-
if (pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException))
935+
// Skip native frames of asm helpers that have the ProcessCLRException set as their personality routine.
936+
// There is nothing to do for those with the new exception handling.
937+
// Also skip all frames when processing unhandled exceptions. That allows them to reach the host app
938+
// level and let 3rd party the chance to handle them.
939+
if (!ExecutionManager::IsManagedCode((PCODE)pDispatcherContext->ControlPc) ||
940+
pThread->HasThreadStateNC(Thread::TSNC_ProcessedUnhandledException))
936941
{
937942
if ((pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING))
938943
{
@@ -8520,7 +8525,7 @@ static StackWalkAction MoveToNextNonSkippedFrame(StackFrameIterator* pStackFrame
85208525
return retVal;
85218526
}
85228527

8523-
extern "C" size_t CallDescrWorkerInternalReturnAddressOffset;
8528+
bool IsCallDescrWorkerInternalReturnAddress(PCODE pCode);
85248529

85258530
extern "C" bool QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollideClauseIdx, bool* fUnwoundReversePInvoke, bool* pfIsExceptionIntercepted)
85268531
{
@@ -8575,8 +8580,7 @@ extern "C" bool QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollideCla
85758580
}
85768581
else
85778582
{
8578-
size_t CallDescrWorkerInternalReturnAddress = (size_t)CallDescrWorkerInternal + CallDescrWorkerInternalReturnAddressOffset;
8579-
if (GetIP(pThis->m_crawl.GetRegisterSet()->pCallerContext) == CallDescrWorkerInternalReturnAddress)
8583+
if (IsCallDescrWorkerInternalReturnAddress(GetIP(pThis->m_crawl.GetRegisterSet()->pCallerContext)))
85808584
{
85818585
invalidRevPInvoke = true;
85828586
}

src/coreclr/vm/exceptmacros.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,22 @@ VOID DECLSPEC_NORETURN UnwindAndContinueRethrowHelperAfterCatch(Frame* pEntryFra
277277
#ifdef TARGET_UNIX
278278
VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHardwareException);
279279

280-
#define INSTALL_MANAGED_EXCEPTION_DISPATCHER \
280+
#define INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX \
281281
PAL_SEHException exCopy; \
282282
bool hasCaughtException = false; \
283283
try {
284284

285-
#define UNINSTALL_MANAGED_EXCEPTION_DISPATCHER \
285+
#define INSTALL_MANAGED_EXCEPTION_DISPATCHER \
286+
INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX
287+
288+
#define UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(nativeRethrow) \
286289
} \
287290
catch (PAL_SEHException& ex) \
288291
{ \
292+
if (nativeRethrow) \
293+
{ \
294+
throw; \
295+
} \
289296
exCopy = std::move(ex); \
290297
hasCaughtException = true; \
291298
} \
@@ -294,6 +301,9 @@ VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHar
294301
DispatchManagedException(exCopy, false);\
295302
}
296303

304+
#define UNINSTALL_MANAGED_EXCEPTION_DISPATCHER \
305+
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(false)
306+
297307
// Install trap that catches unhandled managed exception and dumps its stack
298308
#define INSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP \
299309
try {
@@ -315,7 +325,9 @@ VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHar
315325
#else // TARGET_UNIX
316326

317327
#define INSTALL_MANAGED_EXCEPTION_DISPATCHER
328+
#define INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX
318329
#define UNINSTALL_MANAGED_EXCEPTION_DISPATCHER
330+
#define UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX
319331

320332
#define INSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP
321333
#define UNINSTALL_UNHANDLED_MANAGED_EXCEPTION_TRAP

src/coreclr/vm/prestub.cpp

Lines changed: 73 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2536,6 +2536,20 @@ Stub * MakeInstantiatingStubWorker(MethodDesc *pMD)
25362536
}
25372537
#endif // defined(FEATURE_SHARE_GENERIC_CODE)
25382538

2539+
extern "C" size_t CallDescrWorkerInternalReturnAddressOffset;
2540+
2541+
bool IsCallDescrWorkerInternalReturnAddress(PCODE pCode)
2542+
{
2543+
LIMITED_METHOD_CONTRACT;
2544+
#ifdef FEATURE_EH_FUNCLETS
2545+
size_t CallDescrWorkerInternalReturnAddress = (size_t)CallDescrWorkerInternal + CallDescrWorkerInternalReturnAddressOffset;
2546+
2547+
return pCode == CallDescrWorkerInternalReturnAddress;
2548+
#else // FEATURE_EH_FUNCLETS
2549+
return false;
2550+
#endif // FEATURE_EH_FUNCLETS
2551+
}
2552+
25392553
//=============================================================================
25402554
// This function generates the real code when from Preemptive mode.
25412555
// It is specifically designed to work with the UnmanagedCallersOnlyAttribute.
@@ -2631,60 +2645,76 @@ extern "C" PCODE STDCALL PreStubWorker(TransitionBlock* pTransitionBlock, Method
26312645

26322646
pPFrame->Push(CURRENT_THREAD);
26332647

2634-
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
2635-
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
2648+
EX_TRY
2649+
{
2650+
bool propagateExceptionToNativeCode = IsCallDescrWorkerInternalReturnAddress(pTransitionBlock->m_ReturnAddress);
26362651

2637-
// Make sure the method table is restored, and method instantiation if present
2638-
pMD->CheckRestore();
2639-
CONSISTENCY_CHECK(GetAppDomain()->CheckCanExecuteManagedCode(pMD));
2652+
INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX;
2653+
INSTALL_UNWIND_AND_CONTINUE_HANDLER_EX;
26402654

2641-
MethodTable* pDispatchingMT = NULL;
2642-
if (pMD->IsVtableMethod())
2643-
{
2644-
OBJECTREF curobj = pPFrame->GetThis();
2655+
// Make sure the method table is restored, and method instantiation if present
2656+
pMD->CheckRestore();
2657+
CONSISTENCY_CHECK(GetAppDomain()->CheckCanExecuteManagedCode(pMD));
26452658

2646-
if (curobj != NULL) // Check for virtual function called non-virtually on a NULL object
2659+
MethodTable* pDispatchingMT = NULL;
2660+
if (pMD->IsVtableMethod())
26472661
{
2648-
pDispatchingMT = curobj->GetMethodTable();
2662+
OBJECTREF curobj = pPFrame->GetThis();
26492663

2650-
if (pDispatchingMT->IsIDynamicInterfaceCastable())
2664+
if (curobj != NULL) // Check for virtual function called non-virtually on a NULL object
26512665
{
2652-
MethodTable* pMDMT = pMD->GetMethodTable();
2653-
TypeHandle objectType(pDispatchingMT);
2654-
TypeHandle methodType(pMDMT);
2666+
pDispatchingMT = curobj->GetMethodTable();
26552667

2656-
GCStress<cfg_any>::MaybeTrigger();
2657-
INDEBUG(curobj = NULL); // curobj is unprotected and CanCastTo() can trigger GC
2658-
if (!objectType.CanCastTo(methodType))
2668+
if (pDispatchingMT->IsIDynamicInterfaceCastable())
26592669
{
2660-
// Apparently IDynamicInterfaceCastable magic was involved when we chose this method to be called
2661-
// that's why we better stick to the MethodTable it belongs to, otherwise
2662-
// DoPrestub() will fail not being able to find implementation for pMD in pDispatchingMT.
2670+
MethodTable* pMDMT = pMD->GetMethodTable();
2671+
TypeHandle objectType(pDispatchingMT);
2672+
TypeHandle methodType(pMDMT);
2673+
2674+
GCStress<cfg_any>::MaybeTrigger();
2675+
INDEBUG(curobj = NULL); // curobj is unprotected and CanCastTo() can trigger GC
2676+
if (!objectType.CanCastTo(methodType))
2677+
{
2678+
// Apparently IDynamicInterfaceCastable magic was involved when we chose this method to be called
2679+
// that's why we better stick to the MethodTable it belongs to, otherwise
2680+
// DoPrestub() will fail not being able to find implementation for pMD in pDispatchingMT.
26632681

2664-
pDispatchingMT = pMDMT;
2682+
pDispatchingMT = pMDMT;
2683+
}
26652684
}
2666-
}
26672685

2668-
// For value types, the only virtual methods are interface implementations.
2669-
// Thus pDispatching == pMT because there
2670-
// is no inheritance in value types. Note the BoxedEntryPointStubs are shared
2671-
// between all sharable generic instantiations, so the == test is on
2672-
// canonical method tables.
2686+
// For value types, the only virtual methods are interface implementations.
2687+
// Thus pDispatching == pMT because there
2688+
// is no inheritance in value types. Note the BoxedEntryPointStubs are shared
2689+
// between all sharable generic instantiations, so the == test is on
2690+
// canonical method tables.
26732691
#ifdef _DEBUG
2674-
MethodTable* pMDMT = pMD->GetMethodTable(); // put this here to see what the MT is in debug mode
2675-
_ASSERTE(!pMD->GetMethodTable()->IsValueType() ||
2676-
(pMD->IsUnboxingStub() && (pDispatchingMT->GetCanonicalMethodTable() == pMDMT->GetCanonicalMethodTable())));
2692+
MethodTable* pMDMT = pMD->GetMethodTable(); // put this here to see what the MT is in debug mode
2693+
_ASSERTE(!pMD->GetMethodTable()->IsValueType() ||
2694+
(pMD->IsUnboxingStub() && (pDispatchingMT->GetCanonicalMethodTable() == pMDMT->GetCanonicalMethodTable())));
26772695
#endif // _DEBUG
2696+
}
26782697
}
2679-
}
26802698

2681-
GCX_PREEMP_THREAD_EXISTS(CURRENT_THREAD);
2699+
GCX_PREEMP_THREAD_EXISTS(CURRENT_THREAD);
2700+
{
2701+
pbRetVal = pMD->DoPrestub(pDispatchingMT, CallerGCMode::Coop);
2702+
}
2703+
2704+
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_EX(propagateExceptionToNativeCode);
2705+
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(propagateExceptionToNativeCode);
2706+
}
2707+
EX_CATCH
26822708
{
2683-
pbRetVal = pMD->DoPrestub(pDispatchingMT, CallerGCMode::Coop);
2709+
if (g_isNewExceptionHandlingEnabled)
2710+
{
2711+
OBJECTHANDLE ohThrowable = CURRENT_THREAD->LastThrownObjectHandle();
2712+
_ASSERTE(ohThrowable);
2713+
StackTraceInfo::AppendElement(ohThrowable, 0, (UINT_PTR)pTransitionBlock, pMD, NULL);
2714+
}
2715+
EX_RETHROW;
26842716
}
2685-
2686-
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER;
2687-
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER;
2717+
EX_END_CATCH(SwallowAllExceptions)
26882718

26892719
{
26902720
HardwareExceptionHolder;
@@ -3154,8 +3184,10 @@ EXTERN_C PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBl
31543184

31553185
pEMFrame->Push(CURRENT_THREAD); // Push the new ExternalMethodFrame onto the frame stack
31563186

3157-
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
3158-
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
3187+
bool propagateExceptionToNativeCode = IsCallDescrWorkerInternalReturnAddress(pTransitionBlock->m_ReturnAddress);
3188+
3189+
INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX;
3190+
INSTALL_UNWIND_AND_CONTINUE_HANDLER_EX;
31593191

31603192
bool fVirtual = false;
31613193
MethodDesc * pMD = NULL;
@@ -3397,8 +3429,8 @@ EXTERN_C PCODE STDCALL ExternalMethodFixupWorker(TransitionBlock * pTransitionBl
33973429
}
33983430
// Ready to return
33993431

3400-
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER;
3401-
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER;
3432+
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_EX(propagateExceptionToNativeCode);
3433+
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(propagateExceptionToNativeCode);
34023434

34033435
pEMFrame->Pop(CURRENT_THREAD); // Pop the ExternalMethodFrame from the frame stack
34043436

src/coreclr/vm/virtualcallstub.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,8 @@ ResolveCacheElem* __fastcall VirtualCallStubManager::PromoteChainEntry(ResolveCa
12621262
}
12631263
#endif // CHAIN_LOOKUP
12641264

1265+
bool IsCallDescrWorkerInternalReturnAddress(PCODE pCode);
1266+
12651267
/* Resolve to a method and return its address or NULL if there is none.
12661268
Our return value is the target address that control should continue to. Our caller will
12671269
enter the target address as if a direct call with the original stack frame had been made from
@@ -1309,14 +1311,16 @@ PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock,
13091311

13101312
PCODE target = (PCODE)NULL;
13111313

1314+
bool propagateExceptionToNativeCode = IsCallDescrWorkerInternalReturnAddress(pTransitionBlock->m_ReturnAddress);
1315+
13121316
if (pObj == NULL) {
13131317
pSDFrame->SetForNullReferenceException();
13141318
pSDFrame->Push(CURRENT_THREAD);
1315-
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
1316-
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
1319+
INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX;
1320+
INSTALL_UNWIND_AND_CONTINUE_HANDLER_EX;
13171321
COMPlusThrow(kNullReferenceException);
1318-
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER;
1319-
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER;
1322+
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_EX(propagateExceptionToNativeCode);
1323+
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(propagateExceptionToNativeCode);
13201324
_ASSERTE(!"Throw returned");
13211325
}
13221326

@@ -1351,8 +1355,9 @@ PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock,
13511355

13521356
pSDFrame->SetRepresentativeSlot(pRepresentativeMT, representativeToken.GetSlotNumber());
13531357
pSDFrame->Push(CURRENT_THREAD);
1354-
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
1355-
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
1358+
1359+
INSTALL_MANAGED_EXCEPTION_DISPATCHER_EX;
1360+
INSTALL_UNWIND_AND_CONTINUE_HANDLER_EX;
13561361

13571362
// For Virtual Delegates the m_siteAddr is a field of a managed object
13581363
// Thus we have to report it as an interior pointer,
@@ -1388,8 +1393,8 @@ PCODE VSD_ResolveWorker(TransitionBlock * pTransitionBlock,
13881393

13891394
GCPROTECT_END();
13901395

1391-
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER;
1392-
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER;
1396+
UNINSTALL_UNWIND_AND_CONTINUE_HANDLER_EX(propagateExceptionToNativeCode);
1397+
UNINSTALL_MANAGED_EXCEPTION_DISPATCHER_EX(propagateExceptionToNativeCode);
13931398
pSDFrame->Pop(CURRENT_THREAD);
13941399

13951400
return target;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
4+
using System;
5+
6+
namespace Dependency
7+
{
8+
public class DependencyClass
9+
{
10+
public static void Test()
11+
{
12+
}
13+
}
14+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<CLRTestKind>BuildOnly</CLRTestKind>
4+
<OutputType>Library</OutputType>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="dependencytodelete.cs" />
8+
</ItemGroup>
9+
</Project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
4+
.assembly extern legacy library mscorlib {}
5+
.assembly extern dependencytodelete {}
6+
.assembly 'tailcallinvoker' {}
7+
8+
.class public sequential ansi sealed beforefieldinit TailCallInvoker
9+
extends [mscorlib]System.Object
10+
{
11+
.method public static void Test() cil managed noinlining
12+
{
13+
.maxstack 1
14+
tail. call void [dependencytodelete]Dependency.DependencyClass::Test()
15+
ret
16+
} // end of method TailCallInvoker.Test
17+
} // end of class TailCallInvoker
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk.IL">
2+
<PropertyGroup>
3+
<CLRTestKind>BuildOnly</CLRTestKind>
4+
<OutputType>Library</OutputType>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="tailcallinvoker.il" />
8+
<ProjectReference Include="dependencytodelete.csproj" />
9+
</ItemGroup>
10+
</Project>

0 commit comments

Comments
 (0)