From fcd994103fa573b6d66e0c24ecd474996f817b6c Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 20 Jun 2024 11:04:43 +0200 Subject: [PATCH 1/2] Make APK and shared library alignment configurable Context: https://github.com/dotnet/android/wiki/Android-support-for-devices-with-16k-pages Context: https://github.com/dotnet/android/pull/9020 Starting sometime next year Google will require all packages submitted to the Play Store to be aligned to 16 bytes, in order to support Android devices which use 16kb pages instead of the currently supported 4kb ones. The requirement also applies to native shared libaries (`.so`), which need to be linked so that their loaded sections are aligned to 16kb as well. This commit implements changes which make the alignment configurable, while still defaulting to 4kb alignment. The #9020 PR will enable (when NDK r27 is released) building of our runtime shared libraries with 16kb alignment. While strictly speaking NDK r27 is not necessary to enable 16kb alignment for the runtime (we could just modify our `CMakeLists.txt` script to pass the appropriate flag), I prefer to rely on the "official" support once NDK r27 is out. Linking of application shared libraries, however, doesn't use the NDK at all, and in this case we must pass the appropriate flag to the linker explicitly. This commit also prepares our runtime to verify alignment correctly when changing between 4k and 16k. --- .../Microsoft.Android.Sdk.DefaultProperties.targets | 9 +++++++++ .../Tasks/AndroidZipAlign.cs | 7 +++++-- .../Tasks/GeneratePackageManagerJava.cs | 8 ++++++++ .../Tasks/LinkApplicationSharedLibraries.cs | 11 ++++++++++- .../Utilities/EnvironmentHelper.cs | 12 +++++++++--- .../Utilities/ApplicationConfig.cs | 3 +++ .../ApplicationConfigNativeAssemblyGenerator.cs | 2 ++ .../Xamarin.Android.Common.targets | 4 ++++ src/native/CMakeLists.txt | 1 + src/native/monodroid/embedded-assemblies-zip.cc | 6 +++--- src/native/xamarin-app-stub/application_dso_stub.cc | 5 +++++ src/native/xamarin-app-stub/xamarin-app.hh | 1 + 12 files changed, 60 insertions(+), 9 deletions(-) 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 5d4506284cd..ca499db4505 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 @@ -45,6 +45,15 @@ false + + + <_AndroidZipAlignment Condition=" '$(_AndroidZipAlignment)' == '' ">4 diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs index 262c7af4db1..c0508250737 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs @@ -7,6 +7,10 @@ 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; + public override string TaskPrefix => "AZA"; [Required] @@ -15,7 +19,7 @@ public class AndroidZipAlign : AndroidRunToolTask [Required] public ITaskItem DestinationDirectory { get; set; } - int alignment = 4; + int alignment = DefaultZipAlignment; public int Alignment { get {return alignment;} set {alignment = value;} @@ -53,4 +57,3 @@ protected override void LogEventsFromTextOutput (string singleLine, MessageImpor } } } - diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index 29a2118a86f..bee66ba6ad3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -80,6 +80,7 @@ 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; } @@ -334,6 +335,12 @@ void AddEnvironment () bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey), RegisteredTaskObjectLifetime.Build); + uint zipAlignmentMask = ZipAlignmentPages switch { + 4 => 3, + 16 => 15, + _ => throw new InvalidOperationException ($"Internal error: unsupported zip page alignment value {ZipAlignmentPages}") + }; + var appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, @@ -357,6 +364,7 @@ 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/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index cc021dbae71..16b966dca48 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -44,6 +44,8 @@ sealed class InputFiles [Required] public string AndroidBinUtilsDirectory { get; set; } + public int ZipAlignmentPages { get; set; } = AndroidZipAlign.DefaultZipAlignment; + public override System.Threading.Tasks.Task RunTaskAsync () { return this.WhenAll (GetLinkerConfigs (), RunLinker); @@ -129,7 +131,6 @@ IEnumerable GetLinkerConfigs () "-soname libxamarin-app.so " + "-z relro " + "-z noexecstack " + - "-z max-page-size=4096 " + "--enable-new-dtags " + "--build-id " + "--warn-shared-textrel " + @@ -186,6 +187,14 @@ IEnumerable GetLinkerConfigs () } } + uint maxPageSize = ZipAlignmentPages switch { + 4 => 4096, + 16 => 16384, + _ => throw new InvalidOperationException ($"Internal error: unsupported zip page alignment value {ZipAlignmentPages}") + }; + targetLinkerArgs.Add ("-z"); + targetLinkerArgs.Add ($"max-page-size={maxPageSize}"); + string targetArgs = String.Join (" ", targetLinkerArgs); yield return new Config { LinkerPath = ld, 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 9cd8d26ffc0..9f93faf206e 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,11 +63,12 @@ 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 = 26; + const uint ApplicationConfigFieldCount = 27; const string ApplicationConfigSymbolName = "application_config"; const string AppEnvironmentVariablesSymbolName = "app_environment_variables"; @@ -326,12 +327,17 @@ 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: // mono_components_mask: uint32_t / .word | .long + 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 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 25: // android_package_name: string / [pointer type] + case 26: // 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 96fa8af6f5a..2b24f2d0347 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs @@ -55,6 +55,9 @@ 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 53140f8cf70..642f5183573 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -183,6 +183,7 @@ 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; } @@ -244,6 +245,7 @@ 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/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 915441086a8..a919417c349 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1724,6 +1724,7 @@ because xbuild doesn't support framework reference assemblies. UseAssemblyStore="$(AndroidUseAssemblyStore)" EnableMarshalMethods="$(_AndroidUseMarshalMethods)" CustomBundleConfigFile="$(AndroidBundleConfigurationFile)" + ZipAlignmentPages="$(_AndroidZipAlignment)" > @@ -2010,6 +2011,7 @@ because xbuild doesn't support framework reference assemblies. ApplicationSharedLibraries="@(_ApplicationSharedLibrary)" DebugBuild="$(AndroidIncludeDebugSymbols)" AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)" + ZipAlignmentPages="$(_AndroidZipAlignment)" /> @@ -2354,6 +2356,7 @@ because xbuild doesn't support framework reference assemblies. Date: Thu, 20 Jun 2024 18:19:35 +0200 Subject: [PATCH 2/2] Dry the code a bit --- .../Tasks/GeneratePackageManagerJava.cs | 7 +------ .../Tasks/LinkApplicationSharedLibraries.cs | 6 +----- .../Utilities/MonoAndroidHelper.cs | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index bee66ba6ad3..6340419ca9d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -335,12 +335,7 @@ void AddEnvironment () bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath); var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey), RegisteredTaskObjectLifetime.Build); - uint zipAlignmentMask = ZipAlignmentPages switch { - 4 => 3, - 16 => 15, - _ => throw new InvalidOperationException ($"Internal error: unsupported zip page alignment value {ZipAlignmentPages}") - }; - + uint zipAlignmentMask = MonoAndroidHelper.ZipAlignmentToMask (ZipAlignmentPages); var appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) { UsesMonoAOT = usesMonoAOT, UsesMonoLLVM = EnableLLVM, diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs index 16b966dca48..617ade907a4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkApplicationSharedLibraries.cs @@ -187,11 +187,7 @@ IEnumerable GetLinkerConfigs () } } - uint maxPageSize = ZipAlignmentPages switch { - 4 => 4096, - 16 => 16384, - _ => throw new InvalidOperationException ($"Internal error: unsupported zip page alignment value {ZipAlignmentPages}") - }; + uint maxPageSize = MonoAndroidHelper.ZipAlignmentToPageSize (ZipAlignmentPages); targetLinkerArgs.Add ("-z"); targetLinkerArgs.Add ($"max-page-size={maxPageSize}"); diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index bfd3c67eb48..54c1303c1f8 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -712,5 +712,22 @@ 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) + { + const uint pageSize4k = 4096; + const uint pageMask4k = 3; + const uint pageSize16k = 16384; + const uint pageMask16k = 15; + + return alignment switch { + 4 => needMask ? pageMask4k : pageSize4k, + 16 => needMask ? pageMask16k : pageSize16k, + _ => throw new InvalidOperationException ($"Internal error: unsupported zip page alignment value {alignment}") + }; + } } }