From a715a0c4cafe150ebbbf4245127907309cd389e4 Mon Sep 17 00:00:00 2001 From: Piotr Caban Date: Tue, 21 Apr 2020 13:12:13 +0200 Subject: [PATCH] vcruntime140_1: Add __CxxFrameHandler4 implementation. Signed-off-by: Piotr Caban Signed-off-by: Alexandre Julliard --- dlls/vcruntime140_1/cppexcept.h | 65 ++++ dlls/vcruntime140_1/except_x86_64.c | 471 +++++++++++++++++++++++++++- 2 files changed, 534 insertions(+), 2 deletions(-) diff --git a/dlls/vcruntime140_1/cppexcept.h b/dlls/vcruntime140_1/cppexcept.h index 1183e9e6eb2..067df35a3de 100644 --- a/dlls/vcruntime140_1/cppexcept.h +++ b/dlls/vcruntime140_1/cppexcept.h @@ -46,9 +46,74 @@ typedef struct __cxx_type_info unsigned int copy_ctor; } cxx_type_info; +#define CLASS_IS_SIMPLE_TYPE 1 +#define CLASS_HAS_VIRTUAL_BASE_CLASS 4 + +/* table of C++ types that apply for a given object */ +typedef struct __cxx_type_info_table +{ + UINT count; + unsigned int info[3]; +} cxx_type_info_table; + +struct __cxx_exception_frame; +struct __cxx_function_descr; + +/* type information for an exception object */ +typedef struct +{ + UINT flags; + unsigned int destructor; + unsigned int custom_handler; + unsigned int type_info_table; +} cxx_exception_type; + static inline const char *dbgstr_type_info( const type_info *info ) { if (!info) return "{}"; return wine_dbg_sprintf( "{vtable=%p name=%s (%s)}", info->vtable, info->mangled, info->name ? info->name : "" ); } + +/* compute the this pointer for a base class of a given type */ +static inline void *get_this_pointer( const this_ptr_offsets *off, void *object ) +{ + if (!object) return NULL; + + if (off->vbase_descr >= 0) + { + int *offset_ptr; + + /* move this ptr to vbase descriptor */ + object = (char *)object + off->vbase_descr; + /* and fetch additional offset from vbase descriptor */ + offset_ptr = (int *)(*(char **)object + off->vbase_offset); + object = (char *)object + *offset_ptr; + } + + object = (char *)object + off->this_offset; + return object; +} + +typedef void (__cdecl *_se_translator_function)(unsigned int code, struct _EXCEPTION_POINTERS *info); + +typedef struct _frame_info +{ + void *object; + struct _frame_info *next; +} frame_info; + +typedef struct +{ + frame_info frame_info; + EXCEPTION_RECORD *rec; + CONTEXT *context; +} cxx_frame_info; + +BOOL __cdecl __CxxRegisterExceptionObject(EXCEPTION_POINTERS*, cxx_frame_info*); +void __cdecl __CxxUnregisterExceptionObject(cxx_frame_info*, BOOL); +void __cdecl __DestructExceptionObject(EXCEPTION_RECORD *rec); + +void** __cdecl __current_exception(void); +int* __cdecl __processing_throw(void); +void __cdecl terminate(void); diff --git a/dlls/vcruntime140_1/except_x86_64.c b/dlls/vcruntime140_1/except_x86_64.c index 6705c05606c..5d07a1ac2cb 100644 --- a/dlls/vcruntime140_1/except_x86_64.c +++ b/dlls/vcruntime140_1/except_x86_64.c @@ -20,11 +20,16 @@ #ifdef __x86_64__ +#include "wine/exception.h" #include "wine/debug.h" #include "cppexcept.h" WINE_DEFAULT_DEBUG_CHANNEL(seh); +#define CXX_EXCEPTION 0xe06d7363 + +static DWORD fls_index; + typedef struct { BYTE header; @@ -87,6 +92,24 @@ typedef struct INT state; } ipmap_info; +typedef struct +{ + cxx_frame_info frame_info; + BOOL rethrow; + INT search_state; + INT unwind_state; +} cxx_catch_ctx; + +typedef struct +{ + ULONG64 dest_frame; + ULONG64 orig_frame; + EXCEPTION_RECORD *seh_rec; + DISPATCHER_CONTEXT *dispatch; + const cxx_function_descr *descr; + int trylevel; +} se_translator_ctx; + static UINT decode_uint(BYTE **b) { UINT ret; @@ -211,6 +234,22 @@ static inline void dump_type(UINT type_rva, ULONG64 base) type->size, type->copy_ctor, rva_to_ptr(type->copy_ctor, base)); } +static void dump_exception_type(const cxx_exception_type *type, ULONG64 base) +{ + const cxx_type_info_table *type_info_table = rva_to_ptr(type->type_info_table, base); + UINT i; + + TRACE("flags %x destr %x(%p) handler %x(%p) type info %x(%p)\n", + type->flags, type->destructor, rva_to_ptr(type->destructor, base), + type->custom_handler, rva_to_ptr(type->custom_handler, base), + type->type_info_table, type_info_table); + for (i = 0; i < type_info_table->count; i++) + { + TRACE(" %d: ", i); + dump_type(type_info_table->info[i], base); + } +} + static BOOL validate_cxx_function_descr4(const cxx_function_descr *descr, DISPATCHER_CONTEXT *dispatch) { ULONG64 image_base = dispatch->ImageBase; @@ -274,18 +313,426 @@ static BOOL validate_cxx_function_descr4(const cxx_function_descr *descr, DISPAT return TRUE; } +static inline int ip_to_state4(BYTE *ip_map, UINT count, DISPATCHER_CONTEXT *dispatch, ULONG64 ip) +{ + ULONG64 state_ip; + ipmap_info ii; + int ret = -1; + UINT i; + + state_ip = dispatch->ImageBase + dispatch->FunctionEntry->BeginAddress; + for (i = 0; i < count; i++) + { + read_ipmap_info(&ip_map, &ii); + state_ip += ii.ip_off; + if (ip < state_ip) break; + ret = ii.state; + } + + TRACE("state %d\n", ret); + return ret; +} + +static const cxx_type_info *find_caught_type(cxx_exception_type *exc_type, ULONG64 exc_base, + const type_info *catch_ti, UINT catch_flags) +{ + const cxx_type_info_table *type_info_table = rva_to_ptr(exc_type->type_info_table, exc_base); + UINT i; + + for (i = 0; i < type_info_table->count; i++) + { + const cxx_type_info *type = rva_to_ptr(type_info_table->info[i], exc_base); + const type_info *ti = rva_to_ptr(type->type_info, exc_base); + + if (!catch_ti) return type; /* catch(...) matches any type */ + if (catch_ti != ti) + { + if (strcmp( catch_ti->mangled, ti->mangled )) continue; + } + /* type is the same, now check the flags */ + if ((exc_type->flags & TYPE_FLAG_CONST) && + !(catch_flags & TYPE_FLAG_CONST)) continue; + if ((exc_type->flags & TYPE_FLAG_VOLATILE) && + !(catch_flags & TYPE_FLAG_VOLATILE)) continue; + return type; /* it matched */ + } + return NULL; +} + +static inline void copy_exception(void *object, ULONG64 frame, DISPATCHER_CONTEXT *dispatch, + const catchblock_info *catchblock, const cxx_type_info *type, ULONG64 exc_base) +{ + const type_info *catch_ti = rva_to_ptr(catchblock->type_info, dispatch->ImageBase); + void **dest = rva_to_ptr(catchblock->offset, frame); + + if (!catch_ti || !catch_ti->mangled[0]) return; + if (!catchblock->offset) return; + + if (catchblock->flags & TYPE_FLAG_REFERENCE) + { + *dest = get_this_pointer(&type->offsets, object); + } + else if (type->flags & CLASS_IS_SIMPLE_TYPE) + { + memmove(dest, object, type->size); + /* if it is a pointer, adjust it */ + if (type->size == sizeof(void*)) *dest = get_this_pointer(&type->offsets, *dest); + } + else /* copy the object */ + { + if (type->copy_ctor) + { + if (type->flags & CLASS_HAS_VIRTUAL_BASE_CLASS) + { + void (__cdecl *copy_ctor)(void*, void*, int) = + rva_to_ptr(type->copy_ctor, exc_base); + copy_ctor(dest, get_this_pointer(&type->offsets, object), 1); + } + else + { + void (__cdecl *copy_ctor)(void*, void*) = + rva_to_ptr(type->copy_ctor, exc_base); + copy_ctor(dest, get_this_pointer(&type->offsets, object)); + } + } + else + memmove(dest, get_this_pointer(&type->offsets,object), type->size); + } +} + +static void cxx_local_unwind4(ULONG64 frame, DISPATCHER_CONTEXT *dispatch, + const cxx_function_descr *descr, int trylevel, int last_level) +{ + void (__cdecl *handler_dtor)(void *obj); + BYTE *unwind_data, *last; + unwind_info ui; + void *obj; + int i; + + if (trylevel == -2) + { + trylevel = ip_to_state4(rva_to_ptr(descr->ip_map, dispatch->ImageBase), + descr->ip_count, dispatch, dispatch->ControlPc); + } + + TRACE("current level: %d, last level: %d\n", trylevel, last_level); + + if (trylevel<0 || trylevel>=descr->unwind_count) + { + ERR("invalid trylevel %d\n", trylevel); + terminate(); + } + + unwind_data = rva_to_ptr(descr->unwind_map, dispatch->ImageBase); + last = unwind_data; + for (i = 0; i < trylevel; i++) + { + BYTE *addr = unwind_data; + read_unwind_info(&unwind_data, &ui); + if (i == last_level) last = addr; + } + + while (unwind_data > last) + { + read_unwind_info(&unwind_data, &ui); + unwind_data = ui.prev; + + if (ui.handler) + { + handler_dtor = rva_to_ptr(ui.handler, dispatch->ImageBase); + obj = rva_to_ptr(ui.object, frame); + TRACE("handler: %p object: %p\n", handler_dtor, obj); + handler_dtor(obj); + } + } +} + +static LONG CALLBACK cxx_rethrow_filter(PEXCEPTION_POINTERS eptrs, void *c) +{ + EXCEPTION_RECORD *rec = eptrs->ExceptionRecord; + cxx_catch_ctx *ctx = c; + + FlsSetValue(fls_index, (void*)(DWORD_PTR)ctx->search_state); + + if (rec->ExceptionCode != CXX_EXCEPTION) + return EXCEPTION_CONTINUE_SEARCH; + if (!rec->ExceptionInformation[1] && !rec->ExceptionInformation[2]) + return EXCEPTION_EXECUTE_HANDLER; + if (rec->ExceptionInformation[1] == ((EXCEPTION_RECORD*)*__current_exception())->ExceptionInformation[1]) + ctx->rethrow = TRUE; + return EXCEPTION_CONTINUE_SEARCH; +} + +static void CALLBACK cxx_catch_cleanup(BOOL normal, void *c) +{ + cxx_catch_ctx *ctx = c; + __CxxUnregisterExceptionObject(&ctx->frame_info, ctx->rethrow); + + FlsSetValue(fls_index, (void*)(DWORD_PTR)ctx->unwind_state); +} + +static void* WINAPI call_catch_block4(EXCEPTION_RECORD *rec) +{ + ULONG64 frame = rec->ExceptionInformation[1]; + EXCEPTION_RECORD *prev_rec = (void*)rec->ExceptionInformation[4]; + EXCEPTION_RECORD *untrans_rec = (void*)rec->ExceptionInformation[6]; + CONTEXT *context = (void*)rec->ExceptionInformation[7]; + void* (__cdecl *handler)(ULONG64 unk, ULONG64 rbp) = (void*)rec->ExceptionInformation[5]; + EXCEPTION_POINTERS ep = { prev_rec, context }; + cxx_catch_ctx ctx; + void *ret_addr = NULL; + + TRACE("calling handler %p\n", handler); + + ctx.rethrow = FALSE; + __CxxRegisterExceptionObject(&ep, &ctx.frame_info); + ctx.search_state = rec->ExceptionInformation[2]; + ctx.unwind_state = rec->ExceptionInformation[3]; + (*__processing_throw())--; + __TRY + { + __TRY + { + ret_addr = handler(0, frame); + } + __EXCEPT_CTX(cxx_rethrow_filter, &ctx) + { + TRACE("detect rethrow: exception code: %x\n", prev_rec->ExceptionCode); + ctx.rethrow = TRUE; + + if (untrans_rec) + { + __DestructExceptionObject(prev_rec); + RaiseException(untrans_rec->ExceptionCode, untrans_rec->ExceptionFlags, + untrans_rec->NumberParameters, untrans_rec->ExceptionInformation); + } + else + { + RaiseException(prev_rec->ExceptionCode, prev_rec->ExceptionFlags, + prev_rec->NumberParameters, prev_rec->ExceptionInformation); + } + } + __ENDTRY + } + __FINALLY_CTX(cxx_catch_cleanup, &ctx) + + FlsSetValue(fls_index, (void*)-2); + if (rec->ExceptionInformation[8]) return (void*)rec->ExceptionInformation[8]; + return ret_addr; +} + +static inline BOOL cxx_is_consolidate(const EXCEPTION_RECORD *rec) +{ + return rec->ExceptionCode==STATUS_UNWIND_CONSOLIDATE && rec->NumberParameters==9 && + rec->ExceptionInformation[0]==(ULONG_PTR)call_catch_block4; +} + +static inline void find_catch_block4(EXCEPTION_RECORD *rec, CONTEXT *context, + EXCEPTION_RECORD *untrans_rec, ULONG64 frame, DISPATCHER_CONTEXT *dispatch, + const cxx_function_descr *descr, cxx_exception_type *info, + ULONG64 orig_frame, int trylevel) +{ + ULONG64 exc_base = (rec->NumberParameters == 4 ? rec->ExceptionInformation[3] : 0); + int *processing_throw = __processing_throw(); + EXCEPTION_RECORD catch_record; + BYTE *tryblock_map; + CONTEXT ctx; + UINT i, j; + + (*processing_throw)++; + + if (trylevel == -2) + { + trylevel = ip_to_state4(rva_to_ptr(descr->ip_map, dispatch->ImageBase), + descr->ip_count, dispatch, dispatch->ControlPc); + } + TRACE("current trylevel: %d\n", trylevel); + + tryblock_map = rva_to_ptr(descr->tryblock_map, dispatch->ImageBase); + for (i=0; itryblock_count; i++) + { + tryblock_info tryblock; + BYTE *catchblock; + + read_tryblock_info(&tryblock_map, &tryblock, dispatch->ImageBase); + + if (trylevel < tryblock.start_level) continue; + if (trylevel > tryblock.end_level) continue; + + /* got a try block */ + catchblock = rva_to_ptr(tryblock.catchblock, dispatch->ImageBase); + for (j=0; jImageBase), + ci.flags); + if (!type) continue; + + TRACE("matched type %p in tryblock %d catchblock %d\n", type, i, j); + + /* copy the exception to its destination on the stack */ + copy_exception((void*)rec->ExceptionInformation[1], + orig_frame, dispatch, &ci, type, exc_base); + } + else + { + /* no CXX_EXCEPTION only proceed with a catch(...) block*/ + if (ci.type_info) + continue; + TRACE("found catch(...) block\n"); + } + + /* unwind stack and call catch */ + memset(&catch_record, 0, sizeof(catch_record)); + catch_record.ExceptionCode = STATUS_UNWIND_CONSOLIDATE; + catch_record.ExceptionFlags = EXCEPTION_NONCONTINUABLE; + catch_record.NumberParameters = 9; + catch_record.ExceptionInformation[0] = (ULONG_PTR)call_catch_block4; + catch_record.ExceptionInformation[1] = orig_frame; + catch_record.ExceptionInformation[2] = tryblock.catch_level; + catch_record.ExceptionInformation[3] = tryblock.start_level; + catch_record.ExceptionInformation[4] = (ULONG_PTR)rec; + catch_record.ExceptionInformation[5] = + (ULONG_PTR)rva_to_ptr(ci.handler, dispatch->ImageBase); + catch_record.ExceptionInformation[6] = (ULONG_PTR)untrans_rec; + catch_record.ExceptionInformation[7] = (ULONG_PTR)context; + catch_record.ExceptionInformation[8] = (ULONG_PTR)rva_to_ptr( + ci.ret_addr + dispatch->FunctionEntry->BeginAddress, dispatch->ImageBase); + RtlUnwindEx((void*)frame, (void*)dispatch->ControlPc, &catch_record, NULL, &ctx, NULL); + } + } + + TRACE("no matching catch block found\n"); + (*processing_throw)--; +} + +static LONG CALLBACK se_translation_filter(EXCEPTION_POINTERS *ep, void *c) +{ + se_translator_ctx *ctx = (se_translator_ctx *)c; + EXCEPTION_RECORD *rec = ep->ExceptionRecord; + cxx_exception_type *exc_type; + + if (rec->ExceptionCode != CXX_EXCEPTION) + { + TRACE("non-c++ exception thrown in SEH handler: %x\n", rec->ExceptionCode); + terminate(); + } + + exc_type = (cxx_exception_type *)rec->ExceptionInformation[2]; + find_catch_block4(rec, ep->ContextRecord, ctx->seh_rec, ctx->dest_frame, ctx->dispatch, + ctx->descr, exc_type, ctx->orig_frame, ctx->trylevel); + + __DestructExceptionObject(rec); + return ExceptionContinueSearch; +} + +/* Hacky way to obtain se_translator */ +static inline _se_translator_function get_se_translator(void) +{ + return __current_exception()[-2]; +} + +static DWORD cxx_frame_handler4(EXCEPTION_RECORD *rec, ULONG64 frame, + CONTEXT *context, DISPATCHER_CONTEXT *dispatch, + const cxx_function_descr *descr, int trylevel) +{ + cxx_exception_type *exc_type; + ULONG64 orig_frame = frame; + + if (descr->header & FUNC_DESCR_IS_CATCH) + { + TRACE("nested exception detected\n"); + orig_frame = *(ULONG64*)rva_to_ptr(descr->frame, frame); + TRACE("setting orig_frame to %lx\n", orig_frame); + } + + if (rec->ExceptionFlags & (EH_UNWINDING|EH_EXIT_UNWIND)) + { + int last_level = -1; + if ((rec->ExceptionFlags & EH_TARGET_UNWIND) && cxx_is_consolidate(rec)) + last_level = rec->ExceptionInformation[3]; + else if ((rec->ExceptionFlags & EH_TARGET_UNWIND) && rec->ExceptionCode == STATUS_LONGJUMP) + last_level = ip_to_state4(rva_to_ptr(descr->ip_map, dispatch->ImageBase), + descr->ip_count, dispatch, dispatch->TargetIp); + + cxx_local_unwind4(orig_frame, dispatch, descr, trylevel, last_level); + return ExceptionContinueSearch; + } + if (!descr->tryblock_map) return ExceptionContinueSearch; + + if (rec->ExceptionCode == CXX_EXCEPTION) + { + exc_type = (cxx_exception_type *)rec->ExceptionInformation[2]; + + if (TRACE_ON(seh)) + { + TRACE("handling C++ exception rec %p frame %lx descr %p\n", rec, frame, descr); + dump_exception_type(exc_type, rec->ExceptionInformation[3]); + } + } + else + { + _se_translator_function se_translator = get_se_translator(); + + exc_type = NULL; + TRACE("handling C exception code %x rec %p frame %lx descr %p\n", + rec->ExceptionCode, rec, frame, descr); + + if (se_translator) { + EXCEPTION_POINTERS except_ptrs; + se_translator_ctx ctx; + + ctx.dest_frame = frame; + ctx.orig_frame = orig_frame; + ctx.seh_rec = rec; + ctx.dispatch = dispatch; + ctx.descr = descr; + ctx.trylevel = trylevel; + __TRY + { + except_ptrs.ExceptionRecord = rec; + except_ptrs.ContextRecord = context; + se_translator(rec->ExceptionCode, &except_ptrs); + } + __EXCEPT_CTX(se_translation_filter, &ctx) + { + } + __ENDTRY + } + } + + find_catch_block4(rec, context, NULL, frame, dispatch, descr, exc_type, orig_frame, trylevel); + return ExceptionContinueSearch; +} + EXCEPTION_DISPOSITION __cdecl __CxxFrameHandler4(EXCEPTION_RECORD *rec, ULONG64 frame, CONTEXT *context, DISPATCHER_CONTEXT *dispatch) { cxx_function_descr descr; BYTE *p, *count, *count_end; + int trylevel; - FIXME("%p %lx %p %p\n", rec, frame, context, dispatch); + TRACE("%p %lx %p %p\n", rec, frame, context, dispatch); + + trylevel = (DWORD_PTR)FlsGetValue(fls_index); + FlsSetValue(fls_index, (void*)-2); memset(&descr, 0, sizeof(descr)); p = rva_to_ptr(*(UINT*)dispatch->HandlerData, dispatch->ImageBase); descr.header = *p++; + if ((descr.header & FUNC_DESCR_EHS) && + rec->ExceptionCode != CXX_EXCEPTION && + !cxx_is_consolidate(rec) && + rec->ExceptionCode != STATUS_LONGJUMP) + return ExceptionContinueSearch; /* handle only c++ exceptions */ + if (descr.header & ~(FUNC_DESCR_IS_CATCH | FUNC_DESCR_UNWIND_MAP | FUNC_DESCR_TRYBLOCK_MAP | FUNC_DESCR_EHS)) { @@ -317,7 +764,27 @@ EXCEPTION_DISPOSITION __cdecl __CxxFrameHandler4(EXCEPTION_RECORD *rec, if (!validate_cxx_function_descr4(&descr, dispatch)) return ExceptionContinueSearch; - return ExceptionContinueSearch; + return cxx_frame_handler4(rec, frame, context, dispatch, &descr, trylevel); +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID reserved) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + fls_index = FlsAlloc(NULL); + if (fls_index == FLS_OUT_OF_INDEXES) + return FALSE; + /* fall through */ + case DLL_THREAD_ATTACH: + FlsSetValue(fls_index, (void*)-2); + break; + case DLL_PROCESS_DETACH: + if (reserved) break; + FlsFree(fls_index); + break; + } + return TRUE; } #endif