Make PAL_SEHException smaller (#6267)
authorJan Vorlicek <janvorli@microsoft.com>
Wed, 20 Jul 2016 12:21:08 +0000 (14:21 +0200)
committerGitHub <noreply@github.com>
Wed, 20 Jul 2016 12:21:08 +0000 (14:21 +0200)
The PAL_SEHException is quite large due to the fact that it contains context
and exception records. This causes a problem when we try to throw it and
system gets out of memory. The C++ runtime can throw exceptions even in that
case, but only if they are smaller than certain threshold. So in our case,
it just aborts the process.

This change separates the context and exception records from the exception,
which ensures that the above mentioned mechanism in the C++ runtime can
kick in and the exception can be thrown even when malloc fails to allocate
it.

I have also modified HandleHardwareException to return BOOL indicating whether
the execution should continue at the possibly modified exception context
instead of using RtlRestoreContext. In that case, we return all the way back
to the signal handler, update the ucontext in there and return from it.

src/pal/inc/pal.h
src/pal/src/exception/machexception.cpp
src/pal/src/exception/seh-unwind.cpp
src/pal/src/exception/seh.cpp
src/pal/src/exception/signal.cpp
src/pal/src/include/pal/seh.hpp
src/pal/tests/palsuite/exception_handling/pal_sxs/test1/dlltest1.cpp
src/pal/tests/palsuite/exception_handling/pal_sxs/test1/dlltest2.cpp
src/vm/exceptionhandling.cpp
src/vm/exceptionhandling.h
src/vm/exceptmacros.h

index 5b44661..fe29112 100644 (file)
@@ -6442,6 +6442,13 @@ public:
 
 #include "pal_unwind.h"
 
+PALIMPORT
+VOID
+PALAPI
+PAL_FreeExceptionRecords(
+  IN EXCEPTION_RECORD *exceptionRecord, 
+  IN CONTEXT *contextRecord);
+
 #define EXCEPTION_CONTINUE_SEARCH   0
 #define EXCEPTION_EXECUTE_HANDLER   1
 #define EXCEPTION_CONTINUE_EXECUTION -1
@@ -6450,58 +6457,95 @@ struct PAL_SEHException
 {
 private:
     static const SIZE_T NoTargetFrameSp = SIZE_MAX;
+
+    void Move(PAL_SEHException& ex)
+    {
+        ExceptionPointers.ExceptionRecord = ex.ExceptionPointers.ExceptionRecord;
+        ExceptionPointers.ContextRecord = ex.ExceptionPointers.ContextRecord;
+        TargetFrameSp = ex.TargetFrameSp;
+
+        ex.Clear();
+    }
+
+    void FreeRecords()
+    {
+        if (ExceptionPointers.ExceptionRecord != NULL)
+        {
+            PAL_FreeExceptionRecords(ExceptionPointers.ExceptionRecord, ExceptionPointers.ContextRecord);
+            ExceptionPointers.ExceptionRecord = NULL;
+            ExceptionPointers.ContextRecord = NULL;
+        }
+    }
+
 public:
-    // Note that the following two are actually embedded in this heap-allocated
-    // instance - in contrast to Win32, where the exception record would usually
-    // be allocated on the stack.  This is needed because foreign cleanup handlers
-    // partially unwind the stack on the second pass.
     EXCEPTION_POINTERS ExceptionPointers;
-    EXCEPTION_RECORD ExceptionRecord;
-    CONTEXT ContextRecord;
     // Target frame stack pointer set before the 2nd pass.
     SIZE_T TargetFrameSp;
 
     PAL_SEHException(EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pContextRecord)
     {
-        ExceptionPointers.ExceptionRecord = &ExceptionRecord;
-        ExceptionPointers.ContextRecord = &ContextRecord;
-        ExceptionRecord = *pExceptionRecord;
-        ContextRecord = *pContextRecord;
+        ExceptionPointers.ExceptionRecord = pExceptionRecord;
+        ExceptionPointers.ContextRecord = pContextRecord;
         TargetFrameSp = NoTargetFrameSp;
     }
 
     PAL_SEHException()
     {
+        Clear();
     }    
 
-    PAL_SEHException(const PAL_SEHException& ex)
+    // The copy constructor and copy assignment operators are deleted so that the PAL_SEHException
+    // can never be copied, only moved. This enables simple lifetime management of the exception and
+    // context records, since there is always just one PAL_SEHException instance referring to the same records.
+    PAL_SEHException(const PAL_SEHException& ex) = delete;
+    PAL_SEHException& operator=(const PAL_SEHException& ex) = delete;
+
+    PAL_SEHException(PAL_SEHException&& ex)
     {
-        *this = ex;
+        Move(ex);
     }    
 
-    bool IsFirstPass()
+    PAL_SEHException& operator=(PAL_SEHException&& ex)
     {
-        return (TargetFrameSp == NoTargetFrameSp);
+        FreeRecords();
+        Move(ex);
+        return *this;
     }
 
-    void SecondPassDone()
+    ~PAL_SEHException()
     {
+        FreeRecords();
+    }
+
+    void Clear()
+    {
+        ExceptionPointers.ExceptionRecord = NULL;
+        ExceptionPointers.ContextRecord = NULL;
         TargetFrameSp = NoTargetFrameSp;
     }
 
-    PAL_SEHException& operator=(const PAL_SEHException& ex)
+    CONTEXT* GetContextRecord()
     {
-        ExceptionPointers.ExceptionRecord = &ExceptionRecord;
-        ExceptionPointers.ContextRecord = &ContextRecord;
-        ExceptionRecord = ex.ExceptionRecord;
-        ContextRecord = ex.ContextRecord;
-        TargetFrameSp = ex.TargetFrameSp;
+        return ExceptionPointers.ContextRecord;
+    }
 
-        return *this;
+    EXCEPTION_RECORD* GetExceptionRecord()
+    {
+        return ExceptionPointers.ExceptionRecord;
+    }
+
+    bool IsFirstPass()
+    {
+        return (TargetFrameSp == NoTargetFrameSp);
+    }
+
+    void SecondPassDone()
+    {
+        TargetFrameSp = NoTargetFrameSp;
     }
 };
 
-typedef VOID (PALAPI *PHARDWARE_EXCEPTION_HANDLER)(PAL_SEHException* ex);
+typedef BOOL (PALAPI *PHARDWARE_EXCEPTION_HANDLER)(PAL_SEHException* ex);
 typedef BOOL (PALAPI *PHARDWARE_EXCEPTION_SAFETY_CHECK_FUNCTION)(PCONTEXT contextRecord, PEXCEPTION_RECORD exceptionRecord);
 typedef VOID (PALAPI *PTERMINATION_REQUEST_HANDLER)();
 typedef DWORD (PALAPI *PGET_GCMARKER_EXCEPTION_CODE)(LPVOID ip);
index 508a967..a483509 100644 (file)
@@ -459,12 +459,37 @@ void PAL_DispatchException(DWORD64 dwRDI, DWORD64 dwRSI, DWORD64 dwRDX, DWORD64
     }
 #endif // FEATURE_PAL_SXS
 
-    EXCEPTION_POINTERS pointers;
-    pointers.ExceptionRecord = pExRecord;
-    pointers.ContextRecord = pContext;
+    CONTEXT *contextRecord;
+    EXCEPTION_RECORD *exceptionRecord;
+    AllocateExceptionRecords(&exceptionRecord, &contextRecord);
 
-    TRACE("PAL_DispatchException(EC %08x EA %p)\n", pExRecord->ExceptionCode, pExRecord->ExceptionAddress);
-    SEHProcessException(&pointers);
+    *contextRecord = *pContext;
+    *exceptionRecord = *pExRecord;
+
+    contextRecord->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
+    bool continueExecution;
+
+    {
+        // The exception object takes ownership of the exceptionRecord and contextRecord
+        PAL_SEHException exception(exceptionRecord, contextRecord);
+
+        TRACE("PAL_DispatchException(EC %08x EA %p)\n", pExRecord->ExceptionCode, pExRecord->ExceptionAddress);
+
+        continueExecution = SEHProcessException(&exception);
+        if (continueExecution)
+        {
+            // Make a copy of the exception records so that we can free them before restoring the context
+            *pContext = *contextRecord;
+            *pExRecord = *exceptionRecord;
+        }
+
+        // The exception records are destroyed by the PAL_SEHException destructor now.
+    }
+
+    if (continueExecution)
+    {
+        RtlRestoreContext(pContext, pExRecord);
+    }
 
     // Send the forward request to the exception thread to process
     MachMessage sSendMessage;
index c8710de..c00be51 100644 (file)
@@ -564,6 +564,85 @@ BOOL PAL_VirtualUnwindOutOfProc(CONTEXT *context,
 #endif // !HAVE_UNW_GET_ACCESSORS
 #endif // _AMD64_
 
+struct ExceptionRecords
+{
+    CONTEXT ContextRecord;
+    EXCEPTION_RECORD ExceptionRecord;
+};
+
+// Max number of fallback contexts that are used when malloc fails to allocate ExceptionRecords structure
+static const int MaxFallbackContexts = sizeof(size_t) * 8;
+// Array of fallback contexts
+static ExceptionRecords s_fallbackContexts[MaxFallbackContexts];
+// Bitmap used for allocating fallback contexts - bits set to 1 represent already allocated context.
+static volatile size_t s_allocatedContextsBitmap = 0;
+
+/*++
+Function:
+    AllocateExceptionRecords
+
+    Allocate EXCEPTION_RECORD and CONTEXT structures for an exception.
+Parameters:
+    exceptionRecord - output pointer to the allocated exception record
+    contextRecord - output pointer to the allocated context record
+--*/
+VOID
+AllocateExceptionRecords(EXCEPTION_RECORD** exceptionRecord, CONTEXT** contextRecord)
+{
+    ExceptionRecords* records;
+    if (posix_memalign((void**)&records, alignof(ExceptionRecords), sizeof(ExceptionRecords)) != 0)
+    {
+        size_t bitmap;
+        size_t newBitmap;
+        int index;
+
+        do
+        {
+            bitmap = s_allocatedContextsBitmap;
+            index = __builtin_ffsl(~bitmap) - 1;
+            if (index < 0)
+            {
+                PROCAbort();
+            }
+
+            newBitmap = bitmap | ((size_t)1 << index);
+        }
+        while (__sync_val_compare_and_swap(&s_allocatedContextsBitmap, bitmap, newBitmap) != bitmap);
+
+        records = &s_fallbackContexts[index];
+    }
+
+    *contextRecord = &records->ContextRecord;
+    *exceptionRecord = &records->ExceptionRecord;
+}
+
+/*++
+Function:
+    PAL_FreeExceptionRecords
+
+    Free EXCEPTION_RECORD and CONTEXT structures of an exception that were allocated by the
+    AllocateExceptionRecords.
+Parameters:
+    exceptionRecord - exception record
+    contextRecord - context record
+--*/
+VOID
+PALAPI
+PAL_FreeExceptionRecords(IN EXCEPTION_RECORD *exceptionRecord, IN CONTEXT *contextRecord)
+{
+    // Both records are allocated at once and the allocated memory starts at the contextRecord
+    ExceptionRecords* records = (ExceptionRecords*)contextRecord;
+    if ((records >= &s_fallbackContexts[0]) && (records < &s_fallbackContexts[MaxFallbackContexts]))
+    {
+        int index = records - &s_fallbackContexts[0];
+        __sync_fetch_and_and(&s_allocatedContextsBitmap, ~((size_t)1 << index));
+    }
+    else
+    {
+        free(contextRecord);
+    }
+}
+
 /*++
 Function:
     RtlpRaiseException
@@ -574,7 +653,7 @@ Parameters:
 Note:
     The name of this function and the name of the ExceptionRecord 
     parameter is used in the sos lldb plugin code to read the exception
-    record. See coreclr\src\ToolBox\SOS\lldbplugin\debugclient.cpp.
+    record. See coreclr\src\ToolBox\SOS\lldbplugin\services.cpp.
 
     This function must not be inlined or optimized so the below PAL_VirtualUnwind
     calls end up with RaiseException caller's context and so the above debugger 
@@ -584,32 +663,9 @@ PAL_NORETURN
 __attribute__((noinline))
 __attribute__((optnone))
 static void 
-RtlpRaiseException(EXCEPTION_RECORD *ExceptionRecord)
+RtlpRaiseException(EXCEPTION_RECORD *ExceptionRecord, CONTEXT *ContextRecord)
 {
-    // Capture the context of RtlpRaiseException.
-    CONTEXT ContextRecord;
-    ZeroMemory(&ContextRecord, sizeof(CONTEXT));
-    ContextRecord.ContextFlags = CONTEXT_FULL;
-    CONTEXT_CaptureContext(&ContextRecord);
-
-    // Find the caller of RtlpRaiseException.  
-    PAL_VirtualUnwind(&ContextRecord, NULL);
-
-    // The frame we're looking at now is RaiseException. We have to unwind one 
-    // level further to get the actual context user code could be resumed at.
-    PAL_VirtualUnwind(&ContextRecord, NULL);
-
-#if defined(_X86_)
-    ExceptionRecord->ExceptionAddress = (void *) ContextRecord.Eip;
-#elif defined(_AMD64_)
-    ExceptionRecord->ExceptionAddress = (void *) ContextRecord.Rip;
-#elif defined(_ARM_) || defined(_ARM64_)
-    ExceptionRecord->ExceptionAddress = (void *) ContextRecord.Pc;
-#else
-#error unsupported architecture
-#endif
-
-    throw PAL_SEHException(ExceptionRecord, &ContextRecord);
+    throw PAL_SEHException(ExceptionRecord, ContextRecord);
 }
 
 /*++
@@ -619,6 +675,7 @@ Function:
 See MSDN doc.
 --*/
 // no PAL_NORETURN, as callers must assume this can return for continuable exceptions.
+__attribute__((noinline))
 VOID
 PALAPI
 RaiseException(IN DWORD dwExceptionCode,
@@ -648,20 +705,34 @@ RaiseException(IN DWORD dwExceptionCode,
         nNumberOfArguments = EXCEPTION_MAXIMUM_PARAMETERS;
     }
 
-    EXCEPTION_RECORD exceptionRecord;
-    ZeroMemory(&exceptionRecord, sizeof(EXCEPTION_RECORD));
+    CONTEXT *contextRecord;
+    EXCEPTION_RECORD *exceptionRecord;
+    AllocateExceptionRecords(&exceptionRecord, &contextRecord);
 
-    exceptionRecord.ExceptionCode = dwExceptionCode;
-    exceptionRecord.ExceptionFlags = dwExceptionFlags;
-    exceptionRecord.ExceptionRecord = NULL;
-    exceptionRecord.ExceptionAddress = NULL; // will be set by RtlpRaiseException
-    exceptionRecord.NumberParameters = nNumberOfArguments;
+    ZeroMemory(exceptionRecord, sizeof(EXCEPTION_RECORD));
+
+    exceptionRecord->ExceptionCode = dwExceptionCode;
+    exceptionRecord->ExceptionFlags = dwExceptionFlags;
+    exceptionRecord->ExceptionRecord = NULL;
+    exceptionRecord->ExceptionAddress = NULL; // will be set by RtlpRaiseException
+    exceptionRecord->NumberParameters = nNumberOfArguments;
     if (nNumberOfArguments)
     {
-        CopyMemory(exceptionRecord.ExceptionInformation, lpArguments,
+        CopyMemory(exceptionRecord->ExceptionInformation, lpArguments,
                    nNumberOfArguments * sizeof(ULONG_PTR));
     }
-    RtlpRaiseException(&exceptionRecord);
+
+    // Capture the context of RaiseException.
+    ZeroMemory(contextRecord, sizeof(CONTEXT));
+    contextRecord->ContextFlags = CONTEXT_FULL;
+    CONTEXT_CaptureContext(contextRecord);
+
+    // We have to unwind one level to get the actual context user code could be resumed at.
+    PAL_VirtualUnwind(contextRecord, NULL);
+
+    exceptionRecord->ExceptionAddress = (void *)CONTEXTGetPC(contextRecord);
+
+    RtlpRaiseException(exceptionRecord, contextRecord);
 
     LOGEXIT("RaiseException returns\n");
 }
index 346e94e..5aaa18f 100644 (file)
@@ -39,6 +39,7 @@ Abstract:
 #include <unistd.h>
 #include <pthread.h>
 #include <stdlib.h>
+#include <utility>
 
 using namespace CorUnix;
 
@@ -177,7 +178,7 @@ PAL_ThrowExceptionFromContext(CONTEXT* context, PAL_SEHException* ex)
     // frames that will become obsolete by the ThrowExceptionFromContextInternal and the ThrowExceptionHelper
     // could overwrite the "ex" object by stack e.g. when allocating the low level exception object for "throw".
     static __thread BYTE threadLocalExceptionStorage[sizeof(PAL_SEHException)];
-    ThrowExceptionFromContextInternal(context, new (threadLocalExceptionStorage) PAL_SEHException(*ex));
+    ThrowExceptionFromContextInternal(context, new (threadLocalExceptionStorage) PAL_SEHException(std::move(*ex)));
 }
 
 /*++
@@ -193,44 +194,46 @@ Parameters:
 extern "C"
 void ThrowExceptionHelper(PAL_SEHException* ex)
 {
-    throw *ex;
+    throw std::move(*ex);
 }
 
 /*++
 Function:
     SEHProcessException
 
-    Build the PAL exception and sent it to any handler registered.
+    Send the PAL exception to any handler registered.
 
 Parameters:
-    PEXCEPTION_POINTERS pointers
+    PAL_SEHException* exception
 
 Return value:
-    Returns only if the exception is unhandled
+    Returns TRUE if the exception happened in managed code and the execution should 
+    continue (with possibly modified context).
+    Returns FALSE if the exception happened in managed code and it was not handled.
+    In case the exception was handled by calling a catch handler, it doesn't return at all.
 --*/
-VOID
-SEHProcessException(PEXCEPTION_POINTERS pointers)
+BOOL
+SEHProcessException(PAL_SEHException* exception)
 {
-    pointers->ContextRecord->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
+    CONTEXT* contextRecord = exception->GetContextRecord();
+    EXCEPTION_RECORD* exceptionRecord = exception->GetExceptionRecord();
 
-    if (!IsInDebugBreak(pointers->ExceptionRecord->ExceptionAddress))
+    if (!IsInDebugBreak(exceptionRecord->ExceptionAddress))
     {
-        PAL_SEHException exception(pointers->ExceptionRecord, pointers->ContextRecord);
-
         if (g_hardwareExceptionHandler != NULL)
         {
             _ASSERTE(g_safeExceptionCheckFunction != NULL);
             // Check if it is safe to handle the hardware exception (the exception happened in managed code
             // or in a jitter helper or it is a debugger breakpoint)
-            if (g_safeExceptionCheckFunction(pointers->ContextRecord, pointers->ExceptionRecord))
+            if (g_safeExceptionCheckFunction(contextRecord, exceptionRecord))
             {
-                if (pointers->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
+                if (exceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
                 {
                     // Check if the failed access has hit a stack guard page. In such case, it
                     // was a stack probe that detected that there is not enough stack left.
                     void* stackLimit = CPalThread::GetStackLimit();
                     void* stackGuard = (void*)((size_t)stackLimit - getpagesize());
-                    void* violationAddr = (void*)pointers->ExceptionRecord->ExceptionInformation[1];
+                    void* violationAddr = (void*)exceptionRecord->ExceptionInformation[1];
                     if ((violationAddr >= stackGuard) && (violationAddr < stackLimit))
                     {
                         // The exception happened in the page right below the stack limit,
@@ -240,19 +243,24 @@ SEHProcessException(PEXCEPTION_POINTERS pointers)
                     }
                 }
 
-                // The following callback returns only in case the exception was a single step or 
-                // a breakpoint and it was not handled by the debugger.
-                g_hardwareExceptionHandler(&exception);
+                if (g_hardwareExceptionHandler(exception))
+                {
+                    // The exception happened in managed code and the execution should continue.
+                    return TRUE;
+                }
+
+                // The exception was a single step or a breakpoint and it was not handled by the debugger.
             }
         }
 
         if (CatchHardwareExceptionHolder::IsEnabled())
         {
-            PAL_ThrowExceptionFromContext(&exception.ContextRecord, &exception);
+            PAL_ThrowExceptionFromContext(exception->GetContextRecord(), exception);
         }
     }
 
-    // Unhandled hardware exception pointers->ExceptionRecord->ExceptionCode at pointers->ExceptionRecord->ExceptionAddress 
+    // Unhandled hardware exception pointers->ExceptionRecord->ExceptionCode at pointers->ExceptionRecord->ExceptionAddress
+    return FALSE;
 }
 
 /*++
index adeada1..8dd75ac 100644 (file)
@@ -74,7 +74,7 @@ static void sigint_handler(int code, siginfo_t *siginfo, void *context);
 static void sigquit_handler(int code, siginfo_t *siginfo, void *context);
 static void sigterm_handler(int code, siginfo_t *siginfo, void *context);
 
-static void common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext, int numParams, ...);
+static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext, int numParams, ...);
 
 #ifdef INJECT_ACTIVATION_SIGNAL
 static void inject_activation_handler(int code, siginfo_t *siginfo, void *context);
@@ -217,7 +217,10 @@ static void sigill_handler(int code, siginfo_t *siginfo, void *context)
 {
     if (PALIsInitialized())
     {
-        common_signal_handler(code, siginfo, context, 0);
+        if (common_signal_handler(code, siginfo, context, 0))
+        {
+            return;
+        }
     }
 
     if (g_previous_sigill.sa_sigaction != NULL)
@@ -248,7 +251,10 @@ static void sigfpe_handler(int code, siginfo_t *siginfo, void *context)
 {
     if (PALIsInitialized())
     {
-        common_signal_handler(code, siginfo, context, 0);
+        if (common_signal_handler(code, siginfo, context, 0))
+        {
+            return;
+        }
     }
 
     if (g_previous_sigfpe.sa_sigaction != NULL)
@@ -282,7 +288,10 @@ static void sigsegv_handler(int code, siginfo_t *siginfo, void *context)
         // TODO: First variable parameter says whether a read (0) or write (non-0) caused the
         // fault. We must disassemble the instruction at record.ExceptionAddress
         // to correctly fill in this value.
-        common_signal_handler(code, siginfo, context, 2, (size_t)0, (size_t)siginfo->si_addr);
+        if (common_signal_handler(code, siginfo, context, 2, (size_t)0, (size_t)siginfo->si_addr))
+        {
+            return;
+        }
     }
 
     if (g_previous_sigsegv.sa_sigaction != NULL)
@@ -313,7 +322,10 @@ static void sigtrap_handler(int code, siginfo_t *siginfo, void *context)
 {
     if (PALIsInitialized())
     {
-        common_signal_handler(code, siginfo, context, 0);
+        if (common_signal_handler(code, siginfo, context, 0))
+        {
+            return;
+        }
     }
 
     if (g_previous_sigtrap.sa_sigaction != NULL)
@@ -348,7 +360,10 @@ static void sigbus_handler(int code, siginfo_t *siginfo, void *context)
         // TODO: First variable parameter says whether a read (0) or write (non-0) caused the
         // fault. We must disassemble the instruction at record.ExceptionAddress
         // to correctly fill in this value.
-        common_signal_handler(code, siginfo, context, 2, (size_t)0, (size_t)siginfo->si_addr);
+        if (common_signal_handler(code, siginfo, context, 2, (size_t)0, (size_t)siginfo->si_addr))
+        {
+            return;
+        }
     }
 
     if (g_previous_sigbus.sa_sigaction != NULL)
@@ -562,47 +577,44 @@ Parameters :
     int numParams : number of variable parameters of the exception
     ... : variable parameters of the exception (each of size_t type)
 
-    (no return value)
+    Returns true if the execution should continue or false if the exception was unhandled
 Note:
     the "pointers" parameter should contain a valid exception record pointer,
     but the ContextRecord pointer will be overwritten.
 --*/
-static void common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext, int numParams, ...)
+static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext, int numParams, ...)
 {
     sigset_t signal_set;
-    CONTEXT context;
-    EXCEPTION_RECORD record;
-    EXCEPTION_POINTERS pointers;
+    CONTEXT *contextRecord;
+    EXCEPTION_RECORD *exceptionRecord;
     native_context_t *ucontext;
 
     ucontext = (native_context_t *)sigcontext;
 
-    record.ExceptionCode = CONTEXTGetExceptionCodeForSignal(siginfo, ucontext);
-    record.ExceptionFlags = EXCEPTION_IS_SIGNAL;
-    record.ExceptionRecord = NULL;
-    record.ExceptionAddress = GetNativeContextPC(ucontext);
-    record.NumberParameters = numParams;
+    AllocateExceptionRecords(&exceptionRecord, &contextRecord);
+
+    exceptionRecord->ExceptionCode = CONTEXTGetExceptionCodeForSignal(siginfo, ucontext);
+    exceptionRecord->ExceptionFlags = EXCEPTION_IS_SIGNAL;
+    exceptionRecord->ExceptionRecord = NULL;
+    exceptionRecord->ExceptionAddress = GetNativeContextPC(ucontext);
+    exceptionRecord->NumberParameters = numParams;
 
     va_list params;
     va_start(params, numParams);
 
     for (int i = 0; i < numParams; i++)
     {
-        record.ExceptionInformation[i] = va_arg(params, size_t);
+        exceptionRecord->ExceptionInformation[i] = va_arg(params, size_t);
     }
 
-    pointers.ExceptionRecord = &record;
-
     // Pre-populate context with data from current frame, because ucontext doesn't have some data (e.g. SS register)
     // which is required for restoring context
-    RtlCaptureContext(&context);
+    RtlCaptureContext(contextRecord);
 
     // Fill context record with required information. from pal.h:
     // On non-Win32 platforms, the CONTEXT pointer in the
     // PEXCEPTION_POINTERS will contain at least the CONTEXT_CONTROL registers.
-    CONTEXTFromNativeContext(ucontext, &context, CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT);
-
-    pointers.ContextRecord = &context;
+    CONTEXTFromNativeContext(ucontext, contextRecord, CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT);
 
     /* Unmask signal so we can receive it again */
     sigemptyset(&signal_set);
@@ -613,7 +625,18 @@ static void common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext
         ASSERT("pthread_sigmask failed; error number is %d\n", sigmaskRet);
     }
 
-    SEHProcessException(&pointers);
+    contextRecord->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
+    // The exception object takes ownership of the exceptionRecord and contextRecord
+    PAL_SEHException exception(exceptionRecord, contextRecord);
+
+    if (SEHProcessException(&exception))
+    {
+        // Exception handling may have modified the context, so update it.
+        CONTEXTToNativeContext(contextRecord, ucontext);
+        return true;
+    }
+
+    return false;
 }
 
 /*++
index 4fa3835..3ac93d6 100644 (file)
@@ -63,16 +63,30 @@ SEHCleanup();
 Function:
     SEHProcessException
 
-    Build the PAL exception and sent it to any handler registered.
+    Send the PAL exception to any handler registered.
 
 Parameters:
-    None
+    PAL_SEHException* exception
 
 Return value:
-    Does not return
+    Returns TRUE if the exception happened in managed code and the execution should 
+    continue (with possibly modified context).
+    Returns FALSE if the exception happened in managed code and it was not handled.
+    In case the exception was handled by calling a catch handler, it doesn't return at all.
 --*/
-VOID 
-SEHProcessException(PEXCEPTION_POINTERS pointers);
+BOOL 
+SEHProcessException(PAL_SEHException* exception);
+
+/*++
+Function:
+    AllocateExceptionRecords
+
+Parameters:
+    exceptionRecord - output pointer to the allocated Windows exception record
+    contextRecord - output pointer to the allocated Windows context record
+--*/
+VOID
+AllocateExceptionRecords(EXCEPTION_RECORD** exceptionRecord, CONTEXT** contextRecord);
 
 #if !HAVE_MACH_EXCEPTIONS
 // TODO: Implement for Mach exceptions.  Not in CoreCLR surface area.
index 8aa7a0f..6146908 100644 (file)
@@ -45,7 +45,7 @@ int DllTest1()
         }
 
         // Validate that the faulting address is correct; the contents of "p" (0x11).
