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

Crash when throwing exceptions in ObjC++ due to __cxa_exception struct mismatch #146

Closed
triplef opened this issue Feb 11, 2020 · 5 comments

Comments

@triplef
Copy link
Member

triplef commented Feb 11, 2020

Throwing exceptions in Objective C++ code currently crashes with some setups. #138 adds tests that expose (some of?) these issues.

According to @davidchisnall:

I've had a look and found a few minor bugs that are easy to fix. Unfortunately, when I fix them I find some more interesting issues that arise from the way that we're now creating the exception by hijacking the C++ runtime library.
I think that the solution that we're going to have to end up with is using that path only to figure our which structure we have and then casting to the correct variant structure and creating it ourselves.

I have created cxa_exception_variants.cpp containing the four different possible layouts of the __cxa_exception struct that are found in libobjc2, libcxxrt, and libunwind:

  • There are two different versions of the __cxa_exception struct: without and with the added "referenceCount" field.
  • However the struct also references _Unwind_Exception, which itself comes in two different variants: without and with an "__aligned__" attribute (and an added "reserved" field on x86).

This leaves us with a total of four different variants of __cxa_exception:

  • __cxa_exception_v1: without referenceCount field, using unaligned _Unwind_Exception
  • __cxa_exception_v2: without referenceCount field, using aligned _Unwind_Exception
  • __cxa_exception_v3: with referenceCount field, using unaligned _Unwind_Exception
  • __cxa_exception_v4: with referenceCount field, using aligned _Unwind_Exception

Depending on the architecture some of these will have the same size. Following are the sizes for the different architectures as printed by the attached tool.

x86

_Unwind_Exception_v1	size: 20, alignment: 4
_Unwind_Exception_v2	size: 32, alignment: 16

__cxa_exception_v1	size: 64, alignment: 4
__cxa_exception_v2	size: 80, alignment: 16
__cxa_exception_v3	size: 68, alignment: 4
__cxa_exception_v4	size: 80, alignment: 16

x86_64

_Unwind_Exception_v1	size: 32, alignment: 8
_Unwind_Exception_v2	size: 32, alignment: 16

__cxa_exception_v1	size: 112, alignment: 8
__cxa_exception_v2	size: 112, alignment: 16
__cxa_exception_v3	size: 120, alignment: 8
__cxa_exception_v4	size: 128, alignment: 16

ARM32

_Unwind_Exception_v1	size: 88, alignment: 4
_Unwind_Exception_v2	size: 88, alignment: 8

__cxa_exception_v1	size: 132, alignment: 4
__cxa_exception_v2	size: 136, alignment: 8
__cxa_exception_v3	size: 136, alignment: 4
__cxa_exception_v4	size: 136, alignment: 8

ARM64

_Unwind_Exception_v1	size: 104, alignment: 8
_Unwind_Exception_v2	size: 104, alignment: 8

__cxa_exception_v1	size: 184, alignment: 8
__cxa_exception_v2	size: 184, alignment: 8
__cxa_exception_v3	size: 192, alignment: 8
__cxa_exception_v4	size: 192, alignment: 8

I’m not 100% sure which of these variants are actually used in the wild, although I think that FreeBSD 12.0 is using v2, and libcxxrt was using v3 and is now using v4 after libcxxrt/libcxxrt#1 was merged.

@davidchisnall
Copy link
Member

Thanks. Just to clarify this:

libobjc2, libcxxrt, and libunwind
The only ones that I care about are the ones from libcxxrt / libsupc++ (and their associated unwind headers), we don't need to maintain compatibility with any that we're using internally.

@triplef
Copy link
Member Author

triplef commented Feb 13, 2020

Makes sense. I think it’s like this:

  • libcxxrt master: __cxa_exception_v4
  • libcxxrt from FreeBSD 11–12.1: __cxa_exception_v3
  • libsupc++ from FreeBSD 11-12.1: __cxa_exception_v1 or __cxa_exception_v2

