Mixed mode exception handling (#4777)
[mono.git] / mono / mini / mini-exceptions.c
index c4f32d77cb11e7f74a02c22827f92d66af47b37f..90d85b88ab41d1127f82f118f2e1b0995858bb3e 100644 (file)
@@ -58,6 +58,7 @@
 #include <mono/metadata/object-internals.h>
 #include <mono/metadata/reflection-internals.h>
 #include <mono/metadata/gc-internals.h>
+#include <mono/metadata/debug-internals.h>
 #include <mono/metadata/mono-debug.h>
 #include <mono/metadata/profiler.h>
 #include <mono/metadata/mono-endian.h>
@@ -567,7 +568,7 @@ mono_find_jit_info_ext (MonoDomain *domain, MonoJitTlsData *jit_tls,
        if (!err)
                return FALSE;
 
-       if (*lmf && ((*lmf) != jit_tls->first_lmf) && ((gpointer)MONO_CONTEXT_GET_SP (new_ctx) >= (gpointer)(*lmf))) {
+       if (frame->type != FRAME_TYPE_INTERP_TO_MANAGED && *lmf && ((*lmf) != jit_tls->first_lmf) && ((gpointer)MONO_CONTEXT_GET_SP (new_ctx) >= (gpointer)(*lmf))) {
                /*
                 * Remove any unused lmf.
                 * Mask out the lower bits which might be used to hold additional information.
@@ -630,6 +631,44 @@ mono_find_jit_info_ext (MonoDomain *domain, MonoJitTlsData *jit_tls,
        return TRUE;
 }
 
+typedef struct {
+       gboolean in_interp;
+       MonoInterpStackIter interp_iter;
+} Unwinder;
+
+static void
+unwinder_init (Unwinder *unwinder)
+{
+       memset (unwinder, 0, sizeof (Unwinder));
+}
+
+static gboolean
+unwinder_unwind_frame (Unwinder *unwinder,
+                                          MonoDomain *domain, MonoJitTlsData *jit_tls,
+                                          MonoJitInfo *prev_ji, MonoContext *ctx,
+                                          MonoContext *new_ctx, char **trace, MonoLMF **lmf,
+                                          mgreg_t **save_locations,
+                                          StackFrameInfo *frame)
+{
+       if (unwinder->in_interp) {
+               unwinder->in_interp = mono_interp_frame_iter_next (&unwinder->interp_iter, frame);
+               if (!unwinder->in_interp) {
+                       return unwinder_unwind_frame (unwinder, domain, jit_tls, prev_ji, ctx, new_ctx, trace, lmf, save_locations, frame);
+               }
+               return TRUE;
+       } else {
+               gboolean res = mono_find_jit_info_ext (domain, jit_tls, prev_ji, ctx, new_ctx, trace, lmf,
+                                                                                          save_locations, frame);
+               if (!res)
+                       return FALSE;
+               if (frame->type == FRAME_TYPE_INTERP_TO_MANAGED) {
+                       unwinder->in_interp = TRUE;
+                       mono_interp_frame_iter_init (&unwinder->interp_iter, frame->interp_exit_data);
+               }
+               return TRUE;
+       }
+}
+
 /*
  * This function is async-safe.
  */
@@ -761,10 +800,9 @@ get_method_from_stack_frame (MonoJitInfo *ji, gpointer generic_info)
 
 /**
  * mono_exception_walk_native_trace:
- * @ex: The exception object whose frames should be walked
- * @func: callback to call for each stack frame
- * @user_data: data passed to the callback
- *
+ * \param ex The exception object whose frames should be walked
+ * \param func callback to call for each stack frame
+ * \param user_data data passed to the callback
  * This function walks the stacktrace of an exception. For
  * each frame the callback function is called with the relevant info.
  * The walk ends when no more stack frames are found or when the callback
@@ -917,10 +955,8 @@ mono_runtime_walk_stack_with_ctx (MonoJitStackWalk func, MonoContext *start_ctx,
 }
 /**
  * mono_walk_stack_with_ctx:
- *
- * Unwind the current thread starting at @start_ctx.
- * 
- * If @start_ctx is null, we capture the current context.
+ * Unwind the current thread starting at \p start_ctx.
+ * If \p start_ctx is null, we capture the current context.
  */
 void
 mono_walk_stack_with_ctx (MonoJitStackWalk func, MonoContext *start_ctx, MonoUnwindOptions unwind_options, void *user_data)
@@ -948,14 +984,13 @@ mono_walk_stack_with_ctx (MonoJitStackWalk func, MonoContext *start_ctx, MonoUnw
 
 /**
  * mono_walk_stack_with_state:
- *
- * Unwind a thread described by @state.
+ * Unwind a thread described by \p state.
  *
  * State must be valid (state->valid == TRUE).
  *
  * If you are using this function to unwind another thread, make sure it is suspended.
  * 
- * If @state is null, we capture the current context.
+ * If \p state is null, we capture the current context.
  */
 void
 mono_walk_stack_with_state (MonoJitStackWalk func, MonoThreadUnwindState *state, MonoUnwindOptions unwind_options, void *user_data)
@@ -993,16 +1028,15 @@ mono_walk_stack (MonoJitStackWalk func, MonoUnwindOptions options, void *user_da
 
 /**
  * mono_walk_stack_full:
- * @func: callback to call for each stack frame
- * @domain: starting appdomain, can be NULL to use the current domain
- * @unwind_options: what extra information the unwinder should gather
- * @start_ctx: starting state of the stack walk, can be NULL.
- * @thread: the thread whose stack to walk, can be NULL to use the current thread
- * @lmf: the LMF of @thread, can be NULL to use the LMF of the current thread
- * @user_data: data passed to the callback
- *
+ * \param func callback to call for each stack frame
+ * \param domain starting appdomain, can be NULL to use the current domain
+ * \param unwind_options what extra information the unwinder should gather
+ * \param start_ctx starting state of the stack walk, can be NULL.
+ * \param thread the thread whose stack to walk, can be NULL to use the current thread
+ * \param lmf the LMF of \p thread, can be NULL to use the LMF of the current thread
+ * \param user_data data passed to the callback
  * This function walks the stack of a thread, starting from the state
- * represented by start_ctx. For each frame the callback
+ * represented by \p start_ctx. For each frame the callback
  * function is called with the relevant info. The walk ends when no more
  * managed stack frames are found or when the callback returns a TRUE value.
  */
@@ -1124,6 +1158,8 @@ ves_icall_get_frame_info (gint32 skip, MonoBoolean need_file_info,
        MonoMethod *jmethod = NULL, *actual_method;
        StackFrameInfo frame;
        gboolean res;
+       Unwinder unwinder;
+       int il_offset = -1;
 
        MONO_ARCH_CONTEXT_DEF;
 
@@ -1165,29 +1201,43 @@ ves_icall_get_frame_info (gint32 skip, MonoBoolean need_file_info,
                MONO_INIT_CONTEXT_FROM_FUNC (&ctx, ves_icall_get_frame_info);
 #endif
 
+               unwinder_init (&unwinder);
+
                new_ctx = ctx;
                do {
                        ctx = new_ctx;
-                       res = mono_find_jit_info_ext (domain, jit_tls, NULL, &ctx, &new_ctx, NULL, &lmf, NULL, &frame);
+                       res = unwinder_unwind_frame (&unwinder, domain, jit_tls, NULL, &ctx, &new_ctx, NULL, &lmf, NULL, &frame);
                        if (!res)
                                return FALSE;
-
-                       if (frame.type == FRAME_TYPE_MANAGED_TO_NATIVE ||
-                               frame.type == FRAME_TYPE_DEBUGGER_INVOKE ||
-                               frame.type == FRAME_TYPE_TRAMPOLINE)
+                       switch (frame.type) {
+                       case FRAME_TYPE_MANAGED_TO_NATIVE:
+                       case FRAME_TYPE_DEBUGGER_INVOKE:
+                       case FRAME_TYPE_TRAMPOLINE:
+                       case FRAME_TYPE_INTERP_TO_MANAGED:
                                continue;
+                       case FRAME_TYPE_INTERP:
+                               skip--;
+                               break;
+                       default:
+                               ji = frame.ji;
+                               *native_offset = frame.native_offset;
 
-                       ji = frame.ji;
-                       *native_offset = frame.native_offset;
-
-                       /* The skip count passed by the caller depends on us not filtering out MANAGED_TO_NATIVE */
-                       jmethod = jinfo_get_method (ji);
-                       if (jmethod->wrapper_type != MONO_WRAPPER_NONE && jmethod->wrapper_type != MONO_WRAPPER_DYNAMIC_METHOD && jmethod->wrapper_type != MONO_WRAPPER_MANAGED_TO_NATIVE)
-                               continue;
-                       skip--;
+                               /* The skip count passed by the caller depends on us not filtering out MANAGED_TO_NATIVE */
+                               jmethod = jinfo_get_method (ji);
+                               if (jmethod->wrapper_type != MONO_WRAPPER_NONE && jmethod->wrapper_type != MONO_WRAPPER_DYNAMIC_METHOD && jmethod->wrapper_type != MONO_WRAPPER_MANAGED_TO_NATIVE)
+                                       continue;
+                               skip--;
+                               break;
+                       }
                } while (skip >= 0);
 
-               actual_method = get_method_from_stack_frame (ji, get_generic_info_from_stack_frame (ji, &ctx));
+               if (frame.type == FRAME_TYPE_INTERP) {
+                       jmethod = frame.method;
+                       actual_method = frame.actual_method;
+                       *native_offset = frame.native_offset;
+               } else {
+                       actual_method = get_method_from_stack_frame (ji, get_generic_info_from_stack_frame (ji, &ctx));
+               }
        }
 
        MonoReflectionMethod *rm = mono_method_get_object_checked (domain, actual_method, NULL, &error);
@@ -1197,7 +1247,11 @@ ves_icall_get_frame_info (gint32 skip, MonoBoolean need_file_info,
        }
        mono_gc_wbarrier_generic_store (method, (MonoObject*) rm);
 
-       location = mono_debug_lookup_source_location (jmethod, *native_offset, domain);
+       if (il_offset != -1) {
+               location = mono_debug_lookup_source_location_by_il (jmethod, il_offset, domain);
+       } else {
+               location = mono_debug_lookup_source_location (jmethod, *native_offset, domain);
+       }
        if (location)
                *iloffset = location->il_offset;
        else
@@ -1474,6 +1528,8 @@ mono_handle_exception_internal_first_pass (MonoContext *ctx, MonoObject *obj, gi
        gint32 filter_idx;
        int i;
        MonoObject *ex_obj;
+       Unwinder unwinder;
+       gboolean in_interp;
 
        g_assert (ctx != NULL);
 
@@ -1513,6 +1569,8 @@ mono_handle_exception_internal_first_pass (MonoContext *ctx, MonoObject *obj, gi
        filter_idx = 0;
        initial_ctx = *ctx;
 
+       unwinder_init (&unwinder);
+
        while (1) {
                MonoContext new_ctx;
                guint32 free_stack;
@@ -1524,24 +1582,37 @@ mono_handle_exception_internal_first_pass (MonoContext *ctx, MonoObject *obj, gi
                if (out_prev_ji)
                        *out_prev_ji = ji;
 
-               unwind_res = mono_find_jit_info_ext (domain, jit_tls, NULL, ctx, &new_ctx, NULL, &lmf, NULL, &frame);
-               if (unwind_res) {
-                       if (frame.type == FRAME_TYPE_DEBUGGER_INVOKE ||
-                                       frame.type == FRAME_TYPE_MANAGED_TO_NATIVE ||
-                                       frame.type == FRAME_TYPE_TRAMPOLINE) {
-                               *ctx = new_ctx;
-                               continue;
-                       }
-                       g_assert (frame.type == FRAME_TYPE_MANAGED);
-                       ji = frame.ji;
-               }
-
+               unwind_res = unwinder_unwind_frame (&unwinder, domain, jit_tls, NULL, ctx, &new_ctx, NULL, &lmf, NULL, &frame);
                if (!unwind_res) {
                        setup_stack_trace (mono_ex, dynamic_methods, &trace_ips);
                        g_slist_free (dynamic_methods);
                        return FALSE;
                }
 
+               switch (frame.type) {
+               case FRAME_TYPE_DEBUGGER_INVOKE:
+               case FRAME_TYPE_MANAGED_TO_NATIVE:
+               case FRAME_TYPE_TRAMPOLINE:
+               case FRAME_TYPE_INTERP_TO_MANAGED:
+                       *ctx = new_ctx;
+                       continue;
+               case FRAME_TYPE_INTERP:
+               case FRAME_TYPE_MANAGED:
+                       break;
+               default:
+                       g_assert_not_reached ();
+                       break;
+               }
+
+               in_interp = frame.type == FRAME_TYPE_INTERP;
+               ji = frame.ji;
+
+               gpointer ip;
+               if (in_interp)
+                       ip = (guint16*)ji->code_start + frame.native_offset;
+               else
+                       ip = MONO_CONTEXT_GET_IP (ctx);
+
                frame_count ++;
                method = jinfo_get_method (ji);
                //printf ("M: %s %d.\n", mono_method_full_name (method, TRUE), frame_count);
@@ -1583,7 +1654,7 @@ mono_handle_exception_internal_first_pass (MonoContext *ctx, MonoObject *obj, gi
                        if (free_stack <= (64 * 1024))
                                continue;
 
-                       if (is_address_protected (ji, ei, MONO_CONTEXT_GET_IP (ctx))) {
+                       if (is_address_protected (ji, ei, ip)) {
                                /* catch block */
                                MonoClass *catch_class = get_exception_catch_class (ei, ji, ctx);
 
@@ -1655,7 +1726,8 @@ mono_handle_exception_internal_first_pass (MonoContext *ctx, MonoObject *obj, gi
                                                *out_ji = ji;
 
                                        /* mono_debugger_agent_handle_exception () needs this */
-                                       MONO_CONTEXT_SET_IP (ctx, ei->handler_start);
+                                       if (!in_interp)
+                                               MONO_CONTEXT_SET_IP (ctx, ei->handler_start);
                                        return TRUE;
                                }
                                mono_error_cleanup (&isinst_error);
@@ -1670,9 +1742,9 @@ mono_handle_exception_internal_first_pass (MonoContext *ctx, MonoObject *obj, gi
 
 /**
  * mono_handle_exception_internal:
- * @ctx: saved processor state
- * @obj: the exception object
- * @resume: whenever to resume unwinding based on the state in MonoJitTlsData.
+ * \param ctx saved processor state
+ * \param obj the exception object
+ * \param resume whenever to resume unwinding based on the state in \c MonoJitTlsData.
  */
 static gboolean
 mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resume, MonoJitInfo **out_ji)
@@ -1692,6 +1764,8 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu
        int i;
        MonoObject *ex_obj;
        MonoObject *non_exception = NULL;
+       Unwinder unwinder;
+       gboolean in_interp;
 
        g_assert (ctx != NULL);
        if (!obj) {
@@ -1854,11 +1928,15 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu
        filter_idx = 0;
        initial_ctx = *ctx;
 
+       unwinder_init (&unwinder);
+
        while (1) {
                MonoContext new_ctx;
                guint32 free_stack;
                int clause_index_start = 0;
                gboolean unwind_res = TRUE;
+               StackFrameInfo frame;
+               gpointer ip;
                
                if (resume) {
                        resume = FALSE;
@@ -1869,27 +1947,36 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu
                        first_filter_idx = jit_tls->resume_state.first_filter_idx;
                        filter_idx = jit_tls->resume_state.filter_idx;
                } else {
-                       StackFrameInfo frame;
-
-                       unwind_res = mono_find_jit_info_ext (domain, jit_tls, NULL, ctx, &new_ctx, NULL, &lmf, NULL, &frame);
-                       if (unwind_res) {
-                               if (frame.type == FRAME_TYPE_DEBUGGER_INVOKE ||
-                                               frame.type == FRAME_TYPE_MANAGED_TO_NATIVE ||
-                                               frame.type == FRAME_TYPE_TRAMPOLINE) {
-                                       *ctx = new_ctx;
-                                       continue;
-                               }
-                               g_assert (frame.type == FRAME_TYPE_MANAGED);
-                               ji = frame.ji;
+                       unwind_res = unwinder_unwind_frame (&unwinder, domain, jit_tls, NULL, ctx, &new_ctx, NULL, &lmf, NULL, &frame);
+                       if (!unwind_res) {
+                               *(mono_get_lmf_addr ()) = lmf;
+
+                               jit_tls->abort_func (obj);
+                               g_assert_not_reached ();
+                       }
+                       switch (frame.type) {
+                       case FRAME_TYPE_DEBUGGER_INVOKE:
+                       case FRAME_TYPE_MANAGED_TO_NATIVE:
+                       case FRAME_TYPE_TRAMPOLINE:
+                               *ctx = new_ctx;
+                               continue;
+                       case FRAME_TYPE_INTERP_TO_MANAGED:
+                               continue;
+                       case FRAME_TYPE_INTERP:
+                       case FRAME_TYPE_MANAGED:
+                               break;
+                       default:
+                               g_assert_not_reached ();
+                               break;
                        }
+                       in_interp = frame.type == FRAME_TYPE_INTERP;
+                       ji = frame.ji;
                }
 
-               if (!unwind_res) {
-                       *(mono_get_lmf_addr ()) = lmf;
-
-                       jit_tls->abort_func (obj);
-                       g_assert_not_reached ();
-               }
+               if (in_interp)
+                       ip = (guint16*)ji->code_start + frame.native_offset;
+               else
+                       ip = MONO_CONTEXT_GET_IP (ctx);
 
                method = jinfo_get_method (ji);
                frame_count ++;
@@ -1915,7 +2002,7 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu
                        if (free_stack <= (64 * 1024))
                                continue;
 
-                       if (is_address_protected (ji, ei, MONO_CONTEXT_GET_IP (ctx))) {
+                       if (is_address_protected (ji, ei, ip)) {
                                /* catch block */
                                MonoClass *catch_class = get_exception_catch_class (ei, ji, ctx);
 
@@ -1998,7 +2085,27 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu
                                        mono_profiler_exception_clause_handler (method, ei->flags, i);
                                        jit_tls->orig_ex_ctx_set = FALSE;
                                        mini_set_abort_threshold (ctx);
-                                       MONO_CONTEXT_SET_IP (ctx, ei->handler_start);
+
+                                       if (in_interp) {
+                                               /*
+                                                * ctx->pc points into the interpreter, after the call which transitioned to
+                                                * JITted code. Store the unwind state into the
+                                                * interpeter state, then resume, the interpreter will unwind itself until
+                                                * it reaches the target frame and will continue execution from there.
+                                                * The resuming is kinda hackish, from the native code standpoint, it looks
+                                                * like the call which transitioned to JITted code has succeeded, but the
+                                                * return value register etc. is not set, so we have to be careful.
+                                                */
+                                               mono_interp_set_resume_state (mono_ex, &frame, ei->handler_start);
+                                               /* Undo the IP adjustment done by mono_arch_unwind_frame () */
+#ifdef TARGET_AMD64
+                                               ctx->gregs [AMD64_RIP] ++;
+#else
+                                               NOT_IMPLEMENTED;
+#endif
+                                       } else {
+                                               MONO_CONTEXT_SET_IP (ctx, ei->handler_start);
+                                       }
                                        mono_set_lmf (lmf);
 #ifndef DISABLE_PERFCOUNTERS
                                        mono_perfcounters->exceptions_depth += frame_count;
@@ -2050,7 +2157,10 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu
                                                return 0;
                                        } else {
                                                mini_set_abort_threshold (ctx);
-                                               call_filter (ctx, ei->handler_start);
+                                               if (in_interp)
+                                                       mono_interp_run_finally (&frame, i, ei->handler_start);
+                                               else
+                                                       call_filter (ctx, ei->handler_start);
                                        }
                                }
                        }
@@ -2068,13 +2178,11 @@ mono_handle_exception_internal (MonoContext *ctx, MonoObject *obj, gboolean resu
 
 /**
  * mono_debugger_run_finally:
- * @start_ctx: saved processor state
- *
- * This method is called by the Mono Debugger to call all `finally' clauses of the
- * current stack frame.  It's used when the user issues a `return' command to make
+ * \param start_ctx saved processor state
+ * This method is called by the Mono Debugger to call all \c finally clauses of the
+ * current stack frame.  It's used when the user issues a \c return command to make
  * the current stack frame return.  After returning from this method, the debugger
  * unwinds the stack one frame and gives control back to the user.
- *
  * NOTE: This method is only used when running inside the Mono Debugger.
  */
 void
@@ -2109,8 +2217,8 @@ mono_debugger_run_finally (MonoContext *start_ctx)
 
 /**
  * mono_handle_exception:
- * @ctx: saved processor state
- * @obj: the exception object
+ * \param ctx saved processor state
+ * \param obj the exception object
  */
 gboolean
 mono_handle_exception (MonoContext *ctx, MonoObject *obj)
@@ -2678,11 +2786,11 @@ mono_print_thread_dump_internal (void *sigctx, MonoContext *start_ctx)
        mono_runtime_stdout_fflush ();
 }
 
-/*
+/**
  * mono_print_thread_dump:
  *
- *   Print information about the current thread to stdout.
- * SIGCTX can be NULL, allowing this to be called from gdb.
+ * Print information about the current thread to stdout.
+ * \p sigctx can be NULL, allowing this to be called from gdb.
  */
 void
 mono_print_thread_dump (void *sigctx)
@@ -3002,7 +3110,12 @@ mono_restore_context (MonoContext *ctx)
 guint8*
 mono_jinfo_get_unwind_info (MonoJitInfo *ji, guint32 *unwind_info_len)
 {
-       if (ji->from_aot)
+       if (ji->has_unwind_info) {
+               /* The address/length in the MonoJitInfo structure itself */
+               MonoUnwindJitInfo *info = mono_jit_info_get_unwind_info (ji);
+               *unwind_info_len = info->unw_info_len;
+               return info->unw_info;
+       } else if (ji->from_aot)
                return mono_aot_get_unwind_info (ji, unwind_info_len);
        else
                return mono_get_cached_unwind_info (ji->unwind_info, unwind_info_len);
@@ -3253,3 +3366,31 @@ mono_debug_personality (void)
        g_assert_not_reached ();
 }
 #endif
+
+#ifndef ENABLE_INTERPRETER
+/* Stubs of interpreter functions */
+void
+mono_interp_set_resume_state (MonoException *ex, StackFrameInfo *frame, gpointer handler_ip)
+{
+       g_assert_not_reached ();
+}
+
+void
+mono_interp_run_finally (StackFrameInfo *frame, int clause_index, gpointer handler_ip)
+{
+       g_assert_not_reached ();
+}
+
+void
+mono_interp_frame_iter_init (MonoInterpStackIter *iter, gpointer interp_exit_data)
+{
+       g_assert_not_reached ();
+}
+
+gboolean
+mono_interp_frame_iter_next (MonoInterpStackIter *iter, StackFrameInfo *frame)
+{
+       g_assert_not_reached ();
+       return FALSE;
+}
+#endif