diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 7f88cfe0caf..ecec32bfab0 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -775,6 +775,77 @@ public void UnhandledExceptionFromButtonClick ([Values (AndroidRuntime.MonoVM, A $"Output did not contain {expectedRaiser}!"); } + [Test] + public void NativeCrashProducesManagedStackTrace ([Values (AndroidRuntime.CoreCLR)] AndroidRuntime runtime) + { + // This test verifies that when a native crash (SIGSEGV) occurs, the .NET runtime + // logs a managed stack trace in logcat BEFORE Android's signal handler takes over. + // This is enabled by the System.Runtime.CrashReportBeforeSignalChaining config option. + // See: https://github.com/dotnet/android/pull/11291 + // See: https://github.com/dotnet/runtime/pull/123735 + // See: https://github.com/dotnet/runtime/pull/123824 + const bool isRelease = true; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { + IsRelease = isRelease, + }; + proj.SetRuntime (runtime); + proj.SetProperty ("AllowUnsafeBlocks", "true"); + proj.SetAndroidSupportedAbis (DeviceAbi); + + proj.MainActivity = proj.DefaultMainActivity + .Replace ("//${USINGS}", "using System.Runtime.InteropServices;") + .Replace ("//${AFTER_ONCREATE}", """ + // Force a native crash (SIGSEGV) via P/Invoke to libc memset with a null pointer. + // The .NET runtime's crash reporting should log the managed stack trace before + // Android's signal handler aborts the process. + CrashHelper.ForceNativeSegfault (); + """) + .Replace ("//${AFTER_MAINACTIVITY}", """ + static class CrashHelper + { + [DllImport ("libc", EntryPoint = "memset")] + static extern unsafe void MemSet (void* ptr, int value, nuint count); + + public static unsafe void ForceNativeSegfault () + { + MemSet (null, 0, (nuint)1); + } + } + """); + + builder = CreateApkBuilder (); + Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); + ClearAdbLogcat (); + AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity"); + + // CoreCLR logs the managed stack trace via the DOTNET logcat tag when crash reporting + // runs before signal chaining. The output includes: + // E DOTNET : Got a SIGSEGV while executing native code. ... + // E DOTNET : at .CrashHelper.ForceNativeSegfault() + string logcatFile = Path.Combine (Root, builder.ProjectDirectory, "crash-logcat.log"); + bool foundSigsegv = false; + bool foundManagedFrame = false; + Assert.IsTrue ( + MonitorAdbLogcat ( + (line) => { + if (line.Contains ("DOTNET") && line.Contains ("SIGSEGV")) { + foundSigsegv = true; + } + if (line.Contains ("DOTNET") && line.Contains ("ForceNativeSegfault")) { + foundManagedFrame = true; + } + return foundSigsegv && foundManagedFrame; + }, + logcatFilePath: logcatFile, + timeout: 30), + "Crash reporting should have logged 'SIGSEGV' and a managed stack trace containing 'ForceNativeSegfault' via the DOTNET logcat tag. " + + "Verify the System.Runtime.CrashReportBeforeSignalChaining config option is set and the runtime supports it."); + } + [Test] [Category ("UsesDevice")] [TestCaseSource (nameof (Get_SmokeTestBuildAndRunWithSpecialCharacters_Data))]