From 7b6abb245f77d42051da9ab84a5b7700f8ec75f9 Mon Sep 17 00:00:00 2001 From: Hanjoung Lee Date: Fri, 24 Feb 2017 16:02:21 +0900 Subject: [PATCH] [x86/Linux] Initial patch for EH funclet (dotnet/coreclr#9601) - 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 | 26 +++++- src/coreclr/src/jit/codegenxarch.cpp | 2 + src/coreclr/src/pal/inc/unixasmmacrosx86.inc | 12 +++ .../src/pal/src/arch/i386/exceptionhelper.S | 7 +- src/coreclr/src/pal/src/exception/seh.cpp | 4 + src/coreclr/src/vm/CMakeLists.txt | 1 + src/coreclr/src/vm/eetwain.cpp | 31 +++++-- src/coreclr/src/vm/exceptionhandling.cpp | 83 ++++++++++++------ src/coreclr/src/vm/exceptionhandling.h | 1 + src/coreclr/src/vm/i386/ehhelpers.S | 98 ++++++++++++++++++++++ 10 files changed, 225 insertions(+), 40 deletions(-) create mode 100644 src/coreclr/src/vm/i386/ehhelpers.S diff --git a/src/coreclr/src/jit/codegencommon.cpp b/src/coreclr/src/jit/codegencommon.cpp index 0c4cba40..75de420 100644 --- a/src/coreclr/src/jit/codegencommon.cpp +++ b/src/coreclr/src/jit/codegencommon.cpp @@ -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 _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); } diff --git a/src/coreclr/src/jit/codegenxarch.cpp b/src/coreclr/src/jit/codegenxarch.cpp index 758156b..acf7a0d 100644 --- a/src/coreclr/src/jit/codegenxarch.cpp +++ b/src/coreclr/src/jit/codegenxarch.cpp @@ -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 { diff --git a/src/coreclr/src/pal/inc/unixasmmacrosx86.inc b/src/coreclr/src/pal/inc/unixasmmacrosx86.inc index 77b3a63..61ad946 100644 --- a/src/coreclr/src/pal/inc/unixasmmacrosx86.inc +++ b/src/coreclr/src/pal/inc/unixasmmacrosx86.inc @@ -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 diff --git a/src/coreclr/src/pal/src/arch/i386/exceptionhelper.S b/src/coreclr/src/pal/src/arch/i386/exceptionhelper.S index 4980b89..f0bf8f8 100644 --- a/src/coreclr/src/pal/src/arch/i386/exceptionhelper.S +++ b/src/coreclr/src/pal/src/arch/i386/exceptionhelper.S @@ -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 diff --git a/src/coreclr/src/pal/src/exception/seh.cpp b/src/coreclr/src/pal/src/exception/seh.cpp index 7682e9e..bc74ccc 100644 --- a/src/coreclr/src/pal/src/exception/seh.cpp +++ b/src/coreclr/src/pal/src/exception/seh.cpp @@ -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); } diff --git a/src/coreclr/src/vm/CMakeLists.txt b/src/coreclr/src/vm/CMakeLists.txt index cdf5a53..0153753 100644 --- a/src/coreclr/src/vm/CMakeLists.txt +++ b/src/coreclr/src/vm/CMakeLists.txt @@ -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 diff --git a/src/coreclr/src/vm/eetwain.cpp b/src/coreclr/src/vm/eetwain.cpp index bf6e1c7..989300d 100644 --- a/src/coreclr/src/vm/eetwain.cpp +++ b/src/coreclr/src/vm/eetwain.cpp @@ -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; } diff --git a/src/coreclr/src/vm/exceptionhandling.cpp b/src/coreclr/src/vm/exceptionhandling.cpp index 1e51467..d7f22d5 100644 --- a/src/coreclr/src/vm/exceptionhandling.cpp +++ b/src/coreclr/src/vm/exceptionhandling.cpp @@ -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(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(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(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) { diff --git a/src/coreclr/src/vm/exceptionhandling.h b/src/coreclr/src/vm/exceptionhandling.h index 71bcab7..4dba095 100644 --- a/src/coreclr/src/vm/exceptionhandling.h +++ b/src/coreclr/src/vm/exceptionhandling.h @@ -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 index 0000000..9f959b1 --- /dev/null +++ b/src/coreclr/src/vm/i386/ehhelpers.S @@ -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 + -- 2.7.4