From 6bd3db586622e86301938fe5fccb0dfecb544cfd Mon Sep 17 00:00:00 2001 From: Frederik Carlier Date: Wed, 21 Feb 2024 09:15:11 +0100 Subject: [PATCH] MinGW: Use _Unwind_RaiseException to throw exceptions (#278) 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. --- CMakeLists.txt | 6 +- eh_win32_mingw.m | 59 ---------- objcxx_eh.cc | 261 ++++---------------------------------------- objcxx_eh_mingw.cc | 98 +++++++++++++++++ objcxx_eh_private.h | 241 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 363 insertions(+), 302 deletions(-) delete mode 100644 eh_win32_mingw.m create mode 100644 objcxx_eh_mingw.cc create mode 100644 objcxx_eh_private.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 01f04c43..4e809665 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,9 +124,7 @@ set(libobjc_CXX_SRCS # Windows does not use DWARF EH, except when using the GNU ABI (MinGW) if (WIN32 AND NOT MINGW) list(APPEND libobjc_CXX_SRCS eh_win32_msvc.cc) -elseif (MINGW) - list(APPEND libobjc_CXX_SRCS eh_win32_mingw.m) -else () +elseif (NOT MINGW) list(APPEND libobjc_C_SRCS eh_personality.c) endif () @@ -230,7 +228,7 @@ if (WIN32 AND NOT MINGW) message(STATUS "Using MSVC-compatible exception model") elseif (MINGW) message(STATUS "Using MinGW-compatible exception model") - list(APPEND libobjc_CXX_SRCS objcxx_eh.cc) + list(APPEND libobjc_CXX_SRCS objcxx_eh.cc objcxx_eh_mingw.cc) else () separate_arguments(EH_PERSONALITY_FLAGS NATIVE_COMMAND ${CMAKE_CXX_FLAGS}) if (CMAKE_CXX_COMPILER_TARGET) diff --git a/eh_win32_mingw.m b/eh_win32_mingw.m deleted file mode 100644 index 687913c2..00000000 --- a/eh_win32_mingw.m +++ /dev/null @@ -1,59 +0,0 @@ -#include "objc/runtime.h" -#include "objc/objc-exception.h" -#include "objc/hooks.h" -#include - -#include - -#define STATUS_GCC_THROW 0x20474343 - -extern void *__cxa_current_exception_type(void); -extern void __cxa_rethrow(); - -BOOL handler_installed = NO; -_Thread_local BOOL in_handler = NO; - -// This vectored exception handler is the last handler to get invoke for every exception (Objective C or foreign). -// It calls _objc_unexpected_exception only when the exception is a C++ exception (ex->ExceptionCode == STATUS_GCC_THROW) -// and the exception is an Objective C exception. -// It always returns EXCEPTION_CONTINUE_SEARCH, so Windows will continue handling the exception. -static LONG CALLBACK _objc_vectored_exception_handler(EXCEPTION_POINTERS* exceptionInfo) -{ - const EXCEPTION_RECORD* ex = exceptionInfo->ExceptionRecord; - - if (_objc_unexpected_exception != 0 - && ex->ExceptionCode == STATUS_GCC_THROW - && !in_handler) - { - // Rethrow the current exception and use the @catch clauses to determine whether it's an Objective C exception - // or a foreign exception. - if (__cxa_current_exception_type()) { - in_handler = YES; - @try { - __cxa_rethrow(); - } @catch (id e) { - // Invoke _objc_unexpected_exception for Objective C exceptions - (*_objc_unexpected_exception)((id)e); - } @catch (...) { - // Ignore foreign exceptions. - } - in_handler = NO; - } - } - - // EXCEPTION_CONTINUE_SEARCH instructs the exception handler to continue searching for appropriate exception handlers. - return EXCEPTION_CONTINUE_SEARCH; -} - -OBJC_PUBLIC extern objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler) -{ - objc_uncaught_exception_handler previousHandler = __atomic_exchange_n(&_objc_unexpected_exception, handler, __ATOMIC_SEQ_CST); - - // Add a vectored exception handler to support the hook. We only need to do this once. - if (!handler_installed) { - AddVectoredExceptionHandler(0 /* The handler is the last handler to be called */ , _objc_vectored_exception_handler); - handler_installed = YES; - } - - return previousHandler; -} diff --git a/objcxx_eh.cc b/objcxx_eh.cc index 757de78e..8ef5baf3 100644 --- a/objcxx_eh.cc +++ b/objcxx_eh.cc @@ -1,19 +1,11 @@ -typedef struct objc_object* id; #include #include #include #include "dwarf_eh.h" +#include "objcxx_eh_private.h" #include "objcxx_eh.h" -#include "visibility.h" -#include "objc/runtime.h" #include "objc/objc-arc.h" -#ifndef DEBUG_EXCEPTIONS -#define DEBUG_LOG(...) -#else -#define DEBUG_LOG(str, ...) fprintf(stderr, str, ## __VA_ARGS__) -#endif - /** * Helper function that has a custom personality function. * This calls `cxx_throw` and has a destructor that must be run. We intercept @@ -23,92 +15,8 @@ int eh_trampoline(); uint64_t cxx_exception_class; -/** - * Our own definitions of C++ ABI functions and types. These are provided - * because this file must not include cxxabi.h. We need to handle subtly - * different variations of the ABI and including one specific implementation - * would make that very difficult. - */ -namespace __cxxabiv1 -{ - /** - * Type info for classes. Forward declared because the GNU ABI provides a - * method on all type_info objects that the dynamic the dynamic cast header - * needs. - */ - struct __class_type_info; - /** - * The C++ in-flight exception object. We will derive the offset of fields - * in this, so we do not ever actually see a concrete definition of it. - */ - struct __cxa_exception; - /** - * The public ABI structure for current exception state. - */ - struct __cxa_eh_globals - { - /** - * The current exception that has been caught. - */ - __cxa_exception *caughtExceptions; - /** - * The number of uncaught exceptions still in flight. - */ - unsigned int uncaughtExceptions; - }; - /** - * Retrieve the above structure. - */ - extern "C" __cxa_eh_globals *__cxa_get_globals(); -} - -namespace std -{ - struct type_info; -} - using namespace __cxxabiv1; -// Define some C++ ABI types here, rather than including them. This prevents -// conflicts with the libstdc++ headers, which expose only a subset of the -// type_info class (the part required for standards compliance, not the -// implementation details). - -typedef void (*unexpected_handler)(); -typedef void (*terminate_handler)(); - -namespace std -{ - /** - * std::type_info, containing the minimum requirements for the ABI. - * Public headers on some implementations also expose some implementation - * details. The layout of our subclasses must respect the layout of the - * C++ runtime library, but also needs to be portable across multiple - * implementations and so should not depend on internal symbols from those - * libraries. - */ - class type_info - { - public: - virtual ~type_info(); - bool operator==(const type_info &) const; - bool operator!=(const type_info &) const; - bool before(const type_info &) const; - type_info(); - private: - type_info(const type_info& rhs); - type_info& operator= (const type_info& rhs); - const char *__type_name; - protected: - type_info(const char *name): __type_name(name) { } - public: - const char* name() const { return __type_name; } - }; -} - -extern "C" void __cxa_throw(void*, std::type_info*, void(*)(void*)); -extern "C" void __cxa_rethrow(); - namespace { /** @@ -140,49 +48,6 @@ std::atomic type_info_offset; */ std::atomic exception_struct_size; -/** - * Helper function to find a particular value scanning backwards in a - * structure. - */ -template -ptrdiff_t find_backwards(void *addr, T val) -{ - T *ptr = reinterpret_cast(addr); - for (ptrdiff_t disp = -1 ; (disp * sizeof(T) > -128) ; disp--) - { - if (ptr[disp] == val) - { - return disp * sizeof(T); - } - } - fprintf(stderr, "Unable to find field in C++ exception structure\n"); - abort(); -} - -/** - * Helper function to find a particular value scanning forwards in a - * structure. - */ -template -ptrdiff_t find_forwards(void *addr, T val) -{ - T *ptr = reinterpret_cast(addr); - for (ptrdiff_t disp = 0 ; (disp * sizeof(T) < 256) ; disp++) - { - if (ptr[disp] == val) - { - return disp * sizeof(T); - } - } - fprintf(stderr, "Unable to find field in C++ exception structure\n"); - abort(); -} - -template -T *pointer_add(void *ptr, ptrdiff_t offset) -{ - return reinterpret_cast(reinterpret_cast(ptr) + offset); -} /** * Exception cleanup function for C++ exceptions that wrap Objective-C @@ -225,76 +90,33 @@ namespace gnustep { namespace libobjc { - /** - * Superclass for the type info for Objective-C exceptions. - */ - struct OBJC_PUBLIC __objc_type_info : std::type_info - { - /** - * Constructor that sets the name. - */ - __objc_type_info(const char *name) : type_info(name) {} - /** - * Helper function used by libsupc++ and libcxxrt to determine if - * this is a pointer type. If so, catches automatically - * dereference the pointer to the thrown pointer in - * `__cxa_begin_catch`. - */ - virtual bool __is_pointer_p() const { return true; } - /** - * Helper function used by libsupc++ and libcxxrt to determine if - * this is a function pointer type. Irrelevant for our purposes. - */ - virtual bool __is_function_p() const { return false; } - /** - * Catch handler. This is used in the C++ personality function. - * `thrown_type` is the type info of the thrown object, `this` is - * the type info at the catch site. `thrown_object` is a pointer - * to a pointer to the thrown object and may be adjusted by this - * function. - */ - virtual bool __do_catch(const type_info *thrown_type, + __objc_type_info::__objc_type_info(const char *name) : type_info(name) {} + + bool __objc_type_info::__is_pointer_p() const { return true; } + + bool __objc_type_info::__is_function_p() const { return false; } + + bool __objc_type_info::__do_catch(const type_info *thrown_type, void **thrown_object, unsigned) const - { - assert(0); - return false; - }; - /** - * Function used for `dynamic_cast` between two C++ class types in - * libsupc++ and libcxxrt. - * - * This should never be called on Objective-C types. - */ - virtual bool __do_upcast( + { + assert(0); + return false; + }; + + bool __objc_type_info::__do_upcast( const __class_type_info *target, void **thrown_object) const - { - return false; - }; + { + return false; }; + + /** - * Singleton type info for the `id` type. + * The `id` type is mangled to `@id`, which is not a valid mangling + * of anything else. */ - struct OBJC_PUBLIC __objc_id_type_info : __objc_type_info - { - /** - * The `id` type is mangled to `@id`, which is not a valid mangling - * of anything else. - */ - __objc_id_type_info() : __objc_type_info("@id") {}; - virtual ~__objc_id_type_info(); - virtual bool __do_catch(const type_info *thrownType, - void **obj, - unsigned outer) const; - }; - struct OBJC_PUBLIC __objc_class_type_info : __objc_type_info - { - virtual ~__objc_class_type_info(); - virtual bool __do_catch(const type_info *thrownType, - void **obj, - unsigned outer) const; - }; + __objc_id_type_info::__objc_id_type_info() : __objc_type_info("@id") {}; } static inline id dereference_thrown_object_pointer(void** obj) { @@ -430,29 +252,7 @@ void* objc_object_for_cxx_exception(void *thrown_exception, int *isValid) } // extern "C" -/** - * C++ structure that is thrown through a frame with the `test_eh_personality` - * personality function. This contains a well-known value that we can search - * for after the unwind header. - */ -struct -PRIVATE -MagicValueHolder -{ - /** - * The constant that we will search for to identify this object. - */ - static constexpr uint32_t magic = 0x01020304; - /** - * The single field in this structure. - */ - uint32_t magic_value; - /** - * Constructor. Initialises the field with the magic constant. - */ - MagicValueHolder() { magic_value = magic; } -}; - +MagicValueHolder::MagicValueHolder() { magic_value = magic; } /** * Function that simply throws an instance of `MagicValueHolder`. @@ -508,21 +308,4 @@ extern "C" void test_cxx_eh_implementation() } assert(caught); } -#else -static void eh_cleanup(void *exception) -{ - DEBUG_LOG("eh_cleanup: Releasing 0x%x\n", *(id*)exception); - objc_release(*(id*)exception); -} - -extern "C" -OBJC_PUBLIC -void objc_exception_throw(id object) -{ - id *exc = (id *)__cxa_allocate_exception(sizeof(id)); - *exc = object; - objc_retain(object); - DEBUG_LOG("objc_exception_throw: Throwing 0x%x\n", *exc); - __cxa_throw(exc, & __objc_id_type_info, eh_cleanup); -} #endif diff --git a/objcxx_eh_mingw.cc b/objcxx_eh_mingw.cc new file mode 100644 index 00000000..c45068bf --- /dev/null +++ b/objcxx_eh_mingw.cc @@ -0,0 +1,98 @@ +#include +#include +#include +#include "dwarf_eh.h" +#include "objcxx_eh_private.h" +#include "objcxx_eh.h" +#include "objc/runtime.h" +#include "objc/objc-arc.h" +#include "objc/objc-exception.h" +#include "objc/hooks.h" + +namespace __cxxabiv1 +{ + struct __cxa_refcounted_exception + { + int referenceCount; + }; +} + +using namespace __cxxabiv1; + +extern "C" __cxa_refcounted_exception* __cxa_init_primary_exception(void *obj, std::type_info *tinfo, void (*dest) (void *)); + +static void eh_cleanup(void *exception) +{ + DEBUG_LOG("eh_cleanup: Releasing 0x%x\n", *(id*)exception); + objc_release(*(id*)exception); +} + +/** + * Flag indicating that we've already inspected a C++ exception and found all + * of the offsets. + */ +std::atomic done_setup; + +/** + * The size of the `_Unwind_Exception` (including padding) in a + * `__cxa_exception`. + */ +std::atomic exception_struct_size; + +extern "C" +OBJC_PUBLIC +void objc_exception_throw(id object) +{ +#ifdef __GLIBCXX__ + // Don't bother with a mutex here. It doesn't matter if two threads set + // these values at the same time. + if (!done_setup) + { + DEBUG_LOG("objc_exception_throw: Doing initial setup\n"); + MagicValueHolder *magicExc = (MagicValueHolder *)__cxa_allocate_exception(sizeof(MagicValueHolder)); + MagicValueHolder x; + *magicExc = x; + + __cxa_refcounted_exception *header = + __cxa_init_primary_exception(magicExc, & __objc_id_type_info, NULL); + exception_struct_size = find_forwards(header, MagicValueHolder::magic); + __cxa_free_exception(magicExc); + + DEBUG_LOG("objc_exception_throw: exception_struct_size: 0x%x\n", unsigned(exception_struct_size)); + + done_setup = true; + } +#endif + + id *exc = (id *)__cxa_allocate_exception(sizeof(id)); + *exc = object; + objc_retain(object); + DEBUG_LOG("objc_exception_throw: Throwing 0x%x\n", *exc); + +#ifndef __GLIBCXX__ + // At the moment, only libstdc++ exposes __cxa_init_primary_exception. + __cxa_throw(exc, & __objc_id_type_info, eh_cleanup); +#else + __cxa_eh_globals *globals = __cxa_get_globals (); + globals->uncaughtExceptions += 1; + __cxa_refcounted_exception *header = + __cxa_init_primary_exception(exc, & __objc_id_type_info, eh_cleanup); + header->referenceCount = 1; + + _Unwind_Exception *unwindHeader = pointer_add<_Unwind_Exception>(header, exception_struct_size - sizeof(_Unwind_Exception)); + _Unwind_Reason_Code err = _Unwind_RaiseException (unwindHeader); + + 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(); +#endif +} + +OBJC_PUBLIC extern objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler) +{ + return __atomic_exchange_n(&_objc_unexpected_exception, handler, __ATOMIC_SEQ_CST); +} diff --git a/objcxx_eh_private.h b/objcxx_eh_private.h new file mode 100644 index 00000000..1642e9ac --- /dev/null +++ b/objcxx_eh_private.h @@ -0,0 +1,241 @@ +typedef struct objc_object* id; + +#include "objc/runtime.h" +#include "visibility.h" + +#ifndef DEBUG_EXCEPTIONS +#define DEBUG_LOG(...) +#else +#define DEBUG_LOG(str, ...) fprintf(stderr, str, ## __VA_ARGS__) +#endif + + +/** + * Our own definitions of C++ ABI functions and types. These are provided + * because this file must not include cxxabi.h. We need to handle subtly + * different variations of the ABI and including one specific implementation + * would make that very difficult. + */ +namespace __cxxabiv1 +{ + /** + * Type info for classes. Forward declared because the GNU ABI provides a + * method on all type_info objects that the dynamic the dynamic cast header + * needs. + */ + struct __class_type_info; + /** + * The C++ in-flight exception object. We will derive the offset of fields + * in this, so we do not ever actually see a concrete definition of it. + */ + struct __cxa_exception; + /** + * The public ABI structure for current exception state. + */ + struct __cxa_eh_globals + { + /** + * The current exception that has been caught. + */ + __cxa_exception *caughtExceptions; + /** + * The number of uncaught exceptions still in flight. + */ + unsigned int uncaughtExceptions; + }; + /** + * Retrieve the above structure. + */ + extern "C" __cxa_eh_globals *__cxa_get_globals(); +} + +namespace std +{ + struct type_info; +} + +// Define some C++ ABI types here, rather than including them. This prevents +// conflicts with the libstdc++ headers, which expose only a subset of the +// type_info class (the part required for standards compliance, not the +// implementation details). + +typedef void (*unexpected_handler)(); +typedef void (*terminate_handler)(); + +namespace std +{ + /** + * std::type_info, containing the minimum requirements for the ABI. + * Public headers on some implementations also expose some implementation + * details. The layout of our subclasses must respect the layout of the + * C++ runtime library, but also needs to be portable across multiple + * implementations and so should not depend on internal symbols from those + * libraries. + */ + class type_info + { + public: + virtual ~type_info(); + bool operator==(const type_info &) const; + bool operator!=(const type_info &) const; + bool before(const type_info &) const; + type_info(); + private: + type_info(const type_info& rhs); + type_info& operator= (const type_info& rhs); + const char *__type_name; + protected: + type_info(const char *name): __type_name(name) { } + public: + const char* name() const { return __type_name; } + }; +} + +extern "C" void __cxa_throw(void*, std::type_info*, void(*)(void*)); +extern "C" void __cxa_rethrow(); + +/** + * Helper function to find a particular value scanning backwards in a + * structure. + */ +template +ptrdiff_t find_backwards(void *addr, T val) +{ + T *ptr = reinterpret_cast(addr); + for (ptrdiff_t disp = -1 ; (disp * sizeof(T) > -128) ; disp--) + { + if (ptr[disp] == val) + { + return disp * sizeof(T); + } + } + fprintf(stderr, "Unable to find field in C++ exception structure\n"); + abort(); +} + +/** + * Helper function to find a particular value scanning forwards in a + * structure. + */ +template +ptrdiff_t find_forwards(void *addr, T val) +{ + T *ptr = reinterpret_cast(addr); + for (ptrdiff_t disp = 0 ; (disp * sizeof(T) < 256) ; disp++) + { + if (ptr[disp] == val) + { + return disp * sizeof(T); + } + } + fprintf(stderr, "Unable to find field in C++ exception structure\n"); + abort(); +} + +template +T *pointer_add(void *ptr, ptrdiff_t offset) +{ + return reinterpret_cast(reinterpret_cast(ptr) + offset); +} + +namespace gnustep +{ + namespace libobjc + { + /** + * Superclass for the type info for Objective-C exceptions. + */ + struct OBJC_PUBLIC __objc_type_info : std::type_info + { + /** + * Constructor that sets the name. + */ + __objc_type_info(const char *name); + /** + * Helper function used by libsupc++ and libcxxrt to determine if + * this is a pointer type. If so, catches automatically + * dereference the pointer to the thrown pointer in + * `__cxa_begin_catch`. + */ + virtual bool __is_pointer_p() const; + /** + * Helper function used by libsupc++ and libcxxrt to determine if + * this is a function pointer type. Irrelevant for our purposes. + */ + virtual bool __is_function_p() const; + /** + * Catch handler. This is used in the C++ personality function. + * `thrown_type` is the type info of the thrown object, `this` is + * the type info at the catch site. `thrown_object` is a pointer + * to a pointer to the thrown object and may be adjusted by this + * function. + */ + virtual bool __do_catch(const type_info *thrown_type, + void **thrown_object, + unsigned) const; + /** + * Function used for `dynamic_cast` between two C++ class types in + * libsupc++ and libcxxrt. + * + * This should never be called on Objective-C types. + */ + virtual bool __do_upcast( + const __cxxabiv1::__class_type_info *target, + void **thrown_object) const; + }; + + /** + * Singleton type info for the `id` type. + */ + struct OBJC_PUBLIC __objc_id_type_info : __objc_type_info + { + __objc_id_type_info(); + virtual ~__objc_id_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + + struct OBJC_PUBLIC __objc_class_type_info : __objc_type_info + { + virtual ~__objc_class_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + } +} + +/** + * Public interface to the Objective-C++ exception mechanism + */ +extern "C" +{ +/** + * The public symbol that the compiler uses to indicate the Objective-C id type. + */ +extern OBJC_PUBLIC gnustep::libobjc::__objc_id_type_info __objc_id_type_info; +} // extern "C" + +/** + * C++ structure that is thrown through a frame with the `test_eh_personality` + * personality function. This contains a well-known value that we can search + * for after the unwind header. + */ +struct +PRIVATE +MagicValueHolder +{ + /** + * The constant that we will search for to identify the MagicValueHolder object. + */ + static constexpr uint32_t magic = 0x01020304; + /** + * The single field in this structure. + */ + uint32_t magic_value; + /** + * Constructor. Initialises the field with the magic constant. + */ + MagicValueHolder(); +};