From 1346e5469452b8368e4d02ae2cec890104482e94 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 12 Sep 2025 18:13:55 +0200 Subject: [PATCH 1/3] feat: auto-suppress SIGABRT for uncaught managed exceptions on iOS --- ...ntimeMarshalManagedExceptionIntegration.cs | 5 +++++ src/Sentry/Platforms/Cocoa/SentryOptions.cs | 3 ++- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 20 +++++++++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/RuntimeMarshalManagedExceptionIntegration.cs b/src/Sentry/Platforms/Cocoa/RuntimeMarshalManagedExceptionIntegration.cs index 396221cb79..4ffee1a827 100644 --- a/src/Sentry/Platforms/Cocoa/RuntimeMarshalManagedExceptionIntegration.cs +++ b/src/Sentry/Platforms/Cocoa/RuntimeMarshalManagedExceptionIntegration.cs @@ -32,6 +32,11 @@ internal void Handle(object sender, MarshalManagedExceptionEventArgs e) if (e.Exception is { } ex) { + // The Obj-C runtime is about to abort. Tag the upcoming duplicate native SIGABRT event for filtering it + // out in ProcessOnBeforeSend. + // TODO: define "_captured_by_sentry-dotnet" as a constant somewhere + SentryCocoaSdk.ConfigureScope(scope => scope.SetTagValue("SIGABRT", "_captured_by_sentry-dotnet")); + ex.SetSentryMechanism( "Runtime.MarshalManagedException", "This exception was caught by the .NET Runtime Marshal Managed Exception global error handler. " + diff --git a/src/Sentry/Platforms/Cocoa/SentryOptions.cs b/src/Sentry/Platforms/Cocoa/SentryOptions.cs index 02ce449556..50a87e5b92 100644 --- a/src/Sentry/Platforms/Cocoa/SentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/SentryOptions.cs @@ -207,11 +207,12 @@ internal NativeOptions(SentryOptions options) /// convenient. /// /// + /// TODO: Explain auto (null) vs. true vs. false. /// Enabling this may prevent the capture of SIGABRT originating from native (not managed) code... so it may /// prevent the capture of genuine native SIGABRT errors. /// /// - public bool SuppressSignalAborts { get; set; } = false; + public bool? SuppressSignalAborts { get; set; } = null; /// /// diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 9cd167dc0e..10910d002c 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -200,21 +200,24 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( /// internal static CocoaSdk.SentryEvent? ProcessOnBeforeSend(SentryOptions options, CocoaSdk.SentryEvent evt, IHub hub) { - if (hub is DisabledHub) - { - return evt; - } - // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. // There should only be one exception on the event in this case - if ((options.Native.SuppressSignalAborts || options.Native.SuppressExcBadAccess) && evt.Exceptions?.Length == 1) + if (evt.Exceptions?.Length == 1) { // It will match the following characteristics var ex = evt.Exceptions[0]; + // TODO: define "_captured_by_sentry-dotnet" as a constant somewhere + if (options.Native.SuppressSignalAborts == null && ex.Type == evt.Tags?["_captured_by_sentry-dotnet"]?.ToString()) + { + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, + ex.Value); + return null; + } + // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter // them out. Here is the function that calls abort(), which we will use as a filter: // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 @@ -239,6 +242,11 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( } } + if (hub is DisabledHub) + { + return evt; + } + // We run our SIGABRT checks first before running managed processors. // Because we delegate to user code, we need to catch/log exceptions. try From 0668d9a7563bc3c6a94ec5c155aaec577ed47e37 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 15 Sep 2025 10:42:02 +0200 Subject: [PATCH 2/3] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc59b8f81..310d274d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Auto-suppress `SIGABRT` for uncaught managed exceptions on iOS ([#4523](https://github.com/getsentry/sentry-dotnet/pull/4523)) + ### Fixes - Fail when building Blazor WASM with Profiling. We don't support profiling in Blazor WebAssembly projects. ([#4512](https://github.com/getsentry/sentry-dotnet/pull/4512)) From d918aec43d1fad0a673db85ee4647230c7ec455a Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 15 Sep 2025 11:07:32 +0200 Subject: [PATCH 3/3] Fix comparison --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 10910d002c..8cb5b41a85 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -221,7 +221,7 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter // them out. Here is the function that calls abort(), which we will use as a filter: // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 - if (options.Native.SuppressSignalAborts && ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && + if (options.Native.SuppressSignalAborts == true && ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) { // Don't send it