Skip to content

Enable symbolication and source context for net9.0-android #4033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 51 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7e7e512
Ensure device tests run on net8.0 and net9.0
jamescrosswell Mar 13, 2025
d569cf0
Move existing reader to V1 directory
jamescrosswell Mar 13, 2025
f660e63
Bumped NSubstitute to versino 5.3.0
jamescrosswell Mar 17, 2025
0b36c8c
Update Sentry.Extensions.Logging.Tests.csproj
jamescrosswell Mar 17, 2025
b94434a
Merge branch 'main' into store-v2
jamescrosswell Mar 17, 2025
4e9183b
Update Sentry.Maui.Tests.csproj
jamescrosswell Mar 17, 2025
0c22666
Update device-tests-android.yml
jamescrosswell Mar 17, 2025
9420d86
Update CHANGELOG.md
jamescrosswell Mar 17, 2025
1291467
Minimal work required to add the store v2 files to the repo with attr…
jamescrosswell Mar 18, 2025
339f4ef
make members internal
jamescrosswell Mar 18, 2025
d18d462
Tests pass on net8.0 and net9.0
jamescrosswell Mar 19, 2025
1c6494e
Merge branch 'main' into store-v2
jamescrosswell Mar 19, 2025
75e15c7
Format code
getsentry-bot Mar 19, 2025
fc3e17a
Removed Xamarin.LibZipSharp dependency
jamescrosswell Mar 19, 2025
d618ac2
Removed dependency on System.IO.Hashing
jamescrosswell Mar 19, 2025
509482b
Format code
getsentry-bot Mar 19, 2025
956f1c6
Update AndroidAssemblyReaderTests.cs
jamescrosswell Mar 19, 2025
8b95555
Merge branch 'store-v2' of github.com:getsentry/sentry-dotnet into st…
jamescrosswell Mar 19, 2025
0a9580b
Revert "Merge branch 'store-v2' of github.com:getsentry/sentry-dotnet…
jamescrosswell Mar 19, 2025
bc58628
Removed junk comments added by dotnet format
jamescrosswell Mar 20, 2025
bbfa502
Format code
getsentry-bot Mar 20, 2025
6f7cbb7
Tweaked ZipFile calls
jamescrosswell Mar 21, 2025
440baf2
Added support for non AssemblyStore APKs
jamescrosswell Mar 21, 2025
de304b9
Format code
getsentry-bot Mar 21, 2025
8a1253d
Merge branch 'main' into store-v2
jamescrosswell Mar 21, 2025
3423f09
Format code
getsentry-bot Mar 21, 2025
45769f9
Removed unused code from ArchiveAssemblyHelper
jamescrosswell Mar 23, 2025
7652f11
Remove unused code from StoreReaderV2
jamescrosswell Mar 24, 2025
fd470f0
Update AndroidAssemblyReaderTests.cs
jamescrosswell Mar 24, 2025
d8ed437
Delete ELFPayloadError.cs
jamescrosswell Mar 24, 2025
e866cee
.
jamescrosswell Mar 25, 2025
d0b8688
Use System.Private.CoreLib.dll instead of System.Runtime.dll in Assem…
jamescrosswell Mar 26, 2025
c024e2e
Merge branch 'main' into store-v2
jamescrosswell Mar 26, 2025
8ede9cd
Test AssemblyReader with APKs published using AOT
jamescrosswell Mar 26, 2025
7ccfa1a
Merge branch 'store-v2' of github.com:getsentry/sentry-dotnet into st…
jamescrosswell Mar 26, 2025
81fac20
Shortened file names for test apks
jamescrosswell Mar 27, 2025
dfc59be
.
jamescrosswell Mar 27, 2025
4af3523
Added logging to the Ubuntu build (Temporary)
jamescrosswell Mar 27, 2025
75bb2ec
Removed dependency on ElfSharp NuGet
jamescrosswell Mar 28, 2025
b3c0948
Add structured build logs to build
jamescrosswell Mar 28, 2025
92a74d8
Format code
getsentry-bot Mar 28, 2025
f22991f
Set RID based on OS Architecture when publishing AndroidTestApp
jamescrosswell Mar 28, 2025
2583dea
Merge branch 'store-v2' of github.com:getsentry/sentry-dotnet into st…
jamescrosswell Mar 28, 2025
9f14f9b
Format code
getsentry-bot Mar 28, 2025
294872c
Update Sentry.Android.AssemblyReader.Tests.csproj
jamescrosswell Mar 28, 2025
d7936f2
Merge branch 'store-v2' of github.com:getsentry/sentry-dotnet into st…
jamescrosswell Mar 28, 2025
2a332b1
Update Sentry.Android.AssemblyReader.Tests.csproj
jamescrosswell Mar 28, 2025
74feb9c
.
jamescrosswell Mar 28, 2025
64fadc1
.
jamescrosswell Mar 28, 2025
7077f90
Merge branch 'main' into store-v2
jamescrosswell Mar 31, 2025
78817b7
Update AndroidAssemblyReaderTests.cs
jamescrosswell Apr 2, 2025
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
8 changes: 7 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@ jobs:
dotnet build src/Sentry/Sentry.csproj -t:InstallAndroidDependencies -f:net8.0-android34.0 -p:AcceptAndroidSDKLicenses=True -p:AndroidSdkPath="/usr/local/lib/android/sdk/"

