Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,29 @@ public static class AndroidAssemblyReaderFactory
/// <returns>The reader</returns>
public static IAndroidAssemblyReader Open(string apkPath, IList<string> supportedAbis, DebugLogger? logger = null)
{
logger?.Invoke("Opening APK: {0}", apkPath);
logger?.Invoke(DebugLoggerLevel.Debug, "Opening APK: {0}", apkPath);

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

logger?.Invoke("APK doesn't use AssemblyStore");
logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore");
return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger);
#else
logger?.Invoke("Reading files using V1 APK layout.");
logger?.Invoke(DebugLoggerLevel.Debug, "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 V1");
logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V1");
return new AndroidAssemblyStoreReaderV1(zipArchive, supportedAbis, logger);
}

logger?.Invoke("APK doesn't use AssemblyStore");
logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore");
return new AndroidAssemblyDirectoryReaderV1(zipArchive, supportedAbis, logger);
#endif
}
Expand Down
2 changes: 1 addition & 1 deletion src/Sentry.Android.AssemblyReader/ArchiveUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal static MemoryStream Extract(this ZipArchiveEntry zipEntry)
Debug.Assert(inputStream.Position == payloadOffset);
var inputLength = (int)(inputStream.Length - payloadOffset);

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

var outputStream = new MemoryStream(decompressedLength);

Expand Down
3 changes: 2 additions & 1 deletion src/Sentry.Android.AssemblyReader/DebugLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Sentry.Android.AssemblyReader;
/// <summary>
/// Writes a log message for debugging.
/// </summary>
/// <param name="level">The debug log level.</param>
/// <param name="message">The message string to write.</param>
/// <param name="args">Arguments for the formatted message string.</param>
public delegate void DebugLogger(string message, params object?[] args);
public delegate void DebugLogger(DebugLoggerLevel level, string message, params object?[] args);
Copy link
Member

Choose a reason for hiding this comment

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

question: breaking change

Since we don't mention Sentry.Android.AssemblyReader in the README as a top-level package,
nor in our documentation as a platform guide,
we do not consider changes to the public surface area of this Assembly a breaking change,
since it's main purpose is to be consumed by the Android TFMs of Sentry,
right?

Copy link
Collaborator Author

@jamescrosswell jamescrosswell Aug 24, 2025

Choose a reason for hiding this comment

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

Hm, actually it's only used from here:

/// <summary>
/// Allows integrations to provide a custom assembly reader.
/// </summary>
/// <remarks>
/// This is for Sentry use only, and can change without a major version bump.
/// </remarks>
#if !__MOBILE__
[CLSCompliant(false)]
#endif
[EditorBrowsable(EditorBrowsableState.Never)]
public Func<string, PEReader?>? AssemblyReader { get; set; }

It looks like Matt added that comment in #2127

@bruno-garcia given that sentry-xamarin is no longer being maintained, could we potentially move the AssemblyReader back into the main Sentry packages and make all this stuff internal again (not in this PR of course - probably for v6.0)?

Copy link
Member

Choose a reason for hiding this comment

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

I'm in favor of that, just get rid of that package and make our lifes easier

Copy link
Member

Choose a reason for hiding this comment

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

to be fair, it should never have been created. It's very little code and only used in 2 package we own. We could have copy pasted things and manually keeped them in sync.

We knew Xamarin was schedule for retirement by then already.

Classic .NET overengineering

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

32 changes: 32 additions & 0 deletions src/Sentry.Android.AssemblyReader/DebugLoggerLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Sentry.Android.AssemblyReader;

/// <summary>
/// Represents the level of debug logging.
/// </summary>
public enum DebugLoggerLevel : short
{
/// <summary>
/// Debug level logging.
/// </summary>
Debug,

/// <summary>
/// Information level logging.
/// </summary>
Info,

/// <summary>
/// Warning level logging.
/// </summary>
Warning,

/// <summary>
/// Error level logging.
/// </summary>
Error,

/// <summary>
/// Fatal level logging.
/// </summary>
Fatal
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ public AndroidAssemblyDirectoryReaderV1(ZipArchive zip, IList<string> supportedA
var zipEntry = FindAssembly(name);
if (zipEntry is null)
{
Logger?.Invoke("Couldn't find assembly {0} in the APK", name);
Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK", name);
return null;
}

Logger?.Invoke("Resolved assembly {0} in the APK at {1}", name, zipEntry.FullName);
Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK at {1}", name, zipEntry.FullName);

// 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ public AndroidAssemblyStoreReaderV1(ZipArchive zip, IList<string> supportedAbis,
var assembly = TryFindAssembly(name);
if (assembly is null)
{
Logger?.Invoke("Couldn't find assembly {0} in the APK AssemblyStore", name);
Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK AssemblyStore", name);
return null;
}

Logger?.Invoke("Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch);
Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch);

var stream = assembly.GetImage();
if (stream is null)
{
Logger?.Invoke("Couldn't access assembly {0} image stream", name);
Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't access assembly {0} image stream", name);
return null;
}

Expand Down Expand Up @@ -119,26 +119,26 @@ private void ProcessStores()
assembly.Hash64 = he.Hash;
if (assembly.RuntimeIndex != he.MappingIndex)
{
_logger?.Invoke(
_logger?.Invoke(DebugLoggerLevel.Debug,
$"assembly with hashes 0x{assembly.Hash32} and 0x{assembly.Hash64} has a different 32-bit runtime index ({assembly.RuntimeIndex}) than the 64-bit runtime index({he.MappingIndex})");
}

if (_manifest.EntriesByHash64.TryGetValue(assembly.Hash64, out var me))
{
if (string.IsNullOrEmpty(assembly.Name))
{
_logger?.Invoke(
_logger?.Invoke(DebugLoggerLevel.Debug,
$"32-bit hash 0x{assembly.Hash32:x} did not match any assembly name in the manifest");
assembly.Name = me.Name;
if (string.IsNullOrEmpty(assembly.Name))
{
_logger?.Invoke(
_logger?.Invoke(DebugLoggerLevel.Debug,
$"64-bit hash 0x{assembly.Hash64:x} did not match any assembly name in the manifest");
}
}
else if (!string.Equals(assembly.Name, me.Name, StringComparison.Ordinal))
{
_logger?.Invoke(
_logger?.Invoke(DebugLoggerLevel.Debug,
$"32-bit hash 0x{assembly.Hash32:x} maps to assembly name '{assembly.Name}', however 64-bit hash 0x{assembly.Hash64:x} for the same entry matches assembly name '{me.Name}'");
}
}
Expand Down Expand Up @@ -176,15 +176,15 @@ void ProcessIndex(List<AssemblyStoreHashEntry> index, string bitness,
{
if (!Stores.TryGetValue(he.StoreId, out var storeList))
{
_logger?.Invoke($"store with id {he.StoreId} not part of the set");
_logger?.Invoke(DebugLoggerLevel.Debug, $"store with id {he.StoreId} not part of the set");
continue;
}

foreach (var store in storeList)
{
if (he.LocalStoreIndex >= (uint)store.Assemblies.Count)
{
_logger?.Invoke(
_logger?.Invoke(DebugLoggerLevel.Debug,
$"{bitness}-bit index entry with hash 0x{he.Hash:x} has invalid store {store.StoreId} index {he.LocalStoreIndex} (maximum allowed is {store.Assemblies.Count})");
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public AndroidAssemblyDirectoryReaderV2(string apkPath, IList<string> supportedA
Logger = logger;
foreach (var abi in supportedAbis)
{
logger?.Invoke("Adding {0} to supported architectures for Directory Reader", abi);
logger?.Invoke(DebugLoggerLevel.Debug, "Adding {0} to supported architectures for Directory Reader", abi);
SupportedArchitectures.Add(abi.AbiToDeviceArchitecture());
}
_archiveAssemblyHelper = new ArchiveAssemblyHelper(apkPath, logger, supportedAbis);
Expand All @@ -26,21 +26,21 @@ public AndroidAssemblyDirectoryReaderV2(string apkPath, IList<string> supportedA
var stream = File.OpenRead(name);
return new PEReader(stream);
}
Logger?.Invoke("File {0} does not exist in the APK", name);
Logger?.Invoke(DebugLoggerLevel.Debug, "File {0} does not exist in the APK", name);

foreach (var arch in SupportedArchitectures)
{
if (_archiveAssemblyHelper.ReadEntry($"assemblies/{name}", arch) is not { } memStream)
{
Logger?.Invoke("Couldn't find entry {0} in the APK for the {1} architecture", name, arch);
Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find entry {0} in the APK for the {1} architecture", name, arch);
continue;
}

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

Logger?.Invoke("Couldn't find assembly {0} in the APK", name);
Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK", name);
return null;
}

Expand Down Expand Up @@ -96,11 +96,11 @@ public ArchiveAssemblyHelper(string archivePath, DebugLogger? logger, IList<stri
ELFPayloadError.NoPayloadSection => $"Entry '{path}' does not contain the 'payload' section",
_ => $"Unknown ELF payload section error for entry '{path}': {error}"
};
_logger?.Invoke(message);
_logger?.Invoke(DebugLoggerLevel.Debug, message);
}
else
{
_logger?.Invoke($"Extracted content from ELF image '{path}'");
_logger?.Invoke(DebugLoggerLevel.Debug, $"Extracted content from ELF image '{path}'");
}

if (elfPayloadOffset == 0)
Expand Down Expand Up @@ -142,14 +142,14 @@ public ArchiveAssemblyHelper(string archivePath, DebugLogger? logger, IList<stri
var potentialEntries = TransformArchiveAssemblyPath(path, arch);
if (potentialEntries == null || potentialEntries.Count == 0)
{
_logger?.Invoke("No potential entries for path '{0}' with arch '{1}'", path, arch);
_logger?.Invoke(DebugLoggerLevel.Debug, "No potential entries for path '{0}' with arch '{1}'", path, arch);
return null;
}

// First we check the base.apk
if (ReadEntryFromApk(_archivePath) is { } baseEntry)
{
_logger?.Invoke("Found entry '{0}' in base archive '{1}'", path, _archivePath);
_logger?.Invoke(DebugLoggerLevel.Debug, "Found entry '{0}' in base archive '{1}'", path, _archivePath);
return baseEntry;
}

Expand All @@ -159,7 +159,7 @@ public ArchiveAssemblyHelper(string archivePath, DebugLogger? logger, IList<stri
var splitFilePath = _archivePath.GetArchivePathForAbi(supportedAbi, _logger);
if (!File.Exists(splitFilePath))
{
_logger?.Invoke("No split config detected at: '{0}'", splitFilePath);
_logger?.Invoke(DebugLoggerLevel.Debug, "No split config detected at: '{0}'", splitFilePath);
}
else if (ReadEntryFromApk(splitFilePath) is { } splitEntry)
{
Expand All @@ -177,7 +177,7 @@ public ArchiveAssemblyHelper(string archivePath, DebugLogger? logger, IList<stri
{
if (zip.GetEntry(assemblyPath) is not { } entry)
{
_logger?.Invoke("No entry found for path '{0}' in archive '{1}'", assemblyPath, archivePath);
_logger?.Invoke(DebugLoggerLevel.Debug, "No entry found for path '{0}' in archive '{1}'", assemblyPath, archivePath);
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ public static bool TryReadStore(string inputFile, IList<string> supportedAbis, D
var (explorers, errorMessage) = AssemblyStoreExplorer.Open(inputFile, logger);
if (explorers is null)
{
logger?.Invoke("Unable to read store information for {0}: {1}", inputFile, errorMessage);
logger?.Invoke(DebugLoggerLevel.Debug, "Unable to read store information for {0}: {1}", inputFile, errorMessage);

// Check for assembly stores in any device specific APKs
foreach (var supportedAbi in supportedAbis)
{
var splitFilePath = inputFile.GetArchivePathForAbi(supportedAbi, logger);
if (!File.Exists(splitFilePath))
{
logger?.Invoke("No split config detected at: '{0}'", splitFilePath);
logger?.Invoke(DebugLoggerLevel.Debug, "No split config detected at: '{0}'", splitFilePath);
continue;
}
(explorers, errorMessage) = AssemblyStoreExplorer.Open(splitFilePath, logger);
Expand All @@ -37,7 +37,7 @@ public static bool TryReadStore(string inputFile, IList<string> supportedAbis, D
}
else
{
logger?.Invoke("Unable to read store information for {0}: {1}", splitFilePath, errorMessage);
logger?.Invoke(DebugLoggerLevel.Debug, "Unable to read store information for {0}: {1}", splitFilePath, errorMessage);
}
}
}
Expand All @@ -62,7 +62,7 @@ public static bool TryReadStore(string inputFile, IList<string> supportedAbis, D

if (supportedExplorers.Count == 0)
{
logger?.Invoke("Could not find V2 AssemblyStoreExplorer for the supported ABIs: {0}", string.Join(", ", supportedAbis));
logger?.Invoke(DebugLoggerLevel.Debug, "Could not find V2 AssemblyStoreExplorer for the supported ABIs: {0}", string.Join(", ", supportedAbis));
reader = null;
return false;
}
Expand All @@ -76,17 +76,17 @@ public static bool TryReadStore(string inputFile, IList<string> supportedAbis, D
var explorerAssembly = TryFindAssembly(name);
if (explorerAssembly is null)
{
_logger?.Invoke("Couldn't find assembly {0} in the APK AssemblyStore", name);
_logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK AssemblyStore", name);
return null;
}

var (explorer, storeItem) = explorerAssembly;
_logger?.Invoke("Resolved assembly {0} in the APK {1} AssemblyStore", name, storeItem.TargetArch);
_logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK {1} AssemblyStore", name, storeItem.TargetArch);

var stream = explorer.ReadImageData(storeItem, false);
if (stream is null)
{
_logger?.Invoke("Couldn't access assembly {0} image stream", name);
_logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't access assembly {0} image stream", name);
return null;
}

Expand Down Expand Up @@ -127,12 +127,12 @@ private bool FindBestAssembly(string name, out ExplorerStoreItem? explorerAssemb
{
if (explorer.AssembliesByName?.TryGetValue(name, out var assembly) is true)
{
_logger?.Invoke("Found best assembly {0} in APK AssemblyStore for target arch {1}", name, explorer.TargetArch);
_logger?.Invoke(DebugLoggerLevel.Debug, "Found best assembly {0} in APK AssemblyStore for target arch {1}", name, explorer.TargetArch);
explorerAssembly = new(explorer, assembly);
return true;
}
}
_logger?.Invoke("No best assembly for {0} in APK AssemblyStore", name);
_logger?.Invoke(DebugLoggerLevel.Warning, "No best assembly for {0} in APK AssemblyStore", name);
explorerAssembly = null;
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private AssemblyStoreExplorer(Stream storeStream, string path, DebugLogger? logg
{
foreach (var item in Assemblies)
{
logger?.Invoke("Assembly {0} indexed from AssemblyStore {1}", item.Name, path);
logger?.Invoke(DebugLoggerLevel.Debug, "Assembly {0} indexed from AssemblyStore {1}", item.Name, path);
dict.Add(item.Name, item);
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ protected override bool IsSupported()
ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section",
_ => $"Unknown ELF payload section error for store '{StorePath}': {error}"
};
Logger?.Invoke(message);
Logger?.Invoke(DebugLoggerLevel.Debug, message);
// Was originally:
// ```
// } else if (elfOffset >= 0) {
Expand All @@ -122,14 +122,14 @@ protected override bool IsSupported()

if (magic != Utils.AssemblyStoreMagic)
{
Logger?.Invoke("Store '{0}' has invalid header magic number.", StorePath);
Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has invalid header magic number.", StorePath);
return false;
}

uint version = reader.ReadUInt32();
if (!supportedVersions.Contains(version))
{
Logger?.Invoke("Store '{0}' has unsupported version 0x{1:x}", StorePath, version);
Logger?.Invoke(DebugLoggerLevel.Debug, "Store '{0}' has unsupported version 0x{1:x}", StorePath, version);
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Sentry/Platforms/Android/AndroidHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static IList<string> GetSupportedAbis()
{
var supportedAbis = GetSupportedAbis();
return AndroidAssemblyReaderFactory.Open(apkPath, supportedAbis,
logger: (message, args) => logger?.Log(SentryLevel.Debug, message, args: args));
logger: (level, message, args) => logger?.Log((SentryLevel)level, message, args: args));
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private IAndroidAssemblyReader GetSut(bool isAot, bool isAssemblyStore, bool isC
// Note: This needs to match the RID used when publishing the test APK
string[] supportedAbis = { "x86_64" };
return AndroidAssemblyReaderFactory.Open(apkPath, supportedAbis,
logger: (message, args) => _output.WriteLine(message, args));
logger: (_, message, args) => _output.WriteLine(message, args));
#endif
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@
{
public static Sentry.Android.AssemblyReader.IAndroidAssemblyReader Open(string apkPath, System.Collections.Generic.IList<string> supportedAbis, Sentry.Android.AssemblyReader.DebugLogger? logger = null) { }
}
public delegate void DebugLogger(string message, params object?[] args);
public delegate void DebugLogger(Sentry.Android.AssemblyReader.DebugLoggerLevel level, string message, params object?[] args);
public enum DebugLoggerLevel : short
{
Debug = 0,
Info = 1,
Warning = 2,
Error = 3,
Fatal = 4,
}
public interface IAndroidAssemblyReader : System.IDisposable
{
System.Reflection.PortableExecutable.PEReader? TryReadAssembly(string name);
Expand Down
Loading
Loading