Follow up to https://gitlab.winehq.org/wine/wine/-/merge_requests/9209#note_120396
-- v6: ucrtbase: Add __C_specific_handler tests. msvcrt: Call the destructor for C++ exceptions in __C_specific_handler.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/msvcrt/except.c | 254 +++++++++++++++++++++++++++++++++++ dlls/msvcrt/except_arm.c | 8 ++ dlls/msvcrt/except_arm64.c | 29 ++++ dlls/msvcrt/except_arm64ec.c | 30 +++++ dlls/msvcrt/msvcrt.spec | 2 +- 5 files changed, 322 insertions(+), 1 deletion(-)
diff --git a/dlls/msvcrt/except.c b/dlls/msvcrt/except.c index 383c67f7437..1b10387a810 100644 --- a/dlls/msvcrt/except.c +++ b/dlls/msvcrt/except.c @@ -43,6 +43,9 @@ #include "cppexcept.h"
WINE_DEFAULT_DEBUG_CHANNEL(seh); +#ifndef __i386__ +WINE_DECLARE_DEBUG_CHANNEL(unwind); +#endif
#if _MSVCR_VER>=70 && _MSVCR_VER<=71 static MSVCRT_security_error_handler security_error_handler; @@ -1077,3 +1080,254 @@ int* CDECL __processing_throw(void) }
#endif /* _MSVCR_VER>=140 */ + +#ifndef __i386__ +#define DUMP_SCOPE_TABLE(base,table) do { \ + for (unsigned int i = 0; i < table->Count; i++) \ + TRACE( " %u: %p-%p handler %p target %p\n", i, \ + (char *)base + table->ScopeRecord[i].BeginAddress, \ + (char *)base + table->ScopeRecord[i].EndAddress, \ + (char *)base + table->ScopeRecord[i].HandlerAddress, \ + (char *)base + table->ScopeRecord[i].JumpTarget ); \ +} while(0) + +LONG __C_ExecuteExceptionFilter( EXCEPTION_POINTERS *ptrs, void *frame, PEXCEPTION_FILTER filter, BYTE *nonvolatile ); +#endif + +/* msvcrt's __C_specific_handler will call the destructor on the exception object while handling C++ exceptions, unlike ntdll. */ + +#if defined(__aarch64__) || defined(__arm64ec__) + +#ifdef __arm64ec__ +#define __C_specific_handler __C_specific_handler_arm64 +#endif + +EXCEPTION_DISPOSITION WINAPI __C_specific_handler( EXCEPTION_RECORD *rec, void *frame, + ARM64_NT_CONTEXT *context, + DISPATCHER_CONTEXT_ARM64 *dispatch ) +{ + const SCOPE_TABLE *table = dispatch->HandlerData; + ULONG_PTR base = dispatch->ImageBase; + ULONG_PTR pc = dispatch->ControlPc; + unsigned int i; + void *handler; + + TRACE( "%p %p %p %p pc %Ix\n", rec, frame, context, dispatch, pc ); + if (TRACE_ON(unwind)) DUMP_SCOPE_TABLE( base, table ); + + if (dispatch->ControlPcIsUnwound) pc -= 4; + + if (rec->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) + { + for (i = dispatch->ScopeIndex; i < table->Count; i++) + { + if (pc < base + table->ScopeRecord[i].BeginAddress) continue; + if (pc >= base + table->ScopeRecord[i].EndAddress) continue; + if (table->ScopeRecord[i].JumpTarget) continue; + + if (rec->ExceptionFlags & EXCEPTION_TARGET_UNWIND && + dispatch->TargetPc >= base + table->ScopeRecord[i].BeginAddress && + dispatch->TargetPc < base + table->ScopeRecord[i].EndAddress) + { + break; + } + handler = (void *)(base + table->ScopeRecord[i].HandlerAddress); + dispatch->ScopeIndex = i + 1; + TRACE( "scope %u calling __finally %p frame %p\n", i, handler, frame ); + __C_ExecuteExceptionFilter( ULongToPtr(TRUE), frame, handler, dispatch->NonVolatileRegisters ); + } + } + else + { + for (i = dispatch->ScopeIndex; i < table->Count; i++) + { + if (pc < base + table->ScopeRecord[i].BeginAddress) continue; + if (pc >= base + table->ScopeRecord[i].EndAddress) continue; + if (!table->ScopeRecord[i].JumpTarget) continue; + + if (table->ScopeRecord[i].HandlerAddress != EXCEPTION_EXECUTE_HANDLER) + { + EXCEPTION_POINTERS ptrs = { rec, (CONTEXT *)context }; + + handler = (void *)(base + table->ScopeRecord[i].HandlerAddress); + TRACE( "scope %u calling filter %p ptrs %p frame %p\n", i, handler, &ptrs, frame ); + switch (__C_ExecuteExceptionFilter( &ptrs, frame, handler, dispatch->NonVolatileRegisters )) + { + case EXCEPTION_EXECUTE_HANDLER: + break; + case EXCEPTION_CONTINUE_SEARCH: + continue; + case EXCEPTION_CONTINUE_EXECUTION: + return ExceptionContinueExecution; + } + } + /* Call the destructor if we're handling a C++ exception. */ + if (is_cxx_exception(rec)) __DestructExceptionObject( rec ); + + TRACE( "unwinding to target %Ix\n", base + table->ScopeRecord[i].JumpTarget ); + RtlUnwindEx( frame, (char *)base + table->ScopeRecord[i].JumpTarget, + rec, ULongToPtr(rec->ExceptionCode), (CONTEXT *)dispatch->ContextRecord, + dispatch->HistoryTable ); + } + } + return ExceptionContinueSearch; +} + +#ifdef __arm64ec__ +#undef __C_specific_handler +#endif + +#endif /* __aarch64__ || __arm64ec__ */ + +#ifdef __arm__ + +EXCEPTION_DISPOSITION WINAPI __C_specific_handler( EXCEPTION_RECORD *rec, void *frame, + CONTEXT *context, DISPATCHER_CONTEXT *dispatch ) +{ + const SCOPE_TABLE *table = dispatch->HandlerData; + ULONG_PTR base = dispatch->ImageBase; + ULONG_PTR pc = dispatch->ControlPc; + unsigned int i; + void *handler; + + TRACE( "%p %p %p %p pc %Ix\n", rec, frame, context, dispatch, pc ); + if (TRACE_ON(unwind)) DUMP_SCOPE_TABLE( base, table ); + + if (dispatch->ControlPcIsUnwound) pc -= 2; + + if (rec->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) + { + for (i = dispatch->ScopeIndex; i < table->Count; i++) + { + if (pc < base + table->ScopeRecord[i].BeginAddress) continue; + if (pc >= base + table->ScopeRecord[i].EndAddress) continue; + if (table->ScopeRecord[i].JumpTarget) continue; + + if (rec->ExceptionFlags & EXCEPTION_TARGET_UNWIND && + dispatch->TargetPc >= base + table->ScopeRecord[i].BeginAddress && + dispatch->TargetPc < base + table->ScopeRecord[i].EndAddress) + { + break; + } + handler = (void *)(base + table->ScopeRecord[i].HandlerAddress); + dispatch->ScopeIndex = i + 1; + TRACE( "scope %u calling __finally %p frame %p\n", i, handler, frame ); + __C_ExecuteExceptionFilter( ULongToPtr(TRUE), frame, handler, dispatch->NonVolatileRegisters ); + } + } + else + { + for (i = dispatch->ScopeIndex; i < table->Count; i++) + { + if (pc < base + table->ScopeRecord[i].BeginAddress) continue; + if (pc >= base + table->ScopeRecord[i].EndAddress) continue; + if (!table->ScopeRecord[i].JumpTarget) continue; + + if (table->ScopeRecord[i].HandlerAddress != EXCEPTION_EXECUTE_HANDLER) + { + EXCEPTION_POINTERS ptrs = { rec, context }; + + handler = (void *)(base + table->ScopeRecord[i].HandlerAddress); + TRACE( "scope %u calling filter %p ptrs %p frame %p\n", i, handler, &ptrs, frame ); + switch (__C_ExecuteExceptionFilter( &ptrs, frame, handler, dispatch->NonVolatileRegisters )) + { + case EXCEPTION_EXECUTE_HANDLER: + break; + case EXCEPTION_CONTINUE_SEARCH: + continue; + case EXCEPTION_CONTINUE_EXECUTION: + return ExceptionContinueExecution; + } + } + /* Call the destructor if we're handling a C++ exception. */ + if (is_cxx_exception( rec )) __DestructExceptionObject( rec ); + + TRACE( "unwinding to target %lx\n", base + table->ScopeRecord[i].JumpTarget ); + RtlUnwindEx( frame, (char *)base + table->ScopeRecord[i].JumpTarget, + rec, ULongToPtr(rec->ExceptionCode), dispatch->ContextRecord, + dispatch->HistoryTable ); + } + } + return ExceptionContinueSearch; +} + +#endif /* __arm__ */ + +#ifdef __x86_64__ + +EXCEPTION_DISPOSITION WINAPI __C_specific_handler( EXCEPTION_RECORD *rec, void *frame, CONTEXT *context, + DISPATCHER_CONTEXT *dispatch ) +{ + const SCOPE_TABLE *table = dispatch->HandlerData; + ULONG_PTR base = dispatch->ImageBase; + ULONG_PTR pc = dispatch->ControlPc; + unsigned int i; + +#ifdef __arm64ec__ + if (RtlIsEcCode( pc )) + return __C_specific_handler_arm64( rec, frame, (ARM64_NT_CONTEXT *)context, + (DISPATCHER_CONTEXT_ARM64 *)dispatch ); +#endif + + TRACE( "%p %p %p %p pc %Ix\n", rec, frame, context, dispatch, pc ); + if (TRACE_ON(unwind)) DUMP_SCOPE_TABLE( base, table ); + + if (rec->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) + { + for (i = dispatch->ScopeIndex; i < table->Count; i++) + { + if (pc < base + table->ScopeRecord[i].BeginAddress) continue; + if (pc >= base + table->ScopeRecord[i].EndAddress) continue; + if (table->ScopeRecord[i].JumpTarget) continue; + + if (rec->ExceptionFlags & EXCEPTION_TARGET_UNWIND && + dispatch->TargetIp >= base + table->ScopeRecord[i].BeginAddress && + dispatch->TargetIp < base + table->ScopeRecord[i].EndAddress) + { + break; + } + else + { + PTERMINATION_HANDLER handler = (void *)(base + table->ScopeRecord[i].HandlerAddress); + dispatch->ScopeIndex = i + 1; + TRACE( "scope %u calling __finally %p frame %p\n", i, handler, frame ); + handler( TRUE, frame ); + } + } + } + else + { + for (i = dispatch->ScopeIndex; i < table->Count; i++) + { + if (pc < base + table->ScopeRecord[i].BeginAddress) continue; + if (pc >= base + table->ScopeRecord[i].EndAddress) continue; + if (!table->ScopeRecord[i].JumpTarget) continue; + + if (table->ScopeRecord[i].HandlerAddress != EXCEPTION_EXECUTE_HANDLER) + { + EXCEPTION_POINTERS ptrs = { rec, context }; + PEXCEPTION_FILTER filter = (void *)(base + table->ScopeRecord[i].HandlerAddress); + + TRACE( "scope %u calling filter %p ptrs %p frame %p\n", i, filter, &ptrs, frame ); + switch (filter( &ptrs, frame )) + { + case EXCEPTION_EXECUTE_HANDLER: + break; + case EXCEPTION_CONTINUE_SEARCH: + continue; + case EXCEPTION_CONTINUE_EXECUTION: + return ExceptionContinueExecution; + } + } + if (is_cxx_exception( rec )) __DestructExceptionObject( rec ); + + TRACE( "unwinding to target %Ix\n", base + table->ScopeRecord[i].JumpTarget ); + RtlUnwindEx( frame, (char *)base + table->ScopeRecord[i].JumpTarget, + rec, ULongToPtr(rec->ExceptionCode), dispatch->ContextRecord, + dispatch->HistoryTable ); + } + } + return ExceptionContinueSearch; +} + +#endif /* __x86_64__ */ \ No newline at end of file diff --git a/dlls/msvcrt/except_arm.c b/dlls/msvcrt/except_arm.c index b2bf94d5338..f54a93784ef 100644 --- a/dlls/msvcrt/except_arm.c +++ b/dlls/msvcrt/except_arm.c @@ -94,4 +94,12 @@ int handle_fpieee_flt( __msvcrt_ulong exception_code, EXCEPTION_POINTERS *ep, return EXCEPTION_CONTINUE_SEARCH; }
+__ASM_GLOBAL_FUNC( __C_ExecuteExceptionFilter, + "push {r3-r11,lr}\n\t" + ".seh_save_regs_w {r3-r11,lr}\n\t" + ".seh_endprologue\n\t" + "ldm r3, {r4-r11}\n\t" + "blx r2\n\t" + "pop {r3-r11,pc}\n\t" ); + #endif /* __arm__ */ diff --git a/dlls/msvcrt/except_arm64.c b/dlls/msvcrt/except_arm64.c index 6d515f8b150..81b0bf5b6ec 100644 --- a/dlls/msvcrt/except_arm64.c +++ b/dlls/msvcrt/except_arm64.c @@ -125,4 +125,33 @@ int handle_fpieee_flt( __msvcrt_ulong exception_code, EXCEPTION_POINTERS *ep, return EXCEPTION_CONTINUE_SEARCH; }
+__ASM_GLOBAL_FUNC( __C_ExecuteExceptionFilter, + "stp x29, x30, [sp, #-96]!\n\t" + ".seh_save_fplr_x 96\n\t" + "stp x19, x20, [sp, #16]\n\t" + ".seh_save_regp x19, 16\n\t" + "stp x21, x22, [sp, #32]\n\t" + ".seh_save_regp x21, 32\n\t" + "stp x23, x24, [sp, #48]\n\t" + ".seh_save_regp x23, 48\n\t" + "stp x25, x26, [sp, #64]\n\t" + ".seh_save_regp x25, 64\n\t" + "stp x27, x28, [sp, #80]\n\t" + ".seh_save_regp x27, 80\n\t" + ".seh_endprologue\n\t" + "ldp x19, x20, [x3, #0]\n\t" /* nonvolatile regs */ + "ldp x21, x22, [x3, #16]\n\t" + "ldp x23, x24, [x3, #32]\n\t" + "ldp x25, x26, [x3, #48]\n\t" + "ldp x27, x28, [x3, #64]\n\t" + "ldr x1, [x3, #80]\n\t" /* x29 = frame */ + "blr x2\n\t" /* filter */ + "ldp x19, x20, [sp, #16]\n\t" + "ldp x21, x22, [sp, #32]\n\t" + "ldp x23, x24, [sp, #48]\n\t" + "ldp x25, x26, [sp, #64]\n\t" + "ldp x27, x28, [sp, #80]\n\t" + "ldp x29, x30, [sp], #96\n\t" + "ret" ); + #endif /* __aarch64__ */ diff --git a/dlls/msvcrt/except_arm64ec.c b/dlls/msvcrt/except_arm64ec.c index b2e723f79b3..fe1d79610a2 100644 --- a/dlls/msvcrt/except_arm64ec.c +++ b/dlls/msvcrt/except_arm64ec.c @@ -166,4 +166,34 @@ void __cdecl __crtCapturePreviousContext( CONTEXT *ctx ) } #endif
+LONG __attribute__((naked)) __C_ExecuteExceptionFilter( EXCEPTION_POINTERS *ptrs, void *frame, + PEXCEPTION_FILTER filter, BYTE *nonvolatile ) +{ + asm( ".seh_proc _C_ExecuteExceptionFilter\n\t" + "stp x29, x30, [sp, #-80]!\n\t" + ".seh_save_fplr_x 80\n\t" + "stp x19, x20, [sp, #16]\n\t" + ".seh_save_regp x19, 16\n\t" + "stp x21, x22, [sp, #32]\n\t" + ".seh_save_regp x21, 32\n\t" + "stp x25, x26, [sp, #48]\n\t" + ".seh_save_regp x25, 48\n\t" + "str x27, [sp, #64]\n\t" + ".seh_save_reg x27, 64\n\t" + ".seh_endprologue\n\t" + "ldp x19, x20, [x3, #0]\n\t" /* nonvolatile regs */ + "ldp x21, x22, [x3, #16]\n\t" + "ldp x25, x26, [x3, #48]\n\t" + "ldr x27, [x3, #64]\n\t" + "ldr x1, [x3, #80]\n\t" /* x29 = frame */ + "blr x2\n\t" /* filter */ + "ldp x19, x20, [sp, #16]\n\t" + "ldp x21, x22, [sp, #32]\n\t" + "ldp x25, x26, [sp, #48]\n\t" + "ldr x27, [sp, #64]\n\t" + "ldp x29, x30, [sp], #80\n\t" + "ret\n\t" + ".seh_endproc" ); +} + #endif /* __arm64ec__ */ diff --git a/dlls/msvcrt/msvcrt.spec b/dlls/msvcrt/msvcrt.spec index 27cd61376e4..c308c1711a7 100644 --- a/dlls/msvcrt/msvcrt.spec +++ b/dlls/msvcrt/msvcrt.spec @@ -174,7 +174,7 @@ @ extern _HUGE MSVCRT__HUGE @ cdecl _Strftime(ptr long str ptr ptr) @ cdecl _XcptFilter(long ptr) -@ stdcall -arch=!i386 __C_specific_handler(ptr long ptr ptr) ntdll.__C_specific_handler +@ stdcall -arch=!i386 __C_specific_handler(ptr long ptr ptr) @ cdecl __CppXcptFilter(long ptr) # stub __CxxCallUnwindDelDtor # stub __CxxCallUnwindDtor
From: Piotr Caban piotr@codeweavers.com
--- dlls/ucrtbase/tests/cpp.c | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+)
diff --git a/dlls/ucrtbase/tests/cpp.c b/dlls/ucrtbase/tests/cpp.c index da06b2cbf2f..e5b52872cd6 100644 --- a/dlls/ucrtbase/tests/cpp.c +++ b/dlls/ucrtbase/tests/cpp.c @@ -24,6 +24,8 @@
#include <windef.h> #include <winbase.h> +#include <winternl.h> +#include <rtlsupportapi.h> #include <verrsrc.h> #include <dbghelp.h> #include <unknwn.h> @@ -323,6 +325,84 @@ static void test___DestructExceptionObject(void) CHECK_CALLED(iunknown_except_Release); }
+#ifdef __x86_64__ +EXCEPTION_DISPOSITION WINAPI __C_specific_handler(EXCEPTION_RECORD*,void*,CONTEXT*,DISPATCHER_CONTEXT*); +DEFINE_EXPECT(filter); +static CONTEXT ctx; + +static ULONG WINAPI iunknown_except2_Release(IUnknown *iface) +{ + CHECK_EXPECT(iunknown_except_Release); + NtContinue(&ctx, FALSE); + return 1; +} + +static IUnknownVtbl iunknown_except2_vtbl = { + iunknown_except_QueryInterface, + iunknown_except_AddRef, + iunknown_except2_Release +}; + +static IUnknown iunknown_except2 = { &iunknown_except2_vtbl }; + +static LONG WINAPI filter(struct _EXCEPTION_POINTERS *ep, void *frame) +{ + CHECK_EXPECT(filter); + + ok(frame == (void*)0x1234, "frame = %p\n", (void*)frame); + SET_EXPECT(iunknown_except_Release); + return EXCEPTION_EXECUTE_HANDLER; +} + +static void test___C_specific_handler(void) +{ + struct + { + UINT flags; + UINT arch_specific_data[8]; + } info = { 0x10 }; + IUnknown *piunk = &iunknown_except2; + DISPATCHER_CONTEXT dispatch; + SCOPE_TABLE scope_table; + EXCEPTION_RECORD rec; + CONTEXT context; + LONG pass = 0; + + InterlockedIncrement(&pass); + RtlCaptureContext(&ctx); + if (InterlockedIncrement(&pass) == 3) + { + CHECK_CALLED(filter); + CHECK_CALLED(iunknown_except_Release); + return; + } + + memset(&rec, 0, sizeof(rec)); + rec.ExceptionCode = 0xe06d7363; + rec.NumberParameters = 4; + rec.ExceptionInformation[0] = 0x19930520; + rec.ExceptionInformation[1] = (ULONG_PTR)&piunk; + rec.ExceptionInformation[2] = (ULONG_PTR)&info; + rec.ExceptionInformation[3] = (ULONG_PTR)GetModuleHandleA(NULL); + + memset(&dispatch, 0, sizeof(dispatch)); + dispatch.ImageBase = (ULONG_PTR)GetModuleHandleA(NULL); + dispatch.ControlPc = dispatch.ImageBase + 0x200; + dispatch.HandlerData = &scope_table; + dispatch.ContextRecord = &context; + scope_table.Count = 1; + scope_table.ScopeRecord[0].BeginAddress = 0x200; + scope_table.ScopeRecord[0].EndAddress = 0x400; + scope_table.ScopeRecord[0].HandlerAddress = (ULONG_PTR)filter - dispatch.ImageBase; + scope_table.ScopeRecord[0].JumpTarget = 1; + memset(&context, 0, sizeof(context)); + + SET_EXPECT(filter); + __C_specific_handler(&rec, (void*)0x1234, &context, &dispatch); + ok(0, "should not be reached\n"); +} +#endif + START_TEST(cpp) { if (!init()) return; @@ -330,4 +410,7 @@ START_TEST(cpp) test___std_type_info(); test___unDName(); test___DestructExceptionObject(); +#ifdef __x86_64__ + test___C_specific_handler(); +#endif }
On Mon Nov 3 15:23:37 2025 +0000, Piotr Caban wrote:
Can we move all architecture specific code to except_ARCH.c files? Or is there anything that prevents __C_specific_handler from being moved there easily?
If I understand it correctly, having all code in architecture specific files was not playing well with ARM64EC builds, as we need both `__C_specific_handler_arm64` and the x86_64 version of `__C_specific_handler` to be built, and I couldn't make that happen easily. It probably has something to do with `except_x86_64.c` having a `!defined(__arm64ec_)` guard, I'll still see if I can move it to arch specific files.
Vibhav Pant (@vibhavp) commented about dlls/ucrtbase/tests/cpp.c:
+};
+static IUnknown iunknown_except2 = { &iunknown_except2_vtbl };
+static LONG WINAPI filter(struct _EXCEPTION_POINTERS *ep, void *frame) +{
- CHECK_EXPECT(filter);
 - ok(frame == (void*)0x1234, "frame = %p\n", (void*)frame);
 - SET_EXPECT(iunknown_except_Release);
 - return EXCEPTION_EXECUTE_HANDLER;
 +}
+static void test___C_specific_handler(void) +{
- struct
 
It might be too complicated, but would it be worth it to also have similar tests for non-TYPE_FLAG_WINRT classes to make sure ucrtbase does this for all C++ classes? Something like a destructor function `dtor`, using `cxx_exception_type` for `info`, and setting info->destructor to `dtor - ImageBase`.
On Mon Nov 3 15:36:54 2025 +0000, Vibhav Pant wrote:
It might be too complicated, but would it be worth it to also have similar tests for non-TYPE_FLAG_WINRT classes to make sure ucrtbase does this for all C++ classes? Something like a destructor function `dtor`, using `cxx_exception_type` for `info`, and setting info->destructor to `dtor - ImageBase`.
FWIW I have tested it with other C++ exceptions. I've added this version of the patch because it's simpler. Yes, it can be extended if you think it's useful.
On Mon Nov 3 15:43:04 2025 +0000, Piotr Caban wrote:
FWIW I have tested it with other C++ exceptions. I've added this version of the patch because it's simpler. Yes, it can be extended if you think it's useful.
Hm, it's probably unnecessary then.
On Mon Nov 3 14:53:59 2025 +0000, Vibhav Pant wrote:
Missed that, thanks.
There's more spec files that should be updated.