From 0aea4b2e9345c6745837f91e44fceaaa0a2257c8 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Wed, 22 Dec 2021 22:39:08 +0100 Subject: [PATCH] Support _objc_unexpected_exception on Win32 --- Test/UnexpectedException.m | 26 ++++++++++++- eh_win32_msvc.cc | 80 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/Test/UnexpectedException.m b/Test/UnexpectedException.m index bf82da2..0f48069 100644 --- a/Test/UnexpectedException.m +++ b/Test/UnexpectedException.m @@ -3,18 +3,40 @@ #include +#ifdef _WIN32 +#include +#endif + id exceptionObj = @"Exception"; -void UncaughtExceptionHandler(id exception) +void _UncaughtExceptionHandler(id exception) { assert(exception == exceptionObj); +#ifdef _WIN32 + // on Windows we will exit in _UnhandledExceptionFilter() below +#else exit(0); +#endif } +#ifdef _WIN32 +LONG WINAPI _UnhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) +{ + assert(exceptionInfo != NULL); + exit(0); +} +#endif + int main(void) { - _objc_unexpected_exception = UncaughtExceptionHandler; +#ifdef _WIN32 + // also verify that an existing handler still gets called after we set ours + SetUnhandledExceptionFilter(&_UnhandledExceptionFilter); +#endif + + _objc_unexpected_exception = _UncaughtExceptionHandler; @throw exceptionObj; assert(0 && "should not be reached!"); + return -1; } diff --git a/eh_win32_msvc.cc b/eh_win32_msvc.cc index 09d05ff..319408c 100644 --- a/eh_win32_msvc.cc +++ b/eh_win32_msvc.cc @@ -4,6 +4,7 @@ #include #include "objc/runtime.h" +#include "objc/hooks.h" #include "visibility.h" #include @@ -19,6 +20,10 @@ #define __builtin_unreachable abort #endif +#define EH_EXCEPTION_NUMBER ('msc' | 0xE0000000) +#define EH_MAGIC_NUMBER1 0x19930520 +#define EXCEPTION_NONCONTINUABLE 0x1 + struct _MSVC_TypeDescriptor { const void* pVFTable; @@ -51,6 +56,9 @@ struct _MSVC_ThrowInfo unsigned long pCatchableTypeArray; }; +static LPTOP_LEVEL_EXCEPTION_FILTER originalUnhandledExceptionFilter = nullptr; +LONG WINAPI _objc_unhandled_exception_filter(struct _EXCEPTION_POINTERS* exceptionInfo); + #if defined(_WIN64) #define IMAGE_RELATIVE(ptr, base) (static_cast((ptr ? ((uintptr_t)ptr - (uintptr_t)base) : (uintptr_t)nullptr))) #else @@ -175,9 +183,6 @@ OBJC_PUBLIC extern "C" void objc_exception_throw(id object) 0, // pfnForwardCompat IMAGE_RELATIVE(exceptTypes, &x) // pCatchableTypeArray }; -# define EH_EXCEPTION_NUMBER ('msc' | 0xE0000000) -# define EH_MAGIC_NUMBER1 0x19930520 -# define EXCEPTION_NONCONTINUABLE 0x1 EXCEPTION_RECORD exception; exception.ExceptionCode = EH_EXCEPTION_NUMBER; exception.ExceptionFlags = EXCEPTION_NONCONTINUABLE; @@ -192,6 +197,17 @@ OBJC_PUBLIC extern "C" void objc_exception_throw(id object) exception.ExceptionInformation[2] = reinterpret_cast(&ti); exception.ExceptionInformation[3] = reinterpret_cast(&x); + // Set unhandled exception filter to support _objc_unexpected_exception hook. + // Unfortunately there doesn't seem to be a better place to call this, as + // installing it at construction time means that it will get overwritten by + // the handler installed by the VC runtime. This way it works, but as a side + // effect throwing exceptions will overwrite any previously installed handler, + // so we save it and call it as part of our handler. + LPTOP_LEVEL_EXCEPTION_FILTER previousExceptionFilter = SetUnhandledExceptionFilter(&_objc_unhandled_exception_filter); + if (previousExceptionFilter != &_objc_unhandled_exception_filter) { + originalUnhandledExceptionFilter = previousExceptionFilter; + } + #ifdef _WIN64 RtlRaiseException(&exception); #else @@ -203,10 +219,66 @@ OBJC_PUBLIC extern "C" void objc_exception_throw(id object) __builtin_unreachable(); } - OBJC_PUBLIC extern "C" void objc_exception_rethrow(void* exc) { _CxxThrowException(nullptr, nullptr); __builtin_unreachable(); } +// rebase_and_cast adds a constant offset to a U value, converting it into a T +template +static std::add_const_t> rebase_and_cast(intptr_t base, U value) { + // U value -> const T* (base+value) + return reinterpret_cast>>(base + (long)(value)); +} + +/** + * Unhandled exception filter that we install to get called when an exception is + * not otherwise handled in a process that is not being debugged. In here we + * check if the exception is an Objective C exception raised by + * objc_exception_throw() above, and if so call the _objc_unexpected_exception + * hook with the Objective-C exception object. + * + * https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter + */ +LONG WINAPI _objc_unhandled_exception_filter(struct _EXCEPTION_POINTERS* exceptionInfo) +{ + const EXCEPTION_RECORD* ex = exceptionInfo->ExceptionRecord; + + if (_objc_unexpected_exception != 0 + && ex->ExceptionCode == EH_EXCEPTION_NUMBER + && ex->ExceptionInformation[0] == EH_MAGIC_NUMBER1 + && ex->NumberParameters >= 3) + { + // On 64-bit platforms, thrown exception catch data are relative virtual addresses off the module base. + intptr_t imageBase = ex->NumberParameters >= 4 ? (intptr_t)(ex->ExceptionInformation[3]) : 0; + + auto throwInfo = reinterpret_cast<_MSVC_ThrowInfo*>(ex->ExceptionInformation[2]); + if (throwInfo && throwInfo->pCatchableTypeArray) { + auto catchableTypes = rebase_and_cast<_MSVC_CatchableTypeArray*>(imageBase, throwInfo->pCatchableTypeArray); + bool foundobjc_object = false; + for (int i = 0; i < catchableTypes->count; ++i) { + const _MSVC_CatchableType* catchableType = rebase_and_cast<_MSVC_CatchableType*>(imageBase, catchableTypes->types[i]); + const _MSVC_TypeDescriptor* typeDescriptor = rebase_and_cast<_MSVC_TypeDescriptor*>(imageBase, catchableType->type); + if (strcmp(typeDescriptor->name, mangleObjcObject().c_str()) == 0) { + foundobjc_object = true; + break; + } + } + + if (foundobjc_object) { + id exception = *reinterpret_cast(ex->ExceptionInformation[1]); + _objc_unexpected_exception(exception); + } + } + } + + // call original exception filter if any + if (originalUnhandledExceptionFilter) { + return originalUnhandledExceptionFilter(exceptionInfo); + } + + // EXCEPTION_CONTINUE_SEARCH instructs the exception handler to continue searching for appropriate exception handlers. + // Since this is the last one, it is not likely to find any more. + return EXCEPTION_CONTINUE_SEARCH; +}