From f9360bf4fe4bbb08394ede2d7864a9af8572a2d0 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 20 Aug 2026 16:43:37 +0200 Subject: [PATCH 1/2] fixed line numbers on android --- .github/workflows/create-unity-matrix.yml | 3 +- CHANGELOG.md | 4 +++ src/Sentry.Unity/Il2CppEventProcessor.cs | 28 +++++++++++++++---- ...UnityIl2CppEventExceptionProcessorTests.cs | 1 + 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.github/workflows/create-unity-matrix.yml b/.github/workflows/create-unity-matrix.yml index 3fffae108..1d7756f53 100644 --- a/.github/workflows/create-unity-matrix.yml +++ b/.github/workflows/create-unity-matrix.yml @@ -19,7 +19,8 @@ on: env: # Unity versions used in PRs - PR_UNITY_VERSIONS: '["2021.3", "6000.5"]' + # TODO revert before merging - temporarily pinned to 2022.3 to validate the Android x86_64 IL2CPP UUID fallback + PR_UNITY_VERSIONS: '["2022.3"]' # Unity versions used on main branch FULL_UNITY_VERSIONS: '["2021.3", "2022.3", "6000.0", "6000.3", "6000.5"]' diff --git a/CHANGELOG.md b/CHANGELOG.md index 175d82380..9d09fc346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- IL2CPP line numbers now work on Android x86/x86_64 builds. il2cpp fails to report the image UUID there, so the SDK falls back to looking the debug image up by name ([#2817](https://github.com/getsentry/sentry-unity/pull/2817)) + ### Features - The SDK now provides line number support for managed exceptions for Unity 6.5 and newer ([#2805](https://github.com/getsentry/sentry-unity/pull/2805)) diff --git a/src/Sentry.Unity/Il2CppEventProcessor.cs b/src/Sentry.Unity/Il2CppEventProcessor.cs index 2f0698ce6..a32e6bb1c 100644 --- a/src/Sentry.Unity/Il2CppEventProcessor.cs +++ b/src/Sentry.Unity/Il2CppEventProcessor.cs @@ -78,6 +78,23 @@ public void Process(Exception incomingException, SentryEvent sentryEvent) var mainLibOffset = long.MaxValue; DebugImage? mainLibImage = null; + var mainImageUuid = NormalizeUuid(nativeStackTrace.ImageUuid); + if (mainImageUuid is null && !string.IsNullOrEmpty(nativeStackTrace.ImageName)) + { + // il2cpp only scans the first PT_NOTE segment when reading the ELF build ID. On x86_64 with NDK r23 the + // notes end up in two segments and the build ID lands in the one il2cpp skips. sentry-native reports + // the image just fine, so we look it up by the name il2cpp gave us. + var imageByName = DebugImagesSorted.Value + .Find(info => string.Equals(info.Image.CodeFile, nativeStackTrace.ImageName))?.Image; + mainImageUuid = NormalizeUuid(imageByName?.DebugId); + if (mainImageUuid is not null) + { + mainLibImage = imageByName; + Options.LogDebug("Unity reported no main image UUID. Resolved '{0}' from the debug images instead.", + mainImageUuid); + } + } + // 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; @@ -96,7 +113,6 @@ public void Process(Exception incomingException, SentryEvent sentryEvent) // 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. @@ -131,14 +147,14 @@ public void Process(Exception incomingException, SentryEvent sentryEvent) if (image is null) { - if (mainImageUUID is null) + if (mainImageUuid is null) { Options.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 ??= DebugImagesSorted.Value.Find((info) => string.Equals(NormalizeUuid(info.Image.DebugId), mainImageUuid))?.Image; mainLibImage ??= new DebugImage { Type = GetPlatformDebugImageType(), @@ -147,7 +163,7 @@ public void Process(Exception incomingException, SentryEvent sentryEvent) // 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, + DebugId = mainImageUuid, ImageAddress = mainLibOffset, }; @@ -197,12 +213,12 @@ public void Process(Exception incomingException, SentryEvent sentryEvent) // while native image UUID we get is 3028cb80-b071-2541-0000-000000000000. internal static string? NormalizeUuid(string? value) { - if (value is null) + if (string.IsNullOrEmpty(value)) { return null; } - value = value.ToLowerInvariant(); + value = value!.ToLowerInvariant(); value = value.Replace("-0000-000000000000", ""); return value.Replace("-", ""); } diff --git a/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs b/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs index a54e3d68d..cbf37b18e 100644 --- a/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs +++ b/test/Sentry.Unity.Tests/UnityIl2CppEventExceptionProcessorTests.cs @@ -6,6 +6,7 @@ public class UnityIl2CppEventExceptionProcessorTests { [Test] [TestCase(null, null)] + [TestCase("", null)] [TestCase("f30fef22-d93e-7f60-0000-000000000000", "f30fef22d93e7f60")] [TestCase("0c9249e5-e223-8bd5-0000-000000000000", "0c9249e5e2238bd5")] [TestCase("6f42afa0-45c8-86e6-2372-a02513d55560", "6f42afa045c886e62372a02513d55560")] From 590d9bec9e8f65154aa1b84c09075afe311c201c Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 20 Aug 2026 17:07:21 +0200 Subject: [PATCH 2/2] better diagnostic logging, restored unity versions for prs --- .github/workflows/create-unity-matrix.yml | 3 +- test/IntegrationTest/Integration.Tests.ps1 | 101 ++++++++++++++++++++- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/.github/workflows/create-unity-matrix.yml b/.github/workflows/create-unity-matrix.yml index 1d7756f53..3fffae108 100644 --- a/.github/workflows/create-unity-matrix.yml +++ b/.github/workflows/create-unity-matrix.yml @@ -19,8 +19,7 @@ on: env: # Unity versions used in PRs - # TODO revert before merging - temporarily pinned to 2022.3 to validate the Android x86_64 IL2CPP UUID fallback - PR_UNITY_VERSIONS: '["2022.3"]' + PR_UNITY_VERSIONS: '["2021.3", "6000.5"]' # Unity versions used on main branch FULL_UNITY_VERSIONS: '["2021.3", "2022.3", "6000.0", "6000.3", "6000.5"]' diff --git a/test/IntegrationTest/Integration.Tests.ps1 b/test/IntegrationTest/Integration.Tests.ps1 index 9dec4f8eb..5a9b6e7fd 100644 --- a/test/IntegrationTest/Integration.Tests.ps1 +++ b/test/IntegrationTest/Integration.Tests.ps1 @@ -132,6 +132,91 @@ BeforeAll { return $runResult } + # Read a property that may be missing from the fetched event. StrictMode turns a missing property + # into a terminating error, which is the last thing we want while diagnosing a failure. + function Get-EventProperty { + param($Object, [string]$Name) + + if ($null -eq $Object) { + return $null + } + + $property = $Object.PSObject.Properties[$Name] + if ($null -eq $property) { + return $null + } + + return $property.Value + } + + # Same, for properties holding a list. Going through @() directly would turn an absent list into a + # single-element array holding $null, making a missing stack trace look like one blank frame. + function Get-EventList { + param($Object, [string]$Name) + + $value = Get-EventProperty $Object $Name + if ($null -eq $value) { + return @() + } + + return @($value) + } + + # Dumps what is needed to tell a symbolication problem apart from a capture problem: the frames as + # Sentry stored them, and any SDK warning the app logged on the way. Written as a collapsed group so + # it stays out of the way until a test actually fails. + function Write-EventDiagnostics { + param($SentryEvent, $RunResult, [string]$Action) + + Write-Host "::group::Event diagnostics ($Action)" + try { + if ($null -eq $SentryEvent) { + Write-Host "No event was fetched from Sentry." + } + else { + $eventId = Get-EventProperty $SentryEvent 'id' + Write-Host "Event $eventId - full JSON in the test results artifact under results/event-$eventId.json" + + $exception = @(Get-EventList (Get-EventProperty $SentryEvent 'exception') 'values') | Select-Object -First 1 + $frames = @(Get-EventList (Get-EventProperty $exception 'stacktrace') 'frames') + + if ($frames.Count -eq 0) { + Write-Host "The event carries no exception stack trace frames." + } + else { + Write-Host "Stack trace frames ($($frames.Count), caller first). An empty Line/AbsPath with an empty" + Write-Host "InstrAddr means the SDK never attached native addresses; with one it means symbolication failed." + $frames | ForEach-Object { + [PSCustomObject]@{ + Module = Get-EventProperty $_ 'module' + Function = Get-EventProperty $_ 'function' + Line = Get-EventProperty $_ 'lineNo' + AbsPath = Get-EventProperty $_ 'absPath' + InstrAddr = Get-EventProperty $_ 'instructionAddr' + Symbolicator = Get-EventProperty $_ 'symbolicatorStatus' + } + } | Format-Table -AutoSize | Out-String -Width 400 | Write-Host + + $images = @(Get-EventList (Get-EventProperty $SentryEvent 'debugmeta') 'images') + Write-Host "Debug images attached to the event: $($images.Count)" + } + } + + $output = if ($null -eq $RunResult) { @() } else { @($RunResult.Output) } + $sdkDiagnostics = @($output | Where-Object { $_ -match 'Sentry \((Warning|Error)\)' }) + if ($sdkDiagnostics.Count -eq 0) { + Write-Host "The app logged no SDK warnings or errors." + } + else { + Write-Host "SDK warnings and errors the app logged ($($sdkDiagnostics.Count)):" + $sdkDiagnostics | ForEach-Object { Write-Host " $_" } + } + } + finally { + Write-Host "::endgroup::" + } + } + # Run integration test action function Invoke-TestAction { param ( @@ -304,6 +389,8 @@ Describe "Unity $($env:SENTRY_TEST_PLATFORM) Integration Tests" { $script:runEvent = Get-SentryTestEvent -EventId "$eventId" Write-Host "::endgroup::" } + + Write-EventDiagnostics -SentryEvent $script:runEvent -RunResult $script:runResult -Action "message-capture" } It "" -ForEach $CommonTestCases { @@ -330,6 +417,8 @@ Describe "Unity $($env:SENTRY_TEST_PLATFORM) Integration Tests" { $script:runEvent = Get-SentryTestEvent -EventId "$eventId" Write-Host "::endgroup::" } + + Write-EventDiagnostics -SentryEvent $script:runEvent -RunResult $script:runResult -Action "exception-capture" } It "" -ForEach $CommonTestCases { @@ -358,11 +447,11 @@ Describe "Unity $($env:SENTRY_TEST_PLATFORM) Integration Tests" { Where-Object { $_.module -eq "IntegrationTester" -and $_.function -eq "ThrowException" } | Select-Object -First 1 - $frame | Should -Not -BeNullOrEmpty - $frame.absPath | Should -Match "[\\/]Assets[\\/]Scripts[\\/]IntegrationTester\.cs$" + $frame | Should -Not -BeNullOrEmpty -Because "the managed stack trace must contain the throwing frame - see the 'Event diagnostics (exception-capture)' group for the frames Sentry stored" + $frame.absPath | Should -Match "[\\/]Assets[\\/]Scripts[\\/]IntegrationTester\.cs$" -Because "IL2CPP line number support must resolve the frame back to its source file. An empty instructionAddr in the diagnostics group means the SDK never attached native addresses (check the SDK warnings), otherwise symbol upload or symbolication is at fault" # Which line exactly gets reported differs between Unity versions, so we only assert that we resolved one. - $frame.lineNo | Should -BeGreaterThan 0 - $frame.symbolicatorStatus | Should -Be "symbolicated" + $frame.lineNo | Should -BeGreaterThan 0 -Because "the frame resolved to a source file, so it must carry a line number too" + $frame.symbolicatorStatus | Should -Be "symbolicated" -Because "Sentry must have symbolicated the frame using the uploaded IL2CPP line mappings" } It "Has error level" { @@ -392,6 +481,8 @@ if ($env:SENTRY_TEST_PLATFORM -ne "WebGL") { $script:runEvent = Get-SentryTestEvent -TagName "test.crash_id" -TagValue "$eventId" -TimeoutSeconds 300 Write-Host "::endgroup::" } + + Write-EventDiagnostics -SentryEvent $script:runEvent -RunResult $script:runResult -Action "crash-capture" } It "" -ForEach $CommonTestCases { @@ -448,6 +539,8 @@ if ($env:SENTRY_TEST_PLATFORM -in "Desktop", "Android" -and -not $isCocoaBackend $script:runEvent = Get-SentryTestEvent -TagName "test.app_hang_id" -TagValue "$hangId" -TimeoutSeconds 300 Write-Host "::endgroup::" } + + Write-EventDiagnostics -SentryEvent $script:runEvent -RunResult $script:runResult -Action "app-hang-capture" } It "" -ForEach $CommonTestCases {