Skip to content
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

MinGW: Use _Unwind_RaiseException to throw exceptions #278

Merged

Conversation

qmfrederik
Copy link
Collaborator

The current implementation uses Vectored Exception Handlers. This implementation is too greedy, and invokes _objc_unexpected_exception for (certain) exceptions which would be handled by the application itself.

@qmfrederik
Copy link
Collaborator Author

@lazka @MehdiChinoune It seems like SetUnhandledExceptionFilter does not work for msys2/mingw64 applications. Any idea what could cause this?

@davidchisnall
Copy link
Member

I think the right way for this to be implemented on MinGW should match the Itanium model, where throwing an exception that doesn’t have a handler returns from the unwind library. Unfortunately, when we tried this, the unwind library did not return. Maybe that’s fixed?

MinGW’s hybrid of SEH and Itanium ABIs is quite painful and more effort to support than either, I wish they’d just use the Visual Studio ABI.

@qmfrederik
Copy link
Collaborator Author

Hmm, interesting. I rewrote objc_exception_throw to use _Unwind_RaiseException instead of __cxa_throw and that does seem to work (at least on mingw64, which uses stdc++).

Is something like this what you had in mind?

extern "C"
OBJC_PUBLIC
void objc_exception_throw(id object)
{
	id *exc = (id *)__cxxabiv1::__cxa_allocate_exception(sizeof(id));
	*exc = object;
	objc_retain(object);
	DEBUG_LOG("objc_exception_throw: Throwing 0x%x\n", *exc);

    __cxa_eh_globals *globals = __cxa_get_globals ();
    globals->uncaughtExceptions += 1;
    // Definitely a primary.
    __cxa_refcounted_exception *header =
        __cxa_init_primary_exception(exc, & __objc_id_type_info, eh_cleanup);
    header->referenceCount = 1;

    _Unwind_Reason_Code err = _Unwind_RaiseException (&header->exc.unwindHeader);

    // Some sort of unwinding error.  Note that terminate is a handler.
    __cxa_begin_catch (&header->exc.unwindHeader);
	DEBUG_LOG("objc_exception_throw: _Unwind_RaiseException returned 0x%x for exception 0x%x\n", err, *exc);

	if (_URC_END_OF_STACK == err && 0 != _objc_unexpected_exception)
	{
	    DEBUG_LOG("Invoking _objc_unexpected_exception\n");
		_objc_unexpected_exception(object);
	}
	DEBUG_LOG("Throw returned %d\n",(int) err);
	abort();
}

@davidchisnall
Copy link
Member

Yes, though I'm not sure you need the __cxa_begin_catch.

@qmfrederik qmfrederik force-pushed the mingw-vectored-exception-handling branch from 1814701 to 4af127a Compare February 13, 2024 14:43
@qmfrederik qmfrederik changed the title MinGW: Don't implement objc_setUncaughtExceptionHandler MinGW: Use _Unwind_RaiseException to throw exceptions Feb 13, 2024
@qmfrederik
Copy link
Collaborator Author

qmfrederik commented Feb 13, 2024

@davidchisnall Here's another attempt. It comes at the expense of having to declare __cxa_exception and __cxa_refcounted_exception (to get the unwind header from the __cxa_refcounted_exception object).

The build on the clang64 fails because that's using libc++ instead of libstdc++, I'll look into that.

@qmfrederik
Copy link
Collaborator Author

ok - this doesn't work yet on msys/clang64 because libc++ doesn't expose __cxa_init_primary_exception, but that's coming in clang 18.1.0: llvm/llvm-project@51e91b6