- name: Build
run: dotnet build Sentry-CI-Build-${{ runner.os }}.slnf -c Release --no-restore --nologo -v:minimal -flp:logfile=build.log -p:CopyLocalLockFileAssemblies=true
run: dotnet build Sentry-CI-Build-${{ runner.os }}.slnf -c Release --no-restore --nologo -v:minimal -flp:logfile=build.log -p:CopyLocalLockFileAssemblies=true -bl:build.binlog

- name: Upload build logs
uses: actions/upload-artifact@v4
with:
name: ${{ runner.os }}-build-logs
path: build.binlog

- name: Test
run: dotnet test Sentry-CI-Build-${{ runner.os }}.slnf -c Release --no-build --nologo -l GitHubActions -l "trx;LogFilePrefix=testresults_${{ runner.os }}" --collect "XPlat Code Coverage"
Expand Down
32 changes: 23 additions & 9 deletions .github/workflows/device-tests-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ on:

jobs:
build:
name: Build (${{ matrix.tfm }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
tfm: [net8.0, net9.0]
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_NOLOGO: 1
Expand All @@ -32,25 +37,35 @@ jobs:
uses: ./.github/actions/buildnative

- name: Build Android Test App
run: pwsh ./scripts/device-test.ps1 android -Build
run: pwsh ./scripts/device-test.ps1 android -Build -Tfm ${{ matrix.tfm }}

- name: Upload Android Test App (net8.0)
if: matrix.tfm == 'net8.0'
uses: actions/upload-artifact@v4
with:
name: device-test-android-net8.0
if-no-files-found: error
path: test/Sentry.Maui.Device.TestApp/bin/Release/net8.0-android/android-x64/io.sentry.dotnet.maui.device.testapp-Signed.apk

- name: Upload Android Test App
- name: Upload Android Test App (net9.0)
if: matrix.tfm == 'net9.0'
uses: actions/upload-artifact@v4
with:
name: device-test-android
name: device-test-android-net9.0
if-no-files-found: error
path: test/Sentry.Maui.Device.TestApp/bin/Release/net8.0-android34.0/android-x64/io.sentry.dotnet.maui.device.testapp-Signed.apk
path: test/Sentry.Maui.Device.TestApp/bin/Release/net9.0-android/android-x64/io.sentry.dotnet.maui.device.testapp-Signed.apk

android:
needs: [build]
name: Run Android API-${{ matrix.api-level }} Test
name: Run Android API-${{ matrix.api-level }} Test (${{ matrix.tfm }})

# Requires a "larger runner", for nested virtualization support
runs-on: ubuntu-latest-4-cores

strategy:
fail-fast: false
matrix:
tfm: [net8.0, net9.0]
# We run against both an older and a newer API
api-level: [27, 33]
env:
Expand All @@ -70,15 +85,14 @@ jobs:
- name: Download test app artifact
uses: actions/download-artifact@v4
with:
name: device-test-android
name: device-test-android-${{ matrix.tfm }}
path: bin

- name: Setup Gradle
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # pin@v3

# Cached AVD setup per https://github.com/ReactiveCircus/android-emulator-runner/blob/main/README.md


- name: Run Tests
timeout-minutes: 40
uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # Tag: v2.33.0
Expand All @@ -92,11 +106,11 @@ jobs:
disk-size: 4096M
emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: pwsh scripts/device-test.ps1 android -Run
script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }}

- name: Upload results
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: device-test-android-${{ matrix.api-level }}-results
name: device-test-android-${{ matrix.api-level }}-${{ matrix.tfm }}-results
path: test_output
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

## Unreleased

### Features

- Exception.HResult is now included in the mechanism data for all exceptions ([#4029](https://github.com/getsentry/sentry-dotnet/pull/4029))

### Fixes

- Fixed symbolication and source context for net9.0-android ([#4033](https://github.com/getsentry/sentry-dotnet/pull/4033))
- Single quotes added to the release name when using MS Build to create Sentry releases on Windows ([#4015](https://github.com/getsentry/sentry-dotnet/pull/4015))
- Target `net9.0` on Sentry.Google.Cloud.Functions to avoid conflict with Sentry.AspNetCore ([#4039](https://github.com/getsentry/sentry-dotnet/pull/4039))
- Changed default value for `SentryOptions.EnableAppHangTrackingV2` to `false` ([#4042](https://github.com/getsentry/sentry-dotnet/pull/4042))
- Missing MAUI `Shell` navigation breadcrumbs on iOS ([#4006](https://github.com/getsentry/sentry-dotnet/pull/4006))

### Features

- Exception.HResult is now included in the mechanism data for all exceptions ([#4029](https://github.com/getsentry/sentry-dotnet/pull/4029))

### Dependencies

- Bump CLI from v2.42.2 to v2.43.0 ([#4036](https://github.com/getsentry/sentry-dotnet/pull/4036), [#4049](https://github.com/getsentry/sentry-dotnet/pull/4049), [#4060](https://github.com/getsentry/sentry-dotnet/pull/4060), [#4062](https://github.com/getsentry/sentry-dotnet/pull/4062))
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Sentry.Benchmarks/Sentry.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.12" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
</ItemGroup>

<ItemGroup>
Expand Down
18 changes: 11 additions & 7 deletions scripts/device-test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ param(
[String] $Platform,

[Switch] $Build,
[Switch] $Run
[Switch] $Run,
[String] $Tfm
)

Set-StrictMode -Version latest
Expand All @@ -21,13 +22,16 @@ $CI = Test-Path env:CI
Push-Location $PSScriptRoot/..
try
{
$tfm = 'net8.0-'
if (!$Tfm)
{
$Tfm = 'net8.0'
Copy link
Member

Choose a reason for hiding this comment

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

once we clear all callsites to pass a tfm, we should delete this and throw an error instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is just a default argument, if you don't specify a target framework. We could try to change the default to latest or something like that - would need a little bit of extra scripting to work out what latest was.

}
$arch = (!$IsWindows -and $(uname -m) -eq 'arm64') ? 'arm64' : 'x64'
if ($Platform -eq 'android')
{
$tfm += 'android34.0'
$Tfm += '-android'
$group = 'android'
$buildDir = $CI ? 'bin' : "test/Sentry.Maui.Device.TestApp/bin/Release/$tfm/android-$arch"
$buildDir = $CI ? 'bin' : "test/Sentry.Maui.Device.TestApp/bin/Release/$Tfm/android-$arch"
$arguments = @(
'--app', "$buildDir/io.sentry.dotnet.maui.device.testapp-Signed.apk",
'--package-name', 'io.sentry.dotnet.maui.device.testapp',
Expand All @@ -43,11 +47,11 @@ try
}
elseif ($Platform -eq 'ios')
{
$tfm += 'ios17.0'
$Tfm += '-ios'
$group = 'apple'
# Always use x64 on iOS, since arm64 doesn't support JIT, which is required for tests using NSubstitute
$arch = 'x64'
$buildDir = "test/Sentry.Maui.Device.TestApp/bin/Release/$tfm/iossimulator-$arch"
$buildDir = "test/Sentry.Maui.Device.TestApp/bin/Release/$Tfm/iossimulator-$arch"
$envValue = $CI ? 'true' : 'false'
$arguments = @(
'--app', "$buildDir/Sentry.Maui.Device.TestApp.app",
Expand All @@ -60,7 +64,7 @@ try
if ($Build)
{
# We disable AOT for device tests: https://github.com/nsubstitute/NSubstitute/issues/834
dotnet build -f $tfm -c Release -p:EnableAot=false -p:NoSymbolStrip=true test/Sentry.Maui.Device.TestApp
dotnet build -f $Tfm -c Release -p:EnableAot=false -p:NoSymbolStrip=true test/Sentry.Maui.Device.TestApp
if ($LASTEXITCODE -ne 0)
{
throw 'Failed to build Sentry.Maui.Device.TestApp'
Expand Down
65 changes: 0 additions & 65 deletions src/Sentry.Android.AssemblyReader/AndroidAssemblyReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,69 +17,4 @@ public void Dispose()
{
ZipArchive.Dispose();
}

protected PEReader CreatePEReader(string assemblyName, MemoryStream inputStream)
{
var decompressedStream = TryDecompressLZ4(assemblyName, inputStream);

// Use the decompressed stream, or if null, i.e. it wasn't compressed, use the original.
return new PEReader(decompressedStream ?? inputStream);
}

/// <summary>
/// The DLL may be LZ4 compressed, see https://github.com/xamarin/xamarin-android/pull/4686
/// The format is:
/// [ 4 byte magic header ] (XALZ)
/// [ 4 byte header index ]
/// [ 4 byte uncompressed payload length ]
/// [rest: lz4 compressed payload]
/// </summary>
/// <seealso href="https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/decompress-assemblies/main.cs#L26" />
private Stream? TryDecompressLZ4(string assemblyName, MemoryStream inputStream)
{
const uint compressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
const int payloadOffset = 12;
var reader = new BinaryReader(inputStream);
if (reader.ReadUInt32() != compressedDataMagic)
{
// Restore the input stream to the beginning if we're not decompressing.
inputStream.Position = 0;
return null;
}
reader.ReadUInt32(); // ignore descriptor index, we don't need it
var decompressedLength = reader.ReadInt32();
Debug.Assert(inputStream.Position == payloadOffset);
var inputLength = (int)(inputStream.Length - payloadOffset);

Logger?.Invoke("Decompressing assembly ({0} bytes uncompressed) using LZ4", decompressedLength);

var outputStream = new MemoryStream(decompressedLength);

// We're writing to the underlying array manually, so we need to set the length.
outputStream.SetLength(decompressedLength);
var outputBuffer = outputStream.GetBuffer();

var inputBuffer = inputStream is MemorySlice slice ? slice.FullBuffer : inputStream.GetBuffer();
var offset = inputStream is MemorySlice memorySlice ? memorySlice.Offset + payloadOffset : payloadOffset;
var decoded = LZ4Codec.Decode(inputBuffer, offset, inputLength, outputBuffer, 0, decompressedLength);
if (decoded != decompressedLength)
{
throw new Exception($"Failed to decompress LZ4 data of assembly {assemblyName} - decoded {decoded} instead of expected {decompressedLength} bytes");
}
return outputStream;
}

// Allows consumer to access the underlying buffer even if the MemoryStream is created as a slice over another.
// Plain MemoryStream would throw "MemoryStream's internal buffer cannot be accessed."
protected class MemorySlice : MemoryStream
{
public readonly int Offset;
public readonly byte[] FullBuffer;

public MemorySlice(MemoryStream other, int offset, int size) : base(other.GetBuffer(), offset, size, writable: false)
{
Offset = offset;
FullBuffer = other.GetBuffer();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using Sentry.Android.AssemblyReader.V1;
using Sentry.Android.AssemblyReader.V2;

namespace Sentry.Android.AssemblyReader;

/// <summary>
Expand All @@ -15,15 +18,29 @@ public static class AndroidAssemblyReaderFactory
public static IAndroidAssemblyReader Open(string apkPath, IList<string> supportedAbis, DebugLogger? logger = null)
{
logger?.Invoke("Opening APK: {0}", apkPath);
var zipArchive = ZipFile.Open(apkPath, ZipArchiveMode.Read);

#if NET9_0
logger?.Invoke("Reading files using V2 APK layout.");
if (AndroidAssemblyStoreReaderV2.TryReadStore(apkPath, supportedAbis, logger, out var readerV2))
{
logger?.Invoke("APK uses AssemblyStore V2");
return readerV2;
}

logger?.Invoke("APK doesn't use AssemblyStore");
return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger);
#else
logger?.Invoke("Reading files using V1 APK layout.");

var zipArchive = ZipFile.OpenRead(apkPath);
if (zipArchive.GetEntry("assemblies/assemblies.manifest") is not null)
{
logger?.Invoke("APK uses AssemblyStore");
return new AndroidAssemblyStoreReader(zipArchive, supportedAbis, logger);
logger?.Invoke("APK uses AssemblyStore V1");
return new AndroidAssemblyStoreReaderV1(zipArchive, supportedAbis, logger);
}

logger?.Invoke("APK doesn't use AssemblyStore");
return new AndroidAssemblyDirectoryReader(zipArchive, supportedAbis, logger);
return new AndroidAssemblyDirectoryReaderV1(zipArchive, supportedAbis, logger);
#endif
}
}
72 changes: 72 additions & 0 deletions src/Sentry.Android.AssemblyReader/ArchiveUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace Sentry.Android.AssemblyReader;

internal static class ArchiveUtils
{
internal static PEReader CreatePEReader(string assemblyName, MemoryStream inputStream, DebugLogger? logger)
{
var decompressedStream = TryDecompressLZ4(assemblyName, inputStream, logger); // Returns null if not compressed
return new PEReader(decompressedStream ?? inputStream);
}

internal static MemoryStream Extract(this ZipArchiveEntry zipEntry)
{
var memStream = new MemoryStream((int)zipEntry.Length);
using var zipStream = zipEntry.Open();
zipStream.CopyTo(memStream);
memStream.Position = 0;
return memStream;
}

/// <summary>
/// The DLL may be LZ4 compressed, see https://github.com/xamarin/xamarin-android/pull/4686
/// In particular: https://github.com/dotnet/android/blob/44c5c30d3da692c54ca27d4a41571ef20b73670f/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs#L96-L104
/// The format is:
/// [ 4 byte magic header ] (XALZ)
/// [ 4 byte descriptor header index ]
/// [ 4 byte uncompressed payload length ]
/// [rest: lz4 compressed payload]
/// </summary>
/// <seealso href="https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/decompress-assemblies/main.cs#L26" />
private static Stream? TryDecompressLZ4(string assemblyName, MemoryStream inputStream, DebugLogger? logger)
{
const uint compressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
const int payloadOffset = 12;
var reader = new BinaryReader(inputStream);
if (reader.ReadUInt32() != compressedDataMagic)
{
// Restore the input stream to the beginning if we're not decompressing.
inputStream.Position = 0;
return null;
}
reader.ReadUInt32(); // ignore descriptor index, we don't need it
var decompressedLength = reader.ReadInt32();
Debug.Assert(inputStream.Position == payloadOffset);
var inputLength = (int)(inputStream.Length - payloadOffset);

logger?.Invoke("Decompressing assembly ({0} bytes uncompressed) using LZ4", decompressedLength);

var outputStream = new MemoryStream(decompressedLength);

// We're writing to the underlying array manually, so we need to set the length.
outputStream.SetLength(decompressedLength);
var outputBuffer = outputStream.GetBuffer();

var inputBuffer = inputStream is MemorySlice slice ? slice.FullBuffer : inputStream.GetBuffer();
var offset = inputStream is MemorySlice memorySlice ? memorySlice.Offset + payloadOffset : payloadOffset;
var decoded = LZ4Codec.Decode(inputBuffer, offset, inputLength, outputBuffer, 0, decompressedLength);
if (decoded != decompressedLength)
{
throw new Exception($"Failed to decompress LZ4 data of assembly {assemblyName} - decoded {decoded} instead of expected {decompressedLength} bytes");
}
return outputStream;
}

// Allows consumer to access the underlying buffer even if the MemoryStream is created as a slice over another.
// Plain MemoryStream would throw "MemoryStream's internal buffer cannot be accessed."
internal class MemorySlice(MemoryStream other, int offset, int size)
: MemoryStream(other.GetBuffer(), offset, size, writable: false)
{
public readonly int Offset = offset;
public readonly byte[] FullBuffer = other.GetBuffer();
}
}
Loading
Loading