Skip to content

Commit 08758d8

Browse files
janvorlijkotas
andauthored
Fix caught foreign native exception handling take 2 (#116693)
* Fix reporting of foreign native exception not handled by runtime take 2 The previous fix was reverted, as it didn't take into account hardware exceptions stemming from managed code. This has broken debugger tests. When a native exception occurs on a foreign thread, it is propagated through managed frames and then flows into the foreign native frames and is handled there, the runtime still reports the exception as unhandled via AppDomain.CurrentDomain.UnhandledException event and also prints out the unhandled exception message. Even though the process then continues running just fine. This change fixes the behavior so that only managed exceptions thrown by the .NET runtime are reported as unhandled in such case. Foreign exceptions, like e.g. C++ exceptions, are not reported as unhandled and they are just propagated into the foreign native frames. If that exception is not handled by the native frames, then C++ reports them as unhandled. I have also added a test that fails without this fix. Close #115654 * Fix the problem of the previous solution Also add hardware unhandled exception test * Update src/coreclr/vm/exceptionhandling.cpp Co-authored-by: Jan Kotas <jkotas@microsoft.com> --------- Co-authored-by: Jan Kotas <jkotas@microsoft.com>
1 parent 62fabc3 commit 08758d8

File tree

5 files changed

+85
-5
lines changed

5 files changed

+85
-5
lines changed

src/coreclr/vm/exceptionhandling.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4014,13 +4014,16 @@ extern "C" CLR_BOOL QCALLTYPE SfiNext(StackFrameIterator* pThis, uint* uExCollid
40144014
// Check if there are any further managed frames on the stack or a catch for all exceptions in native code (marked by
40154015
// DebuggerU2MCatchHandlerFrame with CatchesAllExceptions() returning true).
40164016
// If not, the exception is unhandled.
4017-
if ((pFrame == FRAME_TOP) ||
4017+
bool isNotHandledByRuntime =
4018+
(pFrame == FRAME_TOP) ||
40184019
(IsTopmostDebuggerU2MCatchHandlerFrame(pFrame) && !((DebuggerU2MCatchHandlerFrame*)pFrame)->CatchesAllExceptions())
40194020
#ifdef HOST_UNIX
40204021
// Don't allow propagating exceptions from managed to non-runtime native code
40214022
|| isPropagatingToExternalNativeCode
40224023
#endif
4023-
)
4024+
;
4025+
4026+
if (isNotHandledByRuntime && IsExceptionFromManagedCode(pTopExInfo->m_ptrs.ExceptionRecord))
40244027
{
40254028
EH_LOG((LL_INFO100, "SfiNext (pass %d): no more managed frames on the stack, the exception is unhandled", pTopExInfo->m_passNumber));
40264029
if (pTopExInfo->m_passNumber == 1)

src/tests/baseservices/exceptions/exceptioninterop/ExceptionInterop.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ static void ThrowInFrameWithFinally()
158158
}
159159

160160
[DllImport(nameof(ExceptionInteropNative))]
161-
public static extern void InvokeCallbackCatchCallbackAndRethrow(delegate*unmanaged<void> callBack1, delegate*unmanaged<void> callBack2);
161+
public static extern void InvokeCallbackCatchCallbackAndRethrow(delegate* unmanaged<void> callBack1, delegate* unmanaged<void> callBack2);
162162

163163
[UnmanagedCallersOnly]
164164
static void CallPInvoke()
@@ -193,4 +193,25 @@ public static void PropagateAndRethrowCppException()
193193
Console.WriteLine($"Caught {ex}");
194194
}
195195
}
196+
197+
[DllImport(nameof(ExceptionInteropNative))]
198+
public static extern void InvokeCallbackOnNewThread(delegate*unmanaged<void> callBack);
199+
200+
[Fact]
201+
[PlatformSpecific(TestPlatforms.Windows)]
202+
[SkipOnMono("Exception interop not supported on Mono.")]
203+
public static void PropagateAndCatchCppException()
204+
{
205+
bool reportedUnhandledException = false;
206+
UnhandledExceptionEventHandler handler = (sender, e) =>
207+
{
208+
Console.WriteLine($"Exception reported as unhandled: {e.ExceptionObject}");
209+
reportedUnhandledException = true;
210+
};
211+
212+
AppDomain.CurrentDomain.UnhandledException += handler;
213+
InvokeCallbackOnNewThread(&CallPInvoke);
214+
AppDomain.CurrentDomain.UnhandledException -= handler;
215+
Assert.False(reportedUnhandledException, "Exception should not be reported as unhandled");
216+
}
196217
}

