From 738cd6dff3feb7dea7cf44103bc615eca38483c1 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 2 Jul 2024 17:35:34 +0200 Subject: [PATCH 1/4] Refine 16k page alignment support Context: ddb215b762d94801b61b72acc879b2eefe3e9e46 Context: https://github.com/google/bundletool/releases/tag/1.17.0 * ddb215b7 added an field to the `application_config` structure which specified the mask required to check whether entries in the APK that we read at run time are properly alignment at the 4k or 16k page boundary. This PR removes that code because it's possible that `bundletool` will change the package alignment **after** our code is already built. This would cause a false negative and an abort during application execution. Instead, we simply check whether the entry is 4k **or** 16k aligned. * Bump `bundletool` to 1.17.0, which now defaults to producing 16k-aligned archives. * Pass required alignment flags to Mono AOT compiler to produce properly aligned shared libraries. * Don't align 32-bit shared libraries to 16k, always use 4k alignment. This is in line with what NDK r27 does. --- Configuration.props | 2 +- .../targets/Microsoft.Android.Sdk.Aot.targets | 3 ++- ...soft.Android.Sdk.DefaultProperties.targets | 5 ++-- .../Tasks/AndroidZipAlign.cs | 8 +++--- .../Tasks/GeneratePackageManagerJava.cs | 3 --- .../Tasks/GetAotArguments.cs | 25 +++++++++++++++++++ .../Tasks/LinkApplicationSharedLibraries.cs | 8 ++++-- .../Utilities/EnvironmentHelper.cs | 12 +++------ .../Utilities/ApplicationConfig.cs | 3 --- ...pplicationConfigNativeAssemblyGenerator.cs | 2 -- .../Utilities/MonoAndroidHelper.cs | 1 - .../Xamarin.Android.Common.targets | 7 +++--- .../monodroid/embedded-assemblies-zip.cc | 6 ++--- .../xamarin-app-stub/application_dso_stub.cc | 1 - src/native/xamarin-app-stub/xamarin-app.hh | 1 - 15 files changed, 49 insertions(+), 38 deletions(-) diff --git a/Configuration.props b/Configuration.props index 788285bd9b9..f20d819c98b 100644 --- a/Configuration.props +++ b/Configuration.props @@ -125,7 +125,7 @@ 35.0.0 34.0.5 - 1.15.1 + 1.17.0 $(NUGET_PACKAGES) $(userprofile)\.nuget\packages $(HOME)/.nuget/packages diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets index 7b9ebac3621..1ae80b94409 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets @@ -90,7 +90,8 @@ They run in a context of an inner build with a single $(RuntimeIdentifier). RuntimeIdentifier="$(RuntimeIdentifier)" EnableLLVM="$(EnableLLVM)" Profiles="@(AndroidAotProfile)" - StripLibraries="$(_AndroidAotStripLibraries)"> + StripLibraries="$(_AndroidAotStripLibraries)" + ZipAlignmentPages="$(AndroidZipAlignment)"> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets index 7fcd3d31fe7..576f3a5406b 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets @@ -48,12 +48,11 @@ - <_AndroidZipAlignment Condition=" '$(_AndroidZipAlignment)' == '' ">4 + 16 diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs index c0508250737..1f8dc2d66f9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs @@ -7,9 +7,9 @@ namespace Xamarin.Android.Tasks { public class AndroidZipAlign : AndroidRunToolTask { - // Sometime next year the default value should be changed to 16 since it's going to be a Google Play store requirement for - // application submissions - internal const int DefaultZipAlignment = 4; + // Default to 16 since it's going to be a Google Play store requirement for application submissions sometime next year + internal const int DefaultZipAlignment64Bit = 16; + internal const int ZipAlignment32Bit = 4; // This must never change public override string TaskPrefix => "AZA"; @@ -19,7 +19,7 @@ public class AndroidZipAlign : AndroidRunToolTask [Required] public ITaskItem DestinationDirectory { get; set; } - int alignment = DefaultZipAlignment; + int alignment = DefaultZipAlignment64Bit; public int Alignment { get {return alignment;} set {alignment = value;} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 6340419ca9d..29a2118a86f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -80,7 +80,6 @@ public class GeneratePackageManagerJava : AndroidTask public string AndroidSequencePointsMode { get; set; } public bool EnableSGenConcurrent { get; set; } public string? CustomBundleConfigFile { get; set; } - public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment; [Output] public string BuildId { get; set; } @@ -335,7 +334,6 @@ void AddEnvironment () bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey), RegisteredTaskObjectLifetime.Build); - uint zipAlignmentMask = MonoAndroidHelper.ZipAlignmentToMask (ZipAlignmentPages); var appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, @@ -359,7 +357,6 @@ void AddEnvironment () JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token, JniRemappingReplacementTypeCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementTypeCount, JniRemappingReplacementMethodIndexEntryCount = jniRemappingNativeCodeInfo == null ? 0 : jniRemappingNativeCodeInfo.ReplacementMethodIndexEntryCount, - ZipAlignmentMask = zipAlignmentMask, MarshalMethodsEnabled = EnableMarshalMethods, IgnoreSplitConfigs = ShouldIgnoreSplitConfigs (), }; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs index af3fcf957cd..36ffaefa73b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs @@ -54,6 +54,8 @@ public abstract class GetAotArguments : AsyncTask public ITaskItem [] Profiles { get; set; } = Array.Empty (); + public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment64Bit; + [Required, Output] public ITaskItem [] ResolvedAssemblies { get; set; } = Array.Empty (); @@ -325,6 +327,29 @@ string GetLdFlags (NdkTools ndk, AndroidTargetArch arch, int level, string toolP ldFlags.Append ("-s"); } + uint maxPageSize; + switch (arch) { + case AndroidTargetArch.Arm64: + case AndroidTargetArch.X86_64: + maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (ZipAlignmentPages); + break; + + case AndroidTargetArch.Arm: + case AndroidTargetArch.X86: + maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (AndroidZipAlign.ZipAlignment32Bit); + break; + + default: + throw new InvalidOperationException ($"Internal error: unsupported target architecture {arch}"); + } + + if (ldFlags.Length > 0) { + ldFlags.Append (' '); + } + + ldFlags.Append ("-z "); + ldFlags.Append ($"max-page-size={maxPageSize}"); + return ldFlags.ToString (); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index cddfbfb242a..6fb2dac8967 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -43,7 +43,7 @@ sealed class InputFiles [Required] public string AndroidBinUtilsDirectory { get; set; } - public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment; + public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment64Bit; public override System.Threading.Tasks.Task RunTaskAsync () { @@ -145,10 +145,12 @@ IEnumerable GetLinkerConfigs () targetLinkerArgs.Clear (); string elf_arch; + uint maxPageSize; switch (abi) { case "armeabi-v7a": targetLinkerArgs.Add ("-X"); elf_arch = "armelf_linux_eabi"; + maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (AndroidZipAlign.ZipAlignment32Bit); break; case "arm64": @@ -156,14 +158,17 @@ IEnumerable GetLinkerConfigs () case "aarch64": targetLinkerArgs.Add ("--fix-cortex-a53-843419"); elf_arch = "aarch64linux"; + maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (ZipAlignmentPages); break; case "x86": elf_arch = "elf_i386"; + maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (AndroidZipAlign.ZipAlignment32Bit); break; case "x86_64": elf_arch = "elf_x86_64"; + maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (ZipAlignmentPages); break; default: @@ -186,7 +191,6 @@ IEnumerable GetLinkerConfigs () } } - uint maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (ZipAlignmentPages); targetLinkerArgs.Add ("-z"); targetLinkerArgs.Add ($"max-page-size={maxPageSize}"); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs index 9f93faf206e..9cd8d26ffc0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs @@ -63,12 +63,11 @@ public sealed class ApplicationConfig public uint jnienv_registerjninatives_method_token; public uint jni_remapping_replacement_type_count; public uint jni_remapping_replacement_method_index_entry_count; - public uint zip_alignment_mask; public uint mono_components_mask; public string android_package_name = String.Empty; } - const uint ApplicationConfigFieldCount = 27; + const uint ApplicationConfigFieldCount = 26; const string ApplicationConfigSymbolName = "application_config"; const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; @@ -327,17 +326,12 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile) ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 24: // zip_alignment_mask: uint32_t / .word | .long - Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); - ret.zip_alignment_mask = ConvertFieldToUInt32 ("zip_alignment_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); - break; - - case 25: // mono_components_mask: uint32_t / .word | .long + case 24: // mono_components_mask: uint32_t / .word | .long Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]); break; - case 26: // android_package_name: string / [pointer type] + case 25: // android_package_name: string / [pointer type] Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}"); pointers.Add (field [1].Trim ()); break; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs index 2b24f2d0347..96fa8af6f5a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -55,9 +55,6 @@ sealed class ApplicationConfig public uint jni_remapping_replacement_type_count; public uint jni_remapping_replacement_method_index_entry_count; - // 3, for 4-byte alignment (4k memory pages); 15, for 16-byte alignment (16k memory pages) - public uint zip_alignment_mask; - [NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)] public uint mono_components_mask; public string android_package_name = String.Empty; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 642f5183573..53140f8cf70 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -183,7 +183,6 @@ sealed class XamarinAndroidBundledAssembly public int JNIEnvRegisterJniNativesToken { get; set; } public int JniRemappingReplacementTypeCount { get; set; } public int JniRemappingReplacementMethodIndexEntryCount { get; set; } - public uint ZipAlignmentMask { get; set; } public MonoComponent MonoComponents { get; set; } public PackageNamingPolicy PackageNamingPolicy { get; set; } public List NativeLibraries { get; set; } @@ -245,7 +244,6 @@ protected override void Construct (LlvmIrModule module) jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken, jni_remapping_replacement_type_count = (uint)JniRemappingReplacementTypeCount, jni_remapping_replacement_method_index_entry_count = (uint)JniRemappingReplacementMethodIndexEntryCount, - zip_alignment_mask = ZipAlignmentMask, mono_components_mask = (uint)MonoComponents, android_package_name = AndroidPackageName, }; diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 54c1303c1f8..24d113a6b66 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -713,7 +713,6 @@ internal static void DumpMarshalMethodsToConsole (string heading, IDictionary ZipAlignmentToMaskOrPageSize (alignment, needMask: true); public static uint ZipAlignmentToPageSize (int alignment) => ZipAlignmentToMaskOrPageSize (alignment, needMask: false); static uint ZipAlignmentToMaskOrPageSize (int alignment, bool needMask) diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index a919417c349..6f98a37186a 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1724,7 +1724,6 @@ because xbuild doesn't support framework reference assemblies. UseAssemblyStore="$(AndroidUseAssemblyStore)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" CustomBundleConfigFile="$(AndroidBundleConfigurationFile)" - ZipAlignmentPages="$(_AndroidZipAlignment)" > @@ -2011,7 +2010,7 @@ because xbuild doesn't support framework reference assemblies. ApplicationSharedLibraries="@(_ApplicationSharedLibrary)" DebugBuild="$(AndroidIncludeDebugSymbols)" AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)" - ZipAlignmentPages="$(_AndroidZipAlignment)" + ZipAlignmentPages="$(AndroidZipAlignment)" /> @@ -2356,7 +2355,7 @@ because xbuild doesn't support framework reference assemblies. Date: Wed, 3 Jul 2024 13:17:43 +0200 Subject: [PATCH 2/4] Verify alignment of shared libraries --- .../Tasks/BuildApk.cs | 23 ++++-- .../Utilities/ELFHelper.cs | 82 +++++++++++++++++++ .../Xamarin.Android.Common.targets | 2 + 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs index 45778d60204..d87cc2f418e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs @@ -107,6 +107,8 @@ public class BuildApk : AndroidTask public string ZipFlushSizeLimit { get; set; } + public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment64Bit; + [Required] public string ProjectFullPath { get; set; } @@ -683,6 +685,7 @@ sealed class LibInfo public string Link; public string Abi; public string ArchiveFileName; + public ITaskItem Item; } CompressionMethod GetCompressionMethod (string fileName) @@ -690,7 +693,7 @@ CompressionMethod GetCompressionMethod (string fileName) return uncompressedFileExtensions.Contains (Path.GetExtension (fileName)) ? UncompressedMethod : CompressionMethod.Default; } - void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName) + void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName, ITaskItem taskItem) { string archivePath = MakeArchiveLibPath (abi, inArchiveFileName); existingEntries.Remove (archivePath); @@ -700,6 +703,7 @@ void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemP return; } Log.LogDebugMessage ($"Adding native library: {filesystemPath} (APK path: {archivePath})"); + ELFHelper.AssertValidLibraryAlignment (Log, ZipAlignmentPages, filesystemPath, taskItem); apk.AddEntryAndFlush (archivePath, File.OpenRead (filesystemPath), compressionMethod); } @@ -709,7 +713,7 @@ void AddRuntimeLibraries (ZipArchiveEx apk, string [] supportedAbis) foreach (ITaskItem item in ApplicationSharedLibraries) { if (String.Compare (abi, item.GetMetadata ("abi"), StringComparison.Ordinal) != 0) continue; - AddNativeLibraryToArchive (apk, abi, item.ItemSpec, Path.GetFileName (item.ItemSpec)); + AddNativeLibraryToArchive (apk, abi, item.ItemSpec, Path.GetFileName (item.ItemSpec), item); } } } @@ -762,7 +766,8 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis) Path = v.ItemSpec, Link = v.GetMetadata ("Link"), Abi = GetNativeLibraryAbi (v), - ArchiveFileName = GetArchiveFileName (v) + ArchiveFileName = GetArchiveFileName (v), + Item = v, }); AddNativeLibraries (files, supportedAbis, frameworkLibs); @@ -773,7 +778,8 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis) Path = v.ItemSpec, Link = v.GetMetadata ("Link"), Abi = GetNativeLibraryAbi (v), - ArchiveFileName = GetArchiveFileName (v) + ArchiveFileName = GetArchiveFileName (v), + Item = v, } ); @@ -854,8 +860,9 @@ void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis, System. string.Join (", ", libs.Where (lib => lib.Abi == null).Select (lib => lib.Path))); libs = libs.Where (lib => lib.Abi != null); libs = libs.Where (lib => supportedAbis.Contains (lib.Abi)); - foreach (var info in libs) - AddNativeLibrary (files, info.Path, info.Abi, info.ArchiveFileName); + foreach (var info in libs) { + AddNativeLibrary (files, info.Path, info.Abi, info.ArchiveFileName, info.Item); + } } private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supportedAbis) @@ -868,12 +875,13 @@ private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supp Path = l.ItemSpec, Abi = AndroidRidAbiHelper.GetNativeLibraryAbi (l), ArchiveFileName = l.GetMetadata ("ArchiveFileName"), + Item = l, }); AddNativeLibraries (files, supportedAbis, libs); } - void AddNativeLibrary (ArchiveFileList files, string path, string abi, string archiveFileName) + void AddNativeLibrary (ArchiveFileList files, string path, string abi, string archiveFileName, ITaskItem? taskItem = null) { string fileName = string.IsNullOrEmpty (archiveFileName) ? Path.GetFileName (path) : archiveFileName; var item = (filePath: path, archivePath: MakeArchiveLibPath (abi, fileName)); @@ -882,6 +890,7 @@ void AddNativeLibrary (ArchiveFileList files, string path, string abi, string ar return; } + ELFHelper.AssertValidLibraryAlignment (Log, ZipAlignmentPages, path, taskItem); if (!ELFHelper.IsEmptyAOTLibrary (Log, item.filePath)) { files.Add (item); } else { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs index a7a00b4af60..2154752af1b 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Concurrent; using System.IO; +using System.Text; using ELFSharp; using ELFSharp.ELF; using ELFSharp.ELF.Sections; +using ELFSharp.ELF.Segments; using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using ELFSymbolType = global::ELFSharp.ELF.Sections.SymbolType; @@ -15,6 +18,85 @@ namespace Xamarin.Android.Tasks { static class ELFHelper { + public static void AssertValidLibraryAlignment (TaskLoggingHelper log, int alignmentInPages, string path, ITaskItem? item) + { + if (String.IsNullOrEmpty (path) || !File.Exists (path)) { + return; + } + + log.LogDebugMessage ($"Checking alignment to {alignmentInPages}k page boundary in shared library {path}"); + try { + AssertValidLibraryAlignment (log, MonoAndroidHelper.ZipAlignmentToPageSize (alignmentInPages), path, ELFReader.Load (path), item); + } catch (Exception ex) { + log.LogWarning ($"Attempt to check whether '{path}' is a correctly aligned ELF file failed with exception, ignoring alignment check for the file."); + log.LogWarningFromException (ex, showStackTrace: true); + } + } + + static void AssertValidLibraryAlignment (TaskLoggingHelper log, uint pageSize, string path, IELF elf, ITaskItem? item) + { + if (elf.Class == Class.Bit32 || elf.Class == Class.NotELF) { + log.LogDebugMessage ($" Not a 64-bit ELF image. Ignored."); + return; + } + + var elf64 = elf as ELF; + if (elf64 == null) { + throw new InvalidOperationException ($"Internal error: {elf} is not ELF"); + } + + // We need to find all segments of Load type and make sure their alignment is as expected. + foreach (ISegment segment in elf64.Segments) { + if (segment.Type != SegmentType.Load) { + continue; + } + + var segment64 = segment as Segment; + if (segment64 == null) { + throw new InvalidOperationException ($"Internal error: {segment} is not Segment"); + } + + // TODO: what happens if the library is aligned at, say, 64k while 16k is required? Should we erorr out? + // We will need more info about that, have to wait till Google formally announce the requirement. + // At this moment the script https://developer.android.com/guide/practices/page-sizes#test they + // provide suggests it's a strict requirement, so we test for equality below. + if (segment64.Alignment == pageSize) { + continue; + } + log.LogDebugMessage ($" expected segment alignment of 0x{pageSize:x}, found 0x{segment64.Alignment:x}"); + + // TODO: turn into a coded warning and, eventually, error. Need better wording. + // Until dotnet runtime produces properly aligned libraries, this should be a plain message as a warning + // would break all the tests that require no warnings to be produced during build. + log.LogMessage ($"Native {elf64.Machine} shared library '{Path.GetFileName (path)}', from NuGet package {GetNugetPackageInfo ()} isn't properly aligned."); + break; + } + + string GetNugetPackageInfo () + { + const string Unknown = ""; + + if (item == null) { + return Unknown; + } + + var sb = new StringBuilder (); + string? metaValue = item.GetMetadata ("NuGetPackageId"); + if (String.IsNullOrEmpty (metaValue)) { + return Unknown; + } + + sb.Append (metaValue); + metaValue = item.GetMetadata ("NuGetPackageVersion"); + if (!String.IsNullOrEmpty (metaValue)) { + sb.Append (" version "); + sb.Append (metaValue); + } + + return sb.ToString (); + } + } + public static bool IsEmptyAOTLibrary (TaskLoggingHelper log, string path) { if (String.IsNullOrEmpty (path) || !File.Exists (path)) { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 6f98a37186a..3bb913af8f5 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -2092,6 +2092,7 @@ because xbuild doesn't support framework reference assemblies. IncludeFiles="@(AndroidPackagingOptionsInclude)" ZipFlushFilesLimit="$(_ZipFlushFilesLimit)" ZipFlushSizeLimit="$(_ZipFlushSizeLimit)" + ZipAlignmentPages="$(AndroidZipAlignment)" UseAssemblyStore="$(AndroidUseAssemblyStore)"> @@ -2129,6 +2130,7 @@ because xbuild doesn't support framework reference assemblies. IncludeFiles="@(AndroidPackagingOptionsInclude)" ZipFlushFilesLimit="$(_ZipFlushFilesLimit)" ZipFlushSizeLimit="$(_ZipFlushSizeLimit)" + ZipAlignmentPages="$(AndroidZipAlignment)" UseAssemblyStore="$(AndroidUseAssemblyStore)"> From fc13b7a3e092d10bcdb7e2830e0169799e5a82e9 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 8 Jul 2024 12:47:27 +0200 Subject: [PATCH 3/4] Bump ELFSharp --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index b86419905d1..228f6612b7d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -52,7 +52,7 @@ 6.12.0.148 8.0.0 6.0.0 - 2.13.1 + 2.17.3 2.14.1 5.9.2.4 From 162464bae8a399fcb0137e16149ceb4603208f83 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 22 Jul 2024 10:58:14 +0200 Subject: [PATCH 4/4] Use a coded warning --- Documentation/docs-mobile/messages/index.md | 3 ++- Documentation/docs-mobile/messages/xa0141.md | 14 +++++++++++ .../Properties/Resources.resx | 10 +++++++- .../Utilities/ELFHelper.cs | 23 +++++++++---------- 4 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 Documentation/docs-mobile/messages/xa0141.md diff --git a/Documentation/docs-mobile/messages/index.md b/Documentation/docs-mobile/messages/index.md index f5cb16b1759..8d46b161ab2 100644 --- a/Documentation/docs-mobile/messages/index.md +++ b/Documentation/docs-mobile/messages/index.md @@ -102,7 +102,8 @@ Please file an issue with the exact error message using the 'Help->Send Feedback or 'Help->Report a Problem' in Visual Studio for Mac. + [XA0138](xa0138.md): %(AndroidAsset.AssetPack) and %(AndroidAsset.AssetPack) item metadata are only supported when `$(AndroidApplication)` is `true`. + [XA0139](xa0139.md): `@(AndroidAsset)` `{0}` has invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow` -+ [XA0140](xa0140.md): ++ [XA0140](xa0140.md): ++ [XA0141](xa0141.md): NuGet package '{0}' version '{1}' contains a shared library '{2}' which is not correctly aligned. See https://developer.android.com/guide/practices/page-sizes for more details ## XA1xxx: Project related diff --git a/Documentation/docs-mobile/messages/xa0141.md b/Documentation/docs-mobile/messages/xa0141.md new file mode 100644 index 00000000000..15bb99a6d04 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa0141.md @@ -0,0 +1,14 @@ +--- +title: .NET for Android warning XA0141 +description: XA0141 warning code +ms.date: 22/07/2024 +--- +# .NET for Android warning XA0141 + +## Issue + +NuGet package '{0}' version '{1}' contains a shared library '{2}' which is not correctly aligned. See https://developer.android.com/guide/practices/page-sizes for more details + +## Solution + +The indicated native shared library must be recompiled and relinked with the 16k alignment, as per URL indicated in the message. diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx index ca26aa0bbec..6dee6524d06 100644 --- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx +++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx @@ -1059,4 +1059,12 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS {0} - NuGet package id {1} - NuGet package version - \ No newline at end of file + + NuGet package '{0}' version '{1}' contains a shared library '{2}' which is not correctly aligned. See https://developer.android.com/guide/practices/page-sizes for more details + The following is a literal name and should not be translated: NuGet +{0} - NuGet package id +{1} - NuGet package version +{2} - shared library file name + + + diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs index 2154752af1b..1f1e3abbc24 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs @@ -65,35 +65,34 @@ static void AssertValidLibraryAlignment (TaskLoggingHelper log, uint pageSize, s } log.LogDebugMessage ($" expected segment alignment of 0x{pageSize:x}, found 0x{segment64.Alignment:x}"); - // TODO: turn into a coded warning and, eventually, error. Need better wording. - // Until dotnet runtime produces properly aligned libraries, this should be a plain message as a warning - // would break all the tests that require no warnings to be produced during build. - log.LogMessage ($"Native {elf64.Machine} shared library '{Path.GetFileName (path)}', from NuGet package {GetNugetPackageInfo ()} isn't properly aligned."); + (string packageId, string packageVersion) = GetNugetPackageInfo (); + log.LogCodedWarning ("XA0141", packageId, packageVersion, Path.GetFileName (path)); break; } - string GetNugetPackageInfo () + (string packageId, string packageVersion) GetNugetPackageInfo () { const string Unknown = ""; if (item == null) { - return Unknown; + return (Unknown, Unknown); } - var sb = new StringBuilder (); string? metaValue = item.GetMetadata ("NuGetPackageId"); if (String.IsNullOrEmpty (metaValue)) { - return Unknown; + return (Unknown, Unknown); } - sb.Append (metaValue); + string id = metaValue; + string version; metaValue = item.GetMetadata ("NuGetPackageVersion"); if (!String.IsNullOrEmpty (metaValue)) { - sb.Append (" version "); - sb.Append (metaValue); + version = metaValue; + } else { + version = Unknown; } - return sb.ToString (); + return (id, version); } }