diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e5c17f4..7197aa266 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)) + ### Dependencies - Bump .NET SDK from v6.8.0 to v6.9.0 ([#2815](https://github.com/getsentry/sentry-unity/pull/2815)) 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/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 { 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")]