Skip to content

fix: AOT interop with managed .NET runtimes#6193

Merged
itaybre merged 15 commits intomainfrom
jpnurmi/mono-interop
Apr 9, 2026
Merged

fix: AOT interop with managed .NET runtimes#6193
itaybre merged 15 commits intomainfrom
jpnurmi/mono-interop

Conversation

@jpnurmi
Copy link
Copy Markdown
Collaborator

@jpnurmi jpnurmi commented Sep 18, 2025

📜 Description

TLDR; reorder .NET/Mono vs. Sentry Cocoa signal handlers to allow the .NET runtime to convert certain signals to managed .NET exceptions where appropriate, and chain actual native crashes to Sentry Cocoa.

Before

┌──────────────┐     ┌───────────┐     ┌────────┐
│ Sentry Cocoa │────>│ .NET/Mono │────>│ System │
└──────────────┘     └───────────┘     └────────┘

After:

┌───────────┐     ┌──────────────┐     ┌────────┐
│ .NET/Mono │────>│ Sentry Cocoa │────>│ System │
└───────────┘     └──────────────┘     └────────┘

Changes

Mostly gated by the SENTRY_CRASH_MANAGED_RUNTIME compile-time flag to minimize impact on normal SDK operation:

  1. Signal handler preloading — A __attribute__((constructor)) preloads SentryCrash's signal handlers before the managed runtime starts, ensuring the correct handler chain order: managed runtime → SentryCrash → system. This lets the managed runtime handle signals like SIGSEGV (for NullReferenceException) and SIGFPE (for DivideByZeroException) before SentryCrash sees them.

  2. Mach exception mask filtering — Excludes EXC_MASK_BAD_ACCESS and EXC_MASK_ARITHMETIC from Mach exception monitoring, since these correspond to signals the managed runtime handles. Other Mach exceptions (EXC_BAD_INSTRUCTION, EXC_SOFTWARE, EXC_BREAKPOINT) are still monitored.

  3. ignoreNextSignal: API — New method on PrivateSentrySDKOnly that tells SentryCrash to ignore the next occurrence of a given signal on the calling thread (thread-local, one-shot). Used by hybrid SDKs to prevent duplicate crash reports when the managed runtime is about to raise a signal (e.g. SIGABRT via abort()) for an exception that has already been captured: https://github.com/dotnet/macios/blob/d0d53e8230a79fd505ddf7cef2642493249ccb78/runtime/runtime.m#L1003-L1011

  4. NULL-guard g_onExceptionEvent — Prevents a crash if a signal fires between the constructor preload and sentrycrash_install().

  5. volatile crash pointer — The +[SentrySDK crash] test method now uses volatile to force an actual SIGSEGV instead of letting the compiler optimize the null dereference into a trap instruction (SIGTRAP).

💡 Motivation and Context

Fixes redundant native crash events when using Sentry with .NET on iOS:

  • Managed exceptions (e.g. ApplicationException) produced a duplicate SIGABRT event
  • NullReferenceException in AOT mode produced a duplicate EXC_BAD_ACCESS event

See: getsentry/sentry-dotnet#3954

💚 How did you test it?

Sentry .NET integration tests for iOS:

📝 Checklist

You have to check all boxes before merging:

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

@codecov
Copy link
Copy Markdown

codecov bot commented Sep 18, 2025

Codecov Report

❌ Patch coverage is 22.72727% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.419%. Comparing base (bab8d5e) to head (27df7b7).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ash/Recording/Monitors/SentryCrashMonitor_Signal.c 17.647% 14 Missing ⚠️
Sources/Sentry/PrivateSentrySDKOnly.m 0.000% 1 Missing ⚠️
Sources/Sentry/SentrySDKInternal.m 0.000% 1 Missing ⚠️
Sources/SentryCrash/Recording/SentryCrashC.c 0.000% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##              main     #6193       +/-   ##
=============================================
- Coverage   85.455%   85.419%   -0.037%     
=============================================
  Files          487       487               
  Lines        29166     29183       +17     
  Branches     12612     12643       +31     
