diff --git a/src/ldc/attributes.d b/src/ldc/attributes.d index ce073f1546..fc190ec83f 100644 --- a/src/ldc/attributes.d +++ b/src/ldc/attributes.d @@ -57,3 +57,21 @@ struct section { struct target { string specifier; } + +/** + * When applied to a function and building for Win32, creates a SAFESEH + * entry in the binary, so that the function is considered safe to be called + * as an exception handler. + * + * Examples: + * --- + * import ldc.attributes; + * + * @safeseh() extern(C) int myExceptionHandler(void*, void*, void*, void*) + * { + * return ExceptionContinueSearch; + * } + * --- + */ +struct safeseh { +}; diff --git a/src/ldc/eh/win32.d b/src/ldc/eh/win32.d index f2b9e91827..ae3f38827a 100644 --- a/src/ldc/eh/win32.d +++ b/src/ldc/eh/win32.d @@ -5,13 +5,14 @@ module ldc.eh.win32; version(CRuntime_Microsoft): -version(Win32): +//version(Win32): import ldc.eh.common; +import ldc.attributes; import core.sys.windows.windows; import core.exception : onOutOfMemoryError, OutOfMemoryError; import core.stdc.stdlib : malloc, free; -import core.stdc.string : memcpy; +import core.stdc.string : memcpy, memset; // pointers are image relative for Win64 versions version(Win64) @@ -21,15 +22,17 @@ else alias PMFN = ImgPtr!(void function(void*)); -struct TypeDescriptor(int N) +struct TypeDescriptor { version(_RTTI) const void * pVFTable; // Field overloaded by RTTI + else version(Win64) + const void * pVFTable; else uint hash; // Hash value computed from type's decorated name void * spare; // reserved, possible for RTTI - char[N+1] name; // variable size, zero terminated + char[2] name; // variable size, zero terminated } struct PMD @@ -42,7 +45,7 @@ struct PMD struct CatchableType { uint properties; // Catchable Type properties (Bit field) - ImgPtr!(TypeDescriptor!1*) pType; // Pointer to TypeDescriptor + ImgPtr!(TypeDescriptor*) pType; // Pointer to TypeDescriptor PMD thisDisplacement; // Pointer to instance of catch type within thrown object. int sizeOrOffset; // Size of simple-type object or offset into buffer of 'this' pointer for catch object PMFN copyFunction; // Copy constructor or CC-closure @@ -56,8 +59,8 @@ enum CT_IsStdBadAlloc = 0x00000010; // type is a a std::bad_alloc struct CatchableTypeArray { - int nCatchableTypes; - ImgPtr!(CatchableType*)[2] arrayOfCatchableTypes; + int nCatchableTypes; + ImgPtr!(CatchableType*)[2] arrayOfCatchableTypes; // variable size } struct _ThrowInfo @@ -68,6 +71,27 @@ struct _ThrowInfo ImgPtr!(CatchableTypeArray*) pCatchableTypeArray; // pointer to CatchableTypeArray } +struct ExceptionRecord +{ + uint ExceptionCode; + uint ExceptionFlags; + uint ExceptionRecord; + uint ExceptionAddress; + uint NumberParameters; + union + { + ULONG_PTR[15] ExceptionInformation; + CxxExceptionInfo CxxInfo; + } +} + +struct CxxExceptionInfo +{ + size_t Magic; + Throwable* pThrowable; // null for rethrow + _ThrowInfo* ThrowInfo; +} + enum TI_IsConst = 0x00000001; // thrown object has const qualifier enum TI_IsVolatile = 0x00000002; // thrown object has volatile qualifier enum TI_IsUnaligned = 0x00000004; // thrown object has unaligned qualifier @@ -94,21 +118,21 @@ extern(C) void _d_throw_exception(Object e) if (ti is null) fatalerror("Cannot throw corrupt exception object with null classinfo"); - if (exceptionStack.length > 0) - { - // we expect that the terminate handler will be called, so hook - // it to avoid it actually terminating - if (!old_terminate_handler) - old_terminate_handler = set_terminate(&msvc_eh_terminate); - } exceptionStack.push(cast(Throwable) e); - ULONG_PTR[3] ExceptionInformation; - ExceptionInformation[0] = EH_MAGIC_NUMBER1; - ExceptionInformation[1] = cast(ULONG_PTR) cast(void*) &e; - ExceptionInformation[2] = cast(ULONG_PTR) getThrowInfo(ti); + version(Win64) + enum numArgs = 4; + else + enum numArgs = 3; + + ULONG_PTR[numArgs] args; + args[0] = EH_MAGIC_NUMBER1; + args[1] = cast(ULONG_PTR) cast(void*) &e; + args[2] = cast(ULONG_PTR) getThrowInfo(ti); + version(Win64) + args[3] = cast(ULONG_PTR) ehHeap.base; - RaiseException(STATUS_MSC_EXCEPTION, EXCEPTION_NONCONTINUABLE, 3, ExceptionInformation.ptr); + RaiseException(STATUS_MSC_EXCEPTION, EXCEPTION_NONCONTINUABLE, args.length, args.ptr); } /////////////////////////////////////////////////////////////// @@ -117,9 +141,89 @@ import rt.util.container.hashtab; import core.sync.mutex; __gshared HashTab!(TypeInfo_Class, _ThrowInfo) throwInfoHashtab; -__gshared HashTab!(TypeInfo_Class, CatchableType) catchableHashtab; +__gshared HashTab!(TypeInfo_Class, ImgPtr!(CatchableType*)) catchableHashtab; __gshared Mutex throwInfoMutex; +void* safeMalloc(size_t size) +{ + if (auto ptr = malloc(size)) + return ptr; + onOutOfMemoryError(); + return null; +} + +version(Win32) +{ + ImgPtr!(T*) eh_malloc(T)(size_t size = T.sizeof) + { + return cast(T*) safeMalloc(size); + } + + T* toPointer(T)(ImgPtr!(T*) imgPtr) + { + return imgPtr; + } +} +else +{ + /** + * Heap dedicated for CatchableTypeArray/CatchableType/TypeDescriptor + * structs of cached _ThrowInfos. + * The heap is used to keep these structs tightly together, as they are + * referenced via 32-bit offsets from a common base. We simply use the + * heap's start as base (instead of the actual image base), and malloc() + * returns an offset. + * The allocated structs are all cached and never released, so this heap + * can only grow. The offsets remain constant after a grow, so it's only + * the base which may change. + */ + struct EHHeap + { + void* base; + size_t capacity; + size_t length; + + this(size_t initialCapacity) + { + base = safeMalloc(initialCapacity); + capacity = initialCapacity; + } + + size_t malloc(size_t size) + { + auto offset = length; + enum alignmentMask = size_t.sizeof - 1; + auto newLength = (length + size + alignmentMask) & ~alignmentMask; + auto newCapacity = capacity; + while (newLength > newCapacity) + newCapacity *= 2; + if (newCapacity != capacity) + { + auto newBase = safeMalloc(newCapacity); + newBase[0 .. length] = base[0 .. length]; + free(base); + base = newBase; + capacity = newCapacity; + } + length = newLength; + return offset; + } + } + + __gshared EHHeap ehHeap; + + ImgPtr!(T*) eh_malloc(T)(size_t size = T.sizeof) + { + return cast(uint) ehHeap.malloc(size); + } + + // NB: The returned pointer may be invalidated by a consequent grow of ehHeap! + T* toPointer(T)(ImgPtr!(T*) imgPtr) + { + return cast(T*) (ehHeap.base + imgPtr); + } +} + // create and cache throwinfo for ti _ThrowInfo* getThrowInfo(TypeInfo_Class ti) { @@ -130,86 +234,93 @@ _ThrowInfo* getThrowInfo(TypeInfo_Class ti) return p; } - size_t classes = 0; + int classes = 0; for (TypeInfo_Class tic = ti; tic; tic = tic.base) classes++; size_t sz = int.sizeof + classes * ImgPtr!(CatchableType*).sizeof; - auto cta = cast(CatchableTypeArray*) malloc(sz); - if (!cta) - onOutOfMemoryError(); - cta.nCatchableTypes = classes; - + auto cta = eh_malloc!CatchableTypeArray(sz); + toPointer!CatchableTypeArray(cta).nCatchableTypes = classes; size_t c = 0; for (TypeInfo_Class tic = ti; tic; tic = tic.base) - cta.arrayOfCatchableTypes.ptr[c++] = getCatchableType(tic); + { + auto ct = getCatchableType(tic); + toPointer!CatchableTypeArray(cta).arrayOfCatchableTypes.ptr[c++] = ct; + } - _ThrowInfo tinf = { 0, null, null, cta }; - throwInfoHashtab[ti] = tinf; + throwInfoHashtab[ti] = _ThrowInfo(0, PMFN(), PMFN(), cta); auto pti = ti in throwInfoHashtab; throwInfoMutex.unlock(); return pti; } -CatchableType* getCatchableType(TypeInfo_Class ti) +ImgPtr!(CatchableType*) getCatchableType(TypeInfo_Class ti) { if (auto p = ti in catchableHashtab) - return p; + return *p; - size_t sz = TypeDescriptor!1.sizeof + ti.name.length; - auto td = cast(TypeDescriptor!1*) malloc(sz); - if (!td) - onOutOfMemoryError(); + size_t sz = TypeDescriptor.sizeof + ti.name.length; + auto td = eh_malloc!TypeDescriptor(sz); + auto ptd = toPointer!TypeDescriptor(td); + memset(ptd, 0, sz); + ptd.name.ptr[0] = 'D'; + memcpy(ptd.name.ptr + 1, ti.name.ptr, ti.name.length); - td.hash = 0; - td.spare = null; - td.name.ptr[0] = 'D'; - memcpy(td.name.ptr + 1, ti.name.ptr, ti.name.length); - td.name.ptr[ti.name.length + 1] = 0; + auto ct = eh_malloc!CatchableType(); + auto pct = toPointer!CatchableType(ct); + *pct = CatchableType(CT_IsSimpleType, td, PMD(0, -1, 0), ULONG_PTR.sizeof, PMFN()); - CatchableType ct = { CT_IsSimpleType, td, { 0, -1, 0 }, 4, null }; catchableHashtab[ti] = ct; - return ti in catchableHashtab; + return ct; } /////////////////////////////////////////////////////////////// -extern(C) Object _d_eh_enter_catch(void* ptr) +extern(C) Object _d_eh_enter_catch(void* ptr, ClassInfo catchType) { - if (!ptr) - return null; // null for "catch all" in scope(failure), will rethrow - Throwable e = *(cast(Throwable*) ptr); - - while(exceptionStack.length > 0) + assert(ptr); + + // is this a thrown D exception? + auto e = *(cast(Throwable*) ptr); + size_t pos = exceptionStack.find(e); + if (pos >= exceptionStack.length()) + return null; + + auto caught = e; + // append inner unhandled thrown exceptions + for (size_t p = pos + 1; p < exceptionStack.length(); p++) + e = chainExceptions(e, exceptionStack[p]); + exceptionStack.shrink(pos); + + // given the bad semantics of Errors, we are fine with passing + // the test suite with slightly inaccurate behaviour by just + // rethrowing a collateral Error here, though it might need to + // be caught by a catch handler in an inner scope + if (e !is caught) { - Throwable t = exceptionStack.pop(); - if (t is e) - break; + if (_d_isbaseof(typeid(e), catchType)) + *cast(Throwable*) ptr = e; // the current catch can also catch this Error + else + _d_throw_exception(e); + } + return e; +} - auto err = cast(Error) t; - if (err && !cast(Error)e) +Throwable chainExceptions(Throwable e, Throwable t) +{ + if (!cast(Error) e) + if (auto err = cast(Error) t) { - // there is an Error in flight, but we caught an Exception - // so we convert it and rethrow the Error err.bypassedException = e; - throw err; + return err; } - t.next = e.next; - e.next = t; - } + auto pChain = &e.next; + while (*pChain) + pChain = &(pChain.next); + *pChain = t; return e; } -alias terminate_handler = void function(); - -extern(C) void** __current_exception(); -extern(C) void** __current_exception_context(); -extern(C) int* __processing_throw(); - -extern(C) terminate_handler set_terminate(terminate_handler new_handler); - -terminate_handler old_terminate_handler; // explicitely per thread - ExceptionStack exceptionStack; struct ExceptionStack @@ -233,11 +344,25 @@ nothrow: return _p[--_length]; } + void shrink(size_t sz) + { + while (_length > sz) + _p[--_length] = null; + } + ref inout(Throwable) opIndex(size_t idx) inout { return _p[idx]; } + size_t find(Throwable e) + { + for (size_t i = _length; i > 0; ) + if (exceptionStack[--i] is e) + return i; + return ~0; + } + @property size_t length() const { return _length; } @property bool empty() const { return !length; } @@ -260,71 +385,165 @@ private: size_t _cap; } -// helper to access TLS from naked asm -int tlsUncaughtExceptions() nothrow +/////////////////////////////////////////////////////////////// +struct FrameInfo { - return exceptionStack.length; -} + FrameInfo* next; + void* handler; // typeof(&_d_unwindExceptionHandler) causes compilation error + void* continuationAddress; + void* returnAddress; -auto tlsOldTerminateHandler() nothrow -{ - return old_terminate_handler; -} + size_t ebx; + size_t ecx; + size_t edi; + size_t esi; + + size_t ebp; + size_t esp; +}; + +// "offsetof func" does not work in inline asm +__gshared handler = &_d_unwindExceptionHandler; -void msvc_eh_terminate() nothrow +/////////////////////////////////////////////////////////////// +extern(C) bool _d_enter_cleanup(void* ptr) { - asm nothrow { + // setup an exception handler here (ptr passes the address + // of a 40 byte stack area in a parent function scope) to deal with + // unhandled exceptions during unwinding. + + asm + { naked; - call tlsUncaughtExceptions; - cmp EAX, 0; - je L_term; - - // hacking into the call chain to return EXCEPTION_EXECUTE_HANDLER - // as the return value of __FrameUnwindFilter so that - // __FrameUnwindToState continues with the next unwind block - - // restore ptd->__ProcessingThrow - push EAX; - call __processing_throw; - pop [EAX]; - - // undo one level of exception frames from terminate() - mov EAX,FS:[0]; - mov EAX,[EAX]; + // fill the frame with information for continuation similar + // to setjmp/longjmp when an exception is thrown during cleanup + mov EAX,[ESP+4]; // ptr + mov EDX,[ESP]; // return address + mov [EAX+12], EDX; + call cont; // push continuation address + jmp catch_handler; + cont: + pop dword ptr [EAX+8]; + + mov [EAX+16], EBX; + mov [EAX+20], ECX; + mov [EAX+24], EDI; + mov [EAX+28], ESI; + mov [EAX+32], EBP; + mov [EAX+36], ESP; + + mov EDX, handler; + mov [EAX+4], EDX; + // add link to exception chain on parent stack + mov EDX, FS:[0]; + mov [EAX], EDX; mov FS:[0], EAX; + mov AL, 1; + ret; - // assume standard stack frames for callers - mov EAX,EBP; // frame pointer of terminate() - mov EAX,[EAX]; // frame pointer of __FrameUnwindFilter - mov ESP,EAX; // restore stack - pop EBP; // and frame pointer - mov EAX, 1; // return EXCEPTION_EXECUTE_HANDLER + catch_handler: + // EAX set to frame, FS:[0] restored to previous frame + mov EBX, [EAX+16]; + mov ECX, [EAX+20]; + mov EDI, [EAX+24]; + mov ESI, [EAX+28]; + mov EBP, [EAX+32]; + mov ESP, [EAX+36]; + + mov EDX, [EAX+12]; + mov [ESP], EDX; + mov AL, 0; ret; + } +} - L_term: - call tlsOldTerminateHandler; - cmp EAX, 0; - je L_ret; - jmp EAX; - L_ret: +extern(C) void _d_leave_cleanup(void* ptr) +{ + asm + { + naked; + // unlink from exception chain + // for a regular call, ptr should be the same as FS:[0] + // if an exception has been caught in _d_enter_cleanup, + // FS:[0] is already the next frame, but setting it again + // should do no harm + mov EAX, [ESP+4]; // ptr + mov EAX, [EAX]; + mov FS:[0], EAX; ret; } } -/////////////////////////////////////////////////////////////// -extern(C) bool _d_enter_cleanup(void* ptr) +enum EXCEPTION_DISPOSITION { - // currently just used to avoid that a cleanup handler that can - // be inferred to not return, is removed by the LLVM optimizer - // - // TODO: setup an exception handler here (ptr passes the address - // of a 16 byte stack area in a parent fuction scope) to deal with - // unhandled exceptions during unwinding. - return true; + ExceptionContinueExecution, + ExceptionContinueSearch, + ExceptionNestedException, + ExceptionCollidedUnwind } -extern(C) void _d_leave_cleanup(void* ptr) +// @safeseh to be marked as "safe" for the OS securtity check +extern(C) @safeseh() +EXCEPTION_DISPOSITION _d_unwindExceptionHandler(ExceptionRecord* exceptionRecord, + FrameInfo* frame, + CONTEXT* context, + FrameInfo** dispatcherContext) { + // catch any D exception + Throwable excObj = null; + if (exceptionRecord.CxxInfo.Magic == EH_MAGIC_NUMBER1) + excObj = *exceptionRecord.CxxInfo.pThrowable; + + // pass through non-D exceptions (should be wrapped?) + if (!excObj || exceptionStack.find(excObj) >= exceptionStack.length()) + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; + + // unwind inner frames + doRtlUnwind(frame, exceptionRecord, &RtlUnwind); + + // continue in + exceptionRecord.ExceptionFlags &= ~EXCEPTION_NONCONTINUABLE; + *dispatcherContext = frame; + context.Eip = cast(size_t) frame.continuationAddress; + context.Eax = cast(size_t) frame; + return EXCEPTION_DISPOSITION.ExceptionContinueExecution; +} + +extern(Windows) +void RtlUnwind(void *targetFrame, void *targetIp, ExceptionRecord *pExceptRec, void *valueForEAX); + +extern(C) +int doRtlUnwind(void *pFrame, ExceptionRecord *eRecord, typeof(RtlUnwind)* handler) +{ + asm { + naked; + push EBP; + mov EBP,ESP; + push ECX; + push EBX; + push ESI; + push EDI; + push EBP; + + push 0; + push dword ptr 12[EBP]; // eRecord + call __system_unwind; // push targetIp + jmp __unwind_exit; + __system_unwind: + push dword ptr 8[EBP]; // pFrame + mov EAX, 16[EBP]; + call EAX; // RtlUnwind; + __unwind_exit: + + pop EBP; + pop EDI; + pop ESI; + pop EBX; + pop ECX; + mov ESP,EBP; + pop EBP; + ret; + } } /////////////////////////////////////////////////////////////// @@ -332,6 +551,9 @@ void msvc_eh_init() { throwInfoMutex = new Mutex; + version(Win64) + ehHeap = EHHeap(65536); + // preallocate type descriptors likely to be needed getThrowInfo(typeid(Exception)); // better not have to allocate when this is thrown: diff --git a/src/ldc/eh/win64.d b/src/ldc/eh/win64.d index 3a48caa852..987bc8a811 100644 --- a/src/ldc/eh/win64.d +++ b/src/ldc/eh/win64.d @@ -5,7 +5,7 @@ module ldc.eh.win64; version (CRuntime_Microsoft): -version (Win64): +version (none /*Win64*/): // debug = EH_personality; // debug = EH_personality_verbose;