2008-08-29 Geoff Norton <gnorton@novell.com>
[mono.git] / mono / mini / exceptions-x86.c
index 845d57da0daf575e061c3b7bd0ca9f25a089ead8..0384d6d97c11365ddf77ca30a800467df89c5dfd 100644 (file)
 #include <mono/metadata/exception.h>
 #include <mono/metadata/gc-internal.h>
 #include <mono/metadata/mono-debug.h>
-#include <mono/metadata/mono-debug-debugger.h>
+#include <mono/utils/mono-mmap.h>
 
 #include "mini.h"
 #include "mini-x86.h"
 
 #ifdef PLATFORM_WIN32
+static void (*restore_stack) (void *);
+
 static MonoW32ExceptionHandler fpe_handler;
 static MonoW32ExceptionHandler ill_handler;
 static MonoW32ExceptionHandler segv_handler;
@@ -35,6 +37,122 @@ static LPTOP_LEVEL_EXCEPTION_FILTER old_handler;
 #define W32_SEH_HANDLE_EX(_ex) \
        if (_ex##_handler) _ex##_handler((int)sctx)
 
+/*
+ * mono_win32_get_handle_stackoverflow (void):
+ *
+ * Returns a pointer to a method which restores the current context stack
+ * and calls handle_exceptions, when done restores the original stack.
+ */
+static gpointer
+mono_win32_get_handle_stackoverflow (void)
+{
+       static guint8 *start = NULL;
+       guint8 *code;
+
+       if (start)
+               return start;
+
+       /* restore_contect (void *sigctx) */
+       start = code = mono_global_codeman_reserve (128);
+
+       /* load context into ebx */
+       x86_mov_reg_membase (code, X86_EBX, X86_ESP, 4, 4);
+
+       /* move current stack into edi for later restore */
+       x86_mov_reg_reg (code, X86_EDI, X86_ESP, 4);
+
+       /* use the new freed stack from sigcontext */
+       x86_mov_reg_membase (code, X86_ESP, X86_EBX,  G_STRUCT_OFFSET (struct sigcontext, esp), 4);
+
+       /* get the current domain */
+       x86_call_code (code, mono_domain_get);
+
+       /* get stack overflow exception from domain object */
+       x86_mov_reg_membase (code, X86_EAX, X86_EAX, G_STRUCT_OFFSET (MonoDomain, stack_overflow_ex), 4);
+
+       /* call mono_arch_handle_exception (sctx, stack_overflow_exception_obj, FALSE) */
+       x86_push_imm (code, 0);
+       x86_push_reg (code, X86_EAX);
+       x86_push_reg (code, X86_EBX);
+       x86_call_code (code, mono_arch_handle_exception);
+
+       /* restore the SEH handler stack */
+       x86_mov_reg_reg (code, X86_ESP, X86_EDI, 4);
+
+       /* return */
+       x86_ret (code);
+
+       return start;
+}
+
+/* Special hack to workaround the fact that the
+ * when the SEH handler is called the stack is
+ * to small to recover.
+ *
+ * Stack walking part of this method is from mono_handle_exception
+ *
+ * The idea is simple; 
+ *  - walk the stack to free some space (64k)
+ *  - set esp to new stack location
+ *  - call mono_arch_handle_exception with stack overflow exception
+ *  - set esp to SEH handlers stack
+ *  - done
+ */
+static void 
+win32_handle_stack_overflow (EXCEPTION_POINTERS* ep, struct sigcontext *sctx) 
+{
+    SYSTEM_INFO si;
+    DWORD page_size;
+       MonoDomain *domain = mono_domain_get ();
+       MonoJitInfo *ji, rji;
+       MonoJitTlsData *jit_tls = TlsGetValue (mono_jit_tls_id);
+       MonoLMF *lmf = jit_tls->lmf;            
+       MonoContext initial_ctx;
+       MonoContext ctx;
+       guint32 free_stack = 0;
+
+       /* convert sigcontext to MonoContext (due to reuse of stack walking helpers */
+       mono_arch_sigctx_to_monoctx (sctx, &ctx);
+       
+       /* get our os page size */
+    GetSystemInfo(&si);
+       page_size = si.dwPageSize;
+
+       /* Let's walk the stack to recover
+        * the needed stack space (if possible)
+        */
+       memset (&rji, 0, sizeof (rji));
+
+       initial_ctx = ctx;
+       free_stack = (guint8*)(MONO_CONTEXT_GET_BP (&ctx)) - (guint8*)(MONO_CONTEXT_GET_BP (&initial_ctx));
+
+       /* try to free 64kb from our stack */
+       do {
+               MonoContext new_ctx;
+
+               ji = mono_arch_find_jit_info (domain, jit_tls, &rji, &rji, &ctx, &new_ctx, &lmf, NULL);
+               if (!ji) {
+                       g_warning ("Exception inside function without unwind info");
+                       g_assert_not_reached ();
+               }
+
+               if (ji != (gpointer)-1) {
+                       free_stack = (guint8*)(MONO_CONTEXT_GET_BP (&ctx)) - (guint8*)(MONO_CONTEXT_GET_BP (&initial_ctx));
+               }
+
+               /* todo: we should call abort if ji is -1 */
+               ctx = new_ctx;
+       } while (free_stack < 64 * 1024 && ji != (gpointer) -1);
+
+       /* convert into sigcontext to be used in mono_arch_handle_exception */
+       mono_arch_monoctx_to_sigctx (&ctx, sctx);
+
+       /* todo: install new stack-guard page */
+
+       /* use the new stack and call mono_arch_handle_exception () */
+       restore_stack (sctx);
+}
+
 /*
  * Unhandled Exception Filter
  * Top-level per-process exception handler.
@@ -64,6 +182,9 @@ LONG CALLBACK seh_handler(EXCEPTION_POINTERS* ep)
        sctx->eip = ctx->Eip;
 
        switch (er->ExceptionCode) {
+       case EXCEPTION_STACK_OVERFLOW:
+               win32_handle_stack_overflow (ep, sctx);
+               break;
        case EXCEPTION_ACCESS_VIOLATION:
                W32_SEH_HANDLE_EX(segv);
                break;
@@ -93,11 +214,17 @@ LONG CALLBACK seh_handler(EXCEPTION_POINTERS* ep)
        ctx->Edi = sctx->edi;
        ctx->Eip = sctx->eip;
 
+       g_free (sctx);
+
        return res;
 }
 
 void win32_seh_init()
 {
+       /* install restore stack helper */
+       if (!restore_stack)
+               restore_stack = mono_win32_get_handle_stackoverflow ();
+
        old_handler = SetUnhandledExceptionFilter(seh_handler);
 }
 