=============================================
+ Hits         24924     24928        +4     
- Misses        4192      4204       +12     
- Partials        50        51        +1     
Files with missing lines Coverage Δ
...entryCrash/Recording/Monitors/SentryCrashMonitor.c 84.210% <100.000%> (+0.210%) ⬆️
...ording/Monitors/SentryCrashMonitor_MachException.c 36.286% <ø> (ø)
Sources/Sentry/PrivateSentrySDKOnly.m 72.727% <0.000%> (-0.607%) ⬇️
Sources/Sentry/SentrySDKInternal.m 84.716% <0.000%> (ø)
Sources/SentryCrash/Recording/SentryCrashC.c 76.829% <0.000%> (-0.949%) ⬇️
...ash/Recording/Monitors/SentryCrashMonitor_Signal.c 55.200% <17.647%> (-6.062%) ⬇️

... and 6 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bab8d5e...27df7b7. Read the comment docs.

jpnurmi added a commit to getsentry/sentry-dotnet that referenced this pull request Sep 18, 2025
This reverts "Use pre-built version of sentry-cocoa SDK (#3727)"
commit d179ec9 and restores the
modules/sentry-cocoa Git module checked out at:
getsentry/sentry-cocoa#6193
@jpnurmi jpnurmi force-pushed the jpnurmi/mono-interop branch 2 times, most recently from 2f90356 to ed98b04 Compare September 18, 2025 11:46
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Sep 18, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1222.57 ms 1262.87 ms 40.30 ms
Size 24.14 KiB 1.13 MiB 1.11 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
4d1f3fb 1217.77 ms 1253.84 ms 36.08 ms
f84c826 1216.38 ms 1241.98 ms 25.60 ms
ee272e8 1210.98 ms 1245.10 ms 34.12 ms
2c4362a 1231.50 ms 1255.95 ms 24.45 ms
5b1e2a1 1217.63 ms 1245.49 ms 27.86 ms
bffd360 1218.16 ms 1249.86 ms 31.70 ms
3060db6 1229.84 ms 1257.53 ms 27.70 ms
5d67f5d 1225.33 ms 1262.76 ms 37.43 ms
e701dc8 1215.89 ms 1254.06 ms 38.17 ms
a8ec727 1225.29 ms 1259.59 ms 34.30 ms

App size

Revision Plain With Sentry Diff
4d1f3fb 24.14 KiB 1.13 MiB 1.10 MiB
f84c826 24.14 KiB 1.04 MiB 1.02 MiB
ee272e8 24.14 KiB 1.10 MiB 1.08 MiB
2c4362a 24.14 KiB 1.07 MiB 1.04 MiB
5b1e2a1 24.14 KiB 1.11 MiB 1.09 MiB
bffd360 24.14 KiB 1.11 MiB 1.09 MiB
3060db6 24.14 KiB 1.12 MiB 1.10 MiB
5d67f5d 24.14 KiB 1.06 MiB 1.04 MiB
e701dc8 24.14 KiB 1.06 MiB 1.04 MiB
a8ec727 24.14 KiB 1.12 MiB 1.10 MiB

Previous results on branch: jpnurmi/mono-interop

Startup times

Revision Plain With Sentry Diff
efedb0c 1218.20 ms 1256.60 ms 38.40 ms
4a46856 1236.53 ms 1262.79 ms 26.26 ms
58546af 1217.57 ms 1248.98 ms 31.40 ms
3a8f309 1223.75 ms 1245.77 ms 22.02 ms
2488cd0 1215.08 ms 1241.24 ms 26.16 ms
446c562 1227.20 ms 1256.17 ms 28.97 ms
444f214 1223.69 ms 1249.73 ms 26.04 ms

App size

