diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f8a5bd..b5a9ac0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,7 +84,6 @@ if (WIN32) list(APPEND libobjc_CXX_SRCS eh_win32_msvc.cc) else () list(APPEND libobjc_C_SRCS eh_personality.c) - set(libobjcxx_CXX_SRCS objcxx_eh.cc libstdcxx_current_primary_exception.cc) endif (WIN32) @@ -264,7 +263,6 @@ if (MSVC) endif() -add_library(objc SHARED ${libobjc_C_SRCS} ${libobjc_ASM_SRCS} ${libobjc_OBJC_SRCS} ${libobjc_OBJCXX_SRCS} ${libobjc_ASM_OBJS}) if (ENABLE_OBJCXX) if (WIN32) @@ -302,9 +300,15 @@ if (ENABLE_OBJCXX) set(ENABLE_OBJCXX false) endif() endif () + add_custom_command(OUTPUT eh_trampoline.s + COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} -fPIC -S "${CMAKE_SOURCE_DIR}/eh_trampoline.cc" -o - -fexceptions -fno-inline | sed "s/__gxx_personality_v0/test_eh_personality/g" > "${CMAKE_BINARY_DIR}/eh_trampoline.s" + MAIN_DEPENDENCY eh_trampoline.cc) + list(APPEND libobjc_ASM_SRCS eh_trampoline.s) + list(APPEND libobjc_CXX_SRCS objcxx_eh.cc) endif () endif (ENABLE_OBJCXX) +add_library(objc SHARED ${libobjc_C_SRCS} ${libobjc_ASM_SRCS} ${libobjc_OBJC_SRCS} ${libobjc_OBJCXX_SRCS} ${libobjc_ASM_OBJS}) if (ENABLE_OBJCXX) if (NOT CXXRT_IS_STDLIB) diff --git a/Test/ObjCXXEHInterop.m b/Test/ObjCXXEHInterop.m index 8d4d275..36be8b9 100644 --- a/Test/ObjCXXEHInterop.m +++ b/Test/ObjCXXEHInterop.m @@ -3,6 +3,7 @@ #import "stdio.h" void poke_objcxx(void); +void check_uncaught_count(void); void rethrow(id x) { @@ -18,5 +19,6 @@ int main(void) } @catch (Test *localException) { printf("In NS_HANDLER block, %p\n", localException); } + check_uncaught_count(); } diff --git a/Test/ObjCXXEHInterop.mm b/Test/ObjCXXEHInterop.mm index 768e507..37527d9 100644 --- a/Test/ObjCXXEHInterop.mm +++ b/Test/ObjCXXEHInterop.mm @@ -1,6 +1,27 @@ #import "Test.h" #import "stdio.h" +#ifdef __unix__ +// Declare these inline. The libsupc++ version of cxxabi.h does not include +// __cxa_eh_globls, even though it's mandated by the ABI. +namespace __cxxabiv1 +{ + struct __cxa_exception; + struct __cxa_eh_globals + { + __cxa_exception *caughtExceptions; + unsigned int uncaughtExceptions; + }; + extern "C" __cxa_eh_globals *__cxa_get_globals(); +} +extern "C" void check_uncaught_count(void) +{ + assert(__cxxabiv1::__cxa_get_globals()->uncaughtExceptions == 0); +} +#else +extern "C" void check_uncaught_count(void) {} +#endif + extern "C" void rethrow(id); diff --git a/eh_personality.c b/eh_personality.c index 3f0f8df..b74ad76 100644 --- a/eh_personality.c +++ b/eh_personality.c @@ -20,6 +20,8 @@ #define __builtin_unreachable abort #endif +void test_cxx_eh_implementation(); + /** * Class of exceptions to distinguish between this and other exception types. @@ -326,6 +328,11 @@ static inline _Unwind_Reason_Code internal_objc_personality(int version, void *object = NULL; #ifndef NO_OBJCXX + if (cxx_exception_class == 0) + { + test_cxx_eh_implementation(); + } + if (exceptionClass == cxx_exception_class) { int objcxx; @@ -486,12 +493,18 @@ BEGIN_PERSONALITY_FUNCTION(__gnustep_objc_personality_v0) } BEGIN_PERSONALITY_FUNCTION(__gnustep_objcxx_personality_v0) + if (cxx_exception_class == 0) + { + test_cxx_eh_implementation(); + } if (exceptionClass == objc_exception_class) { struct objc_exception *ex = objc_exception_from_header(exceptionObject); if (0 == ex->cxx_exception) { ex->cxx_exception = objc_init_cxx_exception(ex->object); + ex->cxx_exception->private_1 = exceptionObject->private_1; + ex->cxx_exception->private_2 = exceptionObject->private_2; } exceptionObject = ex->cxx_exception; exceptionClass = cxx_exception_class; diff --git a/eh_trampoline.cc b/eh_trampoline.cc new file mode 100644 index 0000000..77849e1 --- /dev/null +++ b/eh_trampoline.cc @@ -0,0 +1,9 @@ +void cxx_throw(); + +__attribute((visibility("hidden"))) +int eh_trampoline() +{ + struct X { ~X() {} } x; + cxx_throw(); + return 0; +} diff --git a/libstdcxx_current_primary_exception.cc b/libstdcxx_current_primary_exception.cc deleted file mode 100644 index 5bdd94e..0000000 --- a/libstdcxx_current_primary_exception.cc +++ /dev/null @@ -1,19 +0,0 @@ -#include "visibility.h" -#include - -#ifdef __GLIBCXX__ -/** - * libsupc++ doesn't expose __cxa_current_primary_exception, so implement this - * using the libstdc++ wrapper. The exception pointer in the - * `std::exception_ptr` object is reference counted, so stealing it by poking - * at the pointer directly means that we acquire it with a reference count of - * 1. - */ -PRIVATE extern "C" void *__cxa_current_primary_exception() -{ - std::exception_ptr p = std::current_exception(); - void *obj = *(void**)&p; - *(void**)&p = nullptr; - return obj; -} -#endif diff --git a/objcxx_eh.cc b/objcxx_eh.cc index ade63cc..25466ab 100644 --- a/objcxx_eh.cc +++ b/objcxx_eh.cc @@ -1,16 +1,60 @@ typedef struct objc_object* id; +#include #include #include #include "dwarf_eh.h" #include "objcxx_eh.h" -#include - +#include "visibility.h" #include "objc/runtime.h" +/** + * Helper function that has a custom personality function. + * This calls `cxx_throw` and has a destructor that must be run. We intercept + * the personality function calls and inspect the in-flight C++ exception. + */ +int eh_trampoline(); + +uint64_t cxx_exception_class; +extern "C" void *__cxa_allocate_exception(size_t) noexcept; + +/** + * 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 @@ -18,13 +62,6 @@ namespace std struct type_info; } -extern "C" -void __cxa_throw(void *thrown_exception, std::type_info *tinfo, - void (*dest)(void *)); - -extern "C" -void *__cxa_current_primary_exception(); - using namespace __cxxabiv1; // Define some C++ ABI types here, rather than including them. This prevents @@ -69,10 +106,103 @@ namespace std }; } -using namespace std; +namespace +{ +/** + * Helper needed by the unwind helper headers. + */ +inline _Unwind_Reason_Code continueUnwinding(struct _Unwind_Exception *ex, + struct _Unwind_Context *context) +{ +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) + if (__gnu_unwind_frame(ex, context) != _URC_OK) { return _URC_FAILURE; } +#endif + return _URC_CONTINUE_UNWIND; +} + + +/** + * Flag indicating that we've already inspected a C++ exception and found all + * of the offsets. + */ +std::atomic done_setup; +/** + * The offset of the C++ type_info object in a thrown exception from the unwind + * header in a `__cxa_exception`. + */ +std::atomic type_info_offset; +/** + * The offset of the reference count in a + */ +std::atomic refcount_offset; +/** + * The size of the `_Unwind_Exception` (including padding) in a + * `__cxa_exception`. + */ +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 + * exceptions. + */ +void exception_cleanup(_Unwind_Reason_Code reason, + struct _Unwind_Exception *ex) +{ + // __cxa_exception takes a pointer to the end of the __cxa_exception + // structure, and so we find that by adding the size of the generic + // exception structure + padding to the pointer to the generic exception + // structure field of the enclosing structure. + auto *cxxEx = pointer_add<__cxa_exception>(ex, exception_struct_size); + __cxa_free_exception(cxxEx); +} -static std::atomic exception_object_offset; -static std::atomic exception_type_offset; +} + +using namespace std; static BOOL isKindOfClass(Class thrown, Class type) @@ -189,58 +319,28 @@ gnustep::libobjc::__objc_id_type_info __objc_id_type_info; struct _Unwind_Exception *objc_init_cxx_exception(id obj) { - void *cxxexception = nullptr; - try - { - id *exception_object = static_cast(__cxa_allocate_exception(sizeof(id))); - *exception_object = obj; - __cxa_throw(exception_object, &__objc_id_type_info, nullptr); - } - catch (...) - { - cxxexception = __cxa_current_primary_exception(); - } - assert(cxxexception); - uint64_t *ehcls = reinterpret_cast(cxxexception); - ehcls--; - int count = 1; - while (*ehcls != cxx_exception_class) - { - ehcls--; - count++; - assert((count < 8) && "Exception structure appears to be corrupt"); - } - ptrdiff_t displacement = reinterpret_cast(cxxexception) - reinterpret_cast(ehcls); - assert((exception_object_offset == 0) || (exception_object_offset == displacement)); - - exception_object_offset = displacement; - - std::type_info **ehtype = reinterpret_cast(ehcls); - ehtype--; - count = 1; - while (*ehtype != &__objc_id_type_info) - { - ehtype--; - count++; - assert((count < 32) && "Exception structure appears to be corrupt"); - } - displacement = reinterpret_cast(ehtype) - reinterpret_cast(ehcls); - assert((exception_type_offset == 0) || (exception_type_offset == displacement)); - - exception_type_offset = displacement; - return reinterpret_cast<_Unwind_Exception*>(ehcls); + id *newEx = static_cast(__cxa_allocate_exception(sizeof(id))); + *newEx = obj; + _Unwind_Exception *ex = pointer_add<_Unwind_Exception>(newEx, -exception_struct_size); + *pointer_add(ex, type_info_offset) = &__objc_id_type_info; + ex->exception_class = cxx_exception_class; + ex->exception_cleanup = exception_cleanup; + __cxa_get_globals()->uncaughtExceptions++; + return ex; } void* objc_object_for_cxx_exception(void *thrown_exception, int *isValid) { - ptrdiff_t type_offset = exception_type_offset; + ptrdiff_t type_offset = type_info_offset; if (type_offset == 0) { *isValid = 0; return nullptr; } + const std::type_info *thrownType = - *reinterpret_cast(reinterpret_cast(thrown_exception) + type_offset); + *pointer_add(thrown_exception, type_offset); + if (!dynamic_cast(thrownType) && !dynamic_cast(thrownType)) { @@ -248,18 +348,113 @@ void* objc_object_for_cxx_exception(void *thrown_exception, int *isValid) return 0; } *isValid = 1; - return *reinterpret_cast(reinterpret_cast(thrown_exception) + exception_object_offset); + return *pointer_add(thrown_exception, exception_struct_size); } -/* -void print_type_info(void *thrown_exception) +} // 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 { - __cxa_exception *ex = (__cxa_exception*) ((char*)thrown_exception - - offsetof(struct __cxa_exception, unwindHeader)); - fprintf(stderr, "Type info: %s\n", ex->exceptionType->name()); - fprintf(stderr, "offset is: %d\n", offsetof(struct __cxa_exception, unwindHeader)); + /** + * 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; } +}; + + +/** + * Function that simply throws an instance of `MagicValueHolder`. + */ +PRIVATE void cxx_throw() +{ + MagicValueHolder x; + throw x; } -*/ -} // extern "C" +/** + * Personality function that wraps the C++ personality and inspects the C++ + * exception structure on the way past. This should be used only for the + * `eh_trampoline` function. + */ +extern "C" +PRIVATE +BEGIN_PERSONALITY_FUNCTION(test_eh_personality) + // Don't bother with a mutex here. It doesn't matter if two threads set + // these values at the same time. + if (!done_setup) + { + uint64_t cls = __builtin_bswap64(exceptionClass); + type_info_offset = find_backwards(exceptionObject, &typeid(MagicValueHolder)); +#ifdef __LP64__ + // On 64-bit platforms, the refcount is added to the front of the + // structure. + ptrdiff_t refcount_backwards_offset = type_info_offset - sizeof(uintptr_t); +#else + // On 32-bit platforms, this should be immediately before the + // _Unwind_Exception in some spare padding, but libsupc++ puts it in + // the same place as for 64-bit. Try the one that's definitely in the + // object first and then fall back to the other... + ptrdiff_t refcount_backwards_offset = -sizeof(uint32_t); + auto read_offset = [](void *obj, ptrdiff_t offset) + { + char *addr = reinterpret_cast(obj) + offset; + uintptr_t v = *reinterpret_cast(addr); + return v; + }; + if (read_offset(exceptionObject, refcount_backwards_offset) != 1) + { + refcount_backwards_offset = type_info_offset - sizeof(uintptr_t); + } + if (read_offset(exceptionObject, refcount_backwards_offset) != 1) + { + fprintf(stderr, "Unable to find refcount field\n"); + abort(); + } +#endif + exception_struct_size = find_forwards(exceptionObject, MagicValueHolder::magic); + cxx_exception_class = exceptionClass; + done_setup = true; + } + return CALL_PERSONALITY_FUNCTION(__gxx_personality_v0); +} + +/** + * Probe the C++ exception handling implementation. This throws a C++ + * exception through a function that uses `test_eh_personality` as its + * personality function, allowing us to inspect a C++ exception that is in a + * known state. + */ +extern "C" void test_cxx_eh_implementation() +{ + if (done_setup) + { + return; + } + bool caught = false; + try + { + eh_trampoline(); + } + catch(MagicValueHolder) + { + caught = true; + } + assert(caught); +} diff --git a/objcxx_eh.h b/objcxx_eh.h index 1a117df..9dacc95 100644 --- a/objcxx_eh.h +++ b/objcxx_eh.h @@ -51,7 +51,20 @@ void *objc_object_for_cxx_exception(void *thrown_exception, int *isValid); __attribute__((weak)) void print_type_info(void *thrown_exception); -static const uint64_t cxx_exception_class = EXCEPTION_CLASS('G','N','U','C','C','+','+','\0'); +/** + * The exception class that we've detected that C++ runtime library uses. + */ +extern uint64_t cxx_exception_class; + +/** + * The exception class that libsupc++ and libcxxrt use. + */ +const uint64_t gnu_cxx_exception_class = EXCEPTION_CLASS('G','N','U','C','C','+','+','\0'); + +/** + * The exception class that libc++abi uses. + */ +const uint64_t llvm_cxx_exception_class = EXCEPTION_CLASS('C','L','N','G','C','+','+','\0'); #ifdef __cplusplus } diff --git a/unwind-itanium.h b/unwind-itanium.h index 4afca6f..f7c044d 100644 --- a/unwind-itanium.h +++ b/unwind-itanium.h @@ -79,8 +79,8 @@ struct _Unwind_Exception { uint64_t exception_class; _Unwind_Exception_Cleanup_Fn exception_cleanup; - unsigned long private_1; - unsigned long private_2; + uintptr_t private_1; + uintptr_t private_2; } __attribute__((__aligned__)); extern _Unwind_Reason_Code _Unwind_RaiseException (struct _Unwind_Exception *);