Bug 45742 - Exception stacks are absolutely useless
Summary: Exception stacks are absolutely useless
Status: VERIFIED FIXED
Alias: None
Product: Xamarin.Mac
Classification: Desktop
Component: Runtime (show other bugs)
Version: Master
Hardware: PC Mac OS
: High normal
Target Milestone: (C9)
Assignee: Bugzilla
URL:
: 51162 (view as bug list)
Depends on:
Blocks:
 
Reported: 2016-10-20 14:49 UTC by Aaron Bockover [MSFT]
Modified: 2016-12-30 21:38 UTC (History)
8 users (show)

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


Attachments

Description Aaron Bockover [MSFT] 2016-10-20 14:49:41 UTC
Xamarin.Mac makes debugging exceptions nearly impossible, particularly with storyboards and XIBs (as this path is going to affect basically every application).

When managed code is invoked by native code by way of loading a storyboard and allocating a managed class, anything on the managed stack after the native frame (storyboard load) that may throw a managed exception is lost.

The debugger will simply stop on NSApplication.Main and the trapped exception will have absolutely no useful frames in it, making it impossible to determine where the actual exception may have been thrown.

As an example, create a new Cocoa XM app and simply throw an exception in the default ViewController ViewDidLoad method:

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        throw new Exception ("USELESS");
    }

Instead of the debugger breaking on the exception being thrown in ViewDidLoad, it will break on NSApplication.Main, which was the last managed frame on the stack that the runtime seems to be aware of. Once we call NSApplication.Main, native code reads the interface description from Info.plist, loads the storyboard, and allocates any types needed by the storyboard, including our managed view controller:

Unhandled Exception:
System.Exception: USELESS
  at (wrapper managed-to-native) AppKit.NSApplication:NSApplicationMain (int,string[])
  at AppKit.NSApplication.Main (System.String[] args) [0x00041] in /Users/donblas/Programming/macios/backup/xamarin-macios/src/AppKit/NSApplication.cs:94
  at UselessExceptions.MainClass.Main (System.String[] args) [0x00007] in /Users/aaron/Projects/UselessExceptions/Main.cs:10

I would expect the stack to look something like this:

Unhandled Exception:
System.Exception: USELESS
  at UselessExceptions.ViewController.ViewDidLoad () in /Users/aaron/Projects/UselessExceptions/ViewController.cs:18
  at (wrapper native-to-managed) ...
  ...
  at (wrapper managed-to-native) AppKit.NSApplication:NSApplicationMain (int,string[])
  at AppKit.NSApplication.Main (System.String[] args) [0x00041] in /Users/donblas/Programming/macios/backup/xamarin-macios/src/AppKit/NSApplication.cs:94
  at UselessExceptions.MainClass.Main (System.String[] args) [0x00007] in /Users/aaron/Projects/UselessExceptions/Main.cs:10
Comment 1 Aaron Bockover [MSFT] 2016-10-20 14:52:20 UTC
Full test case here: https://github.com/abock/filed-bug-test-cases/tree/master/Xamarin/bxc45742
Comment 2 Aaron Bockover [MSFT] 2016-10-20 14:53:45 UTC
XM:

Version: 3.0.0.107
Hash: 3cbaa72
Branch: xm_for_workbooks
Build date: 2016-10-10 15:05:17-0500

Mono:
4.8.0 (mono-4.8.0-branch/412e531)

I know the XM I am running is a little custom, but this behavior has been going on for a while, even via the Alpha channel.
Comment 3 Aaron Bockover [MSFT] 2016-10-20 14:55:03 UTC
Note that you *can* actually set breakpoints and they appear to be hit, so the lack of frames seems to be limited to thrown exceptions.
Comment 4 Chris Hamons 2016-10-20 15:01:48 UTC
If you don't mind getting shot in the head, this at least will get you _some_ context on your exception:

--marshal-managed-exceptions:abort

You add it as an additional mmp argument.

Rolf - Can we do better w\ the default? I don't know this code well.
Comment 5 Aaron Bockover [MSFT] 2016-10-20 15:05:12 UTC
With that workaround, here's a real trace as dumped by the abort:

