From 7933f2f4d6735dda77c43c1083c63fdae7ab050f Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 9 Feb 2024 11:38:14 +0200 Subject: [PATCH 1/7] add current thread native stack trace getter (temporary solution) --- package-dev/Runtime/SentryInitialization.cs | 22 +++++++++++++++++++ .../Scripts/NativeSupport/CppPlugin.cpp | 22 +++++++++++++++++++ src/Sentry.Unity/ISentryUnityInfo.cs | 9 ++++++++ 3 files changed, 53 insertions(+) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 7cefc142b..5bf9168b1 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -142,6 +142,7 @@ private Il2CppMethods _il2CppMethods = new Il2CppMethods( Il2CppGcHandleGetTargetShim, Il2CppNativeStackTraceShim, + Il2CppNativeStackTraceCurrentThreadShim, il2cpp_free); #pragma warning disable 8632 @@ -233,6 +234,27 @@ private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, // void il2cpp_native_stack_trace(const Il2CppException * ex, uintptr_t** addresses, int* numFrames, char** imageUUID, char** imageName) [DllImport("__Internal")] private static extern void il2cpp_native_stack_trace(IntPtr exc, out IntPtr addresses, out int numFrames, out IntPtr imageUUID, out IntPtr imageName); + + private static void Il2CppNativeStackTraceCurrentThreadShim(out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) + { + imageName = null; + + var uuidBuffer = IntPtr.Zero; + + get_current_thread_native_stack_trace(out addresses, out numFrames, out uuidBuffer); + + try + { + imageUUID = SanitizeDebugId(uuidBuffer); + } + finally + { + il2cpp_free(uuidBuffer); + } + } + + [DllImport("__Internal")] + private static extern void get_current_thread_native_stack_trace(out IntPtr addresses, out int numFrames, out IntPtr imageUUID); #else private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) { diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp index 3b24c6510..a5f3bf9f6 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp @@ -1,6 +1,9 @@ #include #include #include +#include "il2cpp-api.h" +#include "il2cpp-config.h" +#include "os/Image.h" extern "C" { void crash_in_cpp() @@ -11,6 +14,7 @@ void crash_in_cpp() } extern "C" { + void throw_cpp() { try { @@ -21,4 +25,22 @@ void throw_cpp() throw; } } + +void get_current_thread_native_stack_trace(uintptr_t** addresses, int* numFrames, char* imageUUID) +{ + *numFrames = il2cpp_current_thread_get_stack_depth(); + + *addresses = static_cast(il2cpp_alloc((*numFrames) * sizeof(uintptr_t))); + + for (int32_t i = 0; i < *numFrames; ++i) { + Il2CppStackFrameInfo frame; + if (il2cpp_current_thread_get_frame_at(i, &frame)) { + (*addresses)[i] = frame.raw_ip; + } + } + + imageUUID = il2cpp::os::Image::GetImageUUID(); +} + + } diff --git a/src/Sentry.Unity/ISentryUnityInfo.cs b/src/Sentry.Unity/ISentryUnityInfo.cs index 51792851a..cb4280cbd 100644 --- a/src/Sentry.Unity/ISentryUnityInfo.cs +++ b/src/Sentry.Unity/ISentryUnityInfo.cs @@ -19,15 +19,18 @@ public class Il2CppMethods public Il2CppMethods( Il2CppGcHandleGetTarget il2CppGcHandleGetTarget, Il2CppNativeStackTrace il2CppNativeStackTrace, + Il2CppNativeStackTraceCurrentThread il2CppNativeStackTraceCurrentThread, Il2CppFree il2CppFree) { Il2CppGcHandleGetTarget = il2CppGcHandleGetTarget; Il2CppNativeStackTrace = il2CppNativeStackTrace; + Il2CppNativeStackTraceCurrentThread = il2CppNativeStackTraceCurrentThread; Il2CppFree = il2CppFree; } public Il2CppGcHandleGetTarget Il2CppGcHandleGetTarget { get; } public Il2CppNativeStackTrace Il2CppNativeStackTrace { get; } + public Il2CppNativeStackTraceCurrentThread Il2CppNativeStackTraceCurrentThread { get; } public Il2CppFree Il2CppFree { get; } } @@ -38,5 +41,11 @@ public delegate void Il2CppNativeStackTrace( out int numFrames, out string? imageUUID, out string? imageName); + + public delegate void Il2CppNativeStackTraceCurrentThread( + out IntPtr addresses, + out int numFrames, + out string? imageUUID, + out string? imageName); public delegate void Il2CppFree(IntPtr ptr); } From 6cbd9d450a3123ba3d30e38bcc88e4f49ba9ed30 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Fri, 9 Feb 2024 11:39:00 +0200 Subject: [PATCH 2/7] enhanced Il2Cpp event processor --- src/Sentry.Unity/Il2CppEventProcessor.cs | 245 ++++++++++++----------- 1 file changed, 132 insertions(+), 113 deletions(-) diff --git a/src/Sentry.Unity/Il2CppEventProcessor.cs b/src/Sentry.Unity/Il2CppEventProcessor.cs index a365fb4de..b6cac391c 100644 --- a/src/Sentry.Unity/Il2CppEventProcessor.cs +++ b/src/Sentry.Unity/Il2CppEventProcessor.cs @@ -62,126 +62,131 @@ public void Process(Exception incomingException, SentryEvent sentryEvent) _options.DiagnosticLogger?.LogDebug("NativeStackTrace Image: '{0}' (UUID: {1})", nativeStackTrace.ImageName, nativeStackTrace.ImageUuid); - // Unity by definition only builds a single library which we add once to our list of debug images. - // We use this when we encounter stack frames with relative addresses. - // We want to use an address that is definitely outside of any address range used by real libraries. - // Canonical addresses on x64 leave a gap in the middle of the address space, which is unused. - // This is a range of addresses that we should be able to safely use. - // See https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details - var mainLibOffset = (ulong)1 << 63; - DebugImage? mainLibImage = null; - - // TODO do we really want to continue if these two don't match? - // Wouldn't it cause invalid frame info? - var nativeLen = nativeStackTrace.Frames.Length; - var eventLen = sentryStacktrace.Frames.Count; - if (nativeLen != eventLen) + ProcessNativeCallStack(sentryStacktrace, nativeStackTrace, usedImages); + } + + sentryEvent.DebugImages ??= new List(); + sentryEvent.DebugImages.AddRange(usedImages); + } + + private void ProcessNativeCallStack(SentryStackTrace sentryStacktrace, NativeStackTrace nativeStackTrace, HashSet usedImages) + { + // Unity by definition only builds a single library which we add once to our list of debug images. + // We use this when we encounter stack frames with relative addresses. + // We want to use an address that is definitely outside of any address range used by real libraries. + // Canonical addresses on x64 leave a gap in the middle of the address space, which is unused. + // This is a range of addresses that we should be able to safely use. + // See https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details + var mainLibOffset = (ulong)1 << 63; + DebugImage? mainLibImage = null; + + // TODO do we really want to continue if these two don't match? + // Wouldn't it cause invalid frame info? + var nativeLen = nativeStackTrace.Frames.Length; + var eventLen = sentryStacktrace.Frames.Count; + if (nativeLen != eventLen) + { + _options.DiagnosticLogger?.LogWarning( + "Native and sentry stack trace lengths don't match '({0} != {1})' - this may cause invalid stack traces.", + nativeLen, eventLen); + } + + var len = Math.Min(eventLen, nativeLen); + for (var i = 0; i < len; i++) + { + // The sentry stack trace is sorted parent->child (caller->callee), + // whereas the native stack trace is sorted from callee to caller. + var frame = sentryStacktrace.Frames[i]; + var nativeFrame = nativeStackTrace.Frames[nativeLen - 1 - i]; + var mainImageUUID = NormalizeUuid(nativeStackTrace.ImageUuid); + + // TODO should we do this for all addresses or only relative ones? + // If the former, we should also update `frame.InstructionAddress` down below. + var instructionAddress = (ulong)nativeFrame.ToInt64(); + + // We cannot determine whether this frame is a main library frame just from the address + // because even relative address on the frame may correspond to an absolute address of a loaded library. + // Therefore, if the frame package matches known prefixes, we assume it's a GameAssembly frame. + var isMainLibFrame = frame.Package is not null && ( + frame.Package.StartsWith("UnityEngine.", StringComparison.InvariantCultureIgnoreCase) || + frame.Package.StartsWith("Assembly-CSharp", StringComparison.InvariantCultureIgnoreCase) + ); + + string? notes = null; + DebugImage? image = null; + bool? isRelativeAddress = null; + if (!isMainLibFrame) { - _options.DiagnosticLogger?.LogWarning( - "Native and sentry stack trace lengths don't match '({0} != {1})' - this may cause invalid stack traces.", - nativeLen, eventLen); + image = FindDebugImageContainingAddress(instructionAddress); + if (image is null) + { + isRelativeAddress = true; + notes = "because it looks like a relative address."; + // falls through to the next `if (image is null)` + } + else + { + isRelativeAddress = false; + notes = "because it looks like an absolute address inside the range of this debug image."; + } } - var len = Math.Min(eventLen, nativeLen); - for (var i = 0; i < len; i++) + if (image is null) { - // The sentry stack trace is sorted parent->child (caller->callee), - // whereas the native stack trace is sorted from callee to caller. - var frame = sentryStacktrace.Frames[i]; - var nativeFrame = nativeStackTrace.Frames[nativeLen - 1 - i]; - var mainImageUUID = NormalizeUuid(nativeStackTrace.ImageUuid); - - // TODO should we do this for all addresses or only relative ones? - // If the former, we should also update `frame.InstructionAddress` down below. - var instructionAddress = (ulong)nativeFrame.ToInt64(); - - // We cannot determine whether this frame is a main library frame just from the address - // because even relative address on the frame may correspond to an absolute address of a loaded library. - // Therefore, if the frame package matches known prefixes, we assume it's a GameAssembly frame. - var isMainLibFrame = frame.Package is not null && ( - frame.Package.StartsWith("UnityEngine.", StringComparison.InvariantCultureIgnoreCase) || - frame.Package.StartsWith("Assembly-CSharp", StringComparison.InvariantCultureIgnoreCase) - ); - - string? notes = null; - DebugImage? image = null; - bool? isRelativeAddress = null; - if (!isMainLibFrame) + if (mainImageUUID is null) { - image = FindDebugImageContainingAddress(instructionAddress); - if (image is null) - { - isRelativeAddress = true; - notes = "because it looks like a relative address."; - // falls through to the next `if (image is null)` - } - else - { - isRelativeAddress = false; - notes = "because it looks like an absolute address inside the range of this debug image."; - } + _options.DiagnosticLogger?.LogWarning("Couldn't process stack trace - main image UUID reported as NULL by Unity"); + continue; } - if (image is null) + // First, try to find the image among the loaded ones, otherwise create a dummy one. + mainLibImage ??= DebugImagesSorted.Value.Find((info) => string.Equals(NormalizeUuid(info.Image.DebugId), mainImageUUID))?.Image; + mainLibImage ??= new DebugImage { - if (mainImageUUID is null) - { - _options.DiagnosticLogger?.LogWarning("Couldn't process stack trace - main image UUID reported as NULL by Unity"); - continue; - } - - // First, try to find the image among the loaded ones, otherwise create a dummy one. - mainLibImage ??= DebugImagesSorted.Value.Find((info) => string.Equals(NormalizeUuid(info.Image.DebugId), mainImageUUID))?.Image; - mainLibImage ??= new DebugImage - { - Type = UnityInfo.GetDebugImageType(Application.platform), - // NOTE: il2cpp in some circumstances does not return a correct `ImageName`. - // A null/missing `CodeFile` however would lead to a processing error in sentry. - // Since the code file is not strictly necessary for processing, we just fall back to - // a sentinel value here. - CodeFile = string.IsNullOrEmpty(nativeStackTrace.ImageName) ? "GameAssembly.fallback" : nativeStackTrace.ImageName, - DebugId = mainImageUUID, - ImageAddress = $"0x{mainLibOffset:X8}", - }; - - image = mainLibImage; - if (isMainLibFrame) - { - notes ??= $"based on frame package name ({frame.Package})."; - } + Type = UnityInfo.GetDebugImageType(Application.platform), + // NOTE: il2cpp in some circumstances does not return a correct `ImageName`. + // A null/missing `CodeFile` however would lead to a processing error in sentry. + // Since the code file is not strictly necessary for processing, we just fall back to + // a sentinel value here. + CodeFile = string.IsNullOrEmpty(nativeStackTrace.ImageName) ? "GameAssembly.fallback" : nativeStackTrace.ImageName, + DebugId = mainImageUUID, + ImageAddress = $"0x{mainLibOffset:X8}", + }; + + image = mainLibImage; + if (isMainLibFrame) + { + notes ??= $"based on frame package name ({frame.Package})."; } + } - var imageAddress = Convert.ToUInt64(image.ImageAddress, 16); - isRelativeAddress ??= instructionAddress < imageAddress; + var imageAddress = Convert.ToUInt64(image.ImageAddress, 16); + isRelativeAddress ??= instructionAddress < imageAddress; - if (isRelativeAddress is true) - { - // Shift the instruction address to be absolute. - instructionAddress += imageAddress; - frame.InstructionAddress = $"0x{instructionAddress:X8}"; - } + if (isRelativeAddress is true) + { + // Shift the instruction address to be absolute. + instructionAddress += imageAddress; + frame.InstructionAddress = $"0x{instructionAddress:X8}"; + } - // sanity check that the instruction fits inside the range - var logLevel = SentryLevel.Debug; - if (image.ImageSize is not null) + // sanity check that the instruction fits inside the range + var logLevel = SentryLevel.Debug; + if (image.ImageSize is not null) + { + if (instructionAddress < imageAddress || instructionAddress > imageAddress + (ulong)image.ImageSize) { - if (instructionAddress < imageAddress || instructionAddress > imageAddress + (ulong)image.ImageSize) - { - logLevel = SentryLevel.Warning; - notes ??= "."; - notes += " However, the instruction address falls out of the range of the debug image."; - } + logLevel = SentryLevel.Warning; + notes ??= "."; + notes += " However, the instruction address falls out of the range of the debug image."; } + } - _options.DiagnosticLogger?.Log(logLevel, "Stack frame '{0}' at {1:X8} (originally {2:X8}) belongs to {3} {4}", - null, frame.Function, instructionAddress, nativeFrame.ToInt64(), image.CodeFile, notes ?? ""); + _options.DiagnosticLogger?.Log(logLevel, "Stack frame '{0}' at {1:X8} (originally {2:X8}) belongs to {3} {4}", + null, frame.Function, instructionAddress, nativeFrame.ToInt64(), image.CodeFile, notes ?? ""); - _ = usedImages.Add(image); - } + _ = usedImages.Add(image); } - - sentryEvent.DebugImages ??= new List(); - sentryEvent.DebugImages.AddRange(usedImages); } // Normalizes Debug Image UUID so that we can compare the ones coming from @@ -305,23 +310,34 @@ private IEnumerable EnumerateChainedExceptions(Exception exception) yield return exception; } - private NativeStackTrace GetNativeStackTrace(Exception e) + private NativeStackTrace GetNativeStackTrace(Exception? e) { - // Create a `GCHandle` for the exception, which we can then use to + // Create a `GCHandle` for the exception (if any), which we can then use to // essentially get a pointer to the underlying `Il2CppException` C++ object. - var gch = GCHandle.Alloc(e); - // The `il2cpp_native_stack_trace` allocates and writes the native + GCHandle gch = default; + + // The following code allocates and writes the native // instruction pointers to the `addresses`/`numFrames` out-parameters. var addresses = IntPtr.Zero; try { - var gchandle = GCHandle.ToIntPtr(gch); - var addr = _il2CppMethods.Il2CppGcHandleGetTarget(gchandle); - var numFrames = 0; string? imageUuid = null; string? imageName = null; - _il2CppMethods.Il2CppNativeStackTrace(addr, out addresses, out numFrames, out imageUuid, out imageName); + + if (e != null) + { + gch = GCHandle.Alloc(e); + + var gchandle = GCHandle.ToIntPtr(gch); + var addr = _il2CppMethods.Il2CppGcHandleGetTarget(gchandle); + + _il2CppMethods.Il2CppNativeStackTrace(addr, out addresses, out numFrames, out imageUuid, out imageName); + } + else + { + _il2CppMethods.Il2CppNativeStackTraceCurrentThread(out addresses, out numFrames, out imageUuid, out imageName); + } // Convert the C-Array to a managed "C#" Array, and free the underlying memory. var frames = new IntPtr[numFrames]; @@ -336,8 +352,11 @@ private NativeStackTrace GetNativeStackTrace(Exception e) } finally { - // We are done with the `GCHandle`. - gch.Free(); + if (gch.IsAllocated) + { + // We are done with the `GCHandle`. + gch.Free(); + } if (addresses != IntPtr.Zero) { From d38a90a1fca7f978cee4a8bd2397b7ea672153e6 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Mon, 12 Feb 2024 15:38:59 +0200 Subject: [PATCH 3/7] update il2cpp processor --- package-dev/Runtime/SentryInitialization.cs | 9 ++-- src/Sentry.Unity/Il2CppEventProcessor.cs | 50 +++++++++++++++++-- .../ScriptableSentryUnityOptions.cs | 2 +- .../SentryUnityOptionsExtensions.cs | 6 ++- ...UnityIl2CppEventExceptionProcessorTests.cs | 4 +- 5 files changed, 59 insertions(+), 12 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 5bf9168b1..933c6a9db 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -237,24 +237,25 @@ private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, private static void Il2CppNativeStackTraceCurrentThreadShim(out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) { - imageName = null; - var uuidBuffer = IntPtr.Zero; + var imageNameBuffer = IntPtr.Zero; - get_current_thread_native_stack_trace(out addresses, out numFrames, out uuidBuffer); + get_current_thread_native_stack_trace(out addresses, out numFrames, out uuidBuffer, out imageNameBuffer); try { imageUUID = SanitizeDebugId(uuidBuffer); + imageName = (imageNameBuffer == IntPtr.Zero) ? null : Marshal.PtrToStringAnsi(imageNameBuffer); } finally { il2cpp_free(uuidBuffer); + il2cpp_free(imageNameBuffer); } } [DllImport("__Internal")] - private static extern void get_current_thread_native_stack_trace(out IntPtr addresses, out int numFrames, out IntPtr imageUUID); + private static extern void get_current_thread_native_stack_trace(out IntPtr addresses, out int numFrames, out IntPtr imageUUID, out IntPtr imageName); #else private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) { diff --git a/src/Sentry.Unity/Il2CppEventProcessor.cs b/src/Sentry.Unity/Il2CppEventProcessor.cs index b6cac391c..72d1abd99 100644 --- a/src/Sentry.Unity/Il2CppEventProcessor.cs +++ b/src/Sentry.Unity/Il2CppEventProcessor.cs @@ -10,13 +10,13 @@ namespace Sentry.Unity { - internal class UnityIl2CppEventExceptionProcessor : ISentryEventExceptionProcessor + internal class UnityIl2CppEventProcessor : ISentryEventProcessor, ISentryEventExceptionProcessor { private readonly SentryUnityOptions _options; private static ISentryUnityInfo UnityInfo = null!; // private static will be initialized in the constructor private readonly Il2CppMethods _il2CppMethods; - public UnityIl2CppEventExceptionProcessor(SentryUnityOptions options, ISentryUnityInfo unityInfo) + public UnityIl2CppEventProcessor(SentryUnityOptions options, ISentryUnityInfo unityInfo) { _options = options; UnityInfo = unityInfo; @@ -25,6 +25,50 @@ public UnityIl2CppEventExceptionProcessor(SentryUnityOptions options, ISentryUni _options.SdkIntegrationNames.Add("IL2CPPLineNumbers"); } + public SentryEvent Process(SentryEvent @event) + { + _options.DiagnosticLogger?.LogDebug("Running Unity IL2CPP event processor on: Event {0}", @event.EventId); + + var sentryExceptions = @event.SentryExceptions; + if (sentryExceptions?.Any() == true) + { + return @event; + } + + var usedImages = new HashSet(); + Logger = _options.DiagnosticLogger; + + if (@event.SentryThreads != null) + { + foreach (var thread in @event.SentryThreads) + { + var sentryStacktrace = thread.Stacktrace; + if (sentryStacktrace == null) + { + // We will only augment an existing stack trace with native + // instructions, so with no stack trace, there is nothing to do + return @event; + } + + sentryStacktrace.AddressAdjustment = + Application.platform == RuntimePlatform.Android + ? InstructionAddressAdjustment.None + : InstructionAddressAdjustment.All; + + var nativeStackTrace = GetNativeStackTrace(); + + _options.DiagnosticLogger?.LogDebug("NativeStackTrace Image: '{0}' (UUID: {1})", nativeStackTrace.ImageName, nativeStackTrace.ImageUuid); + + ProcessNativeCallStack(sentryStacktrace, nativeStackTrace, usedImages); + } + } + + @event.DebugImages ??= new List(); + @event.DebugImages.AddRange(usedImages); + + return @event; + } + public void Process(Exception incomingException, SentryEvent sentryEvent) { _options.DiagnosticLogger?.LogDebug("Running Unity IL2CPP event exception processor on: Event {0}", sentryEvent.EventId); @@ -310,7 +354,7 @@ private IEnumerable EnumerateChainedExceptions(Exception exception) yield return exception; } - private NativeStackTrace GetNativeStackTrace(Exception? e) + private NativeStackTrace GetNativeStackTrace(Exception? e = null) { // Create a `GCHandle` for the exception (if any), which we can then use to // essentially get a pointer to the underlying `Il2CppException` C++ object. diff --git a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs index 5d85fd59b..dfaedb83d 100644 --- a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs +++ b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs @@ -220,7 +220,7 @@ internal SentryUnityOptions ToSentryUnityOptions(bool isBuilding, ISentryUnityIn if (!application.IsEditor && options.Il2CppLineNumberSupportEnabled && unityInfo is not null) { - options.AddIl2CppExceptionProcessor(unityInfo); + options.AddIl2CppEventProcessor(unityInfo); } HandlePlatformRestrictions(options, application, unityInfo); diff --git a/src/Sentry.Unity/SentryUnityOptionsExtensions.cs b/src/Sentry.Unity/SentryUnityOptionsExtensions.cs index e8d5b13ee..4c18221ad 100644 --- a/src/Sentry.Unity/SentryUnityOptionsExtensions.cs +++ b/src/Sentry.Unity/SentryUnityOptionsExtensions.cs @@ -63,11 +63,13 @@ internal static void SetupLogging(this SentryUnityOptions options) } } - internal static void AddIl2CppExceptionProcessor(this SentryUnityOptions options, ISentryUnityInfo unityInfo) + internal static void AddIl2CppEventProcessor(this SentryUnityOptions options, ISentryUnityInfo unityInfo) { if (unityInfo.Il2CppMethods is not null) { - options.AddExceptionProcessor(new UnityIl2CppEventExceptionProcessor(options, unityInfo)); + var processor = new UnityIl2CppEventProcessor(options, unityInfo); + options.AddEventProcessor(processor); + options.AddExceptionProcessor(processor); } else { diff --git a/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs b/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs index ec2101a4a..823d71601 100644 --- a/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs +++ b/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs @@ -2,7 +2,7 @@ namespace Sentry.Unity.Tests { - public class UnityIl2CppEventExceptionProcessorTests + public class UnityIl2CppEventProcessorTests { [Test] [TestCase(null, null)] @@ -13,7 +13,7 @@ public class UnityIl2CppEventExceptionProcessorTests [TestCase("94552647-48dc-4fe4-ba75-7ccd3c43c44d-917f8072", "9455264748dc4fe4ba757ccd3c43c44d917f8072")] public void NormalizeUuid_ReturnValueMatchesExpected(string input, string expected) { - var actual = UnityIl2CppEventExceptionProcessor.NormalizeUuid(input); + var actual = UnityIl2CppEventProcessor.NormalizeUuid(input); Assert.AreEqual(actual, expected); } From 22fbcfaa6b0aa30bb577cdbf88b5c515fe8ea24f Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Wed, 14 Feb 2024 09:58:12 +0200 Subject: [PATCH 4/7] add image name --- .../unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp index a5f3bf9f6..7f51bb95d 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp @@ -26,7 +26,7 @@ void throw_cpp() } } -void get_current_thread_native_stack_trace(uintptr_t** addresses, int* numFrames, char* imageUUID) +void get_current_thread_native_stack_trace(uintptr_t** addresses, int* numFrames, char* imageUUID, char* imageName) { *numFrames = il2cpp_current_thread_get_stack_depth(); @@ -37,9 +37,10 @@ void get_current_thread_native_stack_trace(uintptr_t** addresses, int* numFrames if (il2cpp_current_thread_get_frame_at(i, &frame)) { (*addresses)[i] = frame.raw_ip; } - } + } imageUUID = il2cpp::os::Image::GetImageUUID(); + imageName = il2cpp::os::Image::GetImageName(); } From 3018b153b0561e5040b074e503e4caeebeeacc02 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Thu, 15 Feb 2024 13:00:44 +0200 Subject: [PATCH 5/7] remove cpp implementation --- package-dev/Runtime/SentryInitialization.cs | 63 ++++++++++++++++--- .../Scripts/NativeSupport/CppPlugin.cpp | 20 ------ 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index 933c6a9db..cb3c9cc90 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -16,6 +16,7 @@ using System; using Sentry.Extensibility; +using AOT; #if UNITY_2020_3_OR_NEWER using System.Buffers; using System.Runtime.InteropServices; @@ -211,12 +212,51 @@ void SwapHexByte(IntPtr buffer, Int32 offset1, Int32 offset2) [DllImport("__Internal")] private static extern void il2cpp_free(IntPtr ptr); -#if UNITY_2021_3_OR_NEWER - private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) + [StructLayout(LayoutKind.Sequential)] + private struct Il2CppStackFrameInfo + { + public IntPtr method; + public IntPtr raw_ip; + + public int sourceCodeLineNumber; + public int ilOffset; + + [MarshalAs(UnmanagedType.LPStr)] + public string filePath; + } + + private static void Il2CppNativeStackTraceCurrentThreadShim(out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) { var uuidBuffer = IntPtr.Zero; var imageNameBuffer = IntPtr.Zero; - il2cpp_native_stack_trace(exc, out addresses, out numFrames, out uuidBuffer, out imageNameBuffer); + + numFrames = il2cpp_current_thread_get_stack_depth(); + + var frames = new IntPtr[numFrames]; + + for(int i = 0; i < numFrames; ++i) + { + IntPtr farameInfoPtr = IntPtr.Zero; + try + { + farameInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); + var res = il2cpp_current_thread_get_frame_at(i, farameInfoPtr); + if(res) + { + Il2CppStackFrameInfo frameInfo = Marshal.PtrToStructure(farameInfoPtr); + frames[i] = frameInfo.raw_ip; + } + } + finally + { + Marshal.FreeHGlobal(farameInfoPtr); + } + } + + int size = Marshal.SizeOf(typeof(IntPtr)) * numFrames; + addresses = Marshal.AllocHGlobal(size); + + Marshal.Copy(frames, 0, addresses, numFrames); try { @@ -230,17 +270,18 @@ private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, } } - // Definition from Unity `2021.3` (and later): - // void il2cpp_native_stack_trace(const Il2CppException * ex, uintptr_t** addresses, int* numFrames, char** imageUUID, char** imageName) [DllImport("__Internal")] - private static extern void il2cpp_native_stack_trace(IntPtr exc, out IntPtr addresses, out int numFrames, out IntPtr imageUUID, out IntPtr imageName); + private static extern int il2cpp_current_thread_get_stack_depth(); - private static void Il2CppNativeStackTraceCurrentThreadShim(out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) + [DllImport("__Internal")] + private static extern bool il2cpp_current_thread_get_frame_at(int offset, IntPtr frame); + +#if UNITY_2021_3_OR_NEWER + private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) { var uuidBuffer = IntPtr.Zero; var imageNameBuffer = IntPtr.Zero; - - get_current_thread_native_stack_trace(out addresses, out numFrames, out uuidBuffer, out imageNameBuffer); + il2cpp_native_stack_trace(exc, out addresses, out numFrames, out uuidBuffer, out imageNameBuffer); try { @@ -254,8 +295,10 @@ private static void Il2CppNativeStackTraceCurrentThreadShim(out IntPtr addresses } } + // Definition from Unity `2021.3` (and later): + // void il2cpp_native_stack_trace(const Il2CppException * ex, uintptr_t** addresses, int* numFrames, char** imageUUID, char** imageName) [DllImport("__Internal")] - private static extern void get_current_thread_native_stack_trace(out IntPtr addresses, out int numFrames, out IntPtr imageUUID, out IntPtr imageName); + private static extern void il2cpp_native_stack_trace(IntPtr exc, out IntPtr addresses, out int numFrames, out IntPtr imageUUID, out IntPtr imageName); #else private static void Il2CppNativeStackTraceShim(IntPtr exc, out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) { diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp index 7f51bb95d..ab58d6ea1 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp @@ -14,7 +14,6 @@ void crash_in_cpp() } extern "C" { - void throw_cpp() { try { @@ -25,23 +24,4 @@ void throw_cpp() throw; } } - -void get_current_thread_native_stack_trace(uintptr_t** addresses, int* numFrames, char* imageUUID, char* imageName) -{ - *numFrames = il2cpp_current_thread_get_stack_depth(); - - *addresses = static_cast(il2cpp_alloc((*numFrames) * sizeof(uintptr_t))); - - for (int32_t i = 0; i < *numFrames; ++i) { - Il2CppStackFrameInfo frame; - if (il2cpp_current_thread_get_frame_at(i, &frame)) { - (*addresses)[i] = frame.raw_ip; - } - } - - imageUUID = il2cpp::os::Image::GetImageUUID(); - imageName = il2cpp::os::Image::GetImageName(); -} - - } From 4a27123e4d5d6e280bd1ea3a3cd8a96bda876262 Mon Sep 17 00:00:00 2001 From: Ivan Tustanivskyi Date: Tue, 20 Feb 2024 10:10:56 +0200 Subject: [PATCH 6/7] Fix crash in release build --- package-dev/Runtime/SentryInitialization.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package-dev/Runtime/SentryInitialization.cs b/package-dev/Runtime/SentryInitialization.cs index cb3c9cc90..8a6ae6bdd 100644 --- a/package-dev/Runtime/SentryInitialization.cs +++ b/package-dev/Runtime/SentryInitialization.cs @@ -221,12 +221,12 @@ private struct Il2CppStackFrameInfo public int sourceCodeLineNumber; public int ilOffset; - [MarshalAs(UnmanagedType.LPStr)] - public string filePath; + public IntPtr filePath; } private static void Il2CppNativeStackTraceCurrentThreadShim(out IntPtr addresses, out int numFrames, out string? imageUUID, out string? imageName) { + // Currently there is no obvious way to obtain image UUID and name var uuidBuffer = IntPtr.Zero; var imageNameBuffer = IntPtr.Zero; @@ -240,6 +240,8 @@ private static void Il2CppNativeStackTraceCurrentThreadShim(out IntPtr addresses try { farameInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); + + // Is it reliable to query current stack trace here? var res = il2cpp_current_thread_get_frame_at(i, farameInfoPtr); if(res) { From 8be0040d868ad6f70de252e8e34fbf6e3dc394c3 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 20 Feb 2024 08:27:28 +0000 Subject: [PATCH 7/7] Format code --- modules/sentry-cocoa | 2 +- modules/sentry-java | 2 +- .../Assets/Scripts/NativeSupport/CppPlugin.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/sentry-cocoa b/modules/sentry-cocoa index 3b9a8e69c..b847a202a 160000 --- a/modules/sentry-cocoa +++ b/modules/sentry-cocoa @@ -1 +1 @@ -Subproject commit 3b9a8e69ca296bd8cd0e317ad7a448e5daf4a342 +Subproject commit b847a202a517a90763e8fd0656d8028aeee7b78d diff --git a/modules/sentry-java b/modules/sentry-java index f7abca1cf..f5871f9d9 160000 --- a/modules/sentry-java +++ b/modules/sentry-java @@ -1 +1 @@ -Subproject commit f7abca1cfb968b08b91a88fd835fc98839b43ba7 +Subproject commit f5871f9d9f18be1cdaed6175f0f1ab65d6c5aa8e diff --git a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp index ab58d6ea1..72451d9a2 100644 --- a/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp +++ b/samples/unity-of-bugs/Assets/Scripts/NativeSupport/CppPlugin.cpp @@ -1,9 +1,9 @@ -#include -#include -#include #include "il2cpp-api.h" #include "il2cpp-config.h" #include "os/Image.h" +#include +#include +#include extern "C" { void crash_in_cpp()