[Debugger] Added support for stepping over await and out of async met…
authorDavid Karlaš <david.karlas@xamarin.com>
Fri, 27 Jan 2017 10:17:40 +0000 (11:17 +0100)
committerDavid Karlaš <david.karlas@xamarin.com>
Fri, 27 Jan 2017 10:17:57 +0000 (11:17 +0100)
…hods

method-to-ir.c: Added sequence points on IL offsets yieldOffsets and resumeOffsets since we need stepping to stop yieldOffset and ability to set breakpoint on resumeOffset
seq-points.c: This change is needed since we need to know if we are at last non-empty SeqPoint in method, but without this change is_last_non_empty method in debugger-agent.c would loop forever since blocks point between each other in cycles
doest-app.cs and dtest.cs: unit test
debugger-agent.c: I will explain how stepping over `await` and stepping out of `async` method works in commit message, code itself should be self explaining with comments

Step-In and Step-Out case: inside `async` method we do everything same except two things:
1. At end of method we switch to step-out logic
This is important because normal stepping-in/over would step out into “thread pool” calling code. Which is not what we want, we want to continue stepping where .Wait, .Result or `await` is waiting for our Task to finish. `is_last_non_empty` is needed because last non-empty SeqPoint is placed before SetResult(see line 33 in decompiled method below) so we can do StepOut logic before SetResult method is called.  I will explain how step-out works below.
2. When stepping is finished we check if we stepped on yieldOffset(this happens when user is about to step over `await` call) and Task has IsCompleted false(didn’t finish immediately)(line 11 in decompiled method). If we stopped on yieldOffset we put breakpoint on resumeOffset SeqPoint(line 19 in decompiled method) and resume execution so when our AsyncStateMachine is called back after Task(the one that our `await` call triggered) is finished we hit breakpoint and user is just after `await` call. It’s important that we set `async_id` before resuming at yieldOffset so we can check when breakpoint is hit if this is our Task or some other that is executing at same time, since we can’t check threads since threads can change before and after `await` call.

Step-Out case: When user requests step-out or user requests step-in/over and we are at end of method, we set breakpoint in special .Net framework method called “NotifyDebuggerOfWaitCompletion” and call “SetNotificationForWaitCompletion” method so after Task is finished(when we call .SetResult, line 33 in decompiled method).

Method that called our `async` method and got our Task(on which we called “SetNotificationForWaitCompletion”) returned. When it calls .Wait(), .Result, or `await` on our task it will call “NotifyDebuggerOfWaitCompletion”(framework does this). At that point our breakpoint(inside “NotifyDebuggerOfWaitCompletion”) is hit and we do step-out until we get to where user called .Wait(), .Result, and `await`.(Of course if ProjectCodeOnly is enabled, otherwise it’s in 1 method above “NotifyDebuggerOfWaitCompletion”).

I added also ppdb dump and monodis if anyone wants more data:

C# code:
public static async Task<int> ss_await_1 () {
    var a = 1;
    await Task.Delay (20);
    return a + 2;
}
C# decompiled:
  1: void IAsyncStateMachine.MoveNext ()
  2: {
  3:     int num = this.<>1__state;
  4:     int result;
  5:     try {
  6:         TaskAwaiter taskAwaiter;
  7:         if (num != 0) {
  8:           this.<a>5__1 = 1;
  9:           taskAwaiter = Task.Delay (20).GetAwaiter ();
10:           if (!taskAwaiter.get_IsCompleted ()) {
11:                this.<>1__state = 0;
12:                this.<>u__1 = taskAwaiter;
13:               Tests.<ss_await_1>d__90 <ss_await_1>d__ = this;
14:               this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Tests.<ss_await_1>d__90> (ref taskAwaiter, ref <ss_await_1>d__);
15:                return;
16:            }
17:        }
18:        else {
19:            taskAwaiter = this.<>u__1;
20:            this.<>u__1 = default(TaskAwaiter);
21:            this.<>1__state = -1;
22:        }
23:        taskAwaiter.GetResult ();
24:        taskAwaiter = default(TaskAwaiter);
25:        result = this.<a>5__1 + 2;
26:    }
27:    catch (Exception exception) {
28:        this.<>1__state = -2;
29:        this.<>t__builder.SetException (exception);
30:        return;
31:    }
32:    this.<>1__state = -2;
33:    this.<>t__builder.SetResult (result);
34:}

ppdb dump for this method:
<method containingType="Tests+&lt;ss_await_1&gt;d__90" name="MoveNext">
    <sequencePoints>
    <entry offset="0x0" hidden="true" document="1" />
    <entry offset="0x7" hidden="true" document="1" />
    <entry offset="0xe" startLine="744" startColumn="46" endLine="744" endColumn="47" document="1" />
    <entry offset="0xf" startLine="745" startColumn="3" endLine="745" endColumn="13" document="1" />
    <entry offset="0x16" startLine="746" startColumn="3" endLine="746" endColumn="25" document="1" />
    <entry offset="0x23" hidden="true" document="1" />
    <entry offset="0x7c" startLine="747" startColumn="3" endLine="747" endColumn="16" document="1" />
    <entry offset="0x87" hidden="true" document="1" />
    <entry offset="0xa1" startLine="748" startColumn="2" endLine="748" endColumn="3" document="1" />
    <entry offset="0xa9" hidden="true" document="1" />
    </sequencePoints>
    <scope startOffset="0x0" endOffset="0xb7" />
    <asyncInfo>
    <kickoffMethod declaringType="Tests" methodName="ss_await_1" />
    <await yield="0x35" resume="0x50" declaringType="Tests+&lt;ss_await_1&gt;d__90" methodName="MoveNext" />
    </asyncInfo>
</method>

monodis:
.method private final virtual hidebysig newslot
        instance default void MoveNext ()  cil managed
{
    // Method begins at RVA 0x41f0
.override Could not decode method override class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext due to (null)
// Code size 183 (0xb7)
.maxstack 3
.locals init (
    int32 V_0,
    int32 V_1,
    valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiterV_2,
    class Tests/'<ss_await_1>d__90' V_3,
    class [mscorlib]System.Exception V_4)
IL_0000:  ldarg.0
IL_0001:  ldfld int32 Tests/'<ss_await_1>d__90'::'<>1__state'
IL_0006:  stloc.0
.try { // 0
    IL_0007:  ldloc.0
    IL_0008:  brfalse.s IL_000c

    IL_000a:  br.s IL_000e

    IL_000c:  br.s IL_0050

    IL_000e:  nop
    IL_000f:  ldarg.0
    IL_0010:  ldc.i4.1
    IL_0011:  stfld int32 Tests/'<ss_await_1>d__90'::'<a>5__1'
    IL_0016:  ldc.i4.s 0x14
    IL_0018:  call class [mscorlib]System.Threading.Tasks.Task class [mscorlib]System.Threading.Tasks.Task::Delay(int32)
    IL_001d:  callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter class [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
    IL_0022:  stloc.2
    IL_0023:  ldloca.s 2
    IL_0025:  call instance bool valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted()
    IL_002a:  brtrue.s IL_006c

    IL_002c:  ldarg.0
    IL_002d:  ldc.i4.0
    IL_002e:  dup
    IL_002f:  stloc.0
    IL_0030:  stfld int32 Tests/'<ss_await_1>d__90'::'<>1__state'
    IL_0035:  ldarg.0
    IL_0036:  ldloc.2
    IL_0037:  stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Tests/'<ss_await_1>d__90'::'<>u__1'
    IL_003c:  ldarg.0
    IL_003d:  stloc.3
    IL_003e:  ldarg.0
    IL_003f:  ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Tests/'<ss_await_1>d__90'::'<>t__builder'
    IL_0044:  ldloca.s 2
    IL_0046:  ldloca.s 3
    IL_0048:  call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter, class Tests/'<ss_await_1>d__90'> ([out] !!0&, [out] !!1&)
    IL_004d:  nop
    IL_004e:  leave.s IL_00b6

    IL_0050:  ldarg.0
    IL_0051:  ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Tests/'<ss_await_1>d__90'::'<>u__1'
    IL_0056:  stloc.2
    IL_0057:  ldarg.0
    IL_0058:  ldflda valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Tests/'<ss_await_1>d__90'::'<>u__1'
    IL_005d:  initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
    IL_0063:  ldarg.0
    IL_0064:  ldc.i4.m1
    IL_0065:  dup
    IL_0066:  stloc.0
    IL_0067:  stfld int32 Tests/'<ss_await_1>d__90'::'<>1__state'
    IL_006c:  ldloca.s 2
    IL_006e:  call instance void valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
    IL_0073:  nop
    IL_0074:  ldloca.s 2
    IL_0076:  initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
    IL_007c:  ldarg.0
    IL_007d:  ldfld int32 Tests/'<ss_await_1>d__90'::'<a>5__1'
    IL_0082:  ldc.i4.2
    IL_0083:  add
    IL_0084:  stloc.1
    IL_0085:  leave.s IL_00a1

} // end .try 0
catch class [mscorlib]System.Exception { // 0
    IL_0087:  stloc.s 4
    IL_0089:  ldarg.0
    IL_008a:  ldc.i4.s 0xfffffffe
    IL_008c:  stfld int32 Tests/'<ss_await_1>d__90'::'<>1__state'
    IL_0091:  ldarg.0
    IL_0092:  ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Tests/'<ss_await_1>d__90'::'<>t__builder'
    IL_0097:  ldloc.s 4
    IL_0099:  call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetException(class [mscorlib]System.Exception)
    IL_009e:  nop
    IL_009f:  leave.s IL_00b6

} // end handler 0
IL_00a1:  ldarg.0
IL_00a2:  ldc.i4.s 0xfffffffe
IL_00a4:  stfld int32 Tests/'<ss_await_1>d__90'::'<>1__state'
IL_00a9:  ldarg.0
IL_00aa:  ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Tests/'<ss_await_1>d__90'::'<>t__builder'
IL_00af:  ldloc.1
IL_00b0:  call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetResult(!0)
IL_00b5:  nop
IL_00b6:  ret
} // end of method <ss_await_1>d__90::MoveNext

mono/mini/debugger-agent.c
mono/mini/method-to-ir.c
mono/mini/seq-points.c

index dd0e7353bd5ed0a6b6ef34835370eed6b5f37409..7b0b052f4503743808c91874901a3e239398ae27 100644 (file)
@@ -571,6 +571,10 @@ typedef struct {
        int nframes;
        /* If set, don't stop in methods that are not part of user assemblies */
        MonoAssembly** user_assemblies;
+       /* Used to distinguish stepping breakpoint hits in parallel tasks executions */
+       int async_id;
+       /* Used to know if we are in process of async step-out and distishing from exception breakpoints */
+       MonoMethod* async_stepout_method;
 } SingleStepReq;
 
 /*
@@ -4526,18 +4530,42 @@ static void ss_calculate_framecount (DebuggerTlsData *tls, MonoContext *ctx)
        compute_frame_info (tls->thread, tls);
 }
 
+static gboolean
+ensure_jit (StackFrame* frame)
+{
+       if (!frame->jit) {
+               frame->jit = mono_debug_find_method (frame->api_method, frame->domain);
+               if (!frame->jit && frame->api_method->is_inflated)
+                       frame->jit = mono_debug_find_method(mono_method_get_declaring_generic_method (frame->api_method), frame->domain);
+               if (!frame->jit) {
+                       char *s;
+
+                       /* This could happen for aot images with no jit debug info */
+                       s = mono_method_full_name (frame->api_method, TRUE);
+                       DEBUG_PRINTF(1, "[dbg] No debug information found for '%s'.\n", s);
+                       g_free (s);
+                       return FALSE;
+               }
+       }
+       return TRUE;
+}
+
 /*
  * ss_update:
  *
  * Return FALSE if single stepping needs to continue.
  */
 static gboolean
-ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, DebuggerTlsData *tls, MonoContext *ctx)
+ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, DebuggerTlsData *tls, MonoContext *ctx, MonoMethod* method)
 {
        MonoDebugMethodInfo *minfo;
        MonoDebugSourceLocation *loc = NULL;
        gboolean hit = TRUE;
-       MonoMethod *method;
+
+       if (req->async_stepout_method == method) {
+               DEBUG_PRINTF (1, "[%p] Breakpoint hit during async step-out at %s hit, continuing stepping out.\n", (gpointer)(gsize)mono_native_thread_id_get (), method->name);
+               return FALSE;
+       }
 
        if (req->depth == STEP_DEPTH_OVER && (sp->flags & MONO_SEQ_POINT_FLAG_NONEMPTY_STACK)) {
                /*
@@ -4547,7 +4575,7 @@ ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, DebuggerTlsData *t
                return FALSE;
        }
 
-       if ((req->depth == STEP_DEPTH_OVER || req->depth == STEP_DEPTH_OUT) && hit) {
+       if ((req->depth == STEP_DEPTH_OVER || req->depth == STEP_DEPTH_OUT) && hit && !req->async_stepout_method) {
                gboolean is_step_out = req->depth == STEP_DEPTH_OUT;
 
                ss_calculate_framecount (tls, ctx);
@@ -4563,7 +4591,6 @@ ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, DebuggerTlsData *t
        }
 
        if (req->depth == STEP_DEPTH_INTO && req->size == STEP_SIZE_MIN && (sp->flags & MONO_SEQ_POINT_FLAG_NONEMPTY_STACK) && ss_req->start_method){
-               method = jinfo_get_method (ji);
                ss_calculate_framecount (tls, ctx);
                if (ss_req->start_method == method && req->nframes && tls->frame_count == req->nframes) {//Check also frame count(could be recursion)
                        DEBUG_PRINTF (1, "[%p] Seq point at nonempty stack %x while stepping in, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
@@ -4571,11 +4598,22 @@ ss_update (SingleStepReq *req, MonoJitInfo *ji, SeqPoint *sp, DebuggerTlsData *t
                }
        }
 
+       MonoDebugMethodAsyncInfo* asyncMethod = mono_debug_lookup_method_async_debug_info (method);
+       if (asyncMethod) {
+               for (int i = 0; i < asyncMethod->num_awaits; i++)
+               {
+                       if (asyncMethod->yield_offsets[i] == sp->il_offset || asyncMethod->resume_offsets[i] == sp->il_offset) {
+                               mono_debug_free_method_async_debug_info (asyncMethod);
+                               return FALSE;
+                       }
+               }
+               mono_debug_free_method_async_debug_info (asyncMethod);
+       }
+
        if (req->size != STEP_SIZE_LINE)
                return TRUE;
 
        /* Have to check whenever a different source line was reached */
-       method = jinfo_get_method (ji);
        minfo = mono_debug_lookup_method (method);
 
        if (minfo)
@@ -4608,6 +4646,82 @@ breakpoint_matches_assembly (MonoBreakpoint *bp, MonoAssembly *assembly)
        return bp->method && bp->method->klass->image->assembly == assembly;
 }
 
+static MonoObject*
+get_this (StackFrame *frame)
+{
+       //Logic inspiered by "add_var" method and took out path that happens in async method for getting this
+       MonoDebugVarInfo *var = frame->jit->this_var;
+       if ((var->index & MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS) != MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET)
+               return NULL;
+
+       guint8 * addr = (guint8 *)mono_arch_context_get_int_reg (&frame->ctx, var->index & ~MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS);
+       addr += (gint32)var->offset;
+       return *(MonoObject**)addr;
+}
+
+//This ID is used to figure out if breakpoint hit on resumeOffset belongs to us or not
+//since thread probably changed...
+static int
+get_this_async_id (StackFrame *frame)
+{
+       return get_objid (get_this (frame));
+}
+
+static MonoMethod* set_notification_method_cache = NULL;
+
+static MonoMethod*
+get_set_notification_method ()
+{
+       if(set_notification_method_cache != NULL)
+               return set_notification_method_cache;
+       MonoError error;
+       MonoClass* async_builder_class = mono_class_load_from_name (mono_defaults.corlib, "System.Runtime.CompilerServices", "AsyncTaskMethodBuilder");
+       GPtrArray* array = mono_class_get_methods_by_name (async_builder_class, "SetNotificationForWaitCompletion", 0x24, FALSE, FALSE, &error);
+       mono_error_assert_ok (&error);
+       g_assert (array->len == 1);
+       set_notification_method_cache = (MonoMethod *)g_ptr_array_index (array, 0);
+       g_ptr_array_free (array, TRUE);
+       return set_notification_method_cache;
+}
+
+static void
+set_set_notification_for_wait_completion_flag (StackFrame *frame)
+{
+       MonoObject* obj = get_this (frame);
+       g_assert (obj);
+       MonoClassField *builder_field = mono_class_get_field_from_name (obj->vtable->klass, "<>t__builder");
+       g_assert (builder_field);
+       MonoObject* builder;
+       MonoError error;
+       builder = mono_field_get_value_object_checked (frame->domain, builder_field, obj, &error);
+       mono_error_assert_ok (&error);
+       g_assert (builder);
+
+       void* args [1];
+       gboolean arg = TRUE;
+       args [0] = &arg;
+       mono_runtime_invoke_checked (get_set_notification_method(), mono_object_unbox (builder), args, &error);
+       mono_error_assert_ok (&error);
+       mono_field_set_value (obj, builder_field, mono_object_unbox (builder));
+}
+
+static MonoMethod* notify_debugger_of_wait_completion_method_cache = NULL;
+
+static MonoMethod*
+get_notify_debugger_of_wait_completion_method ()
+{
+       if (notify_debugger_of_wait_completion_method_cache != NULL)
+               return notify_debugger_of_wait_completion_method_cache;
+       MonoError error;
+       MonoClass* task_class = mono_class_load_from_name (mono_defaults.corlib, "System.Threading.Tasks", "Task");
+       GPtrArray* array = mono_class_get_methods_by_name (task_class, "NotifyDebuggerOfWaitCompletion", 0x24, FALSE, FALSE, &error);
+       mono_error_assert_ok (&error);
+       g_assert (array->len == 1);
+       notify_debugger_of_wait_completion_method_cache = (MonoMethod *)g_ptr_array_index (array, 0);
+       g_ptr_array_free (array, TRUE);
+       return notify_debugger_of_wait_completion_method_cache;
+}
+
 static void
 process_breakpoint_inner (DebuggerTlsData *tls, gboolean from_signal)
 {
@@ -4696,10 +4810,45 @@ process_breakpoint_inner (DebuggerTlsData *tls, gboolean from_signal)
                SingleStepReq *ss_req = (SingleStepReq *)req->info;
                gboolean hit;
 
-               if (mono_thread_internal_current () != ss_req->thread)
-                       continue;
+               //if we hit async_stepout_method, it's our no matter which thread
+               if ((ss_req->async_stepout_method != method) && (ss_req->async_id || mono_thread_internal_current () != ss_req->thread)) {
+                       //We have different thread and we don't have async stepping in progress
+                       //it's breakpoint in parallel thread, ignore it
+                       if (ss_req->async_id == 0)
+                               continue;
+
+                       tls->context.valid = FALSE;
+                       tls->async_state.valid = FALSE;
+                       invalidate_frames (tls);
+                       ss_calculate_framecount(tls, ctx);
+                       //make sure we have enough data to get current async method instance id
+                       if (tls->frame_count == 0 || !ensure_jit (tls->frames [0]))
+                               continue;
+
+                       //Check method is async before calling get_this_async_id
+                       MonoDebugMethodAsyncInfo* asyncMethod = mono_debug_lookup_method_async_debug_info (method);
+                       if (!asyncMethod)
+                               continue;
+                       else
+                               mono_debug_free_method_async_debug_info (asyncMethod);
+
+                       //breakpoint was hit in parallelly executing async method, ignore it
+                       if (ss_req->async_id != get_this_async_id (tls->frames [0]))
+                               continue;
+               }
+
+               //Update stepping request to new thread/frame_count that we are continuing on
+               //so continuing with normal stepping works as expected
+               if (ss_req->async_stepout_method || ss_req->async_id) {
+                       tls->context.valid = FALSE;
+                       tls->async_state.valid = FALSE;
+                       invalidate_frames (tls);
+                       ss_calculate_framecount (tls, ctx);
+                       ss_req->thread = mono_thread_internal_current ();
+                       ss_req->nframes = tls->frame_count;
+               }
 
-               hit = ss_update (ss_req, ji, &sp, tls, ctx);
+               hit = ss_update (ss_req, ji, &sp, tls, ctx, method);
                if (hit)
                        g_ptr_array_add (ss_reqs, req);
 
@@ -4937,7 +5086,7 @@ process_single_step_inner (DebuggerTlsData *tls, gboolean from_signal)
 
        il_offset = sp.il_offset;
 
-       if (!ss_update (ss_req, ji, &sp, tls, ctx))
+       if (!ss_update (ss_req, ji, &sp, tls, ctx, method))
                return;
 
        /* Start single stepping again from the current sequence point */
@@ -5106,6 +5255,8 @@ ss_stop (SingleStepReq *ss_req)
                ss_req->bps = NULL;
        }
 
+       ss_req->async_id = 0;
+       ss_req->async_stepout_method = NULL;
        if (ss_req->global) {
                stop_single_stepping ();
                ss_req->global = FALSE;
@@ -5191,6 +5342,28 @@ ss_bp_add_one (SingleStepReq *ss_req, int *ss_req_bp_count, GHashTable **ss_req_
        }
 }
 
+static gboolean
+is_last_non_empty (SeqPoint* sp, MonoSeqPointInfo *info)
+{
+       if (!sp->next_len)
+               return TRUE;
+       SeqPoint* next = g_new (SeqPoint, sp->next_len);
+       mono_seq_point_init_next (info, *sp, next);
+       for (int i = 0; i < sp->next_len; i++) {
+               if (next [i].flags & MONO_SEQ_POINT_FLAG_NONEMPTY_STACK) {
+                       if (!is_last_non_empty (&next [i], info)) {
+                               g_free (next);
+                               return FALSE;
+                       }
+               } else {
+                       g_free (next);
+                       return FALSE;
+               }
+       }
+       g_free (next);
+       return TRUE;
+}
+
 /*
  * ss_start:
  *
@@ -5236,6 +5409,8 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
                        nframes = tls->frame_count;
                }
 
+               MonoDebugMethodAsyncInfo* asyncMethod = mono_debug_lookup_method_async_debug_info (method);
+
                /* Need to stop in catch clauses as well */
                for (i = ss_req->depth == STEP_DEPTH_OUT ? 1 : 0; i < nframes; ++i) {
                        StackFrame *frame = frames [i];
@@ -5243,6 +5418,9 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
                        if (frame->ji) {
                                MonoJitInfo *jinfo = frame->ji;
                                for (j = 0; j < jinfo->num_clauses; ++j) {
+                                       // In case of async method we don't want to place breakpoint on last catch handler(which state machine added for whole method)
+                                       if (asyncMethod && asyncMethod->num_awaits && i == 0 && j + 1 == jinfo->num_clauses)
+                                               break;
                                        MonoJitExceptionInfo *ei = &jinfo->clauses [j];
 
                                        if (mono_find_next_seq_point_for_native_offset (frame->domain, frame->method, (char*)ei->handler_start - (char*)jinfo->code_start, NULL, &local_sp))
@@ -5251,10 +5429,46 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
                        }
                }
 
+               if (asyncMethod && asyncMethod->num_awaits && nframes && ensure_jit (frames [0])) {
+                       //asyncMethod has value and num_awaits > 0, this means we are inside async method with awaits
+
+                       // Check if we hit yield_offset during normal stepping, because if we did...
+                       // Go into special async stepping mode which places breakpoint on resumeOffset
+                       // of this await call and sets async_id so we can distinguish it from parallel executions
+                       for (i = 0; i < asyncMethod->num_awaits; i++) {
+                               if (sp->il_offset == asyncMethod->yield_offsets [i]) {
+                                       ss_req->async_id = get_this_async_id (frames [0]);
+                                       ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, method, asyncMethod->resume_offsets [i]);
+                                       if (ss_req_bp_cache)
+                                               g_hash_table_destroy (ss_req_bp_cache);
+                                       mono_debug_free_method_async_debug_info (asyncMethod);
+                                       return;
+                               }
+                       }
+                       //If we are at end of async method and doing step-in or step-over...
+                       //Switch to step-out, so whole NotifyDebuggerOfWaitCompletion magic happens...
+                       if (is_last_non_empty (sp, info)) {
+                               ss_req->depth = STEP_DEPTH_OUT;//setting depth to step-out is important, don't inline IF, because code later depends on this
+                       }
+                       if (ss_req->depth == STEP_DEPTH_OUT) {
+                               set_set_notification_for_wait_completion_flag (frames [0]);
+                               ss_req->async_id = get_this_async_id (frames [0]);
+                               ss_req->async_stepout_method = get_notify_debugger_of_wait_completion_method ();
+                               ss_bp_add_one (ss_req, &ss_req_bp_count, &ss_req_bp_cache, ss_req->async_stepout_method, 0);
+                               if (ss_req_bp_cache)
+                                       g_hash_table_destroy (ss_req_bp_cache);
+                               mono_debug_free_method_async_debug_info (asyncMethod);
+                               return;
+                       }
+               }
+
+               if (asyncMethod)
+                       mono_debug_free_method_async_debug_info (asyncMethod);
+
                /*
-                * Find the first sequence point in the current or in a previous frame which
-                * is not the last in its method.
-                */
+               * Find the first sequence point in the current or in a previous frame which
+               * is not the last in its method.
+               */
                if (ss_req->depth == STEP_DEPTH_OUT) {
                        /* Ignore seq points in current method */
                        while (frame_index < nframes) {
@@ -9178,20 +9392,9 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
        if (!frame->has_ctx)
                return ERR_ABSENT_INFORMATION;
 
-       if (!frame->jit) {
-               frame->jit = mono_debug_find_method (frame->api_method, frame->domain);
-               if (!frame->jit && frame->api_method->is_inflated)
-                       frame->jit = mono_debug_find_method (mono_method_get_declaring_generic_method (frame->api_method), frame->domain);
-               if (!frame->jit) {
-                       char *s;
+       if (!ensure_jit (frame))
+               return ERR_ABSENT_INFORMATION;
 
-                       /* This could happen for aot images with no jit debug info */
-                       s = mono_method_full_name (frame->api_method, TRUE);
-                       DEBUG_PRINTF (1, "[dbg] No debug information found for '%s'.\n", s);
-                       g_free (s);
-                       return ERR_ABSENT_INFORMATION;
-               }
-       }
        jit = frame->jit;
 
        sig = mono_method_signature (frame->actual_method);
index f5ee5fa74e42e9cd038eca80e073927f82445faf..2519adbeb5da4cb53d8cec1c540f77cb6783b2d0 100644 (file)
@@ -7547,6 +7547,16 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                        mono_bitset_set_fast (seq_point_locs, sps [i].il_offset);
                        }
                        g_free (sps);
+
+                       MonoDebugMethodAsyncInfo* asyncMethod = mono_debug_lookup_method_async_debug_info (method);
+                       if (asyncMethod) {
+                               for (i = 0; asyncMethod != NULL && i < asyncMethod->num_awaits; i++)
+                               {
+                                       mono_bitset_set_fast (seq_point_locs, asyncMethod->resume_offsets[i]);
+                                       mono_bitset_set_fast (seq_point_locs, asyncMethod->yield_offsets[i]);
+                               }
+                               mono_debug_free_method_async_debug_info (asyncMethod);
+                       }
                } else if (!method->wrapper_type && !method->dynamic && mono_debug_image_has_debug_info (method->klass->image)) {
                        /* Methods without line number info like auto-generated property accessors */
                        seq_point_locs = mono_bitset_mem_new (mono_mempool_alloc0 (cfg->mempool, mono_bitset_alloc_size (header->code_size, 0)), header->code_size, 0);
index 237215a06e28c8b5a14119090940e010904a2570..a0294d7dd462a2db2c3efcfee3057d053844ab27 100644 (file)
@@ -168,7 +168,7 @@ mono_save_seq_point_info (MonoCompile *cfg)
                                if (l) {
                                        endfinally_seq_point = (MonoInst *)l->data;
 
-                                       for (bb2 = cfg->bb_entry; bb2; bb2 = bb2->next_bb) {
+                                       for (bb2 = bb->next_bb; bb2; bb2 = bb2->next_bb) {
                                                GSList *l = g_slist_last (bb2->seq_points);
 
                                                if (l) {