2016-10-20 11:00:40.455 Xamarin Workbooks[11728:2164250] Xamarin.Mac: Aborting due to:
Unhandled managed exception:
Value cannot be null.
Parameter name: value (System.ArgumentNullException)
  at AppKit.NSMenuItemCell.set_MenuItem (AppKit.NSMenuItem value) [0x00011] in /Users/donblas/Programming/macios/backup/xamarin-macios/src/build/mac/full/AppKit/NSMenuItemCell.g.cs:409 
  at Xamarin.Interactive.Client.Mac.WorkbookTargetSelector.GetToolbarSize () [0x0000e] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/WorkbookTargetSelector.cs:68 
  at Xamarin.Interactive.Client.Mac.SessionToolbarDelegate+<SessionToolbarDelegate>c__AnonStorey0.<>m__0 () [0x00007] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/SessionToolbarDelegate.cs:81 
  at Xamarin.Interactive.Client.Mac.SessionToolbarDelegate..ctor (Xamarin.Interactive.Client.ClientSession clientSession, Xamarin.Interactive.Client.Mac.MacClientSessionViewControllers viewControllers, AppKit.NSToolbar toolbar) [0x0012b] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/SessionToolbarDelegate.cs:86 
  at Xamarin.Interactive.Client.Mac.SessionWindowController.WindowDidLoad () [0x00115] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/SessionWindowController.cs:63 

Stacktrace:

  at <unknown> <0xffffffff>
  at (wrapper managed-to-native) ObjCRuntime.Messaging.IntPtr_objc_msgSend_IntPtr (intptr,intptr,intptr) <IL 0x0000b, 0x000fd>
  at AppKit.NSStoryboard.InstantiateControllerWithIdentifier (string) [0x00036] in /Users/donblas/Programming/macios/backup/xamarin-macios/src/build/mac/full/AppKit/NSStoryboard.g.cs:103
  at Xamarin.Interactive.Client.Mac.SessionDocument.MakeWindowControllers () [0x00026] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/SessionDocument.cs:52
  at (wrapper runtime-invoke) object.runtime_invoke_void__this__ (object,intptr,intptr,intptr) <IL 0x00020, 0x00163>
  at <unknown> <0xffffffff>
  at (wrapper managed-to-native) ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_IntPtr_bool_ref_IntPtr (intptr,intptr,intptr,bool,intptr&) <IL 0x00018, 0x0016e>
  at AppKit.NSDocumentController.OpenDocument (Foundation.NSUrl,bool,Foundation.NSError&) [0x00069] in /Users/donblas/Programming/macios/backup/xamarin-macios/src/build/mac/full/AppKit/NSDocumentController.g.cs:560
  at Xamarin.Interactive.Client.Mac.SessionDocumentController.OpenDocument (Foundation.NSUrl,bool,Foundation.NSError&) [0x00063] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/SessionDocumentController.cs:106
  at Xamarin.Interactive.Client.Mac.SessionDocumentController.OpenDocument (Foundation.NSUrl) [0x00006] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/SessionDocumentController.cs:43
  at Xamarin.Interactive.Client.Mac.SessionDocumentController.OpenDocument (Xamarin.Interactive.Client.ClientSessionUri) <IL 0x0000c, 0x000d7>
  at Xamarin.Interactive.Client.Mac.NewWorkbookViewController.CreateWorkbook (Foundation.NSObject) <IL 0x00011, 0x00101>
  at (wrapper runtime-invoke) <Module>.runtime_invoke_void__this___object (object,intptr,intptr,intptr) <IL 0x00022, 0x00169>
  at <unknown> <0xffffffff>
  at (wrapper managed-to-native) AppKit.NSApplication.NSApplicationMain (int,string[]) <IL 0x0005a, 0x00359>
  at AppKit.NSApplication.Main (string[]) [0x00041] in /Users/donblas/Programming/macios/backup/xamarin-macios/src/AppKit/NSApplication.cs:94
  at Xamarin.Interactive.Client.Mac.Entry.Main (string[]) [0x00007] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/Entry.cs:18
  at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00051, 0x0030a>


Compared to:


System.ArgumentNullException: Value cannot be null.
Parameter name: value
  at (wrapper managed-to-native) ObjCRuntime.Messaging:IntPtr_objc_msgSendSuper_IntPtr_bool_ref_IntPtr (intptr,intptr,intptr,bool,intptr&)
  at AppKit.NSDocumentController.OpenDocument (Foundation.NSUrl url, System.Boolean displayDocument, Foundation.NSError& outError) [0x00069] in /Users/donblas/Programming/macios/backup/xamarin-macios/src/build/mac/full/AppKit/NSDocumentController.g.cs:560 
  at Xamarin.Interactive.Client.Mac.SessionDocumentController.OpenDocument (Foundation.NSUrl url, System.Boolean displayDocument, Foundation.NSError& outError) [0x00063] in /Users/aaron/src/inspector/Clients/Xamarin.Interactive.Client.Mac/SessionDocumentController.cs:106
Comment 6 Rolf Bjarne Kvinge [MSFT] 2016-10-20 15:26:41 UTC
@Aaron, does the debugger stop where the exception is thrown if you set an exception watchpoint in Xamarin Studio?
Comment 7 Aaron Bockover [MSFT] 2016-10-25 15:22:20 UTC
@rolf yes, catch points *do* work as expected. The IDE pauses at the correct location and the call stack in the IDE is as I would expect.
Comment 8 Rolf Bjarne Kvinge [MSFT] 2016-10-26 15:54:27 UTC
I can reproduce this, but it does not look like anything XM does, it looks like a bug in the Mono runtime somewhere, so I'm re-assigning to there (maybe it's an inlining optimization?)
Comment 9 Chris Hamons 2016-10-26 15:55:57 UTC
@Kumpera (or any other runtime peeps) - This is rather painful for XM users. Please poke me if I can be of assistance.
Comment 10 Rolf Bjarne Kvinge [MSFT] 2016-10-26 16:18:06 UTC
Actually this is something that changed in XM.

Workaround 1: pass "--marshal-managed-exceptions=disable" to mmp. This will get the old behavior.

Workaround 2: add the following code, and the correct stack trace will be printed:

	ObjCRuntime.Runtime.MarshalManagedException += (sender, args) =>
	{
		Console.WriteLine (args.Exception);
	};

Explanation:

We changed to pass an exception pointer to mono_runtime_invoke, which means we'll catch any exceptions (from Mono's perspective) that reaches native code. Later we'll call mono_raise_exception with the same exception, but unfortunately that API will throw the exception, not rethrow it, thus trampling the original stack trace.

Potential fix: add an embedding API to Mono that we can use to rethrow the exception. That will fix the stack trace when printing the exception at least.
Comment 11 Rolf Bjarne Kvinge [MSFT] 2016-10-26 16:35:49 UTC
So Xamarin.Android has the same problem with regards to stopping when the (unhandled) exception is thrown in the IDE, and they disable marshaling when the debugger is attached to work around it.
Comment 12 Rolf Bjarne Kvinge [MSFT] 2016-10-26 16:52:37 UTC
Xamarin.Android is using ExceptionDispatchInfo [1] to capture both stack traces (both the original one and the re-thrown one), so I'll look into that and see if it works for us as well.

[1] https://github.com/xamarin/Java.Interop/commit/9387f2fe16300ea8eb3d5270e50f0513d251bd62
Comment 13 Rolf Bjarne Kvinge [MSFT] 2016-10-26 18:36:27 UTC
PR to use ExceptionDispatchInfo to maintain the original stack trace: https://github.com/xamarin/xamarin-macios/pull/1040
Comment 14 Rolf Bjarne Kvinge [MSFT] 2016-10-28 05:30:52 UTC
PR merged (https://github.com/xamarin/xamarin-macios/commit/94485580b530344fcade82b81dc35241435ebb41).

There are two sides to this issue:

a) The debugger does not stop when the exception is thrown (it's not treated as an unhandled exception when the exception is thrown).
b) The stack trace in the exception object does not contain the original location where the exception was thrown.

A related bug was also filed (bug #45116), I'll leave that bug for part a), and keep part b) for this bug, which means that this bug is fixed.
Comment 16 Brendan Zagaeski (Xamarin Support) 2016-12-30 21:38:00 UTC
*** Bug 51162 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.