@@ -405,7 +532,9 @@ mono_arch_get_throw_corlib_exception (void)
        inited = 1;
        code = start = mono_global_codeman_reserve (64);
 
-       x86_push_membase (code, X86_ESP, 4); /* token */
+       x86_mov_reg_membase (code, X86_EAX, X86_ESP, 4, 4); /* token */
+       x86_alu_reg_imm (code, X86_ADD, X86_EAX, MONO_TOKEN_TYPE_DEF);
+       x86_push_reg (code, X86_EAX);
        x86_push_imm (code, mono_defaults.exception_class->image);
        x86_call_code (code, mono_exception_from_token);
        x86_alu_reg_imm (code, X86_ADD, X86_ESP, 8);
@@ -437,8 +566,7 @@ mono_arch_get_throw_corlib_exception (void)
  */
 MonoJitInfo *
 mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, MonoJitInfo *res, MonoJitInfo *prev_ji, MonoContext *ctx, 
-                        MonoContext *new_ctx, char **trace, MonoLMF **lmf, int *native_offset,
-                        gboolean *managed)
+                        MonoContext *new_ctx, MonoLMF **lmf, gboolean *managed)
 {
        MonoJitInfo *ji;
        gpointer ip = MONO_CONTEXT_GET_IP (ctx);
@@ -495,11 +623,11 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, MonoJitInf
 
                if (*lmf && (MONO_CONTEXT_GET_BP (ctx) >= (gpointer)(*lmf)->ebp)) {
                        /* remove any unused lmf */
-                       *lmf = (*lmf)->previous_lmf;
+                       *lmf = (gpointer)(((guint32)(*lmf)->previous_lmf) & ~1);
                }
 
                /* Pop EBP and the return address */
-               new_ctx->esp = ctx->SC_EBP + (2 * sizeof (gpointer));
+               new_ctx->esp = ctx->ebp + (2 * sizeof (gpointer));
                /* we substract 1, so that the IP points into the call instruction */
                new_ctx->eip = *((int *)ctx->ebp + 1) - 1;
                new_ctx->ebp = *((int *)ctx->ebp);
@@ -517,11 +645,12 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, MonoJitInf
                
                *new_ctx = *ctx;
 
-               if (!(*lmf)->method)
-                       return (gpointer)-1;
-
                if ((ji = mono_jit_info_table_find (domain, (gpointer)(*lmf)->eip))) {
                } else {
+                       if (!(*lmf)->method)
+                               /* Top LMF entry */
+                               return (gpointer)-1;
+                       /* Trampoline lmf frame */
                        memset (res, 0, sizeof (MonoJitInfo));
                        res->method = (*lmf)->method;
                }
@@ -531,11 +660,29 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, MonoJitInf
                new_ctx->ebx = (*lmf)->ebx;
                new_ctx->ebp = (*lmf)->ebp;
                new_ctx->eip = (*lmf)->eip;
-               /* the lmf is always stored on the stack, so the following
-                * expression points to a stack location which can be used as ESP */
-               new_ctx->esp = (unsigned long)&((*lmf)->eip);
 
-               *lmf = (*lmf)->previous_lmf;
+               /* Check if we are in a trampoline LMF frame */
+               if ((guint32)((*lmf)->previous_lmf) & 1) {
+                       /* lmf->esp is set by the trampoline code */
+                       new_ctx->esp = (*lmf)->esp;
+
+                       /* Pop arguments off the stack */
+                       /* FIXME: Handle the delegate case too ((*lmf)->method == NULL) */
+                       /* FIXME: Handle the IMT/vtable case too */
+                       if ((*lmf)->method && (*lmf)->method != MONO_FAKE_IMT_METHOD && (*lmf)->method != MONO_FAKE_VTABLE_METHOD) {
+                               MonoMethod *method = (*lmf)->method;
+                               MonoJitArgumentInfo *arg_info = g_newa (MonoJitArgumentInfo, mono_method_signature (method)->param_count + 1);
+
+                               guint32 stack_to_pop = mono_arch_get_argument_info (mono_method_signature (method), mono_method_signature (method)->param_count, arg_info);
+                               new_ctx->esp += stack_to_pop;
+                       }
+               }
+               else
+                       /* the lmf is always stored on the stack, so the following
+                        * expression points to a stack location which can be used as ESP */
+                       new_ctx->esp = (unsigned long)&((*lmf)->eip);
+
+               *lmf = (gpointer)(((guint32)(*lmf)->previous_lmf) & ~1);
 
                return ji ? ji : res;
        }
