Open rolfbjarne opened 3 months ago
CC @AaronRobinsonMSFT
Tagging subscribers to this area: @dotnet/interop-contrib See info in area-owners.md if you want to be subscribed.
@rolfbjarne Would https://github.com/dotnet/runtime/issues/101560 help with this issue? If not, is this a .NET 9 ask?
@rolfbjarne Would #101560 help with this issue?
No, I don't think so, that's a proposal to support ignoring unhandled exceptions, while our exceptions can't be ignored, the process will terminate soon after no matter what the handler does.
The fatal error handler would eventually be called, but getting the unhandled managed exception (to log it for instance) after the error handled has been called due to a SIGABRT wouldn't be easy, so it's not really a good alternative.
If not, is this a .NET 9 ask?
Yes, please!
Maybe that in the Objective-C unhandled exception callback, we can unwind to the first managed frame and rethrow the managed exception from there. That way, the exception that was not handled by the Objective-C stuff would continue flowing into the managed caller of that code and possibly get even handled in the managed code or hit the standard unhandled exception path that would also raise the AppDomain.UnhandledException event. There are some unknowns to me though that can complicate things. For example, I am not sure if objective-C frames can be unwound using the libunwind.
Maybe that in the Objective-C unhandled exception callback, we can unwind to the first managed frame and rethrow the managed exception from there.
There isn't necessarily any managed frames up the stack (in fact probably won't be any).
Also note that there may be both managed and Objective-C exception handled up the stack... because Apple does something like this sometimes:
void someFunction ()
{
@try {
callUserCode ();
} @catch (NSException *ex) {
terminateDueToUnhandledObjectiveCException (ex);
}
}
Stack trace: https://gist.github.com/rolfbjarne/5ab15ce73bc6476d4cee83cff387dd30
frame 0 is our unhandled Objective-C exception callback. frame 6 is (probably) something like the someFunction from above. frame 20 has an Objective-C catch clause. frames 26-23 are managed frames.
In any case, it doesn't sound like a good idea to try to recover and continue executing from this stack trace:
frame #2: 0x000000018005fbe4 libobjc.A.dylib`_objc_terminate() + 112
frame #3: 0x000000018028e150 libc++abi.dylib`std::__terminate(void (*)()) + 12
frame #4: 0x000000018028e100 libc++abi.dylib`std::terminate() + 52
frame #5: 0x00000001800842d4 libobjc.A.dylib`objc_terminate + 12
That way, the exception that was not handled by the Objective-C stuff would continue flowing into the managed caller of that code and possibly get even handled in the managed code or hit the standard unhandled exception path that would also raise the AppDomain.UnhandledException event.
We end up with a kind of chicken-and-egg problem here: exceptions are only truly unhandled if there are neither Objective-C exception handlers nor managed exception handlers up the stack.
This means that (almost) all unhandled managed exceptions will actually be converted into Objective-C exceptions at some point.
We reach the end when the there are no Objective-C exception handlers on the stack, and the unhandled Objective-C exception callback is called. At this point we can inspect the Objective-C exception, and determine whether it originated from a managed exception (and this is where we want to call AppDomain.UnhandledException).
Ok, I can see I have not considered the cases when objective-C thread is reverse-pinvoking managed code or when there is an objective-C host. What I was talking about was a case when managed code app would call objective-C method. I have also not expected that objective C would call its unhandled exception handler down the call chain from the std::terminate in what I assume is a termination handler. I am not sure if it would be safe to execute any managed code (the AppDomain.UnhandledException) on that thread from that state at all.
@VSadov Do you have any thoughts on adding an additional API to https://github.com/dotnet/runtime/issues/101560? The issue here is attempting to trigger the runtime's UnhandledException infrastructure on demand. It seems like it fits nicely with the aforementioned API. The API itself is likely uninteresting, but the semantics definitely are.
namespace System.Runtime.ExceptionServices
{
public static class ExceptionHandling
{
/// <summary>
/// Triggers the runtime's UnhandledException infrastructure.
/// </summary>
/// <param name="exception">Exception to trigger</exception>
/// <remarks>
/// QUESTION: What happens on return?
/// </remarks>
public static void TriggerUnhandledExceptionHandlers(Exception exception);
}
}
/cc @jkotas
Background and motivation
The ObjectiveCMarshal.Initialize method takes a callback that's called if an exception is supposed to be thrown when returning from managed code to native code.
This works fine, but if we determine that the exception is truly unhandled, there doesn't seem to be a way for us to invoke the AppDomain.UnhandledException event before terminating the process.
This event is often used by customers when logging app crashes, so the fact that we don't always raise it becomes a problem (https://github.com/xamarin/xamarin-macios/issues/15252). We have other events (our own) we raise, but if we could raise the event everybody else uses that would be preferrable.
~Note that we'd need the same for Mono.~ Looks like we can use
mono_unhandled_exception
for Mono.API Proposal
API Usage
The method would be called when we detect that there are no Objective-C exception handlers nor any managed exception handlers on the stack.
Currently this happens:
AppDomain.UnhandledException
event, before aborting. Note that we still have access to the original managed exception at this point.It would also be nice if we could tell the debugger about these unhandled exception as well, but I don't have any idea how that would look.
Alternative Designs
No response
Risks
No response