Browse Source

[Debugger] Added support for stepping over await and out of async met…
…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

David Karlaš 9 years ago
parent
commit
fb5bc8d4c5
3 changed files with 239 additions and 26 deletions
  1. 228 25
      mono/mini/debugger-agent.c
  2. 10 0
      mono/mini/method-to-ir.c
  3. 1 1
      mono/mini/seq-points.c

+ 228 - 25
mono/mini/debugger-agent.c

@@ -571,6 +571,10 @@ typedef struct {
 	int nframes;
 	int nframes;
 	/* If set, don't stop in methods that are not part of user assemblies */
 	/* If set, don't stop in methods that are not part of user assemblies */
 	MonoAssembly** 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;
 } SingleStepReq;
 
 
 /*
 /*
@@ -4526,18 +4530,42 @@ static void ss_calculate_framecount (DebuggerTlsData *tls, MonoContext *ctx)
 	compute_frame_info (tls->thread, tls);
 	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:
  * ss_update:
  *
  *
  * Return FALSE if single stepping needs to continue.
  * Return FALSE if single stepping needs to continue.
  */
  */
 static gboolean
 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;
 	MonoDebugMethodInfo *minfo;
 	MonoDebugSourceLocation *loc = NULL;
 	MonoDebugSourceLocation *loc = NULL;
 	gboolean hit = TRUE;
 	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)) {
 	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;
 		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;
 		gboolean is_step_out = req->depth == STEP_DEPTH_OUT;
 
 
 		ss_calculate_framecount (tls, ctx);
 		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){
 	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);
 		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)
 		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);
 			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)
 	if (req->size != STEP_SIZE_LINE)
 		return TRUE;
 		return TRUE;
 
 
 	/* Have to check whenever a different source line was reached */
 	/* Have to check whenever a different source line was reached */
-	method = jinfo_get_method (ji);
 	minfo = mono_debug_lookup_method (method);
 	minfo = mono_debug_lookup_method (method);
 
 
 	if (minfo)
 	if (minfo)
@@ -4608,6 +4646,82 @@ breakpoint_matches_assembly (MonoBreakpoint *bp, MonoAssembly *assembly)
 	return bp->method && bp->method->klass->image->assembly == 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
 static void
 process_breakpoint_inner (DebuggerTlsData *tls, gboolean from_signal)
 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;
 		SingleStepReq *ss_req = (SingleStepReq *)req->info;
 		gboolean hit;
 		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)
 		if (hit)
 			g_ptr_array_add (ss_reqs, req);
 			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;
 	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;
 		return;
 
 
 	/* Start single stepping again from the current sequence point */
 	/* Start single stepping again from the current sequence point */
@@ -5106,6 +5255,8 @@ ss_stop (SingleStepReq *ss_req)
 		ss_req->bps = NULL;
 		ss_req->bps = NULL;
 	}
 	}
 
 
+	ss_req->async_id = 0;
+	ss_req->async_stepout_method = NULL;
 	if (ss_req->global) {
 	if (ss_req->global) {
 		stop_single_stepping ();
 		stop_single_stepping ();
 		ss_req->global = FALSE;
 		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:
  * ss_start:
  *
  *
@@ -5236,6 +5409,8 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
 			nframes = tls->frame_count;
 			nframes = tls->frame_count;
 		}
 		}
 
 
+		MonoDebugMethodAsyncInfo* asyncMethod = mono_debug_lookup_method_async_debug_info (method);
+
 		/* Need to stop in catch clauses as well */
 		/* Need to stop in catch clauses as well */
 		for (i = ss_req->depth == STEP_DEPTH_OUT ? 1 : 0; i < nframes; ++i) {
 		for (i = ss_req->depth == STEP_DEPTH_OUT ? 1 : 0; i < nframes; ++i) {
 			StackFrame *frame = frames [i];
 			StackFrame *frame = frames [i];
@@ -5243,6 +5418,9 @@ ss_start (SingleStepReq *ss_req, MonoMethod *method, SeqPoint* sp, MonoSeqPointI
 			if (frame->ji) {
 			if (frame->ji) {
 				MonoJitInfo *jinfo = frame->ji;
 				MonoJitInfo *jinfo = frame->ji;
 				for (j = 0; j < jinfo->num_clauses; ++j) {
 				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];
 					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))
 					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) {
 		if (ss_req->depth == STEP_DEPTH_OUT) {
 			/* Ignore seq points in current method */
 			/* Ignore seq points in current method */
 			while (frame_index < nframes) {
 			while (frame_index < nframes) {
@@ -9178,20 +9392,9 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
 	if (!frame->has_ctx)
 	if (!frame->has_ctx)
 		return ERR_ABSENT_INFORMATION;
 		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;
 	jit = frame->jit;
 
 
 	sig = mono_method_signature (frame->actual_method);
 	sig = mono_method_signature (frame->actual_method);

+ 10 - 0
mono/mini/method-to-ir.c

@@ -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);
 					mono_bitset_set_fast (seq_point_locs, sps [i].il_offset);
 			}
 			}
 			g_free (sps);
 			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)) {
 		} 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 */
 			/* 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);
 			seq_point_locs = mono_bitset_mem_new (mono_mempool_alloc0 (cfg->mempool, mono_bitset_alloc_size (header->code_size, 0)), header->code_size, 0);

+ 1 - 1
mono/mini/seq-points.c

@@ -168,7 +168,7 @@ mono_save_seq_point_info (MonoCompile *cfg)
 				if (l) {
 				if (l) {
 					endfinally_seq_point = (MonoInst *)l->data;
 					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);
 						GSList *l = g_slist_last (bb2->seq_points);
 
 
 						if (l) {
 						if (l) {