[x86/Linux] Initial patch for EH funclet (dotnet/coreclr#9601)
authorHanjoung Lee <waterets@gmail.com>
Fri, 24 Feb 2017 07:02:21 +0000 (16:02 +0900)
committerJan Kotas <jkotas@microsoft.com>
Fri, 24 Feb 2017 07:02:21 +0000 (23:02 -0800)
- Generate a simple EH funclet frame and support SP-based stack unwinding for funclets.
- Introduce assembly helpers : CallEHFunclet and CallEHFilterFunclet

Commit migrated from https://github.com/dotnet/coreclr/commit/77f2ad4c07b24fcfe1298dd7cd260876ef46e6c3

src/coreclr/src/jit/codegencommon.cpp
src/coreclr/src/jit/codegenxarch.cpp
src/coreclr/src/pal/inc/unixasmmacrosx86.inc
src/coreclr/src/pal/src/arch/i386/exceptionhelper.S
src/coreclr/src/pal/src/exception/seh.cpp
src/coreclr/src/vm/CMakeLists.txt
src/coreclr/src/vm/eetwain.cpp
src/coreclr/src/vm/exceptionhandling.cpp
src/coreclr/src/vm/exceptionhandling.h
src/coreclr/src/vm/i386/ehhelpers.S [new file with mode: 0644]

index 0c4cba4..75de420 100644 (file)
@@ -10316,6 +10316,22 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
 /*****************************************************************************
  *
  *  Generates code for an EH funclet prolog.
+ *
+ *
+ *  Funclets have the following incoming arguments:
+ *
+ *      catch/filter-handler: eax = the exception object that was caught (see GT_CATCH_ARG)
+ *      filter:               eax = the exception object that was caught (see GT_CATCH_ARG)
+ *      finally/fault:        none
+ *
+ *  Funclets set the following registers on exit:
+ *
+ *      catch/filter-handler: eax = the address at which execution should resume (see BBJ_EHCATCHRET)
+ *      filter:               eax = non-zero if the handler should handle the exception, zero otherwise (see GT_RETFILT)
+ *      finally/fault:        none
+ *
+ *  Funclet prolog/epilog sequence and funclet frame layout are TBD.
+ *
  */
 
 void CodeGen::genFuncletProlog(BasicBlock* block)
@@ -10331,10 +10347,13 @@ void CodeGen::genFuncletProlog(BasicBlock* block)
 
     compiler->unwindBegProlog();
 
-    // TODO Save callee-saved registers
-
     // This is the end of the OS-reported prolog for purposes of unwinding
     compiler->unwindEndProlog();
+
+    // TODO We may need EBP restore sequence here if we introduce PSPSym
+
+    // Add a padding for 16-byte alignment
+    inst_RV_IV(INS_sub, REG_SPBASE, 12, EA_PTRSIZE);
 }
 
 /*****************************************************************************
@@ -10353,7 +10372,8 @@ void CodeGen::genFuncletEpilog()
 
     ScopedSetVariable<bool> _setGeneratingEpilog(&compiler->compGeneratingEpilog, true);
 
-    // TODO Restore callee-saved registers
+    // Revert a padding that was added for 16-byte alignment
+    inst_RV_IV(INS_add, REG_SPBASE, 12, EA_PTRSIZE);
 
     instGen_Return(0);
 }
index 758156b..acf7a0d 100644 (file)
@@ -241,7 +241,9 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block)
     if ((compiler->lvaPSPSym == BAD_VAR_NUM) ||
         (!compiler->compLocallocUsed && (compiler->funCurrentFunc()->funKind == FUNC_ROOT)))
     {
+#ifndef UNIX_X86_ABI
         inst_RV_RV(INS_mov, REG_ARG_0, REG_SPBASE, TYP_I_IMPL);
+#endif // !UNIX_X86_ABI
     }
     else
     {
index 77b3a63..61ad946 100644 (file)
@@ -66,6 +66,18 @@ C_FUNC(\Name\()_End):
         pop ebp
 .endm
 
+.macro ESP_PROLOG_BEG
+.endm
+
+.macro ESP_PROLOG_END
+.endm
+
+.macro ESP_EPILOG_BEG
+.endm
+
+.macro ESP_EPILOG_END
+.endm
+
 .macro PREPARE_EXTERNAL_VAR Name, Reg
 .att_syntax
     call  0f
index 4980b89..f0bf8f8 100644 (file)
@@ -19,8 +19,8 @@
 
 LEAF_ENTRY ThrowExceptionFromContextInternal, _TEXT
         push  ebp
-        mov   eax, [esp + 12] // ebx: PAL_SEHException *
-        mov   ebx, [esp + 8]  // eax: CONTEXT *
+        mov   ecx, [esp + 12] // ecx: PAL_SEHException * (first argument for ThrowExceptionHelper)
+        mov   ebx, [esp + 8]  // ebx: CONTEXT *
 
         mov   ebp, [ebx + CONTEXT_Ebp]
         mov   esp, [ebx + CONTEXT_ResumeEsp]
@@ -33,9 +33,6 @@ LEAF_ENTRY ThrowExceptionFromContextInternal, _TEXT
         // the EBP is no longer saved in the current stack frame.
         .cfi_restore ebp
 
-        // Store PAL_SEHException as the first argument
-        push    eax
-
         // Store return address to the stack
         mov     ebx, [ebx + CONTEXT_Eip]
         push    ebx
index 7682e9e..bc74ccc 100644 (file)
@@ -226,7 +226,11 @@ Parameters:
     PAL_SEHException* ex - the exception to throw.
 --*/
 extern "C"
