Implement basic support for managed exception handling.
authorSergiy Kuryata <sergeyk@microsoft.com>
Fri, 27 Feb 2015 19:50:16 +0000 (11:50 -0800)
committerSergiy Kuryata <sergeyk@microsoft.com>
Fri, 27 Feb 2015 19:56:27 +0000 (11:56 -0800)
Implementation of the managed exception handling in this change is by far not yet complete but it is a good starting point that we can build on top of it. Basically this code allows managed exceptions to be thrown and caught. The finally blocks and nested try/catch works too. But re-throwing an exception from a catch block and many other corner cases do not work yet.

In addition, RtlRestoreContext needs to be properly implemented. This change introduces a very simply implementation that works only in those cases where XMM registers are not used in the code that handles the exception so they don’t need to be restored. I have created a separate issue to track it (https://github.com/dotnet/coreclr/issues/360).

This change also fixes an issue in JIT where JIT was incorrectly passing arguments in the RCX and RDX registers to the finally and catch funclets.

src/jit/codegencommon.cpp
src/jit/codegenxarch.cpp
src/jit/target.h
src/pal/src/arch/i386/context.cpp
src/pal/src/debug/debug.cpp
src/vm/exceptionhandling.cpp
src/vm/exceptmacros.h
src/vm/stackwalk.cpp

index 1181783..c88817c 100644 (file)
@@ -9615,13 +9615,14 @@ void                CodeGen::genFuncletProlog(BasicBlock* block)
     regMaskTP maskArgRegsLiveIn;
     if ((block->bbCatchTyp == BBCT_FINALLY) || (block->bbCatchTyp == BBCT_FAULT))
     {
-        maskArgRegsLiveIn = RBM_ECX;
+        maskArgRegsLiveIn = RBM_ARG_0;
     }
     else
     {
-        maskArgRegsLiveIn = RBM_ECX | RBM_EDX;
+        maskArgRegsLiveIn = RBM_ARG_0 | RBM_ARG_2;
     }
 
+
     regNumber initReg = REG_EBP; // We already saved EBP, so it can be trashed
     bool initRegZeroed = false;
 
@@ -9634,7 +9635,8 @@ void                CodeGen::genFuncletProlog(BasicBlock* block)
     // This is the end of the OS-reported prolog for purposes of unwinding
     compiler->unwindEndProlog();
 
-    getEmitter()->emitIns_R_AR(INS_mov, EA_PTRSIZE, REG_FPBASE, REG_ECX, genFuncletInfo.fiPSP_slot_InitialSP_offset);
+    getEmitter()->emitIns_R_AR(INS_mov, EA_PTRSIZE, REG_FPBASE, REG_ARG_0, genFuncletInfo.fiPSP_slot_InitialSP_offset);
+
     regTracker.rsTrackRegTrash(REG_FPBASE);
 
     getEmitter()->emitIns_AR_R(INS_mov, EA_PTRSIZE, REG_FPBASE, REG_SPBASE, genFuncletInfo.fiPSP_slot_InitialSP_offset);
index 82677a3..2dee895 100644 (file)
@@ -892,7 +892,7 @@ void                CodeGen::genCodeForBBlist()
             //      jmp         finally-return                  // Only for non-retless finally calls
             // The jmp can be a NOP if we're going to the next block.
 
-            getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_RCX, compiler->lvaPSPSym, 0);
+            getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_ARG_0, compiler->lvaPSPSym, 0);
             getEmitter()->emitIns_J(INS_call, block->bbJumpDest);
 
             if (block->bbFlags & BBF_RETLESS_CALL)
index e730481..ea3864d 100644 (file)
@@ -821,9 +821,14 @@ typedef unsigned short          regPairNoSmall; // arm: need 12 bits
   #define REG_SCRATCH              REG_EAX
   #define RBM_SCRATCH              RBM_EAX
 
-  // Where is the exception object on entry to the handler block?
+// Where is the exception object on entry to the handler block?
+#ifndef UNIX_AMD64_ABI
   #define REG_EXCEPTION_OBJECT     REG_EDX
   #define RBM_EXCEPTION_OBJECT     RBM_EDX
+#else
+  #define REG_EXCEPTION_OBJECT     REG_ESI
+  #define RBM_EXCEPTION_OBJECT     RBM_ESI
+#endif // UNIX_AMD64_ABI
 
   #define REG_JUMP_THUNK_PARAM     REG_EAX
   #define RBM_JUMP_THUNK_PARAM     RBM_EAX