@@ -543,43 +690,75 @@ mono_arch_find_jit_info (MonoDomain *domain, MonoJitTlsData *jit_tls, MonoJitInf
        return NULL;
 }
 
+#ifdef __sun
+#define REG_EAX EAX
+#define REG_EBX EBX
+#define REG_ECX ECX
+#define REG_EDX EDX
+#define REG_EBP EBP
+#define REG_ESP ESP
+#define REG_ESI ESI
+#define REG_EDI EDI
+#define REG_EIP EIP
+#endif
+
 void
 mono_arch_sigctx_to_monoctx (void *sigctx, MonoContext *mctx)
 {
+#ifdef MONO_ARCH_USE_SIGACTION
        ucontext_t *ctx = (ucontext_t*)sigctx;
        
-#ifdef MONO_ARCH_USE_SIGACTION
-       mctx->eax = ctx->uc_mcontext.gregs [REG_EAX];
-       mctx->ebx = ctx->uc_mcontext.gregs [REG_EBX];
-       mctx->ecx = ctx->uc_mcontext.gregs [REG_ECX];
-       mctx->edx = ctx->uc_mcontext.gregs [REG_EDX];
-       mctx->ebp = ctx->uc_mcontext.gregs [REG_EBP];
-       mctx->esp = ctx->uc_mcontext.gregs [REG_ESP];
-       mctx->esi = ctx->uc_mcontext.gregs [REG_ESI];
-       mctx->edi = ctx->uc_mcontext.gregs [REG_EDI];
-       mctx->eip = ctx->uc_mcontext.gregs [REG_EIP];
-#else
-       memcpy (mctx, sigctx, sizeof (MonoContext));
+       mctx->eax = UCONTEXT_REG_EAX (ctx);
+       mctx->ebx = UCONTEXT_REG_EBX (ctx);
+       mctx->ecx = UCONTEXT_REG_ECX (ctx);
+       mctx->edx = UCONTEXT_REG_EDX (ctx);
+       mctx->ebp = UCONTEXT_REG_EBP (ctx);
+       mctx->esp = UCONTEXT_REG_ESP (ctx);
+       mctx->esi = UCONTEXT_REG_ESI (ctx);
+       mctx->edi = UCONTEXT_REG_EDI (ctx);
+       mctx->eip = UCONTEXT_REG_EIP (ctx);
+#else  
+       struct sigcontext *ctx = (struct sigcontext *)sigctx;
+
+       mctx->eax = ctx->SC_EAX;
+       mctx->ebx = ctx->SC_EBX;
+       mctx->ecx = ctx->SC_ECX;
+       mctx->edx = ctx->SC_EDX;
+       mctx->ebp = ctx->SC_EBP;
+       mctx->esp = ctx->SC_ESP;
+       mctx->esi = ctx->SC_ESI;
+       mctx->edi = ctx->SC_EDI;
+       mctx->eip = ctx->SC_EIP;
 #endif
 }
 
 void
 mono_arch_monoctx_to_sigctx (MonoContext *mctx, void *sigctx)
 {
+#ifdef MONO_ARCH_USE_SIGACTION
        ucontext_t *ctx = (ucontext_t*)sigctx;
 
-#ifdef MONO_ARCH_USE_SIGACTION
-       ctx->uc_mcontext.gregs [REG_EAX] = mctx->eax;
-       ctx->uc_mcontext.gregs [REG_EBX] = mctx->ebx;
-       ctx->uc_mcontext.gregs [REG_ECX] = mctx->ecx;
-       ctx->uc_mcontext.gregs [REG_EDX] = mctx->edx;
-       ctx->uc_mcontext.gregs [REG_EBP] = mctx->ebp;
-       ctx->uc_mcontext.gregs [REG_ESP] = mctx->esp;
-       ctx->uc_mcontext.gregs [REG_ESI] = mctx->esi;
-       ctx->uc_mcontext.gregs [REG_EDI] = mctx->edi;
-       ctx->uc_mcontext.gregs [REG_EIP] = mctx->eip;
+       UCONTEXT_REG_EAX (ctx) = mctx->eax;
+       UCONTEXT_REG_EBX (ctx) = mctx->ebx;
+       UCONTEXT_REG_ECX (ctx) = mctx->ecx;
+       UCONTEXT_REG_EDX (ctx) = mctx->edx;
+       UCONTEXT_REG_EBP (ctx) = mctx->ebp;
+       UCONTEXT_REG_ESP (ctx) = mctx->esp;
+       UCONTEXT_REG_ESI (ctx) = mctx->esi;
+       UCONTEXT_REG_EDI (ctx) = mctx->edi;
+       UCONTEXT_REG_EIP (ctx) = mctx->eip;
 #else
-       memcpy (ctx, mctx, sizeof (MonoContext));
+       struct sigcontext *ctx = (struct sigcontext *)sigctx;
+
+       ctx->SC_EAX = mctx->eax;
+       ctx->SC_EBX = mctx->ebx;
+       ctx->SC_ECX = mctx->ecx;
+       ctx->SC_EDX = mctx->edx;
+       ctx->SC_EBP = mctx->ebp;
+       ctx->SC_ESP = mctx->esp;
+       ctx->SC_ESI = mctx->esi;
+       ctx->SC_EDI = mctx->edi;
+       ctx->SC_EIP = mctx->eip;
 #endif
 }      
 