qmfrederik added a commit to qmfrederik/MINGW-packages that referenced this pull request Feb 14, 2024
Backport two patches from libobjc2:
- Fix uncaught exception handling (gnustep/libobjc2#278)
- Export forwarders for C++ methods, so that we no longer need to link with the C++ runtime (gnustep/libobjc2#279)
@qmfrederik
Copy link
Collaborator Author

Hi @davidchisnall, would you have the bandwidth to review this and 297 in the near future? I'm fairly confident the gnustep-base unit test will pass if this fix goes in.

Alternatively, as a short-term workaround, I could submit these two fixes for the MSYS2 packages while we work our way through the code review here.

Copy link
Member

@davidchisnall davidchisnall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to break a load of things. If it were guarded behind MinGW checks, I wouldn't mind so much, but we absolutely should not be doing this with the Itanium ABI.

objcxx_eh.cc Outdated
@@ -515,14 +519,71 @@ static void eh_cleanup(void *exception)
objc_release(*(id*)exception);
}

namespace __cxxabiv1
{
struct __cxa_exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used to have a definition like this, but we removed it because different C++ runtimes had subtly different variations on it. This is why we have the thing that dynamically probes it for the fields that we need. I'm not sure that reintroducing it is a good idea.

objcxx_eh.cc Outdated
_Unwind_Exception unwindHeader;
};

struct __cxa_refcounted_exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all runtimes have this, and it causes different offsets because of alignment padding.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, but MSYS2 really only ships with two runtimes -- libstdc++ and libc++.

objcxx_eh.cc Outdated
objc_retain(object);
DEBUG_LOG("objc_exception_throw: Throwing 0x%x\n", *exc);

#ifdef _LIBCPP_VERSION
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is reintroducing tight coupling with C++ runtime implementations, which we had previously removed. It's also totally the wrong check, because you're checking for libc++ but libc++ can be built with libc++abi, libcxxrt, or libsupc++.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This #ifdef is trying to determine whether you're on the mingw64 or ucrt64 MSYS2 environment (which use libstdc++ and have __cxa_init_primary_exception) or the clang64 MSYS2 environment, which doesn't have __cxa_init_primary_exception and for which we revert to the old behavior of using __cxa_throw.

I've redefined the check as #ifndef __GLIBCXX__, but not sure if that really addresses your comment.

As for the tight coupling: yes, I know, though we tried the dynamic check for the exception struct size and that didn't work.

@qmfrederik
Copy link
Collaborator Author

Hi @davidchisnall, thanks for the feedback. I'll go through it in detail later. Just to clarify this comment:

This is going to break a load of things. If it were guarded behind MinGW checks, I wouldn't mind so much, but we absolutely should not be doing this with the Itanium ABI.

All of these changes are guarded by the #ifndef __MINGW32__ check in line 497, so yes, they are for MinGW only.

@qmfrederik
Copy link
Collaborator Author

qmfrederik commented Feb 16, 2024

I spent some more time looking at this, but at the moment think we're stuck between a rock and a hard place:

  • SetUnhandledExceptionFilter does not work on MSYS2.
  • We had many attempts at making the dynamic probing work. That didn't work out for MSYS2.
  • All we need is a variant of __cxa_throw which returns (instead of aborting) if the exception is handled, but no such variant appears to exist in the public ABI.
  • The alternative is to use __cxa_init_primary_exception and _Unwind_RaiseException. __cxa_init_primary_exception returns a __cxa_refcounted_exception. _Unwind_RaiseException expects an unwind header. We must know the layout of __cxa_refcounted_exception to be able to access &header->exc.unwindHeader.
  • I had considered to directly include the libstdc++ headers to avoid having to manually define these types, but those structs are opaque.

So the options I see are:

  • Don't bother with unhandled exception handling on MSYS2, and just let the process abort. The easiest option.
  • Carry a runtime-dependent implementation for MSYS2 as part of libobjc2, guarded within #ifdef __MINGW32__. The environments in MSYS2 are standardized: https://www.msys2.org/docs/environments/, so I think it's safe to assume you are using either libstdc++ or libc++, and we can add all 3 64-bit environments (mingw64, ucrt64 and clang64) to our CI matrix
  • Carry a runtime-dependent implementation for MSYS2 as a patch within the MSYS2 package.

@qmfrederik qmfrederik force-pushed the mingw-vectored-exception-handling branch from 1697a0b to c08f725 Compare February 16, 2024 23:23
@qmfrederik
Copy link
Collaborator Author

So, we can use the runtime check to determine the exception struct size, so that simplifies things a bit. This is as far as I can take it, I'm afraid.

@qmfrederik qmfrederik force-pushed the mingw-vectored-exception-handling branch 2 times, most recently from a399e91 to 1b753d8 Compare February 16, 2024 23:35
@qmfrederik qmfrederik force-pushed the mingw-vectored-exception-handling branch from 1b753d8 to 38ebb38 Compare February 18, 2024 17:20
qmfrederik added a commit to qmfrederik/MINGW-packages that referenced this pull request Feb 18, 2024
libobjc2 used Vectored Exception Handlers to catch uncaught exceptions on mingw. This implementation is too greedy, and invokes `_objc_unexpected_exception` for (certain) exceptions which would be handled by the application itself.

This patch `_Unwind_RaiseException` to throw exceptions instead of `__cxa_throw`, allowing us to process unhandled exceptions.

Upstream PR: gnustep/libobjc2#278
qmfrederik added a commit to qmfrederik/MINGW-packages that referenced this pull request Feb 18, 2024
libobjc2 used Vectored Exception Handlers to catch uncaught exceptions on mingw. This implementation is too greedy, and invokes `_objc_unexpected_exception` for (certain) exceptions which would be handled by the application itself.

This patch `_Unwind_RaiseException` to throw exceptions instead of `__cxa_throw`, allowing us to process unhandled exceptions.

Upstream PR: gnustep/libobjc2#278
@qmfrederik
Copy link
Collaborator Author

@davidchisnall Would you mind having another look at this one?

@davidchisnall
Copy link
Member

I still don’t like the fact that it’s conflating C++ runtimes with standard library implementations or all of the ifdefs. If you can put this code in a separate file that!s built only for MinGW so no one reading the code for less baroque platforms will be confused then it!s probably okay.

@qmfrederik
Copy link
Collaborator Author

qmfrederik commented Feb 20, 2024

@davidchisnall I moved the MinGW code to its own class, which I briefly considered calling objcxx_eh_baroque.c.

The definitions of the C++ ABI functions and types as well as the family of __objc_type_info structs is shared across both, so I moved that to a objcxx_eh_private.h header file. Let me know if that is acceptable or if you'd prefer another approach.

@qmfrederik qmfrederik merged commit 6bd3db5 into gnustep:master Feb 21, 2024
59 checks passed
@qmfrederik qmfrederik deleted the mingw-vectored-exception-handling branch February 21, 2024 08:17
qmfrederik added a commit that referenced this pull request Mar 21, 2024
The current implementation uses Vectored Exception Handlers. This implementation is too greedy, and invokes _objc_unexpected_exception for (certain) exceptions which would be handled by the application itself.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants