Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
28 changes: 22 additions & 6 deletions src/Sentry.Unity/Il2CppEventProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
};

Expand Down Expand Up @@ -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("-", "");
}
Expand Down
101 changes: 97 additions & 4 deletions test/IntegrationTest/Integration.Tests.ps1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

qol for future debugging

Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 "<Name>" -ForEach $CommonTestCases {
Expand All @@ -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 "<Name>" -ForEach $CommonTestCases {
Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -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 "<Name>" -ForEach $CommonTestCases {
Expand Down Expand Up @@ -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 "<Name>" -ForEach $CommonTestCases {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Loading