@@ -588,7 +767,7 @@ mono_arch_ip_from_context (void *sigctx)
 {
 #ifdef MONO_ARCH_USE_SIGACTION
        ucontext_t *ctx = (ucontext_t*)sigctx;
-       return (gpointer)ctx->uc_mcontext.gregs [REG_EIP];
+       return (gpointer)UCONTEXT_REG_EIP (ctx);
 #else
        struct sigcontext *ctx = sigctx;
        return (gpointer)ctx->SC_EIP;
@@ -608,3 +787,99 @@ mono_arch_handle_exception (void *sigctx, gpointer obj, gboolean test_only)
 
        return TRUE;
 }
+
+static void
+restore_soft_guard_pages (void)
+{
+       MonoJitTlsData *jit_tls = TlsGetValue (mono_jit_tls_id);
+       if (jit_tls->stack_ovf_guard_base)
+               mono_mprotect (jit_tls->stack_ovf_guard_base, jit_tls->stack_ovf_guard_size, MONO_MMAP_NONE);
+}
+
+/* 
+ * this function modifies mctx so that when it is restored, it
+ * won't execcute starting at mctx.eip, but in a function that
+ * will restore the protection on the soft-guard pages and return back to
+ * continue at mctx.eip.
+ */
+static void
+prepare_for_guard_pages (MonoContext *mctx)
+{
+       gpointer *sp;
+       sp = (gpointer)(mctx->esp);
+       sp -= 1;
+       /* the resturn addr */
+       sp [0] = (gpointer)(mctx->eip);
+       mctx->eip = (unsigned long)restore_soft_guard_pages;
+       mctx->esp = (unsigned long)sp;
+}
+
+static void
+altstack_handle_and_restore (void *sigctx, gpointer obj, gboolean stack_ovf)
+{
+       void (*restore_context) (MonoContext *);
+       MonoContext mctx;
+
+       restore_context = mono_arch_get_restore_context ();
+       mono_arch_sigctx_to_monoctx (sigctx, &mctx);
+       mono_handle_exception (&mctx, obj, (gpointer)mctx.eip, FALSE);
+       if (stack_ovf)
+               prepare_for_guard_pages (&mctx);
+       restore_context (&mctx);
+}
+
+void
+mono_arch_handle_altstack_exception (void *sigctx, gpointer fault_addr, gboolean stack_ovf)
+{
+#ifdef MONO_ARCH_USE_SIGACTION
+       MonoException *exc = NULL;
+       ucontext_t *ctx = (ucontext_t*)sigctx;
+       MonoJitInfo *ji = mono_jit_info_table_find (mono_domain_get (), (gpointer)UCONTEXT_REG_EIP (ctx));
+       gpointer *sp;
+       int frame_size;
+
+       /* if we didn't find a managed method for the ip address and it matches the fault
+        * address, we assume we followed a broken pointer during an indirect call, so
+        * we try the lookup again with the return address pushed on the stack
+        */
+       if (!ji && fault_addr == (gpointer)UCONTEXT_REG_EIP (ctx)) {
+               glong *sp = (gpointer)UCONTEXT_REG_ESP (ctx);
+               ji = mono_jit_info_table_find (mono_domain_get (), (gpointer)sp [0]);
+               if (ji)
+                       UCONTEXT_REG_EIP (ctx) = sp [0];
+       }
+       if (stack_ovf)
+               exc = mono_domain_get ()->stack_overflow_ex;
+       if (!ji)
+               mono_handle_native_sigsegv (SIGSEGV, sigctx);
+       /* setup a call frame on the real stack so that control is returned there
+        * and exception handling can continue.
+        * If this was a stack overflow the caller already ensured the stack pages
+        * needed have been unprotected.
+        * The frame looks like:
+        *   ucontext struct
+        *   test_only arg
+        *   exception arg
+        *   ctx arg
+        *   return ip
+        */
+       frame_size = sizeof (ucontext_t) + sizeof (gpointer) * 4;
+       frame_size += 15;
+       frame_size &= ~15;
+       sp = (gpointer)(UCONTEXT_REG_ESP (ctx) & ~15);
+       sp = (gpointer)((char*)sp - frame_size);
+       /* the incoming arguments are aligned to 16 bytes boundaries, so the return address IP
+        * goes at sp [-1]
+        */
+       sp [-1] = (gpointer)UCONTEXT_REG_EIP (ctx);
+       sp [0] = sp + 4;
+       sp [1] = exc;
+       sp [2] = (gpointer)stack_ovf;
+       /* may need to adjust pointers in the new struct copy, depending on the OS */
+       memcpy (sp + 4, ctx, sizeof (ucontext_t));
+       /* at the return form the signal handler execution starts in altstack_handle_and_restore() */
+       UCONTEXT_REG_EIP (ctx) = (unsigned long)altstack_handle_and_restore;
+       UCONTEXT_REG_ESP (ctx) = (unsigned long)(sp - 1);
+#endif
+}
+