diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs index eb0a3724295..dc5dde54822 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs @@ -28,13 +28,20 @@ public class CreateAssemblyStore : AndroidTask [Required] public string [] SupportedAbis { get; set; } = []; + [Required] + public string TargetRuntime { get; set; } = ""; + public bool UseAssemblyStore { get; set; } [Output] public ITaskItem [] AssembliesToAddToArchive { get; set; } = []; + AndroidRuntime targetRuntime; + public override bool RunTask () { + targetRuntime = MonoAndroidHelper.ParseAndroidRuntime (TargetRuntime); + // Get all the user and framework assemblies we may need to package var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(ShouldSkipAssembly (asm))).ToArray (); @@ -43,7 +50,7 @@ public override bool RunTask () return !Log.HasLoggedErrors; } - var store_builder = new AssemblyStoreBuilder (Log); + var store_builder = new AssemblyStoreBuilder (Log, targetRuntime); var per_arch_assemblies = MonoAndroidHelper.GetPerArchAssemblies (assemblies, SupportedAbis, true); foreach (var kvp in per_arch_assemblies) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreAssemblyInfo.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreAssemblyInfo.cs index ad4a04cf95a..37189b143af 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreAssemblyInfo.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreAssemblyInfo.cs @@ -16,8 +16,9 @@ class AssemblyStoreAssemblyInfo public byte[] AssemblyNameNoExtBytes { get; } public FileInfo? SymbolsFile { get; set; } public FileInfo? ConfigFile { get; set; } + public bool Ignored { get; } - public AssemblyStoreAssemblyInfo (string sourceFilePath, ITaskItem assembly) + public AssemblyStoreAssemblyInfo (string sourceFilePath, ITaskItem assembly, bool assemblyIsIgnored = false) { Arch = MonoAndroidHelper.GetTargetArch (assembly); if (Arch == AndroidTargetArch.None) { @@ -25,6 +26,7 @@ public AssemblyStoreAssemblyInfo (string sourceFilePath, ITaskItem assembly) } SourceFile = new FileInfo (sourceFilePath); + Ignored = assemblyIsIgnored; string? name = Path.GetFileName (SourceFile.Name); if (name == null) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs index 80a65fb25ce..41195075963 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreBuilder.cs @@ -13,10 +13,12 @@ class AssemblyStoreBuilder { readonly TaskLoggingHelper log; readonly AssemblyStoreGenerator storeGenerator; + readonly AndroidRuntime targetRuntime; - public AssemblyStoreBuilder (TaskLoggingHelper log) + public AssemblyStoreBuilder (TaskLoggingHelper log, AndroidRuntime targetRuntime) { this.log = log; + this.targetRuntime = targetRuntime; storeGenerator = new (log); } @@ -39,6 +41,24 @@ public void AddAssembly (string assemblySourcePath, ITaskItem assemblyItem, bool } storeGenerator.Add (storeAssemblyInfo); + + ClrAddIgnoredNativeImageAssembly (assemblyItem); + } + + // When CoreCLR tries to load an assembly (say `AssemblyName.dll`) it will always first try to load + // a "native image" assembly from `AssemblyName.ni.dll` which will **never** exist. The native image + // assemblies were once supported only on Windows and were never (nor will ever be) supported on + // Unix. In order to speed up load times, we add an empty entry for each `*.ni.dll` to the assembly + // store index. + void ClrAddIgnoredNativeImageAssembly (ITaskItem assemblyItem) + { + if (targetRuntime != AndroidRuntime.CoreCLR) { + return; + } + + string ignoredName = Path.GetFileName (Path.ChangeExtension (assemblyItem.ItemSpec, ".ni.dll")); + var storeAssemblyInfo = new AssemblyStoreAssemblyInfo (ignoredName, assemblyItem, assemblyIsIgnored: true); + storeGenerator.Add (storeAssemblyInfo); } public Dictionary Generate (string outputDirectoryPath) => storeGenerator.Generate (outputDirectoryPath); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.Classes.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.Classes.cs index 680d8966070..81567e4ac0f 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.Classes.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.Classes.cs @@ -32,18 +32,21 @@ public AssemblyStoreHeader (uint magic, uint version, uint entry_count, uint ind sealed class AssemblyStoreIndexEntry { - public const uint NativeSize32 = 2 * sizeof (uint); - public const uint NativeSize64 = sizeof (ulong) + sizeof (uint); + // We treat `bool` as `byte` here, since that's what gets written to the binary + public const uint NativeSize32 = 2 * sizeof (uint) + sizeof (byte); + public const uint NativeSize64 = sizeof (ulong) + sizeof (uint) + sizeof (byte); public readonly string name; public readonly ulong name_hash; public readonly uint descriptor_index; + public readonly bool ignore; - public AssemblyStoreIndexEntry (string name, ulong name_hash, uint descriptor_index) + public AssemblyStoreIndexEntry (string name, ulong name_hash, uint descriptor_index, bool ignore) { this.name = name; this.name_hash = name_hash; this.descriptor_index = descriptor_index; + this.ignore = ignore; } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs index 024f2cf0ac2..067072b7d24 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs @@ -16,6 +16,7 @@ namespace Xamarin.Android.Tasks; // [HEADER] // [INDEX] // [ASSEMBLY_DESCRIPTORS] +// [ASSEMBLY_NAMES] // [ASSEMBLY DATA] // // Formats of the sections above are as follows: @@ -30,6 +31,7 @@ namespace Xamarin.Android.Tasks; // INDEX (variable size, HEADER.ENTRY_COUNT*2 entries, for assembly names with and without the extension) // [NAME_HASH] uint on 32-bit platforms, ulong on 64-bit platforms; xxhash of the assembly name // [DESCRIPTOR_INDEX] uint; index into in-store assembly descriptor array +// [IGNORE] byte; if set to anything other than 0, the assembly is to be ignored when loading // // ASSEMBLY_DESCRIPTORS (variable size, HEADER.ENTRY_COUNT entries), each entry formatted as follows: // [MAPPING_INDEX] uint; index into a runtime array where assembly data pointers are stored @@ -50,8 +52,8 @@ partial class AssemblyStoreGenerator const uint ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian, must match the BUNDLED_ASSEMBLIES_BLOB_MAGIC native constant // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones - const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000002; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant - const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000002; + const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; const uint ASSEMBLY_STORE_ABI_ARM = 0x00020000; @@ -122,19 +124,29 @@ string Generate (string baseOutputDirectory, AndroidTargetArch arch, List UInt32.MaxValue) { - throw new NotSupportedException ("Assembly store size exceeds the maximum supported value"); + if (!info.Ignored) { + curPos += ret.data_size + ret.debug_data_size + ret.config_data_size; + if (curPos > UInt32.MaxValue) { + throw new NotSupportedException ("Assembly store size exceeds the maximum supported value"); + } } return (ret, curPos); @@ -252,8 +266,9 @@ void WriteIndex (BinaryWriter writer, StreamWriter manifestWriter, List ReadIndex (BinaryReader reader, AssemblyStoreHeade } uint descriptor_index = reader.ReadUInt32 (); - index.Add (new AssemblyStoreIndexEntry (String.Empty, name_hash, descriptor_index)); + bool ignored = reader.ReadByte () != 0; + + index.Add (new AssemblyStoreIndexEntry (String.Empty, name_hash, descriptor_index, ignored)); } return index; diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 060cffa8433..633dbc5f93d 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -2288,6 +2288,7 @@ because xbuild doesn't support framework reference assemblies. --> void* { hash_t name_hash = xxhash::hash (name.data (), name.length ()); - log_debug (LOG_ASSEMBLY, "AssemblyStore::open_assembly: looking for bundled name: '{}' (hash {:x})"sv, optional_string (name.data ()), name_hash); if constexpr (Constants::is_debug_build) { - // TODO: implement filesystem lookup here - // In fastdev mode we might not have any assembly store. if (assembly_store_hashes == nullptr) { log_warn (LOG_ASSEMBLY, "Assembly store not registered. Unable to look up assembly '{}'"sv, name); @@ -208,12 +205,15 @@ auto AssemblyStore::open_assembly (std::string_view const& name, int64_t &size) } const AssemblyStoreIndexEntry *hash_entry = find_assembly_store_entry (name_hash, assembly_store_hashes, assembly_store.index_entry_count); - if (hash_entry == nullptr) { - // This message should really be `log_warn`, but since CoreCLR attempts to load `AssemblyName.ni.dll` for each - // `AssemblyName.dll`, it creates a lot of non-actionable noise. - // TODO (in separate PR): generate hashes for the .ni.dll names and ignore them at the top of the function. Then restore - // `log_warn` here. - log_debug (LOG_ASSEMBLY, "Assembly '{}' (hash 0x{:x}) not found"sv, optional_string (name.data ()), name_hash); + if (hash_entry == nullptr) [[unlikely]] { + size = 0; + log_warn (LOG_ASSEMBLY, "Assembly '{}' (hash 0x{:x}) not found"sv, name, name_hash); + return nullptr; + } + + if (hash_entry->ignore != 0) { + size = 0; + log_debug (LOG_ASSEMBLY, "Assembly '{}' ignored"sv, name); return nullptr; } diff --git a/src/native/clr/include/constants.hh b/src/native/clr/include/constants.hh index 14bd7b01270..60f2e00b161 100644 --- a/src/native/clr/include/constants.hh +++ b/src/native/clr/include/constants.hh @@ -3,6 +3,7 @@ #include #include +#include #include #include diff --git a/src/native/clr/include/host/assembly-store.hh b/src/native/clr/include/host/assembly-store.hh index 033e7d3ce1c..eac5a30f5f1 100644 --- a/src/native/clr/include/host/assembly-store.hh +++ b/src/native/clr/include/host/assembly-store.hh @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index b600f9d2e56..019f6e18981 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -31,7 +31,7 @@ static constexpr uint32_t ASSEMBLY_STORE_ABI = 0x00040000; #endif // Increase whenever an incompatible change is made to the assembly store format -static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 2 | ASSEMBLY_STORE_64BIT_FLAG | ASSEMBLY_STORE_ABI; +static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 3 | ASSEMBLY_STORE_64BIT_FLAG | ASSEMBLY_STORE_ABI; static constexpr uint32_t MODULE_MAGIC_NAMES = 0x53544158; // 'XATS', little-endian static constexpr uint32_t MODULE_INDEX_MAGIC = 0x49544158; // 'XATI', little-endian @@ -153,6 +153,7 @@ struct XamarinAndroidBundledAssembly // [HEADER] // [INDEX] // [ASSEMBLY_DESCRIPTORS] +// [ASSEMBLY_NAMES] // [ASSEMBLY DATA] // // Formats of the sections above are as follows: @@ -167,6 +168,7 @@ struct XamarinAndroidBundledAssembly // INDEX (variable size, HEADER.ENTRY_COUNT*2 entries, for assembly names with and without the extension) // [NAME_HASH] uint on 32-bit platforms, ulong on 64-bit platforms; xxhash of the assembly name // [DESCRIPTOR_INDEX] uint; index into in-store assembly descriptor array +// [IGNORE] byte; if set to anything other than 0, the assembly is to be ignored when loading // // ASSEMBLY_DESCRIPTORS (variable size, HEADER.ENTRY_COUNT entries), each entry formatted as follows: // [MAPPING_INDEX] uint; index into a runtime array where assembly data pointers are stored @@ -199,6 +201,7 @@ struct [[gnu::packed]] AssemblyStoreIndexEntry final { xamarin::android::hash_t name_hash; uint32_t descriptor_index; + uint8_t ignore; // Assembly should be ignored when loading, its data isn't actually there }; struct [[gnu::packed]] AssemblyStoreEntryDescriptor final diff --git a/src/native/mono/monodroid/embedded-assemblies.cc b/src/native/mono/monodroid/embedded-assemblies.cc index 5bd839bd714..c3aa3488aa8 100644 --- a/src/native/mono/monodroid/embedded-assemblies.cc +++ b/src/native/mono/monodroid/embedded-assemblies.cc @@ -418,11 +418,16 @@ EmbeddedAssemblies::assembly_store_open_from_bundles (dynamic_local_stringignore != 0) { + log_debug (LOG_ASSEMBLY, "Assembly '{}' ignored"sv, optional_string (name.get ())); + return nullptr; + } + if (hash_entry->descriptor_index >= assembly_store.assembly_count) { Helpers::abort_application ( LOG_ASSEMBLY, diff --git a/src/native/mono/xamarin-app-stub/xamarin-app.hh b/src/native/mono/xamarin-app-stub/xamarin-app.hh index 49777e7614d..d6e9ef89a8a 100644 --- a/src/native/mono/xamarin-app-stub/xamarin-app.hh +++ b/src/native/mono/xamarin-app-stub/xamarin-app.hh @@ -34,7 +34,7 @@ static constexpr uint32_t ASSEMBLY_STORE_ABI = 0x00040000; #endif // Increase whenever an incompatible change is made to the assembly store format -static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 2 | ASSEMBLY_STORE_64BIT_FLAG | ASSEMBLY_STORE_ABI; +static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 3 | ASSEMBLY_STORE_64BIT_FLAG | ASSEMBLY_STORE_ABI; static constexpr uint32_t MODULE_MAGIC_NAMES = 0x53544158; // 'XATS', little-endian static constexpr uint32_t MODULE_INDEX_MAGIC = 0x49544158; // 'XATI', little-endian @@ -149,6 +149,7 @@ struct XamarinAndroidBundledAssembly // INDEX (variable size, HEADER.ENTRY_COUNT*2 entries, for assembly names with and without the extension) // [NAME_HASH] uint on 32-bit platforms, ulong on 64-bit platforms; xxhash of the assembly name // [DESCRIPTOR_INDEX] uint; index into in-store assembly descriptor array +// [IGNORE] byte; if set to anything other than 0, the assembly is to be ignored when loading // // ASSEMBLY_DESCRIPTORS (variable size, HEADER.ENTRY_COUNT entries), each entry formatted as follows: // [MAPPING_INDEX] uint; index into a runtime array where assembly data pointers are stored @@ -181,6 +182,7 @@ struct [[gnu::packed]] AssemblyStoreIndexEntry final { xamarin::android::hash_t name_hash; uint32_t descriptor_index; + uint8_t ignore; // Assembly should be ignored when loading, its data isn't actually there }; struct [[gnu::packed]] AssemblyStoreEntryDescriptor final diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs index d2ee02cf0a4..7e5dbcfd1d5 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs @@ -16,11 +16,13 @@ abstract class AssemblyStoreItem public uint ConfigOffset { get; protected set; } public uint ConfigSize { get; protected set; } public AndroidTargetArch TargetArch { get; protected set; } + public bool Ignore { get; } - protected AssemblyStoreItem (string name, bool is64Bit, List hashes) + protected AssemblyStoreItem (string name, bool is64Bit, List hashes, bool ignore) { Name = name; Hashes = hashes.AsReadOnly (); Is64Bit = is64Bit; + Ignore = ignore; } } diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs index 9dd8a19a054..3b2c8549ff2 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs @@ -32,11 +32,13 @@ sealed class IndexEntry { public readonly ulong name_hash; public readonly uint descriptor_index; + public readonly bool ignore; - public IndexEntry (ulong name_hash, uint descriptor_index) + public IndexEntry (ulong name_hash, uint descriptor_index, bool ignore) { this.name_hash = name_hash; this.descriptor_index = descriptor_index; + this.ignore = ignore; } } @@ -56,8 +58,8 @@ sealed class EntryDescriptor sealed class StoreItem_V2 : AssemblyStoreItem { - public StoreItem_V2 (AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, EntryDescriptor descriptor) - : base (name, is64Bit, IndexToHashes (indexEntries)) + public StoreItem_V2 (AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, EntryDescriptor descriptor, bool ignore) + : base (name, is64Bit, IndexToHashes (indexEntries), ignore) { DataOffset = descriptor.data_offset; DataSize = descriptor.data_size; @@ -84,11 +86,13 @@ sealed class TemporaryItem public readonly string Name; public readonly List IndexEntries = new List (); public readonly EntryDescriptor Descriptor; + public readonly bool Ignored; - public TemporaryItem (string name, EntryDescriptor descriptor) + public TemporaryItem (string name, EntryDescriptor descriptor, bool ignored) { Name = name; Descriptor = descriptor; + Ignored = ignored; } } } diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs index a43960102e2..ef333d96ef7 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs +++ b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs @@ -11,8 +11,8 @@ namespace Xamarin.Android.AssemblyStore; partial class StoreReader_V2 : AssemblyStoreReader { // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones - const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000002; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant - const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000002; + const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; const uint ASSEMBLY_STORE_FORMAT_VERSION_MASK = 0xF0000000; const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; @@ -162,7 +162,8 @@ protected override void Prepare () } uint descriptor_index = reader.ReadUInt32 (); - index.Add (new IndexEntry (name_hash, descriptor_index)); + bool ignore = reader.ReadByte () != 0; + index.Add (new IndexEntry (name_hash, descriptor_index, ignore)); } var descriptors = new List (); @@ -197,7 +198,7 @@ protected override void Prepare () var tempItems = new Dictionary (); foreach (IndexEntry ie in index) { if (!tempItems.TryGetValue (ie.descriptor_index, out TemporaryItem? item)) { - item = new TemporaryItem (names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index]); + item = new TemporaryItem (names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index], ie.ignore); tempItems.Add (ie.descriptor_index, item); } item.IndexEntries.Add (ie); @@ -210,7 +211,7 @@ protected override void Prepare () var storeItems = new List (); foreach (var kvp in tempItems) { TemporaryItem ti = kvp.Value; - var item = new StoreItem_V2 (TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor); + var item = new StoreItem_V2 (TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor, ti.Ignored); storeItems.Add (item); } Assemblies = storeItems.AsReadOnly (); diff --git a/tools/assembly-store-reader-mk2/StorePrettyPrinter.cs b/tools/assembly-store-reader-mk2/StorePrettyPrinter.cs index 8efd16fd162..d49c5d3d5cf 100644 --- a/tools/assembly-store-reader-mk2/StorePrettyPrinter.cs +++ b/tools/assembly-store-reader-mk2/StorePrettyPrinter.cs @@ -36,16 +36,21 @@ public void Show () foreach (AssemblyStoreItem assembly in assemblies) { line.Clear (); line.Append (" "); - line.AppendLine (assembly.Name); - line.Append (" PE image data: "); - FormatOffsetAndSize (line, assembly.DataOffset, assembly.DataSize); - line.AppendLine (); - line.Append (" Debug data: "); - FormatOffsetAndSize (line, assembly.DebugOffset, assembly.DebugSize); - line.AppendLine (); - line.Append (" Config data: "); - FormatOffsetAndSize (line, assembly.ConfigOffset, assembly.ConfigSize); - line.AppendLine (); + line.Append (assembly.Name); + if (assembly.Ignore) { + line.AppendLine (" "); + } else { + line.AppendLine (); + line.Append (" PE image data: "); + FormatOffsetAndSize (line, assembly.DataOffset, assembly.DataSize); + line.AppendLine (); + line.Append (" Debug data: "); + FormatOffsetAndSize (line, assembly.DebugOffset, assembly.DebugSize); + line.AppendLine (); + line.Append (" Config data: "); + FormatOffsetAndSize (line, assembly.ConfigOffset, assembly.ConfigSize); + line.AppendLine (); + } line.Append (" Name hashes: "); FormatHashes (line, assembly.Hashes); line.AppendLine ();