Skip to content

Commit 4cae5f5

Browse files
[One .NET] fix debugging (#4864)
If you tried to debug an app with: dotnet build HelloAndroid/HelloAndroid.csproj -t:Install,_Run -p:AndroidAttachDebugger=true The presence of `$(AndroidAttachDebugger)` causes a crash on startup: E mono : Unhandled Exception: E mono : System.ArgumentNullException: Value cannot be null. (Parameter 'meth') E mono : at System.Reflection.Emit.ILGenerator.Emit(OpCode opcode, MethodInfo meth) E mono : at Android.Runtime.JNINativeWrapper.CreateDelegate(Delegate dlg) E mono : at Android.App.Activity.GetOnCreate_Landroid_os_Bundle_Handler() E mono : at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType nativeClass, Type type, String methods) E mono : at Android.Runtime.JNIEnv.RegisterJniNatives(IntPtr typeName_ptr, Int32 typeName_len, IntPtr jniClass, IntPtr methods_ptr, Int32 methods_len) E mono-rt : [ERROR] FATAL UNHANDLED EXCEPTION: System.ArgumentNullException: Value cannot be null. (Parameter 'meth') E mono-rt : at System.Reflection.Emit.ILGenerator.Emit(OpCode opcode, MethodInfo meth) E mono-rt : at Android.Runtime.JNINativeWrapper.CreateDelegate(Delegate dlg) E mono-rt : at Android.App.Activity.GetOnCreate_Landroid_os_Bundle_Handler() E mono-rt : at Android.Runtime.AndroidTypeManager.RegisterNativeMembers(JniType nativeClass, Type type, String methods) E mono-rt : at Android.Runtime.JNIEnv.RegisterJniNatives(IntPtr typeName_ptr, Int32 typeName_len, IntPtr jniClass, IntPtr methods_ptr, Int32 methods_len) The cause of the crash is because we pass `JNINativeWrapper.mono_unhandled_exception_method` to [`ILGenerator.Emit(OpCode, MethodInfo)`][0], and `mono_unhandled_exception_method` is `null`. `mono_unhandled_exception_method` is a `MethodInfo` to [`Debugger.Mono_UnhandledException()`][1], which does not exist in .NET 5 Mono, which is why it's `null` within .NET 5. To fix `dotnet build -t:_Run` support, cleanup `JNINativeWrapper.get_runtime_types()` to instead check for `JNINativeWrapper.exception_handler_method`, as `exception_handler_method` is consistently initialized properly on both "legacy" and .NET 5 environments. Furthermore, update `get_runtime_types()` so that `mono_unhandled_exception_method` is not passed to `ILGenerator.Emit()` when it is `null`. This avoids the `ArgumentNullException`. This surfaced another problem when using the `_Run` target by itself: Xamarin.Android.Common.Debugging.targets(518,2): error MSB3073: The command ""adb" forward tcp:10000 tcp:10000" exited with code 9009. The `$(_AndroidPlatformToolsDirectory)` property was blank when running the `_Run` target by itself, but things work fine if you run `Install,_Run` at the same time. This behavior broke in commit fb637e0. I accidentally replaced the `AndroidPrepareForBuild` MSBuild target with [the version][2] in `Xamarin.Android.Bindings.Core.targets`. To fix this, I can just move the binding project version of `AndroidPrepareForBuild` target to `Xamarin.Android.Bindings.targets`. I added a new test verifying that breakpoints work, when running under .NET 5. [0]: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.ilgenerator.emit?view=netcore-3.1#System_Reflection_Emit_ILGenerator_Emit_System_Reflection_Emit_OpCode_System_Reflection_MethodInfo_ [1]: https://github.com/mono/mono/blob/2ff424be2933f711207a8139c5792ab5d1b495a9/mcs/class/corlib/System.Diagnostics/Debugger.cs#L119-L127 [2]: https://github.com/xamarin/xamarin-android/blob/f99a3fad1b12d9674de2f624e243f296cfc40fc6/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets#L47
1 parent 73e91a9 commit 4cae5f5

File tree

4 files changed

+82
-6
lines changed

4 files changed

+82
-6
lines changed