Revision Plain With Sentry Diff
efedb0c 24.14 KiB 1.13 MiB 1.10 MiB
4a46856 24.14 KiB 1.13 MiB 1.10 MiB
58546af 24.14 KiB 1.13 MiB 1.11 MiB
3a8f309 24.14 KiB 1.13 MiB 1.10 MiB
2488cd0 24.14 KiB 1.13 MiB 1.11 MiB
446c562 24.14 KiB 1.13 MiB 1.10 MiB
444f214 24.14 KiB 1.13 MiB 1.11 MiB

@jpnurmi
Copy link
Copy Markdown
Collaborator Author

jpnurmi commented Sep 18, 2025

Screen.Recording.2025-09-18.at.11.54.52.mov

@jpnurmi jpnurmi changed the title [WIP] fix: interop with managed Mono/CoreCLR runtimes [WIP] fix: interop with managed .NET runtimes Sep 29, 2025
@jpnurmi jpnurmi force-pushed the jpnurmi/mono-interop branch from ed98b04 to 8559ea9 Compare October 28, 2025 12:47
@jpnurmi jpnurmi changed the title [WIP] fix: interop with managed .NET runtimes [WIP] fix: AOT interop with managed .NET runtimes Oct 28, 2025
@jpnurmi jpnurmi changed the base branch from main to v8.x October 28, 2025 12:58
@jpnurmi jpnurmi force-pushed the jpnurmi/mono-interop branch from db30de0 to dd33f14 Compare October 28, 2025 16:20
@jpnurmi jpnurmi changed the base branch from v8.x to main October 28, 2025 16:20
@jpnurmi jpnurmi force-pushed the jpnurmi/mono-interop branch from dd33f14 to e51c82e Compare October 29, 2025 13:13
@jpnurmi jpnurmi changed the title [WIP] fix: AOT interop with managed .NET runtimes fix: AOT interop with managed .NET runtimes Nov 3, 2025
@philprime
Copy link
Copy Markdown
Member

Hey @jpnurmi what's the progress on this PR?

@jpnurmi
Copy link
Copy Markdown
Collaborator Author

jpnurmi commented Mar 13, 2026

I had quite some trouble passing the CI last time I tried. 😅 I should totally revive this because Sentry .NET is still very much in need of a solution for eliminating redundant EXC_BAD_ACCESS for managed exceptions that were handled by the managed runtime.

@jpnurmi jpnurmi force-pushed the jpnurmi/mono-interop branch from be528ee to e1a14ae Compare March 24, 2026 12:40
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 24, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • Implement strict trace continuation by antonis in #7705

Bug Fixes 🐛

  • (replay) Scope clipOut masking to active clip bounds by denrase in #7780
  • AOT interop with managed .NET runtimes by jpnurmi in #6193

Internal Changes 🔧

Deps

  • Bump addressable from 2.8.9 to 2.9.0 by dependabot in #7783
  • Update clang-format version by sentry-mobile-updater in #7782
  • Bump mikepenz/action-junit-report from 6.3.1 to 6.4.0 by dependabot in #7773
  • Bump ruby/setup-ruby from 1.298.0 to 1.299.0 by dependabot in #7772
  • Bump fastlane-plugin-sentry from 2.5.0 to 2.5.1 by dependabot in #7771

🤖 This preview updates automatically when you update the PR.

@jpnurmi jpnurmi force-pushed the jpnurmi/mono-interop branch from e1a14ae to bab06c0 Compare March 24, 2026 12:47
@jpnurmi jpnurmi marked this pull request as ready for review March 24, 2026 13:16
jpnurmi added 3 commits March 27, 2026 15:31
When the signal monitor is disabled under managed runtime, restore
the previous handler for the signal before re-raising to prevent
an infinite loop. Without SA_NODEFER, the raised signal would be
re-delivered to the same handler after it returns.
The flag is redundant now that handleSignal() restores individual
handlers before re-raising. uninstallSignalHandler() can be a
blanket no-op under SENTRY_CRASH_MANAGED_RUNTIME.
@jpnurmi
Copy link
Copy Markdown
Collaborator Author

jpnurmi commented Mar 27, 2026

@supervacuus thanks for the view! 🙏 I've addressed the close/reinstall scenario.

@jpnurmi
Copy link
Copy Markdown
Collaborator Author

jpnurmi commented Mar 27, 2026

By the way, there are integration tests prepared in sentry-dotnet. This patch fixes both scenarios with redundant native exceptions:

With this PR and expected failures removed:

$ pwsh integration-test/ios.Tests.ps1
[...]
[+] /Users/jpnurmi/Projects/sentry/sentry-dotnet/integration-test/ios.Tests.ps1 180.08s (178.93s|113ms)
Tests completed in 180.08s
Tests Passed: 6, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0

@jpnurmi
Copy link
Copy Markdown
Collaborator Author

jpnurmi commented Mar 31, 2026

Hi @philipphofmann @philprime @noahsmartin @itaybre, this PR is ready for review when you get a chance. It adds managed runtime (.NET/Mono) signal handler interop, solving a long-standing issue for .NET on iOS (getsentry/sentry-dotnet#3954). Most of it is gated behind SENTRY_CRASH_MANAGED_RUNTIME to minimize the impact on normal SDK operation. @supervacuus already provided feedback on the lifecycle concerns, which have been addressed in the latest commits. Thanks! 🙏

Copy link
Copy Markdown
Contributor

@itaybre itaybre left a comment

Choose a reason for hiding this comment

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

Almost LGTM, can we document SENTRY_CRASH_MANAGED_RUNTIME and the constructor in the develop_docs folder for future reference?

@jpnurmi
Copy link
Copy Markdown
Collaborator Author

jpnurmi commented Apr 1, 2026

Thanks for the review! Sounds good, I'll take a look tomorrow.

P.S. The .NET ecosystem likes to own the term "managed" but technically, many other runtimes are managed too. I might rename it to SENTRY_CRASH_DOTNET_RUNTIME, or maybe just SENTRY_CRASH_DOTNET. What do you think?

@itaybre
Copy link
Copy Markdown
Contributor

itaybre commented Apr 1, 2026

Thanks for the review! Sounds good, I'll take a look tomorrow.

P.S. The .NET ecosystem likes to own the term "managed" but technically, many other runtimes are managed too. I might rename it to SENTRY_CRASH_DOTNET_RUNTIME, or maybe just SENTRY_CRASH_DOTNET. What do you think?

I would prefer to avoid DOTNET in the name, just in case some other SDK needs this in the future.
Aside from that, I am fine with that

@jpnurmi
Copy link
Copy Markdown
Collaborator Author

jpnurmi commented Apr 2, 2026

Fair enough, I kept SENTRY_CRASH_MANAGED_RUNTIME and only mentioned .NET/Mono as an example in the developer docs. Hard to say how compatible this would be with some other managed runtimes, but we can leave the door open.

Reset the flag on any signal delivery, not just the matching
one. Prevents a stale flag from silently suppressing a later
unrelated signal.
Instead of returning early from the signal handler when a signal
is ignored, skip crash processing and fall through to the existing
restore + raise path. This avoids undefined behavior when the
ignored signal originates from abort().
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Remove the SENTRY_CRASH_MANAGED_RUNTIME guard so ignored signals
are properly re-raised regardless of the compile flag. Harmless
for the non-managed case where uninstall already restored them.
@jpnurmi jpnurmi requested a review from itaybre April 8, 2026 19:33
@itaybre itaybre enabled auto-merge (squash) April 9, 2026 02:53
Copy link
Copy Markdown
Contributor

@itaybre itaybre left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown
Contributor

@itaybre itaybre left a comment

Choose a reason for hiding this comment

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

LGTM

@itaybre itaybre merged commit 9f9a858 into main Apr 9, 2026
223 of 226 checks passed
@itaybre itaybre deleted the jpnurmi/mono-interop branch April 9, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Use this label to trigger all PR workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants