Skip to content

Commit 13d4a9f

Browse files
Enable symbolication and source context for net9.0-android (#4033)
1 parent 6924e88 commit 13d4a9f

File tree

110 files changed

+4851
-154
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+4851
-154
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,13 @@ jobs:
117117
dotnet build src/Sentry/Sentry.csproj -t:InstallAndroidDependencies -f:net8.0-android34.0 -p:AcceptAndroidSDKLicenses=True -p:AndroidSdkPath="/usr/local/lib/android/sdk/"
118118
119119
- name: Build
120-
run: dotnet build Sentry-CI-Build-${{ runner.os }}.slnf -c Release --no-restore --nologo -v:minimal -flp:logfile=build.log -p:CopyLocalLockFileAssemblies=true
120+
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
121+
122+
- name: Upload build logs
123+
uses: actions/upload-artifact@v4
124+
with:
125+
name: ${{ runner.os }}-build-logs
126+
path: build.binlog
121127

122128
- name: Test
123129
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"

.github/workflows/device-tests-android.yml

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ on:
1111

1212
jobs:
1313
build:
14+
name: Build (${{ matrix.tfm }})
1415
runs-on: ubuntu-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
tfm: [net8.0, net9.0]
1520
env:
1621
DOTNET_CLI_TELEMETRY_OPTOUT: 1
1722
DOTNET_NOLOGO: 1
@@ -32,25 +37,35 @@ jobs:
3237
uses: ./.github/actions/buildnative
3338

3439
- name: Build Android Test App
35-
run: pwsh ./scripts/device-test.ps1 android -Build
40+
run: pwsh ./scripts/device-test.ps1 android -Build -Tfm ${{ matrix.tfm }}
41+
42+
- name: Upload Android Test App (net8.0)
43+
if: matrix.tfm == 'net8.0'
44+
uses: actions/upload-artifact@v4
45+
with:
46+
name: device-test-android-net8.0
47+
if-no-files-found: error
48+
path: test/Sentry.Maui.Device.TestApp/bin/Release/net8.0-android/android-x64/io.sentry.dotnet.maui.device.testapp-Signed.apk
3649

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

4458
android:
4559
needs: [build]
46-
name: Run Android API-${{ matrix.api-level }} Test
60+
name: Run Android API-${{ matrix.api-level }} Test (${{ matrix.tfm }})
4761

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

5165
strategy:
5266
fail-fast: false
5367
matrix:
68+
tfm: [net8.0, net9.0]
5469
# We run against both an older and a newer API
5570
api-level: [27, 33]
5671
env:
@@ -70,15 +85,14 @@ jobs:
7085
- name: Download test app artifact
7186
uses: actions/download-artifact@v4
7287
with:
73-
name: device-test-android
88+
name: device-test-android-${{ matrix.tfm }}
7489
path: bin
7590

7691
- name: Setup Gradle
7792
uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # pin@v3
7893

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

81-
8296
- name: Run Tests
8397
timeout-minutes: 40
8498
uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # Tag: v2.34.0
@@ -92,11 +106,11 @@ jobs:
92106
disk-size: 4096M
93107
emulator-options: -no-snapshot-save -no-window -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
94108
disable-animations: false
95-
script: pwsh scripts/device-test.ps1 android -Run
109+
script: pwsh scripts/device-test.ps1 android -Run -Tfm ${{ matrix.tfm }}
96110

97111
- name: Upload results
98112
if: success() || failure()
99113
uses: actions/upload-artifact@v4
100114
with:
101-
name: device-test-android-${{ matrix.api-level }}-results
115+
name: device-test-android-${{ matrix.api-level }}-${{ matrix.tfm }}-results
102116
path: test_output

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Exception.HResult is now included in the mechanism data for all exceptions ([#4029](https://github.com/getsentry/sentry-dotnet/pull/4029))
8+
59
### Fixes
610

11+
- Fixed symbolication and source context for net9.0-android ([#4033](https://github.com/getsentry/sentry-dotnet/pull/4033))
712
- 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))
813
- Target `net9.0` on Sentry.Google.Cloud.Functions to avoid conflict with Sentry.AspNetCore ([#4039](https://github.com/getsentry/sentry-dotnet/pull/4039))
914
- Changed default value for `SentryOptions.EnableAppHangTrackingV2` to `false` ([#4042](https://github.com/getsentry/sentry-dotnet/pull/4042))
1015
- Missing MAUI `Shell` navigation breadcrumbs on iOS ([#4006](https://github.com/getsentry/sentry-dotnet/pull/4006))
1116
- Prevent application crashes when capturing screenshots on iOS ([#4069](https://github.com/getsentry/sentry-dotnet/pull/4069))
1217

13-
### Features
14-
15-
- Exception.HResult is now included in the mechanism data for all exceptions ([#4029](https://github.com/getsentry/sentry-dotnet/pull/4029))
16-
1718
### Dependencies
1819

1920
- Bump Native SDK from v0.8.1 to v0.8.2 ([#4050](https://github.com/getsentry/sentry-dotnet/pull/4050))

benchmarks/Sentry.Benchmarks/Sentry.Benchmarks.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
1111
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.12" />
12-
<PackageReference Include="NSubstitute" Version="5.1.0" />
12+
<PackageReference Include="NSubstitute" Version="5.3.0" />
1313
</ItemGroup>
1414

1515
<ItemGroup>

scripts/device-test.ps1

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ param(
55
[String] $Platform,
66

77
[Switch] $Build,
8-
[Switch] $Run
8+
[Switch] $Run,
9+
[String] $Tfm
910
)
1011

1112
Set-StrictMode -Version latest
@@ -21,13 +22,16 @@ $CI = Test-Path env:CI
2122
Push-Location $PSScriptRoot/..
2223
try
2324
{
24-
$tfm = 'net8.0-'
25+
if (!$Tfm)
26+
{
27+
$Tfm = 'net8.0'
28+
}
2529
$arch = (!$IsWindows -and $(uname -m) -eq 'arm64') ? 'arm64' : 'x64'
2630
if ($Platform -eq 'android')
2731
{
28-
$tfm += 'android34.0'
32+
$Tfm += '-android'
2933
$group = 'android'
30-
$buildDir = $CI ? 'bin' : "test/Sentry.Maui.Device.TestApp/bin/Release/$tfm/android-$arch"
34+
$buildDir = $CI ? 'bin' : "test/Sentry.Maui.Device.TestApp/bin/Release/$Tfm/android-$arch"
3135
$arguments = @(
3236
'--app', "$buildDir/io.sentry.dotnet.maui.device.testapp-Signed.apk",
3337
'--package-name', 'io.sentry.dotnet.maui.device.testapp',
@@ -43,11 +47,11 @@ try
4347
}
4448
elseif ($Platform -eq 'ios')
4549
{
46-
$tfm += 'ios17.0'
50+
$Tfm += '-ios'
4751
$group = 'apple'
4852
# Always use x64 on iOS, since arm64 doesn't support JIT, which is required for tests using NSubstitute
4953
$arch = 'x64'
50-
$buildDir = "test/Sentry.Maui.Device.TestApp/bin/Release/$tfm/iossimulator-$arch"
54+
$buildDir = "test/Sentry.Maui.Device.TestApp/bin/Release/$Tfm/iossimulator-$arch"
5155
$envValue = $CI ? 'true' : 'false'
5256
$arguments = @(
5357
'--app', "$buildDir/Sentry.Maui.Device.TestApp.app",
@@ -60,7 +64,7 @@ try
6064
if ($Build)
6165
{
6266
# We disable AOT for device tests: https://github.com/nsubstitute/NSubstitute/issues/834
63-
dotnet build -f $tfm -c Release -p:EnableAot=false -p:NoSymbolStrip=true test/Sentry.Maui.Device.TestApp
67+
dotnet build -f $Tfm -c Release -p:EnableAot=false -p:NoSymbolStrip=true test/Sentry.Maui.Device.TestApp
6468
if ($LASTEXITCODE -ne 0)
6569
{
6670
throw 'Failed to build Sentry.Maui.Device.TestApp'

src/Sentry.Android.AssemblyReader/AndroidAssemblyReader.cs

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,69 +17,4 @@ public void Dispose()
1717
{
1818
ZipArchive.Dispose();
1919
}
20-
21-
protected PEReader CreatePEReader(string assemblyName, MemoryStream inputStream)
22-
{
23-
var decompressedStream = TryDecompressLZ4(assemblyName, inputStream);
24-
25-
// Use the decompressed stream, or if null, i.e. it wasn't compressed, use the original.
26-
return new PEReader(decompressedStream ?? inputStream);
27-
}
28-
29-
/// <summary>
30-
/// The DLL may be LZ4 compressed, see https://github.com/xamarin/xamarin-android/pull/4686
31-
/// The format is:
32-
/// [ 4 byte magic header ] (XALZ)
33-
/// [ 4 byte header index ]
34-
/// [ 4 byte uncompressed payload length ]
35-
/// [rest: lz4 compressed payload]
36-
/// </summary>
37-
/// <seealso href="https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/decompress-assemblies/main.cs#L26" />
38-
private Stream? TryDecompressLZ4(string assemblyName, MemoryStream inputStream)
39-
{
40-
const uint compressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
41-
const int payloadOffset = 12;
42-
var reader = new BinaryReader(inputStream);
43-
if (reader.ReadUInt32() != compressedDataMagic)
44-
{
45-
// Restore the input stream to the beginning if we're not decompressing.
46-
inputStream.Position = 0;
47-
return null;
48-
}
49-
reader.ReadUInt32(); // ignore descriptor index, we don't need it
50-
var decompressedLength = reader.ReadInt32();
51-
Debug.Assert(inputStream.Position == payloadOffset);
52-
var inputLength = (int)(inputStream.Length - payloadOffset);
53-
54-
Logger?.Invoke("Decompressing assembly ({0} bytes uncompressed) using LZ4", decompressedLength);
55-
56-
var outputStream = new MemoryStream(decompressedLength);
57-
58-
// We're writing to the underlying array manually, so we need to set the length.
59-
outputStream.SetLength(decompressedLength);
60-
var outputBuffer = outputStream.GetBuffer();
61-
62-
var inputBuffer = inputStream is MemorySlice slice ? slice.FullBuffer : inputStream.GetBuffer();
63-
var offset = inputStream is MemorySlice memorySlice ? memorySlice.Offset + payloadOffset : payloadOffset;
64-
var decoded = LZ4Codec.Decode(inputBuffer, offset, inputLength, outputBuffer, 0, decompressedLength);
65-
if (decoded != decompressedLength)
66-
{
67-
throw new Exception($"Failed to decompress LZ4 data of assembly {assemblyName} - decoded {decoded} instead of expected {decompressedLength} bytes");
68-
}
69-
return outputStream;
70-
}
71-
72-
// Allows consumer to access the underlying buffer even if the MemoryStream is created as a slice over another.
73-
// Plain MemoryStream would throw "MemoryStream's internal buffer cannot be accessed."
74-
protected class MemorySlice : MemoryStream
75-
{
76-
public readonly int Offset;
77-
public readonly byte[] FullBuffer;
78-
79-
public MemorySlice(MemoryStream other, int offset, int size) : base(other.GetBuffer(), offset, size, writable: false)
80-
{
81-
Offset = offset;
82-
FullBuffer = other.GetBuffer();
83-
}
84-
}
8520
}

src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using Sentry.Android.AssemblyReader.V1;
2+
using Sentry.Android.AssemblyReader.V2;
3+
14
namespace Sentry.Android.AssemblyReader;
25

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

22+
#if NET9_0
23+
logger?.Invoke("Reading files using V2 APK layout.");
24+
if (AndroidAssemblyStoreReaderV2.TryReadStore(apkPath, supportedAbis, logger, out var readerV2))
25+
{
26+
logger?.Invoke("APK uses AssemblyStore V2");
27+
return readerV2;
28+
}
29+
30+
logger?.Invoke("APK doesn't use AssemblyStore");
31+
return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger);
32+
#else
33+
logger?.Invoke("Reading files using V1 APK layout.");
34+
35+
var zipArchive = ZipFile.OpenRead(apkPath);
2036
if (zipArchive.GetEntry("assemblies/assemblies.manifest") is not null)
2137
{
22-
logger?.Invoke("APK uses AssemblyStore");
23-
return new AndroidAssemblyStoreReader(zipArchive, supportedAbis, logger);
38+
logger?.Invoke("APK uses AssemblyStore V1");
39+
return new AndroidAssemblyStoreReaderV1(zipArchive, supportedAbis, logger);
2440
}
2541

2642
logger?.Invoke("APK doesn't use AssemblyStore");
27-
return new AndroidAssemblyDirectoryReader(zipArchive, supportedAbis, logger);
43+
return new AndroidAssemblyDirectoryReaderV1(zipArchive, supportedAbis, logger);
44+
#endif
2845
}
2946
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
namespace Sentry.Android.AssemblyReader;
2+
3+
internal static class ArchiveUtils
4+
{
5+
internal static PEReader CreatePEReader(string assemblyName, MemoryStream inputStream, DebugLogger? logger)
6+
{
7+
var decompressedStream = TryDecompressLZ4(assemblyName, inputStream, logger); // Returns null if not compressed
8+
return new PEReader(decompressedStream ?? inputStream);
9+
}
10+
11+
internal static MemoryStream Extract(this ZipArchiveEntry zipEntry)
12+
{
13+
var memStream = new MemoryStream((int)zipEntry.Length);
14+
using var zipStream = zipEntry.Open();
15+
zipStream.CopyTo(memStream);
16+
memStream.Position = 0;
17+
return memStream;
18+
}
19+
20+
/// <summary>
21+
/// The DLL may be LZ4 compressed, see https://github.com/xamarin/xamarin-android/pull/4686
22+
/// In particular: https://github.com/dotnet/android/blob/44c5c30d3da692c54ca27d4a41571ef20b73670f/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs#L96-L104
23+
/// The format is:
24+
/// [ 4 byte magic header ] (XALZ)
25+
/// [ 4 byte descriptor header index ]
26+
/// [ 4 byte uncompressed payload length ]
27+
/// [rest: lz4 compressed payload]
28+
/// </summary>
29+
/// <seealso href="https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/decompress-assemblies/main.cs#L26" />
30+
private static Stream? TryDecompressLZ4(string assemblyName, MemoryStream inputStream, DebugLogger? logger)
31+
{
32+
const uint compressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
33+
const int payloadOffset = 12;
34+
var reader = new BinaryReader(inputStream);
35+
if (reader.ReadUInt32() != compressedDataMagic)
36+
{
37+
// Restore the input stream to the beginning if we're not decompressing.
38+
inputStream.Position = 0;
39+
return null;
40+
}
41+
reader.ReadUInt32(); // ignore descriptor index, we don't need it
42+
var decompressedLength = reader.ReadInt32();
43+
Debug.Assert(inputStream.Position == payloadOffset);
44+
var inputLength = (int)(inputStream.Length - payloadOffset);
45+
46+
logger?.Invoke("Decompressing assembly ({0} bytes uncompressed) using LZ4", decompressedLength);
47+
48+
var outputStream = new MemoryStream(decompressedLength);
49+
50+
// We're writing to the underlying array manually, so we need to set the length.
51+
outputStream.SetLength(decompressedLength);
52+
var outputBuffer = outputStream.GetBuffer();
53+
54+
var inputBuffer = inputStream is MemorySlice slice ? slice.FullBuffer : inputStream.GetBuffer();
55+
var offset = inputStream is MemorySlice memorySlice ? memorySlice.Offset + payloadOffset : payloadOffset;
56+
var decoded = LZ4Codec.Decode(inputBuffer, offset, inputLength, outputBuffer, 0, decompressedLength);
57+
if (decoded != decompressedLength)
58+
{
59+
throw new Exception($"Failed to decompress LZ4 data of assembly {assemblyName} - decoded {decoded} instead of expected {decompressedLength} bytes");
60+
}
61+
return outputStream;
62+
}
63+
64+
// Allows consumer to access the underlying buffer even if the MemoryStream is created as a slice over another.
65+
// Plain MemoryStream would throw "MemoryStream's internal buffer cannot be accessed."
66+
internal class MemorySlice(MemoryStream other, int offset, int size)
67+
: MemoryStream(other.GetBuffer(), offset, size, writable: false)
68+
{
69+
public readonly int Offset = offset;
70+
public readonly byte[] FullBuffer = other.GetBuffer();
71+
}
72+
}

0 commit comments

Comments
 (0)