Skip to content

Commit

Permalink
MSYS2: Export forwarders for __cxa_begin_catch, __cxa_end_catch, …
Browse files Browse the repository at this point in the history
…`__cxa_rethrow` and `__gxx_personality_seh0`

libobjc2 uses native C++ exceptions on MinGW.  The clang compiler will emit references to `__cxa_begin_catch`, `__cxa_end_catch`, `__cxa_rethrow` and `__gxx_personality_seh0` for Objective C code which uses Objective C exceptions.

These symbols are defined in the C++ runtime, not in libobjc2.  As a result, merely linking with libobjc2 is not sufficient.  Objective C code such as GNUstep must be compiled with the `LDFLAGS="-lgcc_s -lstdc++"` or `LDFLAGS="-lc++"`, depending on the environment.

This is tedious.  Additionally, specifying `-lc++` on the msys/clang64 environment causes linker errors:

```
 Linking library libgnustep-base ...
ld.lld: error: libc++.dll.a(libc++.dll): .idata$4 should not refer to special section 0
```

A [similar error has been observed for other libraries](msys2/MINGW-packages#18589)

A solution for this is to define forwarding exports for `__cxa_begin_catch`, `__cxa_end_catch`, `__cxa_rethrow` and `__gxx_personality_seh0`.  This is implemented by adding a `eh_forwards.def` file to the list of libobjc2 source files, which forwards the symbols to the actual C++ runtime.  On MSYS2, the libstdc++ and libc++ runtimes are supported, which covers all MinGW environments: https://www.msys2.org/docs/environments/.

Forwarding exports are discussed here:
- https://learn.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170
- https://devblogs.microsoft.com/oldnewthing/20060719-24/?p=30473
- https://devblogs.microsoft.com/oldnewthing/20121116-00/?p=6073
  • Loading branch information
qmfrederik committed Feb 13, 2024
1 parent e882423 commit 21e5314
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 10 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,12 @@ jobs:
include:
- msystem: ucrt64
package-prefix: ucrt-x86_64
cmake-flags: LDFLAGS="-fuse-ld=lld -lstdc++ -lgcc_s"
cmake-flags: LDFLAGS="-fuse-ld=lld"
- msystem: mingw64
package-prefix: x86_64
cmake-flags: LDFLAGS="-fuse-ld=lld -lstdc++ -lgcc_s"
cmake-flags: LDFLAGS="-fuse-ld=lld"
- msystem: clang64
package-prefix: clang-x86_64
cmake-flags: LDFLAGS="-lc++"
ctest-flags: -E UnexpectedException*
# Don't abort runners if a single one fails
fail-fast: false
Expand Down
24 changes: 24 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ endif ()

INCLUDE (CheckCXXSourceCompiles)
INCLUDE (FetchContent)
INCLUDE (CheckCXXSymbolExists)

set(libobjc_VERSION 4.6)

Expand Down Expand Up @@ -131,6 +132,29 @@ else ()
list(APPEND libobjc_C_SRCS eh_personality.c)
endif ()

# Export the __cxa_begin_catch, __cxa_end_catch and __gxx_personality_seh0 symbols from libobjc2
# and forward them to the C runtime
if (MINGW)
check_cxx_symbol_exists(_GLIBCXX_RELEASE "version" HAVE_LIBSTDCXX)
check_cxx_symbol_exists(_LIBCPP_VERSION "version" HAVE_LIBCXX)

if (HAVE_LIBSTDCXX)
find_library(CXX_RUNTIME "stdc++" REQUIRED)
set(CXX_RUNTIME_NAME "libstdc++-6")
elseif(HAVE_LIBCXX)
find_library(CXX_RUNTIME "c++" REQUIRED)
get_filename_component(CXX_RUNTIME_NAME ${CXX_RUNTIME} NAME_WE CACHE)
else ()
message(WARNING "Could not determine the C++ runtime.")
endif ()

if (CXX_RUNTIME)
message(STATUS "Adding forwarders for __cxa_begin_catch, __cxa_end_catch, __cxa_rethrow and __gxx_personality_seh0 to ${CXX_RUNTIME_NAME}")
configure_file(eh_forwards.def.in ${CMAKE_CURRENT_BINARY_DIR}/eh_forwards.def @ONLY)
list(APPEND libobjc_CXX_SRCS ${CMAKE_CURRENT_BINARY_DIR}/eh_forwards.def)
endif()
endif ()

find_package(tsl-robin-map)

if (NOT tsl-robin-map_FOUND)
Expand Down
5 changes: 5 additions & 0 deletions eh_forwards.def.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
EXPORTS
__cxa_begin_catch = @CXX_RUNTIME_NAME@.__cxa_begin_catch
__cxa_end_catch = @CXX_RUNTIME_NAME@.__cxa_end_catch
__cxa_rethrow = @CXX_RUNTIME_NAME@.__cxa_rethrow
__gxx_personality_seh0 = @CXX_RUNTIME_NAME@.__gxx_personality_seh0
2 changes: 1 addition & 1 deletion objcxx_eh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ PRIVATE void cxx_throw()
throw x;
}

#ifndef __MINGW32__
/**
* Personality function that wraps the C++ personality and inspects the C++
* exception structure on the way past. This should be used only for the
Expand Down Expand Up @@ -490,7 +491,6 @@ BEGIN_PERSONALITY_FUNCTION(test_eh_personality)
* personality function, allowing us to inspect a C++ exception that is in a
* known state.
*/
#ifndef __MINGW32__
extern "C" void test_cxx_eh_implementation()
{
if (done_setup)
Expand Down
19 changes: 13 additions & 6 deletions objcxx_eh.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ extern "C" {
#undef CXA_ALLOCATE_EXCEPTION_SPECIFIER
#define CXA_ALLOCATE_EXCEPTION_SPECIFIER
#endif
__attribute__((weak))

#ifndef __MINGW32__
#define OBJC_WEAK __attribute__((weak))
#else
#define OBJC_WEAK
#endif
OBJC_WEAK
void *__cxa_allocate_exception(size_t thrown_size) CXA_ALLOCATE_EXCEPTION_SPECIFIER;

/**
Expand All @@ -23,32 +29,33 @@ void *__cxa_allocate_exception(size_t thrown_size) CXA_ALLOCATE_EXCEPTION_SPECIF
* _Unwind_Exception structure within this structure, and should be passed to
* the C++ personality function.
*/
__attribute__((weak))
OBJC_WEAK
struct _Unwind_Exception *objc_init_cxx_exception(id thrown_exception);
/**
* The GNU C++ exception personality function, provided by libsupc++ (GNU) or
* libcxxrt (PathScale).
*/
__attribute__((weak)) DECLARE_PERSONALITY_FUNCTION(__gxx_personality_v0);
OBJC_WEAK
DECLARE_PERSONALITY_FUNCTION(__gxx_personality_v0);
/**
* Frees an exception object allocated by __cxa_allocate_exception(). Part of
* the Itanium C++ ABI.
*/
__attribute__((weak))
OBJC_WEAK
void __cxa_free_exception(void *thrown_exception);
/**
* Tests whether a C++ exception contains an Objective-C object, and returns if
* if it does. The second argument is a pointer to a boolean value indicating
* whether this is a valid object.
*/
__attribute__((weak))
OBJC_WEAK
void *objc_object_for_cxx_exception(void *thrown_exception, int *isValid);

/**
* Prints the type info associated with an exception. Used only when
* debugging, not compiled in the normal build.
*/
__attribute__((weak))
OBJC_WEAK
void print_type_info(void *thrown_exception);

/**
Expand Down

0 comments on commit 21e5314

Please sign in to comment.