index cb1d045..b8c0d81 100644 (file)
@@ -24,6 +24,7 @@ Abstract:
 #include "pal/dbgmsg.h"
 #include "pal/context.h"
 #include "pal/debug.h"
+#include "pal/thread.hpp"
 
 #include <sys/ptrace.h> 
 #include <errno.h>
@@ -348,7 +349,7 @@ CONTEXT_GetThreadContext(
 
     if (dwProcessId == GetCurrentProcessId())
     {
-        if (dwThreadId != GetCurrentThreadId())
+        if (dwThreadId != THREADSilentGetCurrentThreadId())
         {
             DWORD flags;
             // There aren't any APIs for this. We can potentially get the
@@ -1013,7 +1014,7 @@ CONTEXT_GetThreadContext(
     
     if (GetCurrentProcessId() == dwProcessId)
     {
-        if (dwThreadId != GetCurrentThreadId())
+        if (dwThreadId != THREADSilentGetCurrentThreadId())
         {
             // the target thread is in the current process, but isn't 
             // the current one: extract the CONTEXT from the Mach thread.            
@@ -1256,7 +1257,7 @@ CONTEXT_SetThreadContext(
         goto EXIT;
     }
 
-    if (dwThreadId != GetCurrentThreadId()) 
+    if (dwThreadId != THREADSilentGetCurrentThreadId())
     {
         // hThread is in the current process, but isn't the current
         // thread.  Extract the CONTEXT from the Mach thread.
index a9fa019..eb28e2e 100644 (file)
@@ -530,7 +530,27 @@ RtlRestoreContext(
   IN PEXCEPTION_RECORD ExceptionRecord
 )
 {
-    ASSERT("UNIXTODO: Implement this");
+    native_context_t ucontext;
+
+    //
+    //TODO: This needs to be properly implemented
+    // because this code does not restore XMM registers
+    //
+
+#if HAVE_GETCONTEXT
+    getcontext(&ucontext);
+#else
+#error Don't know how to get current context on this platform!
+#endif
+
+    CONTEXTToNativeContext(ContextRecord, &ucontext,
+        CONTEXT_CONTROL | CONTEXT_INTEGER);
+
+#if HAVE_SETCONTEXT
+    setcontext(&ucontext);
+#else
+#error Don't know how to set current context on this platform!
+#endif
 }
 
 /*++
index 2c9d329..5feb07c 100644 (file)
@@ -1014,6 +1014,7 @@ ProcessCLRException(IN     PEXCEPTION_RECORD   pExceptionRecord
 
             SetLastError(dwLastError);
 
+#ifndef FEATURE_PAL
             //
             // At this point (the end of the 1st pass) we don't know where
             // we are going to resume to.  So, we pass in an address, which
@@ -1039,6 +1040,14 @@ ProcessCLRException(IN     PEXCEPTION_RECORD   pExceptionRecord
             //
             // doesn't return
             //
+#else
+            // On Unix, we will return ExceptionStackUnwind back to the custom
+            // exception dispatch system. When it sees this disposition, it will
+            // know that we want to handle the exception and will commence unwind
+            // via the custom unwinder.
+            return ExceptionStackUnwind;
+
+#endif // FEATURE_PAL
         }
         else if (SecondPassComplete == status)
         {
@@ -4213,6 +4222,250 @@ static void DoEHLog(
 }
 #endif // _DEBUG
 
+#ifdef FEATURE_PAL
+//---------------------------------------------------------------------------------------
+//
+// This functions return True if the given stack address is
+// within the specified stack boundaries.
+//
+// Arguments:
+//      sp - a stack pointer that needs to be verified
+//      stackLowAddress, stackHighAddress - these values specify stack boundaries
+//
+bool IsSpInStackLimits(ULONG64 sp, ULONG64 stackLowAddress, ULONG64 stackHighAddress)
+{
+    return ((sp > stackLowAddress) && (sp < stackHighAddress));
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This functions performs an unwind procedure for a managed exception. The stack is unwound
+// until the target frame is reached. For each frame we use the its PC value to find
+// a handler using information that has been built by JIT.
+//
+// Arguments:
+//      exceptionRecord          - an exception record that stores information about the managed exception
+//      originalExceptionContext - the context that caused this exception to be thrown
+//      targetFrameSp            - specifies the call frame that is the target of the unwind
+//
+VOID DECLSPEC_NORETURN UnwindManagedException(EXCEPTION_RECORD* exceptionRecord, CONTEXT* originalExceptionContext, UINT_PTR targetFrameSp)
+{
+    UINT_PTR controlPc;
+    EXCEPTION_DISPOSITION disposition;
+    CONTEXT* currentFrameContext;
+    CONTEXT* callerFrameContext;
+    CONTEXT contextStorage;
+    DISPATCHER_CONTEXT dispatcherContext;
+    EECodeInfo codeInfo;
+    ULONG64 establisherFrame = NULL;
+    PVOID handlerData;
+    ULONG64 stackHighAddress = (ULONG64)PAL_GetStackBase();
+    ULONG64 stackLowAddress = (ULONG64)PAL_GetStackLimit();
+
+    // Indicate that we are performing second pass.
+    exceptionRecord->ExceptionFlags = EXCEPTION_UNWINDING;
+
+    currentFrameContext = originalExceptionContext;
+    callerFrameContext = &contextStorage;
+
+    memset(&dispatcherContext, 0, sizeof(DISPATCHER_CONTEXT));
+    disposition = ExceptionContinueSearch;
+
+    do
+    {
+        controlPc = currentFrameContext->Rip;
+
+        codeInfo.Init(controlPc);
+        dispatcherContext.FunctionEntry = codeInfo.GetFunctionEntry();
+        dispatcherContext.ControlPc = controlPc;
+        dispatcherContext.ImageBase = codeInfo.GetModuleBase();
+
+        // Check whether we have a function table entry for the current controlPC.
+        // If yes, then call RtlVirtualUnwind to get the establisher frame pointer.
+        if (dispatcherContext.FunctionEntry != NULL)
+        {
+            // Create a copy of the current context because we don't want
+            // the current context record to be updated by RtlVirtualUnwind.
+            memcpy(callerFrameContext, currentFrameContext, sizeof(CONTEXT));
+            RtlVirtualUnwind(UNW_FLAG_EHANDLER,
+                dispatcherContext.ImageBase,
+                dispatcherContext.ControlPc,
+                dispatcherContext.FunctionEntry,
+                callerFrameContext,
+                &handlerData,
+                &establisherFrame,
+                NULL);
+
+            // Make sure that the establisher frame pointer is within stack boundaries
+            // and we did not go below that target frame.
+            // TODO: make sure the establisher frame is properly aligned.
+            if (!IsSpInStackLimits(establisherFrame, stackLowAddress, stackHighAddress) ||
+                establisherFrame > targetFrameSp)
+            {
+                // TODO: add better error handling
+                UNREACHABLE();
+            }
+
+            dispatcherContext.EstablisherFrame = establisherFrame;
+            dispatcherContext.ContextRecord = currentFrameContext;
+
+            if (establisherFrame == targetFrameSp)
+            {
+                // We have reached the frame that will handle the exception.
+                exceptionRecord->ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
+            }
+
+            // Perform unwinding of the current frame
+            disposition = ProcessCLRException(exceptionRecord,
+                establisherFrame,
+                currentFrameContext,
+                &dispatcherContext);
+
+            if (disposition == ExceptionContinueSearch)
+            {
+                // Exception handler not found. Try the parent frame.
+                CONTEXT* temp = currentFrameContext;
+                currentFrameContext = callerFrameContext;
+                callerFrameContext = temp;
+            }
+            else
+            {
+                // TODO: This needs to implemented. Make it fail for now.
+                UNREACHABLE();
+            }
+        }
+        else
+        {
+            controlPc = Thread::VirtualUnwindLeafCallFrame(currentFrameContext);
+        }
+
+        //
+        //TODO: check whether we are crossing managed-to-native boundary
+        // and add support for this case.
+        //
+    } while (IsSpInStackLimits(currentFrameContext->Rsp, stackLowAddress, stackHighAddress) &&
+        (establisherFrame != targetFrameSp));
+
+    printf("UnwindManagedException: Unwinding failed. Reached the end of the stack.\n");
+    UNREACHABLE();
+}
+
+//---------------------------------------------------------------------------------------
+//
+// This functions performs dispatching of a managed exception.
+// It tries to find an exception handler by examining each frame in the call stack.
+// The search is started from the managed frame caused the exception to be thrown.
+// For each frame we use the its PC value to find a handler using information that
+// has been built by JIT. If an exception handler is found then this function initiates
+// the second pass to unwind the stack and execute the handler.
+//
+// Arguments:
+//      ex - a PAL_SEHException that stores information about the managed
+//           exception that needs to be dispatched.
+//
+VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex)
+{
+    CONTEXT frameContext;
+    CONTEXT originalExceptionContext;
+    EXCEPTION_DISPOSITION disposition;
+    EXCEPTION_RECORD exceptionRecord;
+    DISPATCHER_CONTEXT dispatcherContext;
+    EECodeInfo codeInfo;
+    UINT_PTR controlPc;
+    ULONG64 establisherFrame = NULL;
+    PVOID handlerData;
+    ULONG64 stackHighAddress = (ULONG64)PAL_GetStackBase();
+    ULONG64 stackLowAddress = (ULONG64)PAL_GetStackLimit();
+
+    // TODO: is there a better way to get the first managed frame?
+    originalExceptionContext.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
+    GetThread()->GetThreadContext(&originalExceptionContext);
+    controlPc = Thread::VirtualUnwindToFirstManagedCallFrame(&originalExceptionContext);
+
+    // Preserve the context that caused the exception. We need to pass it
+    // to ProcessCLRException for each frame that will be unwound.
+    memcpy(&frameContext, &originalExceptionContext, sizeof(CONTEXT));
+
+    // Setup an exception record based on the information from the PAL exception and
+    // point it to the location in managed code where the exception has been thrown from.
+    memcpy(&exceptionRecord, &ex.ExceptionRecord, sizeof(EXCEPTION_RECORD));
+    exceptionRecord.ExceptionFlags = 0;
+    exceptionRecord.ExceptionAddress = (PVOID)controlPc;
+
+    memset(&dispatcherContext, 0, sizeof(DISPATCHER_CONTEXT));
+    disposition = ExceptionContinueSearch;
+
+    do
+    {
+        codeInfo.Init(controlPc);
+        dispatcherContext.FunctionEntry = codeInfo.GetFunctionEntry();
+        dispatcherContext.ControlPc = controlPc;
+        dispatcherContext.ImageBase = codeInfo.GetModuleBase();
+
+        // Check whether we have a function table entry for the current controlPC.
+        // If yes, then call RtlVirtualUnwind to get the establisher frame pointer
+        // and then check whether an exception handler exists for the frame.
+        if (dispatcherContext.FunctionEntry != NULL)
+        {
+            RtlVirtualUnwind(UNW_FLAG_EHANDLER,
+                dispatcherContext.ImageBase,
+                dispatcherContext.ControlPc,
+                dispatcherContext.FunctionEntry,
+                &frameContext,
+                &handlerData,
+                &establisherFrame,
+                NULL);
+
+            // Make sure that the establisher frame pointer is within stack boundaries.
+            // TODO: make sure the establisher frame is properly aligned.
+            if (!IsSpInStackLimits(establisherFrame, stackLowAddress, stackHighAddress))
+            {
+                // TODO: add better error handling
+                UNREACHABLE();
+            }
+
+            dispatcherContext.EstablisherFrame = establisherFrame;
+            dispatcherContext.ContextRecord = &frameContext;
+
+            // Find exception handler in the current frame
+            disposition = ProcessCLRException(&exceptionRecord,
+                establisherFrame,
+                &originalExceptionContext,
+                &dispatcherContext);
+
+            if (disposition == ExceptionContinueSearch)
+            {
+                // Exception handler not found. Try the parent frame.
+                controlPc = frameContext.Rip;
+            }
+            else if (disposition == ExceptionStackUnwind)
+            {
+                // The first pass is complete. We have found the frame that
+                // will handle the exception. Start the second pass.
+                UnwindManagedException(&exceptionRecord, &originalExceptionContext, establisherFrame);
+            }
+            else
+            {
+                // TODO: This needs to implemented. Make it fail for now.
+                UNREACHABLE();
+            }
+        }
+        else
+        {
+            controlPc = Thread::VirtualUnwindLeafCallFrame(&frameContext);
+        }
+
+        //
+        //TODO: check whether we are crossing managed-to-native boundary
+        // and add support for this case.
+        //
+    } while (IsSpInStackLimits(frameContext.Rsp, stackLowAddress, stackHighAddress));
+
+    printf("DispatchManagedException: Failed to find a handler. Reached the end of the stack.\n");
+    UNREACHABLE();
+}
+#endif // FEATURE_PAL
+
 void ClrUnwindEx(EXCEPTION_RECORD* pExceptionRecord, UINT_PTR ReturnValue, UINT_PTR TargetIP, UINT_PTR TargetFrameSp)
 {
 #ifndef FEATURE_PAL
index da4b759..dd27eac 100644 (file)
@@ -318,12 +318,33 @@ VOID DECLSPEC_NORETURN RaiseTheExceptionInternalOnly(OBJECTREF throwable, BOOL r
 void UnwindAndContinueRethrowHelperInsideCatch(Frame* pEntryFrame, Exception* pException);
 VOID DECLSPEC_NORETURN UnwindAndContinueRethrowHelperAfterCatch(Frame* pEntryFrame, Exception* pException);
 
+#ifdef FEATURE_PAL
+VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex);
+
+#define INSTALL_MANAGED_EXCEPTION_DISPATCHER        \
+        try { \
+
+#define UNINSTALL_MANAGED_EXCEPTION_DISPATCHER      \
+        }                                           \
+        catch (PAL_SEHException& ex)                \
+        {                                           \
+            DispatchManagedException(ex);           \
+        }                                           \
+
+#else
+
+#define INSTALL_MANAGED_EXCEPTION_DISPATCHER
+#define UNINSTALL_MANAGED_EXCEPTION_DISPATCHER
+
+#endif // FEATURE_PAL
+
 #define INSTALL_UNWIND_AND_CONTINUE_HANDLER_NO_PROBE                                        \
     {                                                                                       \
         MAKE_CURRENT_THREAD_AVAILABLE();                                                    \
         Exception* __pUnCException  = NULL;                                                 \
         Frame*     __pUnCEntryFrame = CURRENT_THREAD->GetFrame();                           \
         bool       __fExceptionCatched = false;                                             \
+        INSTALL_MANAGED_EXCEPTION_DISPATCHER                                                \
         SCAN_EHMARKER();                                                                    \
         if (true) PAL_CPP_TRY {                                                             \
             SCAN_EHMARKER_TRY();                                                            \
@@ -346,6 +367,7 @@ VOID DECLSPEC_NORETURN UnwindAndContinueRethrowHelperAfterCatch(Frame* pEntryFra
         Exception* __pUnCException  = NULL;                                                 \
         Frame*     __pUnCEntryFrame = (pHelperFrame);                                       \
         bool       __fExceptionCatched = false;                                             \
+        INSTALL_MANAGED_EXCEPTION_DISPATCHER                                                \
         SCAN_EHMARKER();                                                                    \
         if (true) PAL_CPP_TRY {                                                             \
             SCAN_EHMARKER_TRY();                                                            \
@@ -371,6 +393,7 @@ VOID DECLSPEC_NORETURN UnwindAndContinueRethrowHelperAfterCatch(Frame* pEntryFra
             SCAN_EHMARKER_CATCH();                                                          \
             UnwindAndContinueRethrowHelperAfterCatch(__pUnCEntryFrame, __pUnCException);    \
         }                                                                                   \
+        UNINSTALL_MANAGED_EXCEPTION_DISPATCHER                                              \
     }                                                                                       \
 
 #define UNINSTALL_UNWIND_AND_CONTINUE_HANDLER                                               \
index e677cef..627765a 100644 (file)
@@ -606,10 +606,11 @@ PCODE Thread::VirtualUnwindCallFrame(T_CONTEXT* pContext,
                                             ARM_ONLY((DWORD*))(&uImageBase),
                                             NULL);
 #else // !FEATURE_PAL
-        // For PAL, this method should never be called without valid pCodeInfo, since there is no
-        // other way to get the function entry.
-        pFunctionEntry = NULL;
-        UNREACHABLE_MSG("VirtualUnwindCallFrame called with NULL pCodeInfo");
+        EECodeInfo codeInfo;
+
+        codeInfo.Init(uControlPc);
+        pFunctionEntry = codeInfo.GetFunctionEntry();
+        uImageBase = (UINT_PTR)codeInfo.GetModuleBase();
 #endif // !FEATURE_PAL
     }
     else