-        if (ex.ExceptionRecord.ExceptionInformation[1] != 0x11)
+        if (ex.GetExceptionRecord()->ExceptionInformation[1] != 0x11)
         {
             Fail("ERROR: PAL_EXCEPT ExceptionInformation[1] != 0x11\n");
         }
index fe5c8e0..1e85821 100644 (file)
@@ -45,7 +45,7 @@ int DllTest2()
         }
 
         // Validate that the faulting address is correct; the contents of "p" (0x22).
-        if (ex.ExceptionRecord.ExceptionInformation[1] != 0x22)
+        if (ex.GetExceptionRecord()->ExceptionInformation[1] != 0x22)
         {
             Fail("ERROR: PAL_EXCEPT ExceptionInformation[1] != 0x22\n");
         }
index 3b60de0..a99a20b 100644 (file)
@@ -94,7 +94,7 @@ bool FixNonvolatileRegisters(UINT_PTR  uOriginalSP,
 MethodDesc * GetUserMethodForILStub(Thread * pThread, UINT_PTR uStubSP, MethodDesc * pILStubMD, Frame ** ppFrameOut);
 
 #ifdef FEATURE_PAL
-VOID PALAPI HandleHardwareException(PAL_SEHException* ex);
+BOOL PALAPI HandleHardwareException(PAL_SEHException* ex);
 BOOL PALAPI IsSafeToHandleHardwareException(PCONTEXT contextRecord, PEXCEPTION_RECORD exceptionRecord);
 #endif // FEATURE_PAL
 
@@ -4367,7 +4367,7 @@ VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartConte
     PVOID handlerData;
 
     // Indicate that we are performing second pass.
-    ex.ExceptionRecord.ExceptionFlags = EXCEPTION_UNWINDING;
+    ex.GetExceptionRecord()->ExceptionFlags = EXCEPTION_UNWINDING;
 
     currentFrameContext = unwindStartContext;
     callerFrameContext = &contextStorage;
@@ -4415,14 +4415,18 @@ VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartConte
             dispatcherContext.EstablisherFrame = establisherFrame;
             dispatcherContext.ContextRecord = currentFrameContext;
 
+            EXCEPTION_RECORD* exceptionRecord = ex.GetExceptionRecord();
+
             if (establisherFrame == ex.TargetFrameSp)
             {
                 // We have reached the frame that will handle the exception.
-                ex.ExceptionRecord.ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
+                ex.GetExceptionRecord()->ExceptionFlags |= EXCEPTION_TARGET_UNWIND;
+                ExceptionTracker* pTracker = GetThread()->GetExceptionState()->GetCurrentExceptionTracker();
+                pTracker->TakeExceptionPointersOwnership(&ex);
             }
 
             // Perform unwinding of the current frame
-            disposition = ProcessCLRException(&ex.ExceptionRecord,
+            disposition = ProcessCLRException(exceptionRecord,
                 establisherFrame,
                 currentFrameContext,
                 &dispatcherContext);
@@ -4436,7 +4440,6 @@ VOID UnwindManagedExceptionPass2(PAL_SEHException& ex, CONTEXT* unwindStartConte
             }
             else
             {
-                // TODO: This needs to implemented. Make it fail for now.
                 UNREACHABLE();
             }
         }
@@ -4513,15 +4516,15 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT
     controlPc = GetIP(frameContext);
     unwindStartContext = *frameContext;
 
-    if (!ExecutionManager::IsManagedCode(GetIP(&ex.ContextRecord)))
+    if (!ExecutionManager::IsManagedCode(GetIP(ex.GetContextRecord())))
     {
         // This is the first time we see the managed exception, set its context to the managed frame that has caused
         // the exception to be thrown
-        ex.ContextRecord = *frameContext;
-        ex.ExceptionRecord.ExceptionAddress = (VOID*)controlPc;
+        *ex.GetContextRecord() = *frameContext;
+        ex.GetExceptionRecord()->ExceptionAddress = (VOID*)controlPc;
     }
 
-    ex.ExceptionRecord.ExceptionFlags = 0;
+    ex.GetExceptionRecord()->ExceptionFlags = 0;
 
     memset(&dispatcherContext, 0, sizeof(DISPATCHER_CONTEXT));
     disposition = ExceptionContinueSearch;
@@ -4562,9 +4565,9 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT
             dispatcherContext.ContextRecord = frameContext;
 
             // Find exception handler in the current frame
-            disposition = ProcessCLRException(&ex.ExceptionRecord,
+            disposition = ProcessCLRException(ex.GetExceptionRecord(),
                 establisherFrame,
-                &ex.ContextRecord,
+                ex.GetContextRecord(),
                 &dispatcherContext);
 
             if (disposition == ExceptionContinueSearch)
@@ -4657,7 +4660,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHar
             // If the exception is hardware exceptions, we use the exception's context record directly
             if (isHardwareException)
             {
-                frameContext = ex.ContextRecord;
+                frameContext = *ex.GetContextRecord();
             }
             else
             {
@@ -4696,7 +4699,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHar
         catch (PAL_SEHException& ex2)
         {
             isHardwareException = false;
-            ex = ex2;
+            ex = std::move(ex2);
         }
 
     }
@@ -4717,7 +4720,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHar
         if (pEHTracker == NULL)
         {
             CorruptionSeverity severity = NotCorrupting;
-            if (CEHelper::IsProcessCorruptedStateException(ex.ExceptionRecord.ExceptionCode))
+            if (CEHelper::IsProcessCorruptedStateException(ex.GetExceptionRecord()->ExceptionCode))
             {
                 severity = ProcessCorrupting;
             }
@@ -5082,19 +5085,20 @@ BOOL PALAPI IsSafeToHandleHardwareException(PCONTEXT contextRecord, PEXCEPTION_R
         IsIPInMarkedJitHelper(controlPc));
 }
 
-VOID PALAPI HandleHardwareException(PAL_SEHException* ex)
+BOOL PALAPI HandleHardwareException(PAL_SEHException* ex)
 {
-    _ASSERTE(IsSafeToHandleHardwareException(&ex->ContextRecord, &ex->ExceptionRecord));
+    _ASSERTE(IsSafeToHandleHardwareException(ex->GetContextRecord(), ex->GetExceptionRecord()));
 
-    if (ex->ExceptionRecord.ExceptionCode != STATUS_BREAKPOINT && ex->ExceptionRecord.ExceptionCode != STATUS_SINGLE_STEP)
+    if (ex->GetExceptionRecord()->ExceptionCode != STATUS_BREAKPOINT && ex->GetExceptionRecord()->ExceptionCode != STATUS_SINGLE_STEP)
     {
         // A hardware exception is handled only if it happened in a jitted code or 
         // in one of the JIT helper functions (JIT_MemSet, ...)
-        PCODE controlPc = GetIP(&ex->ContextRecord);
-        if (ExecutionManager::IsManagedCode(controlPc) && IsGcMarker(ex->ExceptionRecord.ExceptionCode, &ex->ContextRecord))
+        PCODE controlPc = GetIP(ex->GetContextRecord());
+        if (ExecutionManager::IsManagedCode(controlPc) && IsGcMarker(ex->GetExceptionRecord()->ExceptionCode, ex->GetContextRecord()))
         {
-            RtlRestoreContext(&ex->ContextRecord, &ex->ExceptionRecord);
-            UNREACHABLE();
+            // Exception was handled, let the signal handler return to the exception context. Some registers in the context can
+            // have been modified by the GC.
+            return TRUE;
         }
 
 #ifdef _AMD64_
@@ -5104,11 +5108,11 @@ VOID PALAPI HandleHardwareException(PAL_SEHException* ex)
         //
         // Thus, we will attempt to decode the instruction @ RIP to determine if that
         // is the case using the faulting context.
-        if ((ex->ExceptionRecord.ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) &&
-            IsDivByZeroAnIntegerOverflow(&ex->ContextRecord))
+        if ((ex->GetExceptionRecord()->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) &&
+            IsDivByZeroAnIntegerOverflow(ex->GetContextRecord()))
         {
             // The exception was an integer overflow, so augment the exception code.
-            ex->ExceptionRecord.ExceptionCode = EXCEPTION_INT_OVERFLOW;
+            ex->GetExceptionRecord()->ExceptionCode = EXCEPTION_INT_OVERFLOW;
         }
 #endif //_AMD64_
 
@@ -5125,16 +5129,16 @@ VOID PALAPI HandleHardwareException(PAL_SEHException* ex)
                 // managed code that called the helper, otherwise the stack
                 // walker would skip all the managed frames upto the next
                 // explicit frame.
-                PAL_VirtualUnwind(&ex->ContextRecord, NULL);
-                ex->ExceptionRecord.ExceptionAddress = (PVOID)GetIP(&ex->ContextRecord);
+                PAL_VirtualUnwind(ex->GetContextRecord(), NULL);
+                ex->GetExceptionRecord()->ExceptionAddress = (PVOID)GetIP(ex->GetContextRecord());
             }
 #ifdef _TARGET_ARM_
             else if (IsIPinVirtualStub(controlPc)) 
             {
-                AdjustContextForVirtualStub(&ex->ExceptionRecord, &ex->ContextRecord);
+                AdjustContextForVirtualStub(ex->GetExceptionRecord(), ex->GetContextRecord());
             }
 #endif
-            fef.InitAndLink(&ex->ContextRecord);
+            fef.InitAndLink(ex->GetContextRecord());
         }
 
         DispatchManagedException(*ex, true /* isHardwareException */);
@@ -5146,24 +5150,27 @@ VOID PALAPI HandleHardwareException(PAL_SEHException* ex)
         Thread *pThread = GetThread();
         if (pThread != NULL && g_pDebugInterface != NULL)
         {
-            if (ex->ExceptionRecord.ExceptionCode == STATUS_BREAKPOINT)
+            if (ex->GetExceptionRecord()->ExceptionCode == STATUS_BREAKPOINT)
             {
                 // If this is breakpoint context, it is set up to point to an instruction after the break instruction.
                 // But debugger expects to see context that points to the break instruction, that's why we correct it.
-                SetIP(&ex->ContextRecord, GetIP(&ex->ContextRecord) - CORDbg_BREAK_INSTRUCTION_SIZE);
-                ex->ExceptionRecord.ExceptionAddress = (void *)GetIP(&ex->ContextRecord);
+                SetIP(ex->GetContextRecord(), GetIP(ex->GetContextRecord()) - CORDbg_BREAK_INSTRUCTION_SIZE);
+                ex->GetExceptionRecord()->ExceptionAddress = (void *)GetIP(ex->GetContextRecord());
             }
 
-            if (g_pDebugInterface->FirstChanceNativeException(&ex->ExceptionRecord,
-                &ex->ContextRecord,
-                ex->ExceptionRecord.ExceptionCode,
+            if (g_pDebugInterface->FirstChanceNativeException(ex->GetExceptionRecord(),
+                ex->GetContextRecord(),
+                ex->GetExceptionRecord()->ExceptionCode,
                 pThread))
             {
-                RtlRestoreContext(&ex->ContextRecord, &ex->ExceptionRecord);
-                UNREACHABLE();
+                // Exception was handled, let the signal handler return to the exception context. Some registers in the context can
+                // have been modified by the debugger.
+                return TRUE;
             }
         }
     }
+
+    return FALSE;
 }
 
 #endif // FEATURE_PAL
@@ -6997,8 +7004,14 @@ void ExceptionTracker::ReleaseResources()
 
 #ifndef FEATURE_PAL 
     // Clear any held Watson Bucketing details
-    GetWatsonBucketTracker()->ClearWatsonBucketDetails();   
-#endif // !FEATURE_PAL 
+    GetWatsonBucketTracker()->ClearWatsonBucketDetails();
+#else // !FEATURE_PAL
+    if (m_fOwnsExceptionPointers)
+    {
+        PAL_FreeExceptionRecords(m_ptrs.ExceptionRecord, m_ptrs.ContextRecord);
+        m_fOwnsExceptionPointers = FALSE;
+    }
+#endif // !FEATURE_PAL
 #endif // DACCESS_COMPILE
 }
 
index 2a8181b..340cbc0 100644 (file)
@@ -108,6 +108,10 @@ public:
         m_pInitialExplicitFrame = NULL;
         m_pLimitFrame = NULL;
         m_csfEHClauseOfCollapsedTracker.Clear();
+
+#ifdef FEATURE_PAL
+        m_fOwnsExceptionPointers = FALSE;
+#endif
     }
 
     ExceptionTracker(DWORD_PTR             dwExceptionPc,
@@ -167,6 +171,10 @@ public:
         m_sfLastUnwoundEstablisherFrame.Clear();
         m_pInitialExplicitFrame = NULL;
         m_csfEHClauseOfCollapsedTracker.Clear();
+
+#ifdef FEATURE_PAL
+        m_fOwnsExceptionPointers = FALSE;
+#endif
     }
 
     ~ExceptionTracker()
