Follow up to https://gitlab.winehq.org/wine/wine/-/merge_requests/9209#note_120396
-- v4: 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 | 9 +- dlls/msvcrt/except_arm64.c | 30 ++++- dlls/msvcrt/except_arm64ec.c | 31 ++++- 4 files changed, 321 insertions(+), 3 deletions(-)
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..35aa7f52e7d 100644 --- a/dlls/msvcrt/except_arm.c +++ b/dlls/msvcrt/except_arm.c @@ -38,7 +38,6 @@
WINE_DEFAULT_DEBUG_CHANNEL(seh);
- extern void *call_exc_handler( void *handler, ULONG_PTR frame, UINT flags, BYTE *nonvol_regs ); __ASM_GLOBAL_FUNC( call_exc_handler, "push {r1,r4-r11,lr}\n\t" @@ -94,4 +93,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..66f085c0199 100644 --- a/dlls/msvcrt/except_arm64.c +++ b/dlls/msvcrt/except_arm64.c @@ -39,7 +39,6 @@
WINE_DEFAULT_DEBUG_CHANNEL(seh);
- extern void *call_exc_handler( void *handler, ULONG_PTR frame, UINT flags, BYTE *nonvol_regs ); __ASM_GLOBAL_FUNC( call_exc_handler, "stp x29, x30, [sp, #-96]!\n\t" @@ -125,4 +124,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..8ebc7ea4f55 100644 --- a/dlls/msvcrt/except_arm64ec.c +++ b/dlls/msvcrt/except_arm64ec.c @@ -39,7 +39,6 @@
WINE_DEFAULT_DEBUG_CHANNEL(seh);
- static void * __attribute__((naked,used)) call_handler_arm64( void *func, uintptr_t frame, UINT flags, BYTE *nonvol_regs ) { @@ -166,4 +165,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__ */
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 12:52:02 2025 +0000, Piotr Caban wrote:
First patch is conceptually good. Please add the code to existing dlls/msvcrt/except_ARCH.c files instead of adding new ones. I'm also expecting that the change should be added to all C-runtime versions. 2nd and 3rd patch is wrong. It's vccorlib140 job to release the extra reference and there's no way of doing it correctly from vcruntime140.dll. It looks like there are still some more problems that we didn't debug yet but patches 2 and 3 are not going in good direction.
Sure, thanks.
v3: * Move `__C_specific_handler` to msvcrt. * Revert the `Release` calls in `copy_exception`.