Bug 51684 - Unlike the Visual Studio debugger, the Mono soft debugger does not break directly on unhandled exceptions in `async` methods even when "Debug project code only" is enabled
Summary: Unlike the Visual Studio debugger, the Mono soft debugger does not break dire...
Status: CONFIRMED
Alias: None
Product: Runtime
Classification: Mono
Component: Debugger (show other bugs)
Version: 4.8.0 (C9)
Hardware: PC Mac OS
: --- normal
Target Milestone: ---
Assignee: Zoltan Varga
URL:
: 45540 (view as bug list)
Depends on: 39371
Blocks:
  Show dependency tree
 
Reported: 2017-01-21 04:19 UTC by Brendan Zagaeski (Xamarin Support)
Modified: 2017-01-22 23:49 UTC (History)
4 users (show)

See Also:
Tags: papercut
Is this bug a regression?: No
Last known good build:


Attachments

Description Brendan Zagaeski (Xamarin Support) 2017-01-21 04:19:25 UTC
Unlike the Visual Studio debugger, the Mono soft debugger does not break directly on unhandled exceptions in `async` methods even when "Debug project code only" is enabled




## Motivation and background

The original observation from Xamarin users that prompted this investigation is that unhandled exceptions in async methods in Xamarin.iOS and Xamarin.Android apps don't break where expected in Xamarin Studio or Visual Studio.




## Note that "Enable Just My Code" is required to see the "good" results in Visual Studio

Visual Studio shows the desired behavior for this console C# test case, but _only_ when "Tools > Options > Debugging > General > Enable Just My Code" is enabled.

Based on that observation, it might be sensible to take care of Bug 39371 and Bug 39345 first and then re-test this bug to see if those fixes also resolve this issue.




## Partial workaround

Set the debugger to break on all thrown `System.Exception` exceptions via "Debug > Exceptions" in VS 2013, "Debug > Windows > Exception Settings" in VS 2015, or "Run > New Exception Catchpoint" in Xamarin Studio.

The Mono debugger will then break at the desired location: Program.cs, line 22.

This is only a partial workaround because the debugger will also break on the _handled_ exception in the sample code.  The ideal behavior would be for the debugger to ignore that exception in this particular case.




## Steps to replicate


1. Compile the following program, for example by running `mcs /debug Program.cs`.

```
using System;

namespace Program
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            try
            {
                throw new Exception("Ignore this handled exception");
            }
            catch
            {
            }
            ThrowAnExceptionAsync();
            Console.ReadKey();
        }
        
        public static async void ThrowAnExceptionAsync()
        {
            throw new Exception("An Exception");
        }
    }
}
```


2. Open Xamarin Studio and ensure that "Xamarin Studio > Preferences > Debugger > Debug project code only; do not step into framework code." is enabled.


3. Debug the program.  For example, you can launch the .exe file via "Run > Debug Application", or you can paste the source code into a ".NET > Console Project" and then use "Run > Start Debugging".




## Results

The debugger breaks at `System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()` rather than at the line in the source code where the exception was originally thrown.

In contrast, the `StackTrace` property of the `$exception` variable _does_ reference the desired line of code:

>  at Program.MainClass+<ThrowAnExceptionAsync>c__async0.MoveNext () [0x00018] in /Users/macuser/Desktop/Program.cs:22 


### Example of the full "bad" Call Stack from Xamarin Studio

> System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143
> System.Runtime.CompilerServices.AsyncMethodBuilderCore.AnonymousMethod__1(System.Runtime.ExceptionServices.ExceptionDispatchInfo state) in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1034
> System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Threading.QueueUserWorkItemCallback state) in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1304
> System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Threading.QueueUserWorkItemCallback state, bool preserveSyncCtx) in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:957
> System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Threading.QueueUserWorkItemCallback state, bool preserveSyncCtx) in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:904
> System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1281
> System.Threading.ThreadPoolWorkQueue.Dispatch() in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:854
> System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() in /private/tmp/source-mono-4.8.0/bockbuild-mono-4.8.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1209



## Expected results (in Visual Studio)



### Steps followed to test

1. Create a new "Visual C# > Console Application" project in Visual Studio.

2. Paste in the program.  (If you prefer, you could instead compile by hand with `csc /debug Program.cs` and then set "Project Properties > Debug > Start Action" to "Start external program".  The results are the same either way.)

3. Ensure that "Tools > Options > Debugging > General > Enable Just My Code" is enabled.

4. Select "Debug > Start Debugging" in the "Debug|Any CPU" configuration.



### Example of the "good" Call Stack from Visual Studio with "Enable Just My Code" enabled

> Program.exe!Program.MainClass.ThrowAnExceptionAsync() Line 22	C#
> Program.exe!Program.MainClass.Main(string[] args) Line 16	C#
> [Native to Managed Transition]	
> [Managed to Native Transition]	
> mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string[] args)	Unknown
> Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()	Unknown
> mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state)	Unknown
> mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)	Unknown
> mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)	Unknown
> mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state)	Unknown
> mscorlib.dll!System.Threading.ThreadHelper.ThreadStart()	Unknown


### Example of the "bad" Call Stack from Visual Studio with "Enable Just My Code" _disabled_

(Note the close similarity to the Xamarin Studio call stack.)

> mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.ThrowAsync.AnonymousMethod__6_1(object state)	Unknown
> mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)	Unknown
> mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)	Unknown
> mscorlib.dll!System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()	Unknown
> mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()	Unknown


### Example of the "bad" Call Stack from Visual Studio when there is no .pdb file at all

If you set "Project Properties > Build > Advanced > Debug Info" to "None" for the Debug configuration, the call stack looks even more similar to Xamarin Studio's:

> mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.ThrowAsync.AnonymousMethod__6_1(object state)	Unknown
> mscorlib.dll!System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(object state)	Unknown
> mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)	Unknown
> mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)	Unknown
> mscorlib.dll!System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()	Unknown
> mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()	Unknown
> mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()	Unknown


### If you remove the `async` keyword, then the call stack is _always_ good

Good even when "Enable Just My Code" is disabled:
> Program.exe!Program.MainClass.ThrowAnExceptionAsync() Line 22	C#
> Program.exe!Program.MainClass.Main(string[] args) Line 16	C#

Good even when "Debug Info" set to "None":
> Program.exe!Program.MainClass.ThrowAnExceptionAsync()	Unknown
> Program.exe!Program.MainClass.Main(string[] args)	Unknown



## Results summary

It would seem that the "Enable Just My Code" feature in Visual Studio provides a special case for unhandled exceptions from `async` methods so that the debugger can break at the original location of the exception rather than at the re-thrown location.




## Testing environment info



### Mac

Xamarin Studio 6.2 (build 1757) (d769a24)
Mono 4.8.0 (mono-4.8.0-branch/cd26828)
sdb 1.5 (master/b8b3296)

Mac OS 10.11.6



### Windows

Microsoft Visual Studio Enterprise 2015
Version 14.0.25431.01 Update 3
Microsoft .NET Framework
Version 4.6.01586

Visual C# Compiler version 1.3.1.60616

Windows 10 (64-bit) Version 1607 (OS Build 14393.694)
US English locale, US Eastern time zone
Comment 1 Brendan Zagaeski (Xamarin Support) 2017-01-21 04:24:56 UTC
## Supplemental test with .NET Core in Visual Studio Code on Mac

1. Create a directory named "Program" and `cd` into it.

2. Run `dotnet new`.

3. Paste the program into the Program.cs file.

4. Open Visual Studio Code and open the "Program" directory.

5. Let Visual Studio Code add the required assets and restore the unresolved dependencies.

6. Install the ms-vscode.csharp VS Code extension (if not already installed).

7. Launch the app for debugging via the ".NET Core Launch (console)" button in the "Debug" sidebar.




## Example of "good" Call Stack in Visual Studio Code

> Program.MainClass.ThrowAnExceptionAsync() Program.cs 22
> Program.MainClass.Main(string[] args) Program.cs 16
> [External Code] Unknown Source 0



## Testing environment info

.NET Core 1.0.0-preview2-1-003177
VS Code 1.8.1 (ee428b0)
ms-vscode.csharp extension 1.6.2

Mac OS 10.11.6
Comment 2 Brendan Zagaeski (Xamarin Support) 2017-01-21 04:49:50 UTC
*** Bug 45540 has been marked as a duplicate of this bug. ***

Note You need to log in before you can comment on or make changes to this bug.