summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/jit/codegencommon.cpp8
-rw-r--r--src/jit/codegenxarch.cpp2
-rw-r--r--src/jit/target.h7
-rw-r--r--src/pal/src/arch/i386/context.cpp7
-rw-r--r--src/pal/src/debug/debug.cpp22
-rw-r--r--src/vm/exceptionhandling.cpp253
-rw-r--r--src/vm/exceptmacros.h23
-rw-r--r--src/vm/stackwalk.cpp9
8 files changed, 318 insertions, 13 deletions
diff --git a/src/jit/codegencommon.cpp b/src/jit/codegencommon.cpp
index 1181783391..c88817c88c 100644
--- a/src/jit/codegencommon.cpp
+++ b/src/jit/codegencommon.cpp
@@ -9615,13 +9615,14 @@ void CodeGen::genFuncletProlog(BasicBlock* block)
regMaskTP maskArgRegsLiveIn;
if ((block->bbCatchTyp == BBCT_FINALLY) || (block->bbCatchTyp == BBCT_FAULT))
{
- maskArgRegsLiveIn = RBM_ECX;
+ maskArgRegsLiveIn = RBM_ARG_0;
}
else
{
- maskArgRegsLiveIn = RBM_ECX | RBM_EDX;
+ maskArgRegsLiveIn = RBM_ARG_0 | RBM_ARG_2;
}
+
regNumber initReg = REG_EBP; // We already saved EBP, so it can be trashed
bool initRegZeroed = false;
@@ -9634,7 +9635,8 @@ void CodeGen::genFuncletProlog(BasicBlock* block)
// This is the end of the OS-reported prolog for purposes of unwinding
compiler->unwindEndProlog();
- getEmitter()->emitIns_R_AR(INS_mov, EA_PTRSIZE, REG_FPBASE, REG_ECX, genFuncletInfo.fiPSP_slot_InitialSP_offset);
+ getEmitter()->emitIns_R_AR(INS_mov, EA_PTRSIZE, REG_FPBASE, REG_ARG_0, genFuncletInfo.fiPSP_slot_InitialSP_offset);
+
regTracker.rsTrackRegTrash(REG_FPBASE);
getEmitter()->emitIns_AR_R(INS_mov, EA_PTRSIZE, REG_FPBASE, REG_SPBASE, genFuncletInfo.fiPSP_slot_InitialSP_offset);
diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp
index 82677a3b6a..2dee895a68 100644
--- a/src/jit/codegenxarch.cpp
+++ b/src/jit/codegenxarch.cpp
@@ -892,7 +892,7 @@ void CodeGen::genCodeForBBlist()
// jmp finally-return // Only for non-retless finally calls
// The jmp can be a NOP if we're going to the next block.
- getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_RCX, compiler->lvaPSPSym, 0);
+ getEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_ARG_0, compiler->lvaPSPSym, 0);
getEmitter()->emitIns_J(INS_call, block->bbJumpDest);
if (block->bbFlags & BBF_RETLESS_CALL)
diff --git a/src/jit/target.h b/src/jit/target.h
index e730481a31..ea3864d772 100644
--- a/src/jit/target.h
+++ b/src/jit/target.h
@@ -821,9 +821,14 @@ typedef unsigned short regPairNoSmall; // arm: need 12 bits
#define REG_SCRATCH REG_EAX
#define RBM_SCRATCH RBM_EAX
- // Where is the exception object on entry to the handler block?
+// Where is the exception object on entry to the handler block?
+#ifndef UNIX_AMD64_ABI
#define REG_EXCEPTION_OBJECT REG_EDX
#define RBM_EXCEPTION_OBJECT RBM_EDX
+#else
+ #define REG_EXCEPTION_OBJECT REG_ESI
+ #define RBM_EXCEPTION_OBJECT RBM_ESI
+#endif // UNIX_AMD64_ABI
#define REG_JUMP_THUNK_PARAM REG_EAX
#define RBM_JUMP_THUNK_PARAM RBM_EAX
diff --git a/src/pal/src/arch/i386/context.cpp b/src/pal/src/arch/i386/context.cpp
index cb1d045e7d..b8c0d816d0 100644
--- a/src/pal/src/arch/i386/context.cpp
+++ b/src/pal/src/arch/i386/context.cpp
@@ -24,6 +24,7 @@ Abstract:
#include "pal/dbgmsg.h"
#include "pal/context.h"
#include "pal/debug.h"
+#include "pal/thread.hpp"
#include <sys/ptrace.h>
#include <errno.h>
@@ -348,7 +349,7 @@ CONTEXT_GetThreadContext(
if (dwProcessId == GetCurrentProcessId())
{
- if (dwThreadId != GetCurrentThreadId())
+ if (dwThreadId != THREADSilentGetCurrentThreadId())
{
DWORD flags;
// There aren't any APIs for this. We can potentially get the
@@ -1013,7 +1014,7 @@ CONTEXT_GetThreadContext(
if (GetCurrentProcessId() == dwProcessId)
{
- if (dwThreadId != GetCurrentThreadId())
+ if (dwThreadId != THREADSilentGetCurrentThreadId())
{
// the target thread is in the current process, but isn't
// the current one: extract the CONTEXT from the Mach thread.
@@ -1256,7 +1257,7 @@ CONTEXT_SetThreadContext(
goto EXIT;
}
- if (dwThreadId != GetCurrentThreadId())
+ if (dwThreadId != THREADSilentGetCurrentThreadId())
{
// hThread is in the current process, but isn't the current
// thread. Extract the CONTEXT from the Mach thread.
diff --git a/src/pal/src/debug/debug.cpp b/src/pal/src/debug/debug.cpp
index a9fa01978f..eb28e2edcb 100644
--- a/src/pal/src/debug/debug.cpp
+++ b/src/pal/src/debug/debug.cpp
@@ -530,7 +530,27 @@ RtlRestoreContext(
IN PEXCEPTION_RECORD ExceptionRecord
)
{
- ASSERT("UNIXTODO: Implement this");
+ native_context_t ucontext;
+
+ //
+ //TODO: This needs to be properly implemented
+ // because this code does not restore XMM registers
+ //
+
+#if HAVE_GETCONTEXT
+ getcontext(&ucontext);
+#else
+#error Don't know how to get current context on this platform!
+#endif
+
+ CONTEXTToNativeContext(ContextRecord, &ucontext,
+ CONTEXT_CONTROL | CONTEXT_INTEGER);
+
+#if HAVE_SETCONTEXT
+ setcontext(&ucontext);
+#else
+#error Don't know how to set current context on this platform!
+#endif
}
/*++
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