dotnet / corert

This repo contains CoreRT, an experimental .NET Core runtime optimized for AOT (ahead of time compilation) scenarios, with the accompanying compiler toolchain.
http://dot.net
MIT License
2.91k stars 511 forks source link

Exception Unwinding Issue #1515

Closed wjk closed 8 years ago

wjk commented 8 years ago

I am writing a program that does both forward and reverse P/Invoke with Win32 user interface libraries. The flow of control looks something like this:

  1. Main()
  2. Managed code
  3. Win32 code
  4. Managed code
  5. Throw exception

The exception unwinding code works fine until it reaches the first Win32 stack frame (one of about 11), at which point it asserts here and the program crashes. It seems to me that the exception unwinder should be ignoring frames outside of the known ILC-emitted code and proceed to the next frame, but it asserts instead. Any way this could be fixed? Thanks!

MichalStrehovsky commented 8 years ago

In CoreRT (and CoreCLR on non-Windows platforms for that matter), managed exceptions are not allowed to propagate out of reverse pinvokes (i.e. delegate callback). There is no cross-platform (or cross-compiler) ABI that defines exception handling.

On Windows and CLR, this worked because exception handling is implemented on top of SEH. Originally, the CLR had only a custom exception dispatch mechanism, i.e. it was not implemented on top of Win32 SEH (except "on the edges" to interact with hardware exceptions such as divide-by-zero, stack overflow, access violation, etc). This was changed in order to support the original Managed C++, which had the mantra of "It Just Works". One of the tenants there was to allow the MC++ compiler to make its own decisions as to whether a given function was implemented as unmanaged or managed code. This resulted in very 'mixed' stacks, with many managed/unmanaged interleavings of C++ functions--across which normal C++-language exceptions must be allowed to propagate. So it seemed natural, at the time, to use Win32 SEH as the underlying glue.

But this came with enormous costs in terms of complexity to the system. Win32 SEH is different on every target architecture of Windows, so it's not even portable when confined to Windows, let alone any other OS. We have two majorly different EH implementations in CLR because of the x86 / non-x86 differences in SEH (FS:0 handlers vs 'personality routines'), and things aren't even identical between the "personality routine" implementations on x64/arm/ia64/arm64.

Take a look at the main exception dispatch code in .NET Native here: https://github.com/dotnet/corert/blob/master/src/Runtime.Base/src/System/Runtime/ExceptionHandling.cs#L636

And compare it with the main personality routine for x64 CLR here: https://github.com/dotnet/coreclr/blob/master/src/vm/exceptionhandling.cpp#L741

When this is all implemented, instead of an assert you'll see a FailFast.

The portable way to do this is to implement wrappers at the managed/native boundaries that catch all exceptions and rethrow them as appropriate. We use this mechanism within the compiler too (since the compiler runs on top of CoreCLR where this is not supported). Look at the way how we wrap the JitInterface that RyuJIT uses to talk to the managed code in the compiler.

@jkotas Do you know if we have any sort of writeup on this? This is a popular topic and I pretty much just pasted the above response from an old email from @smosier.

wjk commented 8 years ago

@MichalStrehovsky Thanks! That explains it. I'm going to close this now; feel free to reopen if appropriate.

jkotas commented 8 years ago

It is sort of documented here https://docs.microsoft.com/en-us/dotnet/articles/standard/native-interop via the linked Mono documentation at the very end. I am going leave comment on the page to mention it explicitly.