@@ -385,6 +393,16 @@ public:
 
     bool IsStackOverflowException();
 
+#ifdef FEATURE_PAL
+    void TakeExceptionPointersOwnership(PAL_SEHException* ex)
+    {
+        _ASSERTE(ex->GetExceptionRecord() == m_ptrs.ExceptionRecord);
+        _ASSERTE(ex->GetContextRecord() == m_ptrs.ContextRecord);
+        ex->Clear();
+        m_fOwnsExceptionPointers = TRUE;
+    }
+#endif // FEATURE_PAL
+
 private:
     DWORD_PTR
         CallHandler(UINT_PTR                dwHandlerStartPC,
@@ -700,6 +718,9 @@ private: ;
 
     StackRange              m_ScannedStackRange;
     DAC_EXCEPTION_POINTERS  m_ptrs;
+#ifdef FEATURE_PAL
+    BOOL                    m_fOwnsExceptionPointers;
+#endif
     OBJECTHANDLE            m_hThrowable;
     StackTraceInfo          m_StackTraceInfo;
     UINT_PTR                m_uCatchToCallPC;
index e705311..efed993 100644 (file)
@@ -308,7 +308,7 @@ VOID DECLSPEC_NORETURN DispatchManagedException(PAL_SEHException& ex, bool isHar
         }                                           \
         catch (PAL_SEHException& ex)                \
         {                                           \
-            exCopy = ex;                            \
+            exCopy = std::move(ex);                 \
             hasCaughtException = true;              \
         }                                           \
         if (hasCaughtException)                     \