src/Mono.Android/Android.Runtime/JNINativeWrapper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static class JNINativeWrapper {
1313

1414
static void get_runtime_types ()
1515
{
16-
if (mono_unhandled_exception_method != null)
16+
if (exception_handler_method != null)
1717
return;
1818
#if MONOANDROID1_0
1919
mono_unhandled_exception_method = typeof (System.Diagnostics.Debugger).GetMethod (
@@ -70,10 +70,10 @@ public static Delegate CreateDelegate (Delegate dlg)
7070
ig.Emit (OpCodes.Leave, label);
7171

7272
bool filter = Debugger.IsAttached || !JNIEnv.PropagateExceptions;
73-
if (filter) {
73+
if (filter && mono_unhandled_exception_method != null) {
7474
ig.BeginExceptFilterBlock ();
7575

76-
ig.Emit (OpCodes.Call, mono_unhandled_exception_method!);
76+
ig.Emit (OpCodes.Call, mono_unhandled_exception_method);
7777
ig.Emit (OpCodes.Ldc_I4_1);
7878
ig.BeginCatchBlock (null!);
7979
} else {

src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Bindings.Core.targets

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ It is shared between "legacy" binding projects and .NET 5 projects.
4444
</PropertyGroup>
4545
</Target>
4646

47-
<Target Name="AndroidPrepareForBuild" DependsOnTargets="GetReferenceAssemblyPaths" />
48-
4947
<Target Name="GenerateBindings"
5048
Condition=" '$(UsingAndroidNETSdk)' != 'true' Or '@(InputJar)' != '' Or '@(EmbeddedJar)' != '' "
5149
DependsOnTargets="ExportJarToXml;_ResolveMonoAndroidSdks"

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Bindings.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ Copyright (C) 2012 Xamarin Inc. All rights reserved.
7575
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
7676
</PropertyGroup>
7777

78+
<Target Name="AndroidPrepareForBuild" DependsOnTargets="GetReferenceAssemblyPaths" />
79+
7880
<!-- Warn about deprecated configurations.
7981
Do it here because we want them to appear on every build,
8082
even if we aren't rerunning generator -->

tests/MSBuildDeviceIntegration/Tests/XASdkDeployTests.cs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.Linq;
5+
using System.Net;
46
using System.Reflection;
7+
using System.Threading;
8+
using Mono.Debugging.Client;
9+
using Mono.Debugging.Soft;
510
using NUnit.Framework;
611
using Xamarin.ProjectTools;
712

@@ -26,7 +31,6 @@ public void DotNetInstallAndRun ([Values (false, true)] bool isRelease, [Values
2631
IsRelease = isRelease
2732
};
2833
}
29-
proj.SetProperty (KnownProperties.AndroidSupportedAbis, DeviceAbi);
3034
proj.SetRuntimeIdentifier (DeviceAbi);
3135

3236
var relativeProjDir = Path.Combine ("temp", TestName);
@@ -43,5 +47,77 @@ public void DotNetInstallAndRun ([Values (false, true)] bool isRelease, [Values
4347
RunAdbCommand ($"uninstall {proj.PackageName}");
4448
Assert.IsTrue(didLaunch, "Activity should have started.");
4549
}
50+
51+
[Test]
52+
public void DotNetDebug ()
53+
{
54+
if (!HasDevices)
55+
Assert.Ignore ("Skipping Test. No devices available.");
56+
57+
XASdkProject proj;
58+
proj = new XASdkProject ();
59+
proj.SetRuntimeIdentifier (DeviceAbi);
60+
61+
var relativeProjDir = Path.Combine ("temp", TestName);
62+
var fullProjDir = Path.Combine (Root, relativeProjDir);
63+
TestOutputDirectories [TestContext.CurrentContext.Test.ID] = fullProjDir;
64+
var files = proj.Save ();
65+
proj.Populate (relativeProjDir, files);
66+
proj.CopyNuGetConfig (relativeProjDir);
67+
var dotnet = new DotNetCLI (proj, Path.Combine (fullProjDir, proj.ProjectFilePath));
68+
Assert.IsTrue (dotnet.Build ("Install"), "`dotnet build` should succeed");
69+
70+
bool breakpointHit = false;
71+
ManualResetEvent resetEvent = new ManualResetEvent (false);
72+
var sw = new Stopwatch ();
73+
// setup the debugger
74+
var session = new SoftDebuggerSession ();
75+
session.Breakpoints = new BreakpointStore {
76+
{ Path.Combine (Root, dotnet.ProjectDirectory, "MainActivity.cs"), 19 },
77+
};
78+
session.TargetHitBreakpoint += (sender, e) => {
79+
Console.WriteLine ($"BREAK {e.Type}");
80+
breakpointHit = true;
81+
session.Continue ();
82+
};
83+
var rnd = new Random ();
84+
int port = rnd.Next (10000, 20000);
85+
TestContext.Out.WriteLine ($"{port}");
86+
var args = new SoftDebuggerConnectArgs ("", IPAddress.Loopback, port) {
87+
MaxConnectionAttempts = 10,
88+
};
89+
var startInfo = new SoftDebuggerStartInfo (args) {
90+
WorkingDirectory = Path.Combine (dotnet.ProjectDirectory, proj.IntermediateOutputPath, "android", "assets"),
91+
};
92+
var options = new DebuggerSessionOptions () {
93+
EvaluationOptions = EvaluationOptions.DefaultOptions,
94+
};
95+
options.EvaluationOptions.UseExternalTypeResolver = true;
96+
ClearAdbLogcat ();
97+
Assert.True (dotnet.Build ("_Run", new string [] {
98+
$"AndroidSdbTargetPort={port}",
99+
$"AndroidSdbHostPort={port}",
100+
"AndroidAttachDebugger=True",
101+
}), "Project should have run.");
102+
103+
Assert.IsTrue (WaitForDebuggerToStart (Path.Combine (Root, dotnet.ProjectDirectory, "logcat.log")), "Activity should have started");
104+
// we need to give a bit of time for the debug server to start up.
105+
WaitFor (2000);
106+
session.LogWriter += (isStderr, text) => { Console.WriteLine (text); };
107+
session.OutputWriter += (isStderr, text) => { Console.WriteLine (text); };
108+
session.DebugWriter += (level, category, message) => { Console.WriteLine (message); };
109+
session.Run (startInfo, options);
110+
WaitFor (TimeSpan.FromSeconds (30), () => session.IsConnected);
111+
Assert.True (session.IsConnected, "Debugger should have connected but it did not.");
112+
// we need to wait here for a while to allow the breakpoints to hit
113+
// but we need to timeout
114+
TimeSpan timeout = TimeSpan.FromSeconds (60);
115+
while (session.IsConnected && !breakpointHit && timeout >= TimeSpan.Zero) {
116+
Thread.Sleep (10);
117+
timeout = timeout.Subtract (TimeSpan.FromMilliseconds (10));
118+
}
119+
WaitFor (2000);
120+
Assert.IsTrue (breakpointHit, "Should have a breakpoint");
121+
}
46122
}
47123
}

0 commit comments

Comments
 (0)