/** * Implementation of exception handling support routines for Windows. * * Copyright: Copyright Digital Mars 1999 - 2010. * License: Boost License 1.0. * Authors: Walter Bright */ /* Copyright Digital Mars 1999 - 2010. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) */ module rt.deh; import core.sys.windows.windows; //import core.stdc.stdio; enum EXCEPTION_DISPOSITION { ExceptionContinueExecution, ExceptionContinueSearch, ExceptionNestedException, ExceptionCollidedUnwind } /+ enum { EXCEPTION_EXECUTE_HANDLER = 1, EXCEPTION_CONTINUE_SEARCH = 0, EXCEPTION_CONTINUE_EXECUTION = -1 } +/ extern(Windows) { void RaiseException(DWORD, DWORD, DWORD, void *); } // used in EXCEPTION_RECORD enum : DWORD { STATUS_WAIT_0 = 0, STATUS_ABANDONED_WAIT_0 = 0x00000080, STATUS_USER_APC = 0x000000C0, STATUS_TIMEOUT = 0x00000102, STATUS_PENDING = 0x00000103, STATUS_SEGMENT_NOTIFICATION = 0x40000005, STATUS_GUARD_PAGE_VIOLATION = 0x80000001, STATUS_DATATYPE_MISALIGNMENT = 0x80000002, STATUS_BREAKPOINT = 0x80000003, STATUS_SINGLE_STEP = 0x80000004, STATUS_ACCESS_VIOLATION = 0xC0000005, STATUS_IN_PAGE_ERROR = 0xC0000006, STATUS_INVALID_HANDLE = 0xC0000008, STATUS_NO_MEMORY = 0xC0000017, STATUS_ILLEGAL_INSTRUCTION = 0xC000001D, STATUS_NONCONTINUABLE_EXCEPTION = 0xC0000025, STATUS_INVALID_DISPOSITION = 0xC0000026, STATUS_ARRAY_BOUNDS_EXCEEDED = 0xC000008C, STATUS_FLOAT_DENORMAL_OPERAND = 0xC000008D, STATUS_FLOAT_DIVIDE_BY_ZERO = 0xC000008E, STATUS_FLOAT_INEXACT_RESULT = 0xC000008F, STATUS_FLOAT_INVALID_OPERATION = 0xC0000090, STATUS_FLOAT_OVERFLOW = 0xC0000091, STATUS_FLOAT_STACK_CHECK = 0xC0000092, STATUS_FLOAT_UNDERFLOW = 0xC0000093, STATUS_INTEGER_DIVIDE_BY_ZERO = 0xC0000094, STATUS_INTEGER_OVERFLOW = 0xC0000095, STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096, STATUS_STACK_OVERFLOW = 0xC00000FD, STATUS_CONTROL_C_EXIT = 0xC000013A, STATUS_DLL_INIT_FAILED = 0xC0000142, STATUS_DLL_INIT_FAILED_LOGOFF = 0xC000026B, CONTROL_C_EXIT = STATUS_CONTROL_C_EXIT, EXCEPTION_ACCESS_VIOLATION = STATUS_ACCESS_VIOLATION, EXCEPTION_DATATYPE_MISALIGNMENT = STATUS_DATATYPE_MISALIGNMENT, EXCEPTION_BREAKPOINT = STATUS_BREAKPOINT, EXCEPTION_SINGLE_STEP = STATUS_SINGLE_STEP, EXCEPTION_ARRAY_BOUNDS_EXCEEDED = STATUS_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_FLT_DENORMAL_OPERAND = STATUS_FLOAT_DENORMAL_OPERAND, EXCEPTION_FLT_DIVIDE_BY_ZERO = STATUS_FLOAT_DIVIDE_BY_ZERO, EXCEPTION_FLT_INEXACT_RESULT = STATUS_FLOAT_INEXACT_RESULT, EXCEPTION_FLT_INVALID_OPERATION = STATUS_FLOAT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW = STATUS_FLOAT_OVERFLOW, EXCEPTION_FLT_STACK_CHECK = STATUS_FLOAT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW = STATUS_FLOAT_UNDERFLOW, EXCEPTION_INT_DIVIDE_BY_ZERO = STATUS_INTEGER_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW = STATUS_INTEGER_OVERFLOW, EXCEPTION_PRIV_INSTRUCTION = STATUS_PRIVILEGED_INSTRUCTION, EXCEPTION_IN_PAGE_ERROR = STATUS_IN_PAGE_ERROR, EXCEPTION_ILLEGAL_INSTRUCTION = STATUS_ILLEGAL_INSTRUCTION, EXCEPTION_NONCONTINUABLE_EXCEPTION = STATUS_NONCONTINUABLE_EXCEPTION, EXCEPTION_STACK_OVERFLOW = STATUS_STACK_OVERFLOW, EXCEPTION_INVALID_DISPOSITION = STATUS_INVALID_DISPOSITION, EXCEPTION_GUARD_PAGE = STATUS_GUARD_PAGE_VIOLATION, EXCEPTION_INVALID_HANDLE = STATUS_INVALID_HANDLE } enum MAXIMUM_SUPPORTED_EXTENSION = 512; enum size_t EXCEPTION_MAXIMUM_PARAMETERS = 15; enum DWORD EXCEPTION_NONCONTINUABLE = 1; struct FLOATING_SAVE_AREA { DWORD ControlWord; DWORD StatusWord; DWORD TagWord; DWORD ErrorOffset; DWORD ErrorSelector; DWORD DataOffset; DWORD DataSelector; BYTE[80] RegisterArea; DWORD Cr0NpxState; } struct CONTEXT { DWORD ContextFlags; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; FLOATING_SAVE_AREA FloatSave; DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; DWORD Ebp; DWORD Eip; DWORD SegCs; DWORD EFlags; DWORD Esp; DWORD SegSs; BYTE[MAXIMUM_SUPPORTED_EXTENSION] ExtendedRegisters; } alias CONTEXT* PCONTEXT, LPCONTEXT; struct EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; EXCEPTION_RECORD* ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; DWORD[EXCEPTION_MAXIMUM_PARAMETERS] ExceptionInformation; } alias EXCEPTION_RECORD* PEXCEPTION_RECORD, LPEXCEPTION_RECORD; struct EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } alias EXCEPTION_POINTERS* PEXCEPTION_POINTERS, LPEXCEPTION_POINTERS; enum EXCEPTION_UNWIND = 6; // Flag to indicate if the system is unwinding /+ Values used by Microsoft for Itanium and Win64 are: #define EXCEPTION_NONCONTINUABLE 0x0001 #define EXCEPTION_UNWINDING 0x0002 #define EXCEPTION_EXIT_UNWIND 0x0004 #define EXCEPTION_STACK_INVALID 0x0008 #define EXCEPTION_NESTED_CALL 0x0010 #define EXCEPTION_TARGET_UNWIND 0x0020 #define EXCEPTION_COLLIDED_UNWIND 0x0040 #define EXCEPTION_UNWIND 0x0066 @@@ BUG @@@ We don't have any guarantee that this bit will remain available. Unfortunately, it seems impossible to implement exception handling at all, without relying on undocumented behaviour in several places. +/ enum EXCEPTION_COLLATERAL = 0x100; // Flag used to implement TDPL exception chaining /* Windows Kernel function to initiate a system unwind. Documentation for this function is severely lacking. http://www.nynaeve.net/?p=99 states that the MSDN documentation is incorrect, and gives a corrected form, but it's for x86_64 only. http://www.microsoft.com/msj/0197/exception/exception.aspx says that it was undocumented in 1997. The pExceptRec is what will be passed to the language specific handler. According to MSJ, the targetIp value is unused on Win32. The 'valueForEAX' parameter should always be 0. */ extern(Windows) void RtlUnwind(void *targetFrame, void *targetIp, EXCEPTION_RECORD *pExceptRec, void *valueForEAX); extern(C) { extern __gshared DWORD _except_list; // This is just FS:[0] } extern(C) { void _d_setUnhandled(Object); void _d_createTrace(Object); int _d_isbaseof(ClassInfo b, ClassInfo c); } /+ Implementation of Structured Exception Handling in DMD-Windows Every function which uses exception handling (a 'frame') has a thunk created for it. This thunk is the 'language-specific handler'. The thunks are created in the DMD backend, in nteh_framehandler() in nteh.c. These thunks are of the form: MOV EAX, &scope_table JMP __d_framehandler FS:[0] contains a singly linked list of all active handlers (they'll all be thunks). The list is created on the stack. At the end of this list is _except_handler3, a function in the DMC library. It may be unnecessary. I think it is included for compatibility with MSVC exceptions? The function below is useful for debugging. extern(C) EXCEPTION_DISPOSITION _except_handler3(EXCEPTION_RECORD *eRecord, DEstablisherFrame * frame,CONTEXT *context,void *dispatchercontext); // Walk the exception handler chain void printHandlerChain() { DEstablisherFrame *head; asm { mov EAX, FS:[0]; mov head, EAX; } while (head && head != cast(DEstablisherFrame *)~0) { printf("%p %p ", head, head.handler); if (head.handler == &unwindCollisionExceptionHandler) printf("UnwindCollisionHandler\n"); else if (head.handler == &_except_handler3) printf("excepthandler3\n"); else { ubyte *hnd = cast(ubyte *)head.handler; if (hnd[0] == 0xB8 && hnd[5]==0xE9) // mov EAX, xxx; jmp yyy; { int adr = *cast(int *)(hnd+6); printf("thunk: frametable=%x adr=%x ", *cast(int *)(hnd+1), hnd + adr+10); if (cast(void *)(hnd + adr + 10) == &_d_framehandler) printf("dframehandler\n"); else printf("\n"); } else printf("(unknown)\n"); } head = head.prev; } } Documentation of Windows SEH is hard to find. Here is a brief explanation: When an exception is raised, the OS calls each handler in the FS:[0] list in turn, looking for a catch block. It continues moving down the list, as long as each handler indicates that it has not caught the exception. When a handler is ready to catch the exception, it calls the OS function RtlUnwind. This calls each function in the FS:[0] list again, this time indicating that it is a 'unwind' call. All of the intervening finally blocks are run at this time. The complicated case is a CollidedException, which happens when a finally block throws an exception. The new exception needs to either replace the old one, or be chained to the old one. The other complexity comes from the fact that a single function may have multiple try/catch/finally blocks. Hence, there's a 'handler table' created for each function which uses exceptions. +/ extern(C) { alias EXCEPTION_DISPOSITION function ( EXCEPTION_RECORD *exceptionRecord, DEstablisherFrame *frame, CONTEXT *context, void *dispatcherContext) LanguageSpecificHandler; } // The layout of DEstablisherFrame is the same for C++ struct DEstablisherFrame { DEstablisherFrame *prev; // pointer to previous exception list LanguageSpecificHandler handler; // pointer to routine for exception handler DWORD table_index; // current index into handler_info[] DWORD ebp; // this is EBP of routine }; struct DHandlerInfo { int prev_index; // previous table index uint cioffset; // offset to DCatchInfo data from start of table (!=0 if try-catch) void *finally_code; // pointer to finally code to execute // (!=0 if try-finally) }; // Address of DHandlerTable is passed in EAX to _d_framehandler() struct DHandlerTable { void *fptr; // pointer to start of function uint espoffset; // offset of ESP from EBP uint retoffset; // offset from start of function to return code DHandlerInfo handler_info[1]; }; struct DCatchBlock { ClassInfo type; // catch type uint bpoffset; // EBP offset of catch var void *code; // catch handler code }; // One of these is created for each try-catch struct DCatchInfo { uint ncatches; // number of catch blocks DCatchBlock catch_block[1]; // data for each catch block }; // Macro to make our own exception code template MAKE_EXCEPTION_CODE(int severity, int facility, int exception) { enum int MAKE_EXCEPTION_CODE = (((severity) << 30) | (1 << 29) | (0 << 28) | ((facility) << 16) | (exception)); } enum int STATUS_DIGITAL_MARS_D_EXCEPTION = MAKE_EXCEPTION_CODE!(3,'D',1); /* Head of a linked list of all exceptions which are in flight. * This is used to implement exception chaining as described in TDPL. * Central to making chaining work correctly is that chaining must only occur * when a collision occurs (not merely when two exceptions are in flight, * because one may be caught before it has any effect on the other). * * The 'ExceptionRecord' member of the EXCEPTION_RECORD struct is used to * store a link to the earlier member on the list. * All exceptions which have found their catch handler are linked into this * list. The exceptions which collided are marked by setting a bit in the * ExceptionFlags. I've called this bit EXCEPTION_COLLATERAL. It has never * been used by Microsoft. * * Every member of the list will either eventually collide with the next earlier * exception, having its EXCEPTION_COLLATERAL bit set, or else will be caught. * If it is caught, a D exception object is created, containing all of the * collateral exceptions. * * There are many subtleties in this design: * (1) The exception records are all on the stack, so it's not possible to * modify them very much. In particular, we have very little choice about how * unwinding works, so we have to leave all the exception records essentially * intact. * (2) The length of an exception record is not constant. System exceptions * are shorter than D exceptions, for example. * (3) System exceptions don't have any space for a pointer to a D object. * So we cannot store the collision information in the exception record. * (4) it's important that this list is thread-local. */ EXCEPTION_RECORD * inflightExceptionList = null; /*********************************** * Find the first non-collateral exception in the list. If the last * entry in the list has the EXCEPTION_COLLATERAL bit set, it means * that this fragment will collide with the top exception in the * inflightException list. */ EXCEPTION_RECORD *skipCollateralExceptions(EXCEPTION_RECORD *n); /*********************************** * The frame handler, this is called for each frame that has been registered * in the OS except_list. * Input: * EAX the handler table for the frame */ extern(C) EXCEPTION_DISPOSITION _d_framehandler( EXCEPTION_RECORD *exceptionRecord, DEstablisherFrame *frame, CONTEXT *context, void *dispatcherContext); /*********************************** * Exception filter for use in __try..__except block * surrounding call to Dmain() */ int _d_exception_filter(EXCEPTION_POINTERS *eptrs, int retval, Object *exceptionObject); /*********************************** * Throw a D object. */ extern(C) void _d_throwc(Object h); /*********************************** * Converts a Windows Structured Exception code to a D Throwable Object. */ Throwable _d_translate_se_to_d_exception(EXCEPTION_RECORD *exceptionRecord); /* These next two functions are necessary for dealing with collided exceptions: when an exception has been thrown during unwinding. This happens for example when a throw statement was encountered inside a finally clause. 'frame' is the stack pointer giving the state we were in, when we made the call to RtlUnwind. When we return ExceptionCollidedUnwind, the OS initiates a new SEARCH phase, using the new exception, and it begins this search from the frame we provide in the 'dispatcherContext' output parameter. We change the target frame pointer, by changing dispatcherContext. After this, we'll be back at the start of a SEARCH phase, so we need cancel all existing operations. There are two types of possible collisions. (1) collision during a local unwind. That is, localunwind was called during the SEARCH phase (without going through an additional call to RtlUnwind). We need to cancel the original search pass, so we'll restart from 'frame'. (2) collision during a global unwind. That is, localunwind was called from the UNWIND phase. We need to cancel the unwind pass, AND we need to cancel the search pass that initiated it. So, we need to restart from 'frame.prev'. */ extern (C) EXCEPTION_DISPOSITION searchCollisionExceptionHandler( EXCEPTION_RECORD *exceptionRecord, DEstablisherFrame *frame, CONTEXT *context, void *dispatcherContext); extern(C) EXCEPTION_DISPOSITION unwindCollisionExceptionHandler( EXCEPTION_RECORD *exceptionRecord, DEstablisherFrame *frame, CONTEXT *context, void *dispatcherContext); /************************************** * Call finally blocks in the current stack frame until stop_index. * This is roughly equivalent to _local_unwind() for C in \src\win32\ehsup.c */ extern(C) void _d_local_unwind(DHandlerTable *handler_table, DEstablisherFrame *frame, int stop_index, LanguageSpecificHandler collisionHandler); /+ According to http://www.microsoft.com/msj/0197/exception/exception.aspx, global unwind is just a thin wrapper around RtlUnwind. __global_unwind(void * pRegistFrame) { _RtlUnwind( pRegistFrame, &__ret_label, 0, 0 ); __ret_label: } Apparently Win32 doesn't use the return address anyway. This code seems to be calling RtlUnwind( pFrame, &__retlabel, eRecord, 0); +/ extern(C) int _d_global_unwind(DEstablisherFrame *pFrame, EXCEPTION_RECORD *eRecord); /*********************************** * external version of the unwinder * This is used for 'goto' or 'return', to run any finally blocks * which were skipped. */ extern(C) void _d_local_unwind2(); extern(C) void _d_localUnwindForGoto(DHandlerTable *handler_table, DEstablisherFrame *frame, int stop_index); /*********************************** * The frame handler, this is called for each frame that has been registered * in the OS except_list. * Input: * EAX the handler table for the frame */ extern(C) EXCEPTION_DISPOSITION _d_monitor_handler( EXCEPTION_RECORD *exceptionRecord, DEstablisherFrame *frame, CONTEXT *context, void *dispatcherContext); /*********************************** */ extern(C) void _d_monitor_prolog(void *x, void *y, Object h); /*********************************** */ extern(C) void _d_monitor_epilog(void *x, void *y, Object h);