Skip to content
Merged
37 changes: 34 additions & 3 deletions runtime/monovm-bridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
void
xamarin_bridge_initialize ()
{
#if DOTNET
bool use_mono_workaround = xamarin_init_mono_debug && getenv ("XAMARIN_SKIP_INTERPRETER_DEBUGGING_WORKAROUND") == 0;
#else
bool use_mono_workaround = false;
#endif

if (xamarin_register_modules != NULL)
xamarin_register_modules ();
DEBUG_LAUNCH_TIME_PRINT ("\tAOT register time");
Expand All @@ -69,9 +75,9 @@
DEBUG_LAUNCH_TIME_PRINT ("\tDebug init time");
#endif

if (xamarin_init_mono_debug)
if (xamarin_init_mono_debug && !use_mono_workaround)
mono_debug_init (MONO_DEBUG_FORMAT_MONO);

mono_install_assembly_preload_hook (xamarin_assembly_preload_hook, NULL);
mono_install_load_aot_data_hook (xamarin_load_aot_data, xamarin_free_aot_data, NULL);

Expand All @@ -85,7 +91,32 @@
mono_install_unhandled_exception_hook (xamarin_unhandled_exception_handler, NULL);
mono_install_ftnptr_eh_callback (xamarin_ftnptr_exception_handler);

mono_jit_init_version ("MonoTouch", "mobile");
const char *argc[] = {
"",
"--interp=-all",
"",
NULL,
};

if (use_mono_workaround) {
// This is a workaround for a runtime bug that prevents debugging from working properly.
// We call mono_main to disable interpreter optimizations when debugging, because
// mono's own interpreter initialization doesn't take into account that the debugger
// might be attached, and the subsequent optimizations can break the debugger.
// The stdout dance is to hide mono's "helpful" output telling us the command line
// arguments are incorrect (which they would be normally, but we're abusing mono_main
// to get the side effects).
fflush (stdout);
int originalStdout = dup (STDOUT_FILENO);
int devnull = open ("/dev/null", O_RDWR);
dup2 (devnull, STDOUT_FILENO);
mono_main (2, (char**)argc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain runtime-wise why this works around the problem?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sure.
The issue on runtime side was that we are initializing interpreter before setting the debugger attributes. So when we execute this code: https://github.com/dotnet/runtime/blob/5529dc79e9f35d70e9d01318d31844984b860dd4/src/mono/mono/mini/interp/interp.c#L8121
mdb_optimizations was false, then we keep the default optimizations of the interpreter which was breaking the debugger experience.
In this workaround I'm passing --interp=-all in the parameters, so I'm disabling the interpreter optimizations and the debugger works well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only a problem in .NET 6? Or legacy mono as well? (i.e. mono/mono's 2020-02 branch)

Copy link
Member Author

@thaystg thaystg Jul 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm almost sure that it's a problem only on .net 6, but I would ask someone to test to double check. It's really easy to reproduce:

public static void MethodMyMethod()
{
      var e2 = new Exception("hi thays");
      var e1 = e2;
      if (e1.Equals(e2))
	      Console.WriteLine("equals");
}

Adding a breakpoint on if statement and looking at locals, with the optimizations turned on we only see the content of variable e2, e1 is null.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to have implications for hot reload on .NET MAUI?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. @lambdageek do you have any thoughts about this workaround?

fflush (stdout);
dup2 (originalStdout, STDOUT_FILENO);
} else {
mono_jit_init_version ("MonoTouch", "mobile");
}

/*
As part of mono initialization a preload hook is added that overrides ours, so we need to re-instate it here.
This is wasteful, but there's no way to manipulate the preload hook list except by adding to it.
Expand Down