Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
.
  • Loading branch information
jamescrosswell committed Mar 27, 2025
commit dfc59be9039a09c6fd9268eaf0c271389e672e0c
66 changes: 0 additions & 66 deletions src/Sentry.Android.AssemblyReader/AndroidAssemblyReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,70 +17,4 @@ public void Dispose()
{
ZipArchive.Dispose();
}

internal static PEReader CreatePEReader(string assemblyName, MemoryStream inputStream, DebugLogger? logger)
{
var decompressedStream = TryDecompressLZ4(assemblyName, inputStream, logger);

// 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
/// 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."
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();
}
}
}
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public AndroidAssemblyDirectoryReaderV1(ZipArchive zip, IList<string> supportedA

// We need a seekable stream for the PEReader (or even to check whether the DLL is compressed), so make a copy.
var memStream = zipEntry.Extract();
return CreatePEReader(name, memStream, Logger);
return ArchiveUtils.CreatePEReader(name, memStream, Logger);
}

private ZipArchiveEntry? FindAssembly(string name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public AndroidAssemblyStoreReaderV1(ZipArchive zip, IList<string> supportedAbis,
return null;
}

return CreatePEReader(name, stream, Logger);
return ArchiveUtils.CreatePEReader(name, stream, Logger);
}

private AssemblyStoreAssembly? TryFindAssembly(string name)
Expand Down Expand Up @@ -409,7 +409,7 @@ public AssemblyStoreReader(MemoryStream store, string? arch = null)
assembly.ConfigDataOffset == 0 ? null : GetDataSlice(assembly.ConfigDataOffset, assembly.ConfigDataSize);

private MemoryStream? GetDataSlice(uint offset, uint size) =>
size == 0 ? null : new MemorySlice(_storeData, (int)offset, (int)size);
size == 0 ? null : new ArchiveUtils.MemorySlice(_storeData, (int)offset, (int)size);

public bool HasIdenticalContent(AssemblyStoreReader other)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using Sentry.Android.AssemblyReader.V2;

namespace Sentry.Android.AssemblyReader.V2;

// The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file.
Expand Down Expand Up @@ -36,7 +34,7 @@ public AndroidAssemblyDirectoryReaderV2(string apkPath, IList<string> supportedA
}

Logger?.Invoke("Resolved assembly {0} in the APK", name);
return AndroidAssemblyReader.CreatePEReader(name, memStream, Logger);
return ArchiveUtils.CreatePEReader(name, memStream, Logger);
}

Logger?.Invoke("Couldn't find assembly {0} in the APK", name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static bool TryReadStore(string inputFile, IList<string> supportedAbis, D
return null;
}

return AndroidAssemblyReader.CreatePEReader(name, stream, _logger);
return ArchiveUtils.CreatePEReader(name, stream, _logger);
}

private ExplorerStoreItem? TryFindAssembly(string name)
Expand Down Expand Up @@ -115,17 +115,3 @@ public void Dispose()
// No-op
}
}

internal static class DeviceArchitectureExtensions
{
public static AndroidTargetArch AbiToDeviceArchitecture(this string abi) =>
abi switch
{
"armeabi-v7a" => AndroidTargetArch.Arm,
"arm64-v8a" => AndroidTargetArch.Arm64,
"x86" => AndroidTargetArch.X86,
"x86_64" => AndroidTargetArch.X86_64,
"mips" => AndroidTargetArch.Mips,
_ => AndroidTargetArch.Other,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Sentry.Android.AssemblyReader.V2;

internal static class DeviceArchitectureExtensions
{
public static AndroidTargetArch AbiToDeviceArchitecture(this string abi) =>
abi switch
{
"armeabi-v7a" => AndroidTargetArch.Arm,
"arm64-v8a" => AndroidTargetArch.Arm64,
"x86" => AndroidTargetArch.X86,
"x86_64" => AndroidTargetArch.X86_64,
"mips" => AndroidTargetArch.Mips,
_ => AndroidTargetArch.Other,
};
}
13 changes: 0 additions & 13 deletions src/Sentry.Android.AssemblyReader/ZipUtils.cs

This file was deleted.

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.Tests;

public class AndroidAssemblyReaderTests
Expand Down Expand Up @@ -49,10 +52,10 @@
switch (TargetFramework)
{
case "net9.0":
Assert.IsType<V2.AndroidAssemblyStoreReaderV2>(sut);
Assert.IsType<AndroidAssemblyStoreReaderV2>(sut);
break;
case "net8.0":
Assert.IsType<V1.AndroidAssemblyStoreReaderV1>(sut);
Assert.IsType<AndroidAssemblyStoreReaderV1>(sut);
break;
default:
throw new NotSupportedException($"Unsupported target framework: {TargetFramework}");
Expand All @@ -65,14 +68,14 @@
#if ANDROID
Skip.If(true, "It's unknown whether the current Android app APK is an assembly store or not.");
#endif
using var sut = GetSut(false, false, isCompressed: true);

Check failure on line 71 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.CreatesCorrectArchiveReader

Expected File.Exists(apkPath) to be true, but found False.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
using var sut = GetSut(false, false, isCompressed: true);
using var sut = GetSut(isAot: false, isAssemblyStore: false, isCompressed: true);

switch (TargetFramework)
{
case "net9.0":
Assert.IsType<V2.AndroidAssemblyDirectoryReaderV2>(sut);
Assert.IsType<AndroidAssemblyDirectoryReaderV2>(sut);
break;
case "net8.0":
Assert.IsType<V1.AndroidAssemblyDirectoryReaderV1>(sut);
Assert.IsType<AndroidAssemblyDirectoryReaderV1>(sut);
break;
default:
throw new NotSupportedException($"Unsupported target framework: {TargetFramework}");
Expand Down Expand Up @@ -109,7 +112,7 @@
Skip.If(!isAssemblyStore);
Skip.If(!isCompressed);
#endif
using var sut = GetSut(isAot, isAssemblyStore, isCompressed);

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: False

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: True

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: False

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: False

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: True

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: True

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: False

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: True

Expected File.Exists(apkPath) to be true, but found False.

Check failure on line 115 in test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs

View workflow job for this annotation

GitHub Actions / .NET (ubuntu-22.04)

Sentry.Android.AssemblyReader.Tests.AndroidAssemblyReaderTests.ReadsAssembly(isAot: True

Expected File.Exists(apkPath) to be true, but found False.

var peReader = sut.TryReadAssembly(assemblyName);
Assert.NotNull(peReader);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using Sentry.Android.AssemblyReader.V1;

namespace Sentry.Android.AssemblyReader.Tests;

public class ApiApprovalTests
Expand Down
Loading