summaryrefslogtreecommitdiff
path: root/src/vm
diff options
context:
space:
mode:
authorSergiy Kuryata <sergeyk@microsoft.com>2015-02-27 11:50:16 -0800
committerSergiy Kuryata <sergeyk@microsoft.com>2015-02-27 11:56:27 -0800
commitec08192bfea5fb25dd60c9fdfd813951ab865ebf (patch)
treed3d8a75b91ae861880982b51042f2c38373f8e61 /src/vm
parent63976a08fbe6ece34a2936ebccdf735ea32044a2 (diff)
downloadcoreclr-ec08192bfea5fb25dd60c9fdfd813951ab865ebf.tar.gz
coreclr-ec08192bfea5fb25dd60c9fdfd813951ab865ebf.tar.bz2
coreclr-ec08192bfea5fb25dd60c9fdfd813951ab865ebf.zip
Implement basic support for managed exception handling.
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.
Diffstat (limited to 'src/vm')
-rw-r--r--src/vm/exceptionhandling.cpp253
-rw-r--r--src/vm/exceptmacros.h23
-rw-r--r--src/vm/stackwalk.cpp9
3 files changed, 281 insertions, 4 deletions
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