From ec08192bfea5fb25dd60c9fdfd813951ab865ebf Mon Sep 17 00:00:00 2001 From: Sergiy Kuryata Date: Fri, 27 Feb 2015 11:50:16 -0800 Subject: Implement basic support for managed exception handling. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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/vm/exceptionhandling.cpp | 253 +++++++++++++++++++++++++++++++++++++++++++ src/vm/exceptmacros.h | 23 ++++ src/vm/stackwalk.cpp | 9 +- 3 files changed, 281 insertions(+), 4 deletions(-) (limited to 'src/vm') diff --git a/src/vm/exceptionhandling.cpp b/src/vm/exceptionhandling.cpp index 2c9d3298d6..5feb07cac0 100644 --- a/src/vm/exceptionhandling.cpp +++ b/src/vm/exceptionhandling.cpp @@ -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 diff --git a/src/vm/exceptmacros.h b/src/vm/exceptmacros.h index da4b759b9e..dd27eacd1a 100644 --- a/src/vm/exceptmacros.h +++ b/src/vm/exceptmacros.h @@ -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 \ diff --git a/src/vm/stackwalk.cpp b/src/vm/stackwalk.cpp index e677cef7e9..627765a996 100644 --- a/src/vm/stackwalk.cpp +++ b/src/vm/stackwalk.cpp @@ -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 -- cgit v1.2.3