Skip to content

Commit 4fea27c

Browse files
authored
Notify attached debugger of NAOT exception (dotnet#115678)
Native AOT does not use C++ or SEH exceptions, meaning that debuggers do not recognize the exception throw/catch sequence and cannot break on first-chance exceptions. On Windows, we can use OS functionality to throw and catch an SEH exception when a Native AOT exception is thrown and a debugger is attached, effectively acting as a notification to debuggers that an exception is in flight. The exception code used here is the same one as CoreCLR, so the WinDbg command `sxe clr` would also be sufficient to catch Native AOT exceptions. Fixes dotnet#115514
1 parent b10ea03 commit 4fea27c

File tree

5 files changed

+53
-11
lines changed

5 files changed

+53
-11
lines changed

src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/ExceptionHandling.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,11 +649,18 @@ public static void RhThrowHwEx(uint exceptionCode, ref ExInfo exInfo)
649649
public static void RhThrowEx(object exceptionObj, ref ExInfo exInfo)
650650
{
651651
#if NATIVEAOT
652+
653+
#if TARGET_WINDOWS
654+
// Alert the debugger that we threw an exception.
655+
InternalCalls.RhpFirstChanceExceptionNotification();
656+
#endif // TARGET_WINDOWS
657+
652658
// trigger a GC (only if gcstress) to ensure we can stackwalk at this point
653659
GCStress.TriggerGC();
654660

655661
InternalCalls.RhpValidateExInfoStack();
656-
#endif
662+
#endif // NATIVEAOT
663+
657664
// Transform attempted throws of null to a throw of NullReferenceException.
658665
if (exceptionObj == null)
659666
{
@@ -665,6 +672,7 @@ public static void RhThrowEx(object exceptionObj, ref ExInfo exInfo)
665672
DispatchEx(ref exInfo._frameIter, ref exInfo);
666673
FallbackFailFast(RhFailFastReason.InternalError, null);
667674
}
675+
668676
#if !NATIVEAOT
669677
public static void RhUnwindAndIntercept(ref ExInfo exInfo, UIntPtr interceptStackFrameSP)
670678
{

src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/InternalCalls.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ internal static extern unsafe IntPtr RhpCallPropagateExceptionCallback(
247247
[MethodImpl(MethodImplOptions.InternalCall)]
248248
internal static extern void RhpValidateExInfoStack();
249249

250+
#if TARGET_WINDOWS
251+
[RuntimeImport(Redhawk.BaseName, "RhpFirstChanceExceptionNotification")]
252+
[MethodImpl(MethodImplOptions.InternalCall)]
253+
internal static extern void RhpFirstChanceExceptionNotification();
254+
#endif
255+
250256
#if TARGET_WINDOWS
251257
[RuntimeImport(Redhawk.BaseName, "RhpCopyContextFromExInfo")]
252258
[MethodImpl(MethodImplOptions.InternalCall)]

src/coreclr/nativeaot/Runtime/EHHelpers.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
#include "MethodTable.inl"
3232
#include "CommonMacros.inl"
3333
#include "NativeContext.h"
34+
#include <minipal/debugger.h>
35+
#include "corexcep.h"
3436

3537
struct MethodRegionInfo
3638
{
@@ -85,6 +87,26 @@ FCIMPL0(void, RhpValidateExInfoStack)
8587
}
8688
FCIMPLEND
8789

90+
#ifdef TARGET_WINDOWS
91+
FCIMPL0(void, RhpFirstChanceExceptionNotification)
92+
{
93+
// Throw an SEH exception and immediately catch it. This is used to notify debuggers and other tools
94+
// that an exception has been thrown.
95+
if (minipal_is_native_debugger_present())
96+
{
97+
__try
98+
{
99+
RaiseException(EXCEPTION_COMPLUS, 0, 0, NULL);
100+
}
101+
__except (EXCEPTION_EXECUTE_HANDLER)
102+
{
103+
// Do nothing, we just want to notify the debugger.
104+
}
105+
}
106+
}
107+
FCIMPLEND
108+
#endif // TARGET_WINDOWS
109+
88110
FCIMPL0(void, RhpClearThreadDoNotTriggerGC)
89111
{
90112
Thread * pThisThread = ThreadStore::GetCurrentThread();
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
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+
#include "../../inc/corexcep.h"

src/coreclr/vm/exceptionhandling.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord,
605605
// The 3rd argument passes to PopExplicitFrame is normally the parent SP to correctly handle InlinedCallFrame embbeded
606606
// in parent managed frame. But at this point there are no further managed frames are on the stack, so we can pass NULL.
607607
// Also don't pop the GC frames, their destructor will pop them as the exception propagates.
608-
// NOTE: this needs to be popped in the 2nd pass to ensure that crash dumps and Watson get the dump with these still
608+
// NOTE: this needs to be popped in the 2nd pass to ensure that crash dumps and Watson get the dump with these still
609609
// present.
610610
ExInfo *pExInfo = (ExInfo*)pThread->GetExceptionState()->GetCurrentExceptionTracker();
611611
void *sp = (void*)GetRegdisplaySP(pExInfo->m_frameIter.m_crawl.GetRegisterSet());
@@ -637,7 +637,7 @@ ProcessCLRException(IN PEXCEPTION_RECORD pExceptionRecord,
637637

638638
#ifdef TARGET_X86
639639
CallRtlUnwind((PEXCEPTION_REGISTRATION_RECORD)pEstablisherFrame, NULL, pExceptionRecord, 0);
640-
#else
640+
#else
641641
ClrUnwindEx(pExceptionRecord,
642642
(UINT_PTR)pThread,
643643
INVALID_RESUME_ADDRESS,
@@ -1578,19 +1578,21 @@ BOOL HandleHardwareException(PAL_SEHException* ex)
15781578

15791579
void FirstChanceExceptionNotification()
15801580
{
1581-
#ifndef TARGET_UNIX
1581+
#ifdef TARGET_WINDOWS
1582+
// Throw an SEH exception and immediately catch it. This is used to notify debuggers and other tools
1583+
// that an exception has been thrown.
15821584
if (minipal_is_native_debugger_present())
15831585
{
1584-
PAL_TRY(VOID *, unused, NULL)
1586+
__try
15851587
{
15861588
RaiseException(EXCEPTION_COMPLUS, 0, 0, NULL);
15871589
}
1588-
PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1590+
__except (EXCEPTION_EXECUTE_HANDLER)
15891591
{
1592+
// Do nothing, we just want to notify the debugger.
15901593
}
1591-
PAL_ENDTRY;
15921594
}
1593-
#endif // TARGET_UNIX
1595+
#endif // TARGET_WINDOWS
15941596
}
15951597

15961598
VOID DECLSPEC_NORETURN DispatchManagedException(OBJECTREF throwable, CONTEXT* pExceptionContext, EXCEPTION_RECORD* pExceptionRecord)
@@ -3347,7 +3349,7 @@ extern "C" void QCALLTYPE ResumeAtInterceptionLocation(REGDISPLAY* pvRegDisplay)
33473349
uResumePC = codeInfo.GetJitManager()->GetCodeAddressForRelOffset(codeInfo.GetMethodToken(), static_cast<DWORD>(ulRelOffset));
33483350

33493351
SetIP(pvRegDisplay->pCurrentContext, uResumePC);
3350-
3352+
33513353
STRESS_LOG2(LF_EH, LL_INFO100, "Resuming at interception location at IP=%p, SP=%p\n", uResumePC, GetSP(pvRegDisplay->pCurrentContext));
33523354
ClrRestoreNonvolatileContext(pvRegDisplay->pCurrentContext, targetSSP);
33533355
}
@@ -3442,7 +3444,7 @@ extern "C" CLR_BOOL QCALLTYPE EHEnumInitFromStackFrameIterator(StackFrameIterato
34423444
IJitManager* pJitMan = pFrameIter->m_crawl.GetJitManager();
34433445
const METHODTOKEN& MethToken = pFrameIter->m_crawl.GetMethodToken();
34443446
pExtendedEHEnum->EHCount = pJitMan->InitializeEHEnumeration(MethToken, pEHEnum);
3445-
EH_LOG((LL_INFO100, "Initialized EH enumeration, %d clauses found\n", pExtendedEHEnum->EHCount));
3447+
EH_LOG((LL_INFO100, "Initialized EH enumeration, %d clauses found\n", pExtendedEHEnum->EHCount));
34463448

34473449
if (pExtendedEHEnum->EHCount == 0)
34483450
{
@@ -4071,7 +4073,7 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
40714073

40724074
// Unwind to the frame of the prevExInfo
40734075
ExInfo* pPrevExInfo = pThis->GetNextExInfo();
4074-
EH_LOG((LL_INFO100, "SfiNext: collided with previous exception handling, skipping from IP=%p, SP=%p to IP=%p, SP=%p\n",
4076+
EH_LOG((LL_INFO100, "SfiNext: collided with previous exception handling, skipping from IP=%p, SP=%p to IP=%p, SP=%p\n",
40754077
GetControlPC(&pTopExInfo->m_regDisplay), GetRegdisplaySP(&pTopExInfo->m_regDisplay),
40764078
GetControlPC(&pPrevExInfo->m_regDisplay), GetRegdisplaySP(&pPrevExInfo->m_regDisplay)));
40774079

0 commit comments

Comments
 (0)