+#ifdef _TARGET_X86_
+void __fastcall ThrowExceptionHelper(PAL_SEHException* ex)
+#else // _TARGET_X86_
 void ThrowExceptionHelper(PAL_SEHException* ex)
+#endif // !_TARGET_X86_
 {
     throw std::move(*ex);
 }
index cdf5a53..0153753 100644 (file)
@@ -368,6 +368,7 @@ else(WIN32)
         )
     elseif(CLR_CMAKE_TARGET_ARCH_I386)
         set(VM_SOURCES_WKS_ARCH_ASM
+            ${ARCH_SOURCES_DIR}/ehhelpers.S
             ${ARCH_SOURCES_DIR}/asmhelpers.S
             ${ARCH_SOURCES_DIR}/jithelp.S
             ${ARCH_SOURCES_DIR}/gmsasm.S
index bf6e1c7..989300d 100644 (file)
@@ -3786,10 +3786,11 @@ void UnwindEbpDoubleAlignFrameProlog(
 /*****************************************************************************/
 
 bool UnwindEbpDoubleAlignFrame(
-        PREGDISPLAY pContext, 
-        hdrInfo * info, 
-        PTR_CBYTE methodStart, 
-        unsigned flags,
+        PREGDISPLAY     pContext,
+        EECodeInfo     *pCodeInfo,
+        hdrInfo        *info,
+        PTR_CBYTE       methodStart,
+        unsigned        flags,
         StackwalkCacheUnwindInfo  *pUnwindInfo) // out-only, perf improvement
 {
     LIMITED_METHOD_CONTRACT;
@@ -3806,6 +3807,25 @@ bool UnwindEbpDoubleAlignFrame(
     {
         TADDR baseSP;
 
+#ifdef WIN64EXCEPTIONS
+        // Funclets' frame pointers(EBP) are always restored so they can access to main function's local variables.
+        // Therefore the value of EBP is invalid for unwinder so we should use ESP instead.
+        // TODO If funclet frame layout is changed from CodeGen::genFuncletProlog() and genFuncletEpilog(),
+        //      we need to change here accordingly. It is likely to have changes when introducing PSPSym.
+        // TODO Currently we assume that ESP of funclet frames is always fixed but actually it could change.
+        if (pCodeInfo->IsFunclet())
+        {
+            baseSP = curESP + 12; // padding for 16byte stack alignment allocated in genFuncletProlog()
+
+            pContext->PCTAddr = baseSP;
+            pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
+
+            pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
+
+            return true;
+        }
+#else // WIN64EXCEPTIONS
+
         FrameType frameType = GetHandlerFrameInfo(info, curEBP,
                                                   curESP, (DWORD) IGNORE_VAL,
                                                   &baseSP);
@@ -3860,6 +3880,7 @@ bool UnwindEbpDoubleAlignFrame(
 
             return true;
         }
+#endif // !WIN64EXCEPTIONS
     }
 
     //
@@ -3990,7 +4011,7 @@ bool UnwindStackFrame(PREGDISPLAY     pContext,
          *  Now we know that have an EBP frame
          */
 
-        if (!UnwindEbpDoubleAlignFrame(pContext, info, methodStart, flags, pUnwindInfo))
+        if (!UnwindEbpDoubleAlignFrame(pContext, pCodeInfo, info, methodStart, flags, pUnwindInfo))
             return false;
     }
 
index 1e51467..d7f22d5 100644 (file)
@@ -19,6 +19,7 @@
 
 #if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) || defined(_TARGET_X86_)
 #define ADJUST_PC_UNWOUND_TO_CALL
+#define USE_CALLER_SP_IN_FUNCLET
 #endif // _TARGET_ARM_ || _TARGET_ARM64_ || _TARGET_X86_
 
 #ifndef DACCESS_COMPILE
@@ -574,7 +575,7 @@ UINT_PTR ExceptionTracker::CallCatchHandler(CONTEXT* pContextRecord, bool* pfAbo
     if (!fIntercepted)
     {
         _ASSERTE(m_uCatchToCallPC != 0 && m_pClauseForCatchToken != NULL);
-        uResumePC = CallHandler(m_uCatchToCallPC, sfStackFp, &m_ClauseForCatch, pMD, Catch ARM_ARG(pContextRecord) ARM64_ARG(pContextRecord));
+        uResumePC = CallHandler(m_uCatchToCallPC, sfStackFp, &m_ClauseForCatch, pMD, Catch X86_ARG(pContextRecord) ARM_ARG(pContextRecord) ARM64_ARG(pContextRecord));
     }
     else
     {
@@ -1289,12 +1290,12 @@ void ExceptionTracker::InitializeCurrentContextForCrawlFrame(CrawlFrame* pcfThis
         pRD->SP = sfEstablisherFrame.SP;
         pRD->ControlPC = pDispatcherContext->ControlPc;
 
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)    
+#ifdef USE_CALLER_SP_IN_FUNCLET
         pcfThisFrame->pRD->IsCallerSPValid = TRUE;
         
         // Assert our first pass assumptions for the Arm/Arm64
         _ASSERTE(sfEstablisherFrame.SP == GetSP(pDispatcherContext->ContextRecord));
-#endif // defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)    
+#endif // USE_CALLER_SP_IN_FUNCLET
 
     }
 
@@ -2872,7 +2873,7 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame(
                                 SetEnclosingClauseInfo(fIsFunclet,
                                                               pcfThisFrame->GetRelOffset(),
                                                               GetSP(pcfThisFrame->GetRegisterSet()->pCallerContext));
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#ifdef USE_CALLER_SP_IN_FUNCLET
                                 // On ARM & ARM64, the OS passes us the CallerSP for the frame for which personality routine has been invoked.
                                 // Since IL filters are invoked in the first pass, we pass this CallerSP to the filter funclet which will
                                 // then lookup the actual frame pointer value using it since we dont have a frame pointer to pass to it
@@ -2887,11 +2888,11 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame(
                                 _ASSERTE(pCurRegDisplay->IsCallerContextValid && pCurRegDisplay->IsCallerSPValid);
                                 // 3) CallerSP is intact
                                 _ASSERTE(GetSP(pCurRegDisplay->pCallerContext) == GetRegdisplaySP(pCurRegDisplay));
-#endif // _TARGET_ARM_ || _TARGET_ARM64_
+#endif // USE_CALLER_SP_IN_FUNCLET
                                 {
                                     // CallHandler expects to be in COOP mode.
                                     GCX_COOP();
-                                    dwResult = CallHandler(dwFilterStartPC, sf, &EHClause, pMD, Filter ARM_ARG(pCurRegDisplay->pCallerContext) ARM64_ARG(pCurRegDisplay->pCallerContext));
+                                    dwResult = CallHandler(dwFilterStartPC, sf, &EHClause, pMD, Filter X86_ARG(pCurRegDisplay->pCallerContext) ARM_ARG(pCurRegDisplay->pCallerContext) ARM64_ARG(pCurRegDisplay->pCallerContext));
                                 }
                             }
                             EX_CATCH
@@ -3124,7 +3125,7 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame(
                                 // Since we also forbid GC during second pass, disable it now since
                                 // invocation of managed code can result in a GC.
                                 ENDFORBIDGC();
-                                dwStatus = CallHandler(dwHandlerStartPC, sf, &EHClause, pMD, FaultFinally ARM_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext) ARM64_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext));
+                                dwStatus = CallHandler(dwHandlerStartPC, sf, &EHClause, pMD, FaultFinally X86_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext) ARM_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext) ARM64_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext));
                                 
                                 // Once we return from a funclet, forbid GC again (refer to comment before start of the loop for details)
                                 BEGINFORBIDGC();
@@ -3192,20 +3193,57 @@ lExit:
 
 #define OPTIONAL_SO_CLEANUP_UNWIND(pThread, pFrame)  if (pThread->GetFrame() < pFrame) { UnwindFrameChain(pThread, pFrame); }
 
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#ifdef USE_CALLER_SP_IN_FUNCLET
 // This is an assembly helper that enables us to call into EH funclets.
 EXTERN_C DWORD_PTR STDCALL CallEHFunclet(Object *pThrowable, UINT_PTR pFuncletToInvoke, UINT_PTR *pFirstNonVolReg, UINT_PTR *pFuncletCallerSP);
 
 // This is an assembly helper that enables us to call into EH filter funclets.
 EXTERN_C DWORD_PTR STDCALL CallEHFilterFunclet(Object *pThrowable, TADDR CallerSP, UINT_PTR pFuncletToInvoke, UINT_PTR *pFuncletCallerSP);
-#endif // _TARGET_ARM_ || _TARGET_ARM64_
 
+static inline UINT_PTR CastHandlerFn(HandlerFn *pfnHandler)
+{
+#ifdef _TARGET_ARM_
+    return DataPointerToThumbCode<UINT_PTR, HandlerFn *>(pfnHandler);
+#else
+    return (UINT_PTR)pfnHandler;
+#endif
+}
+
+static inline UINT_PTR *GetFirstNonVolatileRegisterAddress(PCONTEXT pContextRecord)
+{
+#if defined(_TARGET_ARM_)
+    return (UINT_PTR*)&(pContextRecord->R4);
+#elif defined(_TARGET_ARM64_)
+    return (UINT_PTR*)&(pContextRecord->X19);
+#elif defined(_TARGET_X86_)
+    return (UINT_PTR*)&(pContextRecord->Edi);
+#else
+    PORTABILITY_ASSERT("GetFirstNonVolatileRegisterAddress");
+    return NULL;
+#endif
+}
+
+static inline TADDR GetFrameRestoreBase(PCONTEXT pContextRecord)
+{
+#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+    return GetSP(pContextRecord);
+#elif defined(_TARGET_X86_)
+    return pContextRecord->Ebp;
+#else
+    PORTABILITY_ASSERT("GetFrameRestoreBase");
+    return NULL;
+#endif
+}
+
+#endif // USE_CALLER_SP_IN_FUNCLET
+  
 DWORD_PTR ExceptionTracker::CallHandler(
     UINT_PTR               uHandlerStartPC,
     StackFrame             sf,
     EE_ILEXCEPTION_CLAUSE* pEHClause,
     MethodDesc*            pMD,
     EHFuncletType funcletType
+    X86_ARG(PCONTEXT pContextRecord)
     ARM_ARG(PCONTEXT pContextRecord)
     ARM64_ARG(PCONTEXT pContextRecord)
     )
@@ -3260,7 +3298,7 @@ DWORD_PTR ExceptionTracker::CallHandler(
         break;
     }
 
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#ifdef USE_CALLER_SP_IN_FUNCLET
     // Invoke the funclet. We pass throwable only when invoking the catch block.
     // Since the actual caller of the funclet is the assembly helper, pass the reference
     // to the CallerStackFrame instance so that it can be updated.
@@ -3269,14 +3307,9 @@ DWORD_PTR ExceptionTracker::CallHandler(
     if (funcletType != EHFuncletType::Filter)
     {
         dwResumePC = CallEHFunclet((funcletType == EHFuncletType::Catch)?OBJECTREFToObject(throwable):(Object *)NULL, 
-#ifdef _TARGET_ARM_
-                        DataPointerToThumbCode<UINT_PTR, HandlerFn *>(pfnHandler), 
-                        (UINT_PTR*)&(pContextRecord->R4), 
-#else
-                        (UINT_PTR)pfnHandler,
-                        &(pContextRecord->X19), 
-#endif // _TARGET_ARM_
-                        pFuncletCallerSP);
+                                   CastHandlerFn(pfnHandler),
+                                   GetFirstNonVolatileRegisterAddress(pContextRecord),
+                                   pFuncletCallerSP);
     }
     else
     {
@@ -3284,20 +3317,16 @@ DWORD_PTR ExceptionTracker::CallHandler(
         // it will retrieve the framepointer for accessing the locals in the parent
         // method.
         dwResumePC = CallEHFilterFunclet(OBJECTREFToObject(throwable),
-                    GetSP(pContextRecord),
-#ifdef _TARGET_ARM_
-                    DataPointerToThumbCode<UINT_PTR, HandlerFn *>(pfnHandler), 
-#else
-                    (UINT_PTR)pfnHandler,
-#endif // _TARGET_ARM_
-                    pFuncletCallerSP);
+                                         GetFrameRestoreBase(pContextRecord),
+                                         CastHandlerFn(pfnHandler),
+                                         pFuncletCallerSP);
     }
-#else // defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#else // USE_CALLER_SP_IN_FUNCLET
     //
     // Invoke the funclet. 
     //    
     dwResumePC = pfnHandler(sf.SP, OBJECTREFToObject(throwable));
-#endif // _TARGET_ARM_
+#endif // !USE_CALLER_SP_IN_FUNCLET
 
     switch(funcletType)
     {
index 71bcab7..4dba095 100644 (file)
@@ -404,6 +404,7 @@ private:
                     EE_ILEXCEPTION_CLAUSE*  pEHClause,
                     MethodDesc*             pMD,
                     EHFuncletType funcletType
+                    X86_ARG(PT_CONTEXT pContextRecord)
                     ARM_ARG(PT_CONTEXT pContextRecord)
                     ARM64_ARG(PT_CONTEXT pContextRecord)
                     );
diff --git a/src/coreclr/src/vm/i386/ehhelpers.S b/src/coreclr/src/vm/i386/ehhelpers.S
new file mode 100644 (file)
index 0000000..9f959b1
--- /dev/null
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+.intel_syntax noprefix
+#include "unixasmmacros.inc"
+#include "asmconstants.h"
+
+// DWORD_PTR STDCALL CallEHFunclet(Object *pThrowable, UINT_PTR pFuncletToInvoke, UINT_PTR *pFirstNonVolReg, UINT_PTR *pFuncletCallerSP);
+// ESP based frame
+NESTED_ENTRY CallEHFunclet, _TEXT, NoHandler
+
+    ESP_PROLOG_BEG
+    PROLOG_PUSH ebp
+    mov ebp, esp
+    PROLOG_PUSH ebx
+    PROLOG_PUSH esi
+    PROLOG_PUSH edi
+    ESP_PROLOG_END
+
+    // On entry:
+    //
+    // [ebp+ 8] = throwable
+    // [ebp+12] = PC to invoke
+    // [ebp+16] = address of EDI register in CONTEXT record // used to restore the non-volatile registers of CrawlFrame
+    // [ebp+20] = address of the location where the SP of funclet's caller (i.e. this helper) should be saved.
+    //
+
+    // Save the SP of this function
+    mov     eax, [ebp + 20]
+    mov     [eax], esp
+    // Save the funclet PC for later call
+    mov     edx, [ebp + 12]
+    // Pass throwable object to funclet
+    mov     eax, [ebp +  8]
+    // Restore non-volatiles registers
+    mov     ecx, [ebp + 16]
+    mov     edi, [ecx]
+    mov     esi, [ecx +  4]
+    mov     ebx, [ecx +  8]
+    mov     ebp, [ecx + 24]
+    // Invoke the funclet
+    call    edx
+
+    ESP_EPILOG_BEG
+    EPILOG_POP edi
+    EPILOG_POP esi
+    EPILOG_POP ebx
+    EPILOG_POP ebp
+    ESP_EPILOG_END
+
+    ret     16
+
+NESTED_END CallEHFunclet, _TEXT
+
+// DWORD_PTR STDCALL CallEHFilterFunclet(Object *pThrowable, TADDR CallerSP, UINT_PTR pFuncletToInvoke, UINT_PTR *pFuncletCallerSP);
+// ESP based frame
+NESTED_ENTRY CallEHFilterFunclet, _TEXT, NoHandler
+
+    ESP_PROLOG_BEG
+    PROLOG_PUSH ebp
+    mov ebp, esp
+    PROLOG_PUSH ebx
+    PROLOG_PUSH esi
+    PROLOG_PUSH edi
+    ESP_PROLOG_END
+
+    // On entry:
+    //
+    // [ebp+ 8] = throwable
+    // [ebp+12] = FP to restore
+    // [ebp+16] = PC to invoke
+    // [ebp+20] = address of the location where the SP of funclet's caller (i.e. this helper) should be saved.
+    //
+
+    // Save the SP of this function
+    mov     eax, [ebp + 20]
+    mov     [eax], esp
+    // Save the funclet PC for later call
+    mov     edx, [ebp + 16]
+    // Pass throwable object to funclet
+    mov     eax, [ebp +  8]
+    // Restore FP
+    mov     ebp, [ebp + 12]
+    // Invoke the funclet
+    call    edx
+
+    ESP_EPILOG_BEG
+    EPILOG_POP edi
+    EPILOG_POP esi
+    EPILOG_POP ebx
+    EPILOG_POP ebp
+    ESP_EPILOG_END
+
+    ret     16
+
+NESTED_END CallEHFunclet, _TEXT
+