For the last one I’m not sure which unwind.h is included by unwind-cxx.h in libsupc++ – FreeBSD contains 3 different versions:

The last two headers unfortunately contain further variants of _Unwind_Exception depending on ABI and FreeBSD release:

  • aligned but without "reserved" field on x86
  • Special-casing of the private fields for SEH in FreeBSD 12.1:
#if !defined (__USING_SJLJ_EXCEPTIONS__) && defined (__SEH__)
  _Unwind_Word private_[6];
#else
  _Unwind_Word private_1;
  _Unwind_Word private_2;
#endif

If you can tell me which of the above unwind.h are used by libsupc++ I can try to pick it further apart.

@davidchisnall
Copy link
Member

To be on the safe side, I'd aim to support all of the plausible ones. Ignore anything in a defined(__USING_SJLJ_EXCEPTIONS__) or defined(__SEH__) block: We don't support SJLJ exceptions at all and we support SEH exceptions natively, not via a hybrid DWARF+SEH unwinder (though one of the SEH tests is failing for a reason I haven't been able to track down).

@triplef
Copy link
Member Author

triplef commented Feb 14, 2020

All four __cxa_exception variants seem to be plausible:

  • libcxxrt master: __cxa_exception_v4
  • libcxxrt from FreeBSD 11–12.1: __cxa_exception_v3
  • libsupc++ with:
    • include/unwind.h: __cxa_exception_v1 (FreeBSD 11/12/12.1)
    • unwind.h from libunwind:
      • i386: __cxa_exception_v2 (FreeBSD 11/12/12.1)
      • ARM:
        • __cxa_exception_v1 on FreeBSD 11/12
        • __cxa_exception_v2 on FreeBSD 12.1
    • unwind.h from clang: __cxa_exception_v2 (FreeBSD 11/12/12.1)

On top of that I found that libsupc++ on ARM using GCC uses a very different __cxa_exception layout that is enabled by the __ARM_EABI_UNWINDER__ define. Is that something we need to support as well?

Finally I found a small mistake in the definitions, so please use this fixed version of cxa_exception_variants.cpp (also updated above).

@davidchisnall
Copy link
Member

I was a bit surprised to see the v1 and v2 structures used by libsupc++. Looking a bit more carefully at their code, it appears as if this is technically the case, but they put the refcount in a separate structure and always allocate the enclosing structure.

davidchisnall added a commit that referenced this issue Apr 27, 2020
We now, the first time we encounter a foreign exception, throw a C++
exception through a frame that has a custom personality function and
probe the layout of the __cxa_exception structure.
We then use the offsets learned from this along with the public ABI
functions for allocating the structure.

At the same time, add a test that we are correctly setting the count of
uncaught exceptions.

Fixes #146
davidchisnall added a commit that referenced this issue Apr 27, 2020
We now, the first time we encounter a foreign exception, throw a C++
exception through a frame that has a custom personality function and
probe the layout of the __cxa_exception structure.
We then use the offsets learned from this along with the public ABI
functions for allocating the structure.

At the same time, add a test that we are correctly setting the count of
uncaught exceptions.

Fixes #146
davidchisnall added a commit that referenced this issue Apr 27, 2020
We now, the first time we encounter a foreign exception, throw a C++
exception through a frame that has a custom personality function and
probe the layout of the __cxa_exception structure.
We then use the offsets learned from this along with the public ABI
functions for allocating the structure.

At the same time, add a test that we are correctly setting the count of
uncaught exceptions.

Fixes #146
davidchisnall added a commit that referenced this issue Apr 27, 2020
We now, the first time we encounter a foreign exception, throw a C++
exception through a frame that has a custom personality function and
probe the layout of the __cxa_exception structure.
We then use the offsets learned from this along with the public ABI
functions for allocating the structure.

At the same time, add a test that we are correctly setting the count of
uncaught exceptions.

Fixes #146
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants