From 0d643ff20244874eb2a847277c0c632e0cdb7e7d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:58:01 +0100 Subject: [PATCH] feat: NativeAOT iOS support (#2820) Co-authored-by: Stefan Jandl --- CHANGELOG.md | 12 +++++- .../Sentry.Samples.Ios.csproj | 12 +----- samples/Sentry.Samples.Maui/MauiProgram.cs | 2 + .../Sentry.Samples.Maui.csproj | 6 ++- src/Sentry/Internal/DebugStackTrace.cs | 18 ++++++++- src/Sentry/Platforms/iOS/CFunctions.cs | 38 +++++++++++++++++++ src/Sentry/Sentry.csproj | 3 +- src/Sentry/buildTransitive/Sentry.targets | 7 +++- .../Internals/DebugStackTraceTests.verify.cs | 6 ++- .../Exceptions/SentryStackFrameTests.cs | 3 +- 10 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 src/Sentry/Platforms/iOS/CFunctions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index de88f48511..2ef07368ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,9 +97,17 @@ Additionally, we're dropping support for some of the old target frameworks, plea #### Native AOT -Native AOT publishing for compilation support for .NET 7+ has been added to Sentry, Sentry.Serilog, Sentry.Profiling, Sentry.OpenTelemetry and Sentry.NLog. There are some functional differences when publishing Native AOT: +Native AOT publishing support for .NET 8 has been added to Sentry for the following platforms: -- `StackTraceMode.Enhanced` is ignored because it's not available when publishing Native AOT. The mechanism to generate these ehanced stack traces relies heavily on reflection which isn't compatible with trimming. +- Windows +- Linux +- macOS +- Mac Catalyst +- iOS + +There are some functional differences when publishing Native AOT: + +- `StackTraceMode.Enhanced` is ignored because it's not available when publishing Native AOT. The mechanism to generate these enhanced stack traces relies heavily on reflection which isn't compatible with trimming. - Reflection cannot be leveraged for JSON Serialization and you may need to use `SentryOptions.AddJsonSerializerContext` to supply a serialization context for types that you'd like to send to Sentry (e.g. in the `Span.Context`). ([#2732](https://github.com/getsentry/sentry-dotnet/pull/2732), [#2793](https://github.com/getsentry/sentry-dotnet/pull/2793)) - WinUI applications: when publishing Native AOT, Sentry isn't able to automatically register an unhandled exception handler because that relies on reflection. You'll need to [register the unhandled event handler manually](https://github.com/getsentry/sentry-dotnet/issues/2778) instead. diff --git a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj index 743a545c71..8126655549 100644 --- a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj +++ b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj @@ -7,6 +7,7 @@ true 11.0 true + true - - $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) - - - iossimulator-arm64 - - diff --git a/samples/Sentry.Samples.Maui/MauiProgram.cs b/samples/Sentry.Samples.Maui/MauiProgram.cs index dc8d479fe3..119984b80e 100644 --- a/samples/Sentry.Samples.Maui/MauiProgram.cs +++ b/samples/Sentry.Samples.Maui/MauiProgram.cs @@ -16,6 +16,8 @@ public static MauiApp CreateMauiApp() // By default, we will send the last 100 breadcrumbs with each event. // If you want to see everything we can capture from MAUI, you may wish to use a larger value. options.MaxBreadcrumbs = 1000; + + options.Debug = true; }) .ConfigureFonts(fonts => diff --git a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj index 1c765a1852..ab002ae9bd 100644 --- a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj +++ b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj @@ -16,6 +16,7 @@ true enable false + true Sentry.Samples.Maui @@ -58,11 +59,12 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) android-arm64 - iossimulator-arm64 + ios-arm64 + iossimulator-arm64 maccatalyst-arm64 android-x64 - iossimulator-x64 + iossimulator-x64 maccatalyst-x64 diff --git a/src/Sentry/Internal/DebugStackTrace.cs b/src/Sentry/Internal/DebugStackTrace.cs index 1b9bc55709..f2c150248b 100644 --- a/src/Sentry/Internal/DebugStackTrace.cs +++ b/src/Sentry/Internal/DebugStackTrace.cs @@ -1,6 +1,5 @@ using Sentry.Internal.Extensions; using Sentry.Extensibility; -using Sentry.Native; using Sentry.Internal.ILSpy; using Sentry.Protocol; @@ -17,8 +16,11 @@ internal class DebugStackTrace : SentryStackTrace private readonly Dictionary _debugImageIndexByModule = new(); private const int DebugImageMissing = -1; private bool _debugImagesMerged; + +#if NET6_0_OR_GREATER private Dictionary? _nativeDebugImages; private HashSet _usedNativeDebugImages = new(); +#endif /* * NOTE: While we could improve these regexes, doing so might break exception grouping on the backend. @@ -220,6 +222,7 @@ private IEnumerable CreateFrames(StackTrace stackTrace, bool i } } +#if NET6_0_OR_GREATER /// /// Native AOT implementation of CreateFrame. /// Native frames have only limited method information at runtime (and even that can be disabled). @@ -237,7 +240,15 @@ private IEnumerable CreateFrames(StackTrace stackTrace, bool i frame.ImageAddress = imageAddress; frame.InstructionAddress = stackFrame.GetNativeIP(); - _nativeDebugImages ??= C.LoadDebugImages(_options.DiagnosticLogger); +#if __ANDROID__ + // TODO there will be support for NativeAOT in the future. + _nativeDebugImages ??= new(); +#elif __IOS__ || MACCATALYST + _nativeDebugImages ??= Sentry.iOS.C.LoadDebugImages(_options.DiagnosticLogger); +#else + _nativeDebugImages ??= Sentry.Native.C.LoadDebugImages(_options.DiagnosticLogger); +#endif + if (!_usedNativeDebugImages.Contains(imageAddress) && _nativeDebugImages.TryGetValue(imageAddress, out var debugImage)) { _usedNativeDebugImages.Add(imageAddress); @@ -261,6 +272,7 @@ internal static SentryStackFrame ParseNativeAOTToString(string info) } return frame; } +#endif /// /// Default the implementation of CreateFrame. @@ -352,7 +364,9 @@ internal static SentryStackFrame ParseNativeAOTToString(string info) internal SentryStackFrame? CreateFrame(IStackFrame stackFrame) { var frame = TryCreateManagedFrame(stackFrame); +#if NET6_0_OR_GREATER frame ??= TryCreateNativeAOTFrame(stackFrame); +#endif if (frame is null) { return null; diff --git a/src/Sentry/Platforms/iOS/CFunctions.cs b/src/Sentry/Platforms/iOS/CFunctions.cs new file mode 100644 index 0000000000..3463b3f7fa --- /dev/null +++ b/src/Sentry/Platforms/iOS/CFunctions.cs @@ -0,0 +1,38 @@ +using Sentry.Extensibility; +using Sentry.Internal.Extensions; +using Sentry.Protocol; + +namespace Sentry.iOS; + +internal static class C +{ + internal static Dictionary LoadDebugImages(IDiagnosticLogger? logger) + { + logger?.LogDebug("Collecting a list of native debug images."); + var result = new Dictionary(); + try + { + var cList = SentryCocoaHybridSdk.DebugImages; + logger?.LogDebug("There are {0} native debug images, parsing the information.", cList.Length); + foreach (var cItem in cList) + { + if (cItem.ImageAddress?.ParseHexAsLong() is { } imageAddress) + { + result.Add(imageAddress, new DebugImage() + { + CodeFile = cItem.CodeFile, + ImageAddress = imageAddress, + ImageSize = cItem.ImageSize?.LongValue, + DebugId = cItem.DebugID, + Type = cItem.Type, + }); + } + } + } + catch (Exception e) + { + logger?.LogWarning("Error loading the list of debug images", e); + } + return result; + } +} diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index da18095528..5ce21eda74 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -21,7 +21,8 @@ - + true diff --git a/src/Sentry/buildTransitive/Sentry.targets b/src/Sentry/buildTransitive/Sentry.targets index 27f144d690..4384e64f48 100644 --- a/src/Sentry/buildTransitive/Sentry.targets +++ b/src/Sentry/buildTransitive/Sentry.targets @@ -128,7 +128,12 @@ - + true $(PublishDir) diff --git a/test/Sentry.Tests/Internals/DebugStackTraceTests.verify.cs b/test/Sentry.Tests/Internals/DebugStackTraceTests.verify.cs index 45753fc04a..556d1590c7 100644 --- a/test/Sentry.Tests/Internals/DebugStackTraceTests.verify.cs +++ b/test/Sentry.Tests/Internals/DebugStackTraceTests.verify.cs @@ -192,6 +192,7 @@ void CheckStackTraceIsUnchanged(SentryStackTrace stackTrace) } } +#if NET6_0_OR_GREATER [Fact] public void ParseNativeAOTToString() { @@ -214,8 +215,8 @@ public void ParseNativeAOTToString() Assert.Null(frame.Package); } -// TODO: Create integration test to test this behaviour when publishing AOT apps -// See https://github.com/getsentry/sentry-dotnet/issues/2772 + // TODO: Create integration test to test this behaviour when publishing AOT apps + // See https://github.com/getsentry/sentry-dotnet/issues/2772 [Fact] public Task CreateFrame_ForNativeAOT() { @@ -230,6 +231,7 @@ public Task CreateFrame_ForNativeAOT() return VerifyJson(frame.ToJsonString()); } +#endif private class InjectableDebugStackTrace : DebugStackTrace { diff --git a/test/Sentry.Tests/Protocol/Exceptions/SentryStackFrameTests.cs b/test/Sentry.Tests/Protocol/Exceptions/SentryStackFrameTests.cs index ba16e33818..39e65a26af 100644 --- a/test/Sentry.Tests/Protocol/Exceptions/SentryStackFrameTests.cs +++ b/test/Sentry.Tests/Protocol/Exceptions/SentryStackFrameTests.cs @@ -231,6 +231,7 @@ public void ConfigureAppFrame_NativeAOTWithoutMethodInfo_InAppIsNull() Assert.Null(sut.InApp); } +#if NET6_0_OR_GREATER [Fact] public void ConfigureAppFrame_NativeAOTWithoutMethodInfo_InAppIsSet() { @@ -244,5 +245,5 @@ public void ConfigureAppFrame_NativeAOTWithoutMethodInfo_InAppIsSet() sut.ConfigureAppFrame(new()); Assert.True(sut.InApp); } - +#endif }