-
Notifications
You must be signed in to change notification settings - Fork 5k
[NativeAOT] Objective-C exception propagation #80334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NativeAOT] Objective-C exception propagation #80334
Conversation
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsRelated issue: #77472 This is the final part of the Objective-C Marshal API: the ability to turn a managed exception unwinding across a reverse-pinvoke boundary into a native exception. The pieces to support this include:
Issues that need to be resolved before merging
Known limitationsThe callback for exception unwinding expects a MethodBase? method = new StackFrame(ip, needFileInfo: false).GetMethod();
RuntimeMethodInfo? methodInfo = method as RuntimeMethodInfo;
RuntimeMethodHandle lastMethod = methodInfo?.MethodHandle ?? default; For all the cases in the unit test, the Since the code to lookup the
|
@jankots |
It is not guaranteed that we'll be able to produce a RuntimeMethodHandle for an arbitrary function pointer. One thing is that they need to go to the mapping table, which currently skips them. That should not be hard to fix (we just need to do the right thing when attempting to reflection-invoke this, which is why it's blocked - the right thing being "whatever CoreCLR does"). The other thing is that the compiler will only generate these mapping table entries for methods that are visible targets of reflection. We don't have the names/signatures of methods that are not visible targets of reflection. If this is only needed for runtime/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs Lines 600 to 601 in 5da4a9e
It comes with size costs. It would be preferable if we don't need it. |
src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/ExceptionHandling.cs
Outdated
Show resolved
Hide resolved
Yes, the approach looks reasonable.
You do not need to worry about ThreadAbortException. It is not supported.
Xamarion iOS callback does not look at the RuntimeMethodHandle today: https://github.com/xamarin/xamarin-macios/blob/6e1ba31bae568ec54f53259e9e884f0550cfdae3/src/ObjCRuntime/Runtime.CoreCLR.cs#L144-L149 . It converts exceptions for all reverse PInvokes even the ones that have nothing to do with ObjectiveC interop (it is less than ideal). It should be ok to pass in |
We may want to consider introducing declarative scheme for this (mark all methods that want to participate in ObjectiveC interop with an attribute) to make this more pay-for-play and AOT friendly. |
756568f
to
5103dc6
Compare
This comment was marked as resolved.
This comment was marked as resolved.
9c8fac1
to
8758680
Compare
The test now passes on X64 too, after applying the same 8 byte offset to RSP that CoreCLR does: https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/exceptionhandling.cpp#L4343-L4346 |
One thing I'm not sure is right is the back trace looks like this after jumping to
Notice the managed function
|
Yes, it does not look right. All managed frames should be gone when the propagation function is called. |
When I tried to step the StackFrameIterator past the reverse P/Invoke frame, I discovered that it skips directly from the reverse P/Invoke frame to the P/Invoke frame. This skips all the unmanaged frames in between: So I added some hacks to allow the the
@jkotas What do you think of the idea enabling the |
|
1a523c7
to
dadbc7d
Compare
The memory in the CONTEXT structure was not being full initialized. Switching from using the transition frame to unwinding filled the stack with 0xDD values. When VS displayed the backtrace after calling RaiseFailFastException, it was corrupted (showing a selection of unrelated frames). This can reproduced on the main branch by changing the memset here to set 0xDD. So I think this a pre-existing problem.
This is now ready for review. I noticed what appears to be a bug for Windows. When calling |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! @VSadov @janvorli @MichalStrehovsky Any additional feedback?
This reverts commit d2b5cd8.
Not my area of expertise, but looks good to me! |
Co-authored-by: Jan Kotas <[email protected]>
asm offsets on windows look incorrect |
Fixed; too bad that MSVC does not print the expression in |
/azp run runtime-extra-platforms |
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks!
The failures in extra-platforms do not look related to this change.
|
I think it's from @vitek-karas change. I submitted a revert of his pr and it doesn't show these failures: #81259 |
This change broke something around unhandled exceptions on Windows. With the Convenience Visual Studio repro project workflow from here: https://github.com/dotnet/runtime/blob/f1bdd5a6182f43f3928b389b03f7bc26f826c8bc/docs/workflow/building/coreclr/nativeaot.md#convenience-visual-studio-repro-project. Place a Previously, the experience looked like this in the debugger: Now it looks like this (note "Frame not in module" and a useless stack): This change made it into the Preview 1 snap. Unless we roll back, unhandled exceptions are going to be undiagnosable in Preview 1, at least on Windows. |
This is should be fixed by #81010 . |
@MichalStrehovsky Just to be clear, this problem was reproducible before this PR was merged. This PR incidentally has groomed the stack in a way that makes #81010 should probably be considered for servicing for 7.0 as well. |
My only gripe is that this is the first time I'm observing this failure mode and we just snapped 8.0 Preview 1 a couple hours ago, which means we're going to ship preview 1 like this. It's not the end of the world, but not great either. Cc @agocke - see #80334 (comment) |
Sorry about that. In retrospect I should have made sure the PR fixing the uninitialized memory read landed before this PR made the call stacks useless. I'll keep that principal in mind going forward. |
Related issue: #77472
This is the final part of the Objective-C Marshal API: the ability to turn a managed exception unwinding across a reverse-pinvoke boundary into a native exception.
The pieces to support this include:
RhpCallManagedToNativeExceptionCallback
assembly helper to restore the context and jump to the native callback. This helper is based off ofRhpCallCatchFunclet
, with the calling of catch funclet and thread-abort handling removed.Issues that need to be resolved before merging
ThreadAbortException
, which will be swallowed if it goes through this code path. Perhaps changeRhpPInvokeReturn
to check for pending thread aborts? Or maybe thread aborts don't matter sinceRhpInitiateThreadAbort
is not called by anything?StackFrameIterator::UnwindFuncletInvokeThunk
.StackFrameIterator
support unwinding into a least one native framelastMethod
parameter and update comments to reference it: [NativeAot] Objective-C Marshal: find method handle when propagating exceptions #80985When an unhandled exception hits a reverse P/Invoke function on Windows, fix backtrace fromMoved to [NativeAOT] correctly initalize CONTEXT before failing fast #81010.RaiseFailFastException
.Testing done
Built and tested with these commands for ARM64. Similar invocations for testing with X64.
The test passes, which means the C++ exception dispatch code is able to unwind the stack after the new assembly helper restored the context. I added some extra asserts to check that the second pass unwind is done and that a value in the frame being restored is not clobbered after execution resumes. Specifically the
assert(e == a)
assert loadsa
from the stack in debug and the caller-savedx19
register in release. After jumping from the assembly helper to the native method that throws, the stack trace makes sense in the debugger (looks likeCallAndCatch
calledThrowInt
orThrowException
).I also tested this program continues to fail fast as expected on Windows:
Known limitations
The prolog of the AMD64 assembly helper could preserve fewer registers.
The callback for exception unwinding expects a
RuntimeMethodHandle
that represents the managed method that was called via reverse P/Invoke. I tried doing this to convert the instruction pointer into a `RuntimeMethodHandle:For all the cases in the unit test, the
StackFrame
is unable to find the method object. I have not looked at this closely, but I believe this is because unmanaged-callers-only function and reverse-p/invoke stubs do not show up in the reflection invoke map. See this part of ILC for some possibly related UCO logic.Since the code to lookup the
RuntimeMethodHandle
didn't work and Xamarin does not even use this value, I decided to remove the code and always set theRuntimeMethodHandle
to 0.