Skip to content

Commit 390d172

Browse files
janvorlijkotas
andauthored
Fix unhandled exception in tiered compilation thread (#116716)
* Fix unhandled exception in tiered compilation thread The detection the unhandled exception is not working in the case of the tiered compilation thread, because there is no indication that the exception is going to be caught in the `CEEInfo::runWithErrorTrap`. So the EH assumes the exception is going to be unhandled and exits the process. The unhandled exception message was not seen in the repro case of the issue likely because the tiered compilation thread console output didn't get flushed. When running under a debugger and stepping over the `InternalUnhandledExceptionFilter_Worker`, it was actually printed out. The fix is to add `DebuggerU2MCatchHandlerFrame` to the `CEEInfo::runWithErrorTrap`. That allows the EH to see that the exception will be caught there and let it flow through the CallDescrWorker to reach that trap. Closes #116676 * Add regression test and fix GC mode * Make the regression test actually work It needs to be compiled as optimized and the Console.WriteLine is needed to repro the issue. * Modify the regression test to not to print to console * PR feedback * Update src/coreclr/vm/jitinterface.cpp Fix contract Co-authored-by: Jan Kotas <[email protected]> --------- Co-authored-by: Jan Kotas <[email protected]>
1 parent 830a786 commit 390d172

File tree

5 files changed

+65
-144
lines changed

5 files changed

+65
-144
lines changed

src/coreclr/vm/dispatchinfo.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,6 +2169,7 @@ HRESULT DispatchInfo::InvokeMember(SimpleComCallWrapper *pSimpleWrap, DISPID id,
21692169
// which may swallow managed exceptions. The debugger needs this in order to send a
21702170
// CatchHandlerFound (CHF) notification.
21712171
DebuggerU2MCatchHandlerFrame catchFrame(true /* catchesAllExceptions */);
2172+
21722173
EX_TRY
21732174
{
21742175
InvokeMemberDebuggerWrapper(pDispMemberInfo,

src/coreclr/vm/exceptionhandling.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3910,7 +3910,7 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
39103910
QCALL_CONTRACT;
39113911

39123912
StackWalkAction retVal = SWA_FAILED;
3913-
CLR_BOOL invalidRevPInvoke = FALSE;
3913+
CLR_BOOL isPropagatingToNativeCode = FALSE;
39143914
Thread* pThread = GET_THREAD();
39153915
ExInfo* pTopExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker();
39163916

@@ -3958,18 +3958,18 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
39583958
EECodeInfo codeInfo(preUnwindControlPC);
39593959
#ifdef USE_GC_INFO_DECODER
39603960
GcInfoDecoder gcInfoDecoder(codeInfo.GetGCInfoToken(), DECODE_REVERSE_PINVOKE_VAR);
3961-
invalidRevPInvoke = gcInfoDecoder.GetReversePInvokeFrameStackSlot() != NO_REVERSE_PINVOKE_FRAME;
3961+
isPropagatingToNativeCode = gcInfoDecoder.GetReversePInvokeFrameStackSlot() != NO_REVERSE_PINVOKE_FRAME;
39623962
#else // USE_GC_INFO_DECODER
39633963
hdrInfo *hdrInfoBody;
39643964
codeInfo.DecodeGCHdrInfo(&hdrInfoBody);
3965-
invalidRevPInvoke = hdrInfoBody->revPInvokeOffset != INVALID_REV_PINVOKE_OFFSET;
3965+
isPropagatingToNativeCode = hdrInfoBody->revPInvokeOffset != INVALID_REV_PINVOKE_OFFSET;
39663966
#endif // USE_GC_INFO_DECODER
39673967
bool isPropagatingToExternalNativeCode = false;
39683968

3969-
EH_LOG((LL_INFO100, "SfiNext: reached native frame at IP=%p, SP=%p, invalidRevPInvoke=%d\n",
3970-
GetIP(pThis->m_crawl.GetRegisterSet()->pCurrentContext), GetSP(pThis->m_crawl.GetRegisterSet()->pCurrentContext), invalidRevPInvoke));
3969+
EH_LOG((LL_INFO100, "SfiNext: reached native frame at IP=%p, SP=%p, isPropagatingToNativeCode=%d\n",
3970+
GetIP(pThis->m_crawl.GetRegisterSet()->pCurrentContext), GetSP(pThis->m_crawl.GetRegisterSet()->pCurrentContext), isPropagatingToNativeCode));
39713971

3972-
if (invalidRevPInvoke)
3972+
if (isPropagatingToNativeCode)
39733973
{
39743974
#ifdef HOST_UNIX
39753975
void* callbackCxt = NULL;
@@ -3994,20 +3994,20 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
39943994
if (IsCallDescrWorkerInternalReturnAddress(GetIP(pThis->m_crawl.GetRegisterSet()->pCurrentContext)))
39953995
{
39963996
EH_LOG((LL_INFO100, "SfiNext: the native frame is CallDescrWorkerInternal"));
3997-
invalidRevPInvoke = TRUE;
3997+
isPropagatingToNativeCode = TRUE;
39983998
}
39993999
else if (doingFuncletUnwind && codeInfo.GetJitManager()->IsFilterFunclet(&codeInfo))
40004000
{
40014001
EH_LOG((LL_INFO100, "SfiNext: current frame is filter funclet"));
4002-
invalidRevPInvoke = TRUE;
4002+
isPropagatingToNativeCode = TRUE;
40034003
}
40044004
else
40054005
{
40064006
isPropagatingToExternalNativeCode = true;
40074007
}
40084008
}
40094009

4010-
if (invalidRevPInvoke)
4010+
if (isPropagatingToNativeCode)
40114011
{
40124012
pFrame = pThis->m_crawl.GetFrame();
40134013

@@ -4167,7 +4167,7 @@ Exit:;
41674167

41684168
if (fUnwoundReversePInvoke)
41694169
{
4170-
*fUnwoundReversePInvoke = invalidRevPInvoke;
4170+
*fUnwoundReversePInvoke = isPropagatingToNativeCode;
41714171
}
41724172

41734173
if (pThis->GetFrameState() == StackFrameIterator::SFITER_FRAMELESS_METHOD)

src/coreclr/vm/jitinterface.cpp

Lines changed: 12 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -10288,90 +10288,6 @@ int32_t * CEEInfo::getAddrOfCaptureThreadGlobal(void **ppIndirection)
1028810288
return result;
1028910289
}
1029010290

10291-
10292-
// This method is called from CEEInfo::FilterException which
10293-
// is run as part of the SEH filter clause for the JIT.
10294-
// It is fatal to throw an exception while running a SEH filter clause
10295-
// so our contract is NOTHROW, NOTRIGGER.
10296-
//
10297-
LONG EEFilterException(struct _EXCEPTION_POINTERS *pExceptionPointers, void *unused)
10298-
{
10299-
CONTRACTL {
10300-
NOTHROW;
10301-
GC_NOTRIGGER;
10302-
} CONTRACTL_END;
10303-
10304-
int result = 0;
10305-
10306-
JIT_TO_EE_TRANSITION_LEAF();
10307-
10308-
unsigned code = pExceptionPointers->ExceptionRecord->ExceptionCode;
10309-
10310-
#ifdef _DEBUG
10311-
if (code == EXCEPTION_ACCESS_VIOLATION)
10312-
{
10313-
static int hit = 0;
10314-
if (hit++ == 0)
10315-
{
10316-
_ASSERTE(!"Access violation while Jitting!");
10317-
// If you set the debugger to catch access violations and 'go'
10318-
// you will get back to the point at which the access violation occurred
10319-
result = EXCEPTION_CONTINUE_EXECUTION;
10320-
}
10321-
else
10322-
{
10323-
result = EXCEPTION_CONTINUE_SEARCH;
10324-
}
10325-
}
10326-
else
10327-
#endif // _DEBUG
10328-
// No one should be catching breakpoint
10329-
// Similarly the JIT doesn't know how to reset the guard page, so it shouldn't
10330-
// be catching a hard stack overflow
10331-
if (code == EXCEPTION_BREAKPOINT || code == EXCEPTION_SINGLE_STEP || code == EXCEPTION_STACK_OVERFLOW)
10332-
{
10333-
result = EXCEPTION_CONTINUE_SEARCH;
10334-
}
10335-
else if (!IsComPlusException(pExceptionPointers->ExceptionRecord))
10336-
{
10337-
result = EXCEPTION_EXECUTE_HANDLER;
10338-
}
10339-
else
10340-
{
10341-
GCX_COOP();
10342-
10343-
// This is actually the LastThrown exception object.
10344-
OBJECTREF throwable = CLRException::GetThrowableFromExceptionRecord(pExceptionPointers->ExceptionRecord);
10345-
10346-
if (throwable != NULL)
10347-
{
10348-
struct
10349-
{
10350-
OBJECTREF oLastThrownObject;
10351-
} _gc;
10352-
10353-
ZeroMemory(&_gc, sizeof(_gc));
10354-
10355-
// Setup the throwables
10356-
_gc.oLastThrownObject = throwable;
10357-
10358-
GCPROTECT_BEGIN(_gc);
10359-
10360-
// Don't catch ThreadAbort and other uncatchable exceptions
10361-
if (IsUncatchable(&_gc.oLastThrownObject))
10362-
result = EXCEPTION_CONTINUE_SEARCH;
10363-
else
10364-
result = EXCEPTION_EXECUTE_HANDLER;
10365-
10366-
GCPROTECT_END();
10367-
}
10368-
}
10369-
10370-
EE_TO_JIT_TRANSITION_LEAF();
10371-
10372-
return result;
10373-
}
10374-
1037510291
// This code is called if FilterException chose to handle the exception.
1037610292
void CEEInfo::HandleException(struct _EXCEPTION_POINTERS *pExceptionPointers)
1037710293
{
@@ -10550,26 +10466,6 @@ uint32_t CEEInfo::getJitFlags(CORJIT_FLAGS* jitFlags, uint32_t sizeInBytes)
1055010466
}
1055110467

1055210468
/*********************************************************************/
10553-
#if !defined(TARGET_UNIX)
10554-
10555-
struct RunWithErrorTrapFilterParam
10556-
{
10557-
ICorDynamicInfo* m_corInfo;
10558-
void (*m_function)(void*);
10559-
void* m_param;
10560-
EXCEPTION_POINTERS m_exceptionPointers;
10561-
};
10562-
10563-
static LONG RunWithErrorTrapFilter(struct _EXCEPTION_POINTERS* exceptionPointers, void* theParam)
10564-
{
10565-
WRAPPER_NO_CONTRACT;
10566-
10567-
auto* param = reinterpret_cast<RunWithErrorTrapFilterParam*>(theParam);
10568-
param->m_exceptionPointers = *exceptionPointers;
10569-
return EEFilterException(exceptionPointers, nullptr);
10570-
}
10571-
10572-
#endif // !defined(TARGET_UNIX)
1057310469

1057410470
bool CEEInfo::runWithSPMIErrorTrap(void (*function)(void*), void* param)
1057510471
{
@@ -10590,43 +10486,25 @@ bool CEEInfo::runWithSPMIErrorTrap(void (*function)(void*), void* param)
1059010486

1059110487
bool CEEInfo::runWithErrorTrap(void (*function)(void*), void* param)
1059210488
{
10593-
// No dynamic contract here because SEH is used
10594-
STATIC_CONTRACT_THROWS;
10595-
STATIC_CONTRACT_GC_TRIGGERS;
10596-
STATIC_CONTRACT_MODE_PREEMPTIVE;
10489+
CONTRACTL {
10490+
THROWS;
10491+
GC_TRIGGERS;
10492+
MODE_PREEMPTIVE;
10493+
}
10494+
CONTRACTL_END;
1059710495

1059810496
// NOTE: the lack of JIT/EE transition markers in this method is intentional. Any
10599-
// transitions into the EE proper should occur either via the call to
10600-
// `EEFilterException` (which is appropriately marked) or via JIT/EE
10601-
// interface calls made by `function`.
10497+
// transitions into the EE proper should occur via JIT/EE interface calls
10498+
// made by `function`.
1060210499

1060310500
bool success = true;
1060410501

10605-
#if !defined(TARGET_UNIX)
10606-
10607-
RunWithErrorTrapFilterParam trapParam;
10608-
trapParam.m_corInfo = this;
10609-
trapParam.m_function = function;
10610-
trapParam.m_param = param;
10611-
10612-
PAL_TRY(RunWithErrorTrapFilterParam*, pTrapParam, &trapParam)
10613-
{
10614-
pTrapParam->m_function(pTrapParam->m_param);
10615-
}
10616-
PAL_EXCEPT_FILTER(RunWithErrorTrapFilter)
10617-
{
10618-
HandleException(&trapParam.m_exceptionPointers);
10619-
success = false;
10620-
}
10621-
PAL_ENDTRY
10622-
10623-
#else // !defined(TARGET_UNIX)
10502+
GCX_COOP();
10503+
DebuggerU2MCatchHandlerFrame catchFrame(true /* catchesAllExceptions */);
1062410504

10625-
// We shouldn't need PAL_TRY on *nix: any exceptions that we are able to catch
10626-
// ought to originate from the runtime itself and should be catchable inside of
10627-
// EX_TRY/EX_CATCH, including emulated SEH exceptions.
1062810505
EX_TRY
1062910506
{
10507+
GCX_PREEMP();
1063010508
function(param);
1063110509
}
1063210510
EX_CATCH
@@ -10636,7 +10514,7 @@ bool CEEInfo::runWithErrorTrap(void (*function)(void*), void* param)
1063610514
}
1063710515
EX_END_CATCH
1063810516

10639-
#endif
10517+
catchFrame.Pop();
1064010518

1064110519
return success;
1064210520
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.Threading;
5+
using System.Runtime.CompilerServices;
6+
using Xunit;
7+
8+
public class Test116676
9+
{
10+
[UnsafeAccessor(UnsafeAccessorKind.Method)]
11+
extern static void DoesNotExist([UnsafeAccessorType("DoesNotExist")] object a);
12+
13+
[MethodImpl(MethodImplOptions.NoInlining)]
14+
static void Work(bool f)
15+
{
16+
if (f)
17+
DoesNotExist(null);
18+
}
19+
20+
[Fact]
21+
public static void TestEntryPoint()
22+
{
23+
for (int i = 0; i < 200; i++)
24+
{
25+
Thread.Sleep(10);
26+
Work(false);
27+
}
28+
}
29+
}
30+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<CLRTestPriority>1</CLRTestPriority>
4+
<Optimize>True</Optimize>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="test116676.cs" />
8+
</ItemGroup>
9+
<ItemGroup>
10+
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
11+
</ItemGroup>
12+
</Project>

0 commit comments

Comments
 (0)