src/tests/baseservices/exceptions/exceptioninterop/ExceptionInteropNative.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
#include <exception>
55
#include <platformdefines.h>
6+
#ifdef _WIN32
7+
#pragma warning(push)
8+
#pragma warning(disable:4265 4577)
9+
#include <thread>
10+
#pragma warning(pop)
11+
#endif // _WIN32
612

713
extern "C" DLL_EXPORT void STDMETHODCALLTYPE ThrowException()
814
{
@@ -33,3 +39,22 @@ extern "C" DLL_EXPORT void InvokeCallbackCatchCallbackAndRethrow(PFNACTION1 call
3339
}
3440
}
3541

42+
#ifdef _WIN32
43+
extern "C" DLL_EXPORT void STDMETHODCALLTYPE InvokeCallbackAndCatchNative(PFNACTION1 callback)
44+
{
45+
try
46+
{
47+
callback();
48+
}
49+
catch (std::exception& ex)
50+
{
51+
printf("Caught exception %s in native code\n", ex.what());
52+
}
53+
}
54+
55+
extern "C" DLL_EXPORT void STDMETHODCALLTYPE InvokeCallbackOnNewThread(PFNACTION1 callback)
56+
{
57+
std::thread t1(InvokeCallbackAndCatchNative, callback);
58+
t1.join();
59+
}
60+
#endif // _WIN32

src/tests/baseservices/exceptions/unhandled/unhandled.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ static void Main(string[] args)
4848
{
4949
throw new Exception("Test");
5050
}
51+
if (args[0] == "mainhardware")
52+
{
53+
string s = null;
54+
Console.WriteLine(s.Length); // This will cause a NullReferenceException
55+
}
5156
else if (args[0] == "foreign")
5257
{
5358
InvokeCallbackOnNewThread(&ThrowException);
@@ -58,6 +63,22 @@ static void Main(string[] args)
5863
t.Start();
5964
t.Join();
6065
}
66+
else if (args[0] == "secondaryhardware")
67+
{
68+
Thread t = new Thread(() =>
69+
{
70+
string s = null;
71+
Console.WriteLine(s.Length); // This will cause a NullReferenceException
72+
});
73+
t.Start();
74+
t.Join();
75+
}
76+
else if (args[0] == "secondaryunhandled")
77+
{
78+
Thread t = new Thread(() => throw new Exception("Test"));
79+
t.Start();
80+
t.Join();
81+
}
6182
}
6283
}
6384
}

src/tests/baseservices/exceptions/unhandled/unhandledTester.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,23 @@ static void RunExternalProcess(string unhandledType, string assembly)
4747
}
4848
else if (!OperatingSystem.IsWindows())
4949
{
50-
expectedExitCode = 128 + 6;
50+
expectedExitCode = 128 + 6; // SIGABRT
5151
}
5252
else if (TestLibrary.Utilities.IsNativeAot)
5353
{
5454
expectedExitCode = unchecked((int)0xC0000409);
5555
}
5656
else
5757
{
58-
expectedExitCode = unchecked((int)0xE0434352);
58+
if (unhandledType.EndsWith("hardware"))
59+
{
60+
// Null reference exception code
61+
expectedExitCode = unchecked((int)0xC0000005);
62+
}
63+
else
64+
{
65+
expectedExitCode = unchecked((int)0xE0434352);
66+
}
5967
}
6068

6169
if (expectedExitCode != testProcess.ExitCode)
@@ -128,7 +136,9 @@ static void RunExternalProcess(string unhandledType, string assembly)
128136
public static void TestEntryPoint()
129137
{
130138
RunExternalProcess("main", "unhandled.dll");
139+
RunExternalProcess("mainhardware", "unhandled.dll");
131140
RunExternalProcess("secondary", "unhandled.dll");
141+
RunExternalProcess("secondaryhardware", "unhandled.dll");
132142
RunExternalProcess("foreign", "unhandled.dll");
133143
File.Delete(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "dependencytodelete.dll"));
134144
RunExternalProcess("missingdependency", "unhandledmissingdependency.dll");

0 commit comments

Comments
 (0)