From a316f01ffb0866b1409a7dc6d07a687196f2a325 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 25 Aug 2025 19:42:33 +0200 Subject: [PATCH 1/4] When preloading shared JNI libraries, update alias entries too --- ...pplicationConfigNativeAssemblyGenerator.cs | 48 ++++++++++++++++--- src/native/mono/monodroid/monodroid-glue.cc | 48 +++++++++++++++++-- src/native/mono/runtime-base/monodroid-dl.hh | 5 +- .../xamarin-app-stub/application_dso_stub.cc | 1 + .../mono/xamarin-app-stub/xamarin-app.hh | 1 + 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 21c3651516d..c4bd3dd7d41 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -161,7 +161,9 @@ sealed class DsoCacheState { public List> DsoCache = []; public List JniPreloadDSOs = []; + public List JniPreloadNames = []; public List> AotDsoCache = []; + public uint NameMutationsCount = 1; } // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh @@ -274,10 +276,14 @@ protected override void Construct (LlvmIrModule module) }; module.Add (dso_cache); + module.AddGlobalVariable ("dso_jni_preloads_idx_stride", dsoState.NameMutationsCount); + // This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) { Comment = " Indices into dso_cache[] of DSO libraries to preload because of JNI use", ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count, + GetArrayItemCommentCallback = GetPreloadIndicesLibraryName, + GetArrayItemCommentCallbackCallerState = dsoState, BeforeWriteCallback = PopulatePreloadIndices, BeforeWriteCallbackCallerState = dsoState, }; @@ -346,6 +352,21 @@ void AddAssemblyStores (LlvmIrModule module) module.Add (assembly_store); } + string? GetPreloadIndicesLibraryName (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) + { + // Instead of throwing for such a triviality like a comment, we will return error messages as comments instead + var dsoState = callerState as DsoCacheState; + if (dsoState == null) { + return " Internal error: DSO state not present."; + } + + if (index >= (ulong)dsoState.JniPreloadNames.Count) { + return $" Invalid index {index}"; + } + + return $" {dsoState.JniPreloadNames[(int)index]}"; + } + void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) { var indices = variable.Value as List; @@ -358,6 +379,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, throw new InvalidOperationException ($"Internal error: DSO state not present."); } + var dsoNames = new List (); + + // Indices array MUST NOT be sorted, since it groups alias entries together with the main entry foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) { int dsoIdx = dsoState.DsoCache.FindIndex (entry => { if (entry.Instance == null) { @@ -372,8 +396,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, } indices.Add ((uint)dsoIdx); + dsoNames.Add (preload.HashedName ?? String.Empty); } - indices.Sort (); + dsoState.JniPreloadNames = dsoNames; } void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) @@ -427,15 +452,20 @@ DsoCacheState InitDSOCache () var jniPreloads = new List (); var aotDsoCache = new List> (); var nameMutations = new List (); + int nameMutationsCount = -1; for (int i = 0; i < dsos.Count; i++) { string name = dsos[i].name; bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec); bool ignore = dsos[i].ignore; + bool ignore_for_preload = !ApplicationConfigNativeAssemblyGeneratorCLR.DsoCacheJniPreloadIgnore.Contains (name); nameMutations.Clear(); AddNameMutations (name); + if (nameMutationsCount == -1) { + nameMutationsCount = nameMutations.Count; + } // All mutations point to the actual library name, but have hash of the mutated one foreach (string entryName in nameMutations) { @@ -447,16 +477,19 @@ DsoCacheState InitDSOCache () name = name, }; - if (entry.is_jni_library && entry.HashedName == name && !ApplicationConfigNativeAssemblyGeneratorCLR.DsoCacheJniPreloadIgnore.Contains (name)) { - jniPreloads.Add (entry); - } - var item = new StructureInstance (dsoCacheEntryStructureInfo, entry); if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) { aotDsoCache.Add (item); - } else { - dsoCache.Add (item); + continue; } + + // We must add all aliases to the preloads indices array so that all of them have their handle + // set when the library is preloaded. + if (entry.is_jni_library && ignore_for_preload) { + jniPreloads.Add (entry); + } + + dsoCache.Add (item); } } @@ -464,6 +497,7 @@ DsoCacheState InitDSOCache () DsoCache = dsoCache, AotDsoCache = aotDsoCache, JniPreloadDSOs = jniPreloads, + NameMutationsCount = (uint)(nameMutationsCount <= 0 ? 1 : nameMutationsCount), }; void AddNameMutations (string name) diff --git a/src/native/mono/monodroid/monodroid-glue.cc b/src/native/mono/monodroid/monodroid-glue.cc index 08cf4f78320..dd943799ab6 100644 --- a/src/native/mono/monodroid/monodroid-glue.cc +++ b/src/native/mono/monodroid/monodroid-glue.cc @@ -1225,12 +1225,50 @@ MonodroidRuntime::create_and_initialize_domain (JNIEnv* env, jclass runtimeClass { MonoDomain* domain = create_domain (env, runtimeApks, is_root_domain, have_split_apks); - for (size_t i = 0; i < dso_jni_preloads_idx_count; i++) { - DSOCacheEntry &entry = dso_cache[dso_jni_preloads_idx[i]]; + if (application_config.number_of_shared_libraries > 0) [[likely]] { + log_debug (LOG_ASSEMBLY, "DSO jni preloads index stride == {}", dso_jni_preloads_idx_stride); + + if ((dso_jni_preloads_idx_count % dso_jni_preloads_idx_stride) != 0) [[unlikely]] { + Helpers::abort_application ( + LOG_ASSEMBLY, + std::format ( + "DSO preload index is invalid, size ({}) is not a multiple of {}"sv, + dso_jni_preloads_idx_count, + dso_jni_preloads_idx_stride + ) + ); + } + + for (size_t i = 0; i < dso_jni_preloads_idx_count; i += dso_jni_preloads_idx_stride) { + const size_t entry_index = dso_jni_preloads_idx[i]; + DSOCacheEntry &entry = dso_cache[entry_index]; + + log_debug ( + LOG_ASSEMBLY, + "Preloading JNI shared library: {} (entry's index: {}; real name hash: {:x}; name hash: {:x})", + optional_string (entry.name), + entry_index, + entry.real_name_hash, + entry.hash + ); + + char *err = nullptr; + void *handle = MonodroidDl::monodroid_dlopen (&entry, entry.hash, entry.name, RTLD_NOW, &err); - log_debug (LOG_ASSEMBLY, "Preloading JNI shared library: {}", optional_string (entry.name)); - char *err = nullptr; - MonodroidDl::monodroid_dlopen (&entry, entry.hash, entry.name, RTLD_NOW, &err); + // Set handle in all the alias entries + for (size_t j = 1; j < dso_jni_preloads_idx_stride; j++) { + const size_t entry_alias_index = dso_jni_preloads_idx[i + j]; + DSOCacheEntry &entry_alias = dso_cache[entry_alias_index]; + + log_debug ( + LOG_ASSEMBLY, + "Putting JNI library handle in alias entry at index {}: {}", + entry_alias_index, + entry_alias.name + ); + entry_alias.handle = handle; + } + } } // Asserting this on desktop apparently breaks a Designer test diff --git a/src/native/mono/runtime-base/monodroid-dl.hh b/src/native/mono/runtime-base/monodroid-dl.hh index c730723297a..3e2cfe62402 100644 --- a/src/native/mono/runtime-base/monodroid-dl.hh +++ b/src/native/mono/runtime-base/monodroid-dl.hh @@ -60,6 +60,7 @@ namespace xamarin::android::internal ssize_t idx = Search::binary_search (hash, arr, arr_size); if (idx >= 0) { + log_debug (LOG_ASSEMBLY, "Found hash 0x{:x} entry at index {} of the cache", hash, idx); return &arr[idx]; } @@ -153,6 +154,8 @@ namespace xamarin::android::internal } else if (dso->handle != nullptr) { return monodroid_dlopen_log_and_return (dso->handle, err, dso->name, false /* name_needs_free */); } + log_debug (LOG_ASSEMBLY, "monodroid_dlopen: cache entry's real name hash == 0x{:x}; name hash == 0x{:x}", + dso->real_name_hash, dso->hash); if (dso->ignore) { log_info (LOG_ASSEMBLY, "Request to load '{}' ignored, it is known not to exist", dso->name); @@ -197,7 +200,7 @@ namespace xamarin::android::internal } hash_t name_hash = xxhash::hash (name, strlen (name)); - log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); + log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is 0x{:x}", name, name_hash); DSOCacheEntry *dso = nullptr; if (prefer_aot_cache) { diff --git a/src/native/mono/xamarin-app-stub/application_dso_stub.cc b/src/native/mono/xamarin-app-stub/application_dso_stub.cc index 3d937692528..8d01b2ff57c 100644 --- a/src/native/mono/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/mono/xamarin-app-stub/application_dso_stub.cc @@ -145,6 +145,7 @@ DSOCacheEntry dso_cache[] = { }, }; +const uint dso_jni_preloads_idx_stride = 1; const uint dso_jni_preloads_idx_count = 1; const uint dso_jni_preloads_idx[1] = { 0 diff --git a/src/native/mono/xamarin-app-stub/xamarin-app.hh b/src/native/mono/xamarin-app-stub/xamarin-app.hh index f42cb460b32..c0e3d3012d6 100644 --- a/src/native/mono/xamarin-app-stub/xamarin-app.hh +++ b/src/native/mono/xamarin-app-stub/xamarin-app.hh @@ -341,6 +341,7 @@ MONO_API MONO_API_EXPORT AssemblyStoreSingleAssemblyRuntimeData assembly_store_b MONO_API MONO_API_EXPORT AssemblyStoreRuntimeData assembly_store; MONO_API MONO_API_EXPORT DSOCacheEntry dso_cache[]; +MONO_API MONO_API_EXPORT const uint dso_jni_preloads_idx_stride; MONO_API MONO_API_EXPORT const uint dso_jni_preloads_idx_count; MONO_API MONO_API_EXPORT const uint dso_jni_preloads_idx[]; MONO_API MONO_API_EXPORT DSOCacheEntry aot_dso_cache[]; From e522144041fbc53526ac16be5a712489cf49baa8 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 25 Aug 2025 13:40:03 -0500 Subject: [PATCH 2/4] [tests] reproduce issue found with Debugger in VS (#10443) Context: https://github.com/dotnet/android/commit/ee43633113e7704b57d3853484a20c6aec1e177f Hitting F5 in Visual Studio for a `dotnet new maui` app produces a loop such as: 08-25 09:40:36.759 32259 32293 D monodroid-assembly: monodroid_dlopen: hash match found, DSO name is 'libSystem.Security.Cryptography.Native.Android.so' 08-25 09:40:36.759 32259 32293 D monodroid-assembly: Trying to load loading shared JNI library /data/user/0/com.companyname.testgrendel/files/.__override__/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so with System.loadLibrary 08-25 09:40:36.759 32259 32293 D monodroid-assembly: Running DSO loader on thread 32293, dispatching to main thread This does not happen for: * `dotnet new android` project * At the command-line * Using Ctrl+F5 in Visual Studio I was able to also reproduce this issue in a `static MainActivity` constructor that uses `HttpClient`, adding a test doing the same thing. --- .../Resources/Forms/MainActivity.cs | 1 + .../Tests/DebuggingTest.cs | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Forms/MainActivity.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Forms/MainActivity.cs index 2cb1ed6142c..67f0bc43bf1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Forms/MainActivity.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Forms/MainActivity.cs @@ -23,5 +23,6 @@ protected override void OnCreate (Bundle savedInstanceState) //${AFTER_FORMS_INIT} LoadApplication (new App ()); } + //${AFTER_ONCREATE} } } diff --git a/tests/MSBuildDeviceIntegration/Tests/DebuggingTest.cs b/tests/MSBuildDeviceIntegration/Tests/DebuggingTest.cs index 05cc8c9d5f6..00f5cb6fa9f 100755 --- a/tests/MSBuildDeviceIntegration/Tests/DebuggingTest.cs +++ b/tests/MSBuildDeviceIntegration/Tests/DebuggingTest.cs @@ -389,7 +389,27 @@ public Foo () } app.SetProperty ("AndroidPackageFormat", packageFormat); - app.MainPage = app.MainPage.Replace ("InitializeComponent ();", "InitializeComponent (); new Foo ();"); + app.MainPage = app.MainPage + .Replace ("InitializeComponent ();", "InitializeComponent (); new Foo ();") + // NOTE: can trigger deadlock/loop on startup: + // 08-25 09:40:36.759 32259 32293 D monodroid-assembly: monodroid_dlopen: hash match found, DSO name is 'libSystem.Security.Cryptography.Native.Android.so' + // 08-25 09:40:36.759 32259 32293 D monodroid-assembly: Trying to load loading shared JNI library /data/user/0/com.companyname.testgrendel/files/.__override__/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so with System.loadLibrary + // 08-25 09:40:36.759 32259 32293 D monodroid-assembly: Running DSO loader on thread 32293, dispatching to main thread + .Replace ("//${AFTER_MAINACTIVITY}", """ + static MainActivity() + { + try + { + var text = new HttpClient().GetStringAsync("https://www.google.com").GetAwaiter().GetResult(); + Console.WriteLine("Web request:" + text); + } + catch (Exception ex) + { + // Doesn't actually matter if succeeds + Console.WriteLine("Web request failed:" + ex); + } + } + """); app.AddReference (lib); var abis = new [] { DeviceAbi }; app.SetRuntimeIdentifiers (abis); From 281c05757613733ea0b1b6595dd2763d5d1e41de Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 26 Aug 2025 11:04:05 +0200 Subject: [PATCH 3/4] Port the fix to CoreCLR --- src/native/clr/host/host.cc | 61 ++++++++++-- src/native/clr/include/host/host.hh | 2 + src/native/clr/include/xamarin-app.hh | 1 + .../xamarin-app-stub/application_dso_stub.cc | 1 + .../mono/monodroid/monodroid-glue-internal.hh | 1 + src/native/mono/monodroid/monodroid-glue.cc | 96 ++++++++++--------- 6 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index 5048d8eb32a..2146281f28f 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -334,6 +334,60 @@ auto Host::create_delegate ( return delegate; } +[[gnu::flatten, gnu::always_inline]] +void Host::preload_jni_libraries () noexcept +{ + // NOTE: when fixing a bug here, fix also the MonoVM code in src/native/mono/monodroid-glue.cc@preload_jni_libraries + if (application_config.number_of_shared_libraries == 0) [[unlikely]] { + return; + } + + log_debug (LOG_ASSEMBLY, "DSO jni preloads index stride == {}", dso_jni_preloads_idx_stride); + + if ((dso_jni_preloads_idx_count % dso_jni_preloads_idx_stride) != 0) [[unlikely]] { + Helpers::abort_application ( + LOG_ASSEMBLY, + std::format ( + "DSO preload index is invalid, size ({}) is not a multiple of {}"sv, + dso_jni_preloads_idx_count, + dso_jni_preloads_idx_stride + ) + ); + } + + for (size_t i = 0; i < dso_jni_preloads_idx_count; i += dso_jni_preloads_idx_stride) { + const size_t entry_index = dso_jni_preloads_idx[i]; + DSOCacheEntry &entry = dso_cache[entry_index]; + const std::string_view dso_name = MonodroidDl::get_dso_name (&entry); + + log_debug ( + LOG_ASSEMBLY, + "Preloading JNI shared library: {} (entry's index: {}; real name hash: {:x}; name hash: {:x})", + dso_name, + entry_index, + entry.real_name_hash, + entry.hash + ); + + void *handle = MonodroidDl::monodroid_dlopen (&entry, dso_name, RTLD_NOW); + + // Set handle in all the alias entries + for (size_t j = 1; j < dso_jni_preloads_idx_stride; j++) { + const size_t entry_alias_index = dso_jni_preloads_idx[i + j]; + DSOCacheEntry &entry_alias = dso_cache[entry_alias_index]; + const std::string_view entry_alias_name = MonodroidDl::get_dso_name (&entry); + + log_debug ( + LOG_ASSEMBLY, + "Putting JNI library handle in alias entry at index {}: {}", + entry_alias_index, + entry_alias_name + ); + entry_alias.handle = handle; + } + } +} + void Host::Java_mono_android_Runtime_initInternal ( JNIEnv *env, jclass runtimeClass, jstring lang, jobjectArray runtimeApksJava, @@ -433,12 +487,7 @@ void Host::Java_mono_android_Runtime_initInternal ( gettid () ); - for (size_t i = 0; i < dso_jni_preloads_idx_count; i++) { - DSOCacheEntry &entry = dso_cache[dso_jni_preloads_idx[i]]; - const std::string_view dso_name = MonodroidDl::get_dso_name (&entry); - log_debug (LOG_ASSEMBLY, "Preloading JNI shared library: {}", dso_name); - MonodroidDl::monodroid_dlopen (&entry, dso_name, RTLD_NOW); - } + preload_jni_libraries (); struct JnienvInitializeArgs init = {}; init.javaVm = jvm; diff --git a/src/native/clr/include/host/host.hh b/src/native/clr/include/host/host.hh index c1ae90788c2..58d1516ad86 100644 --- a/src/native/clr/include/host/host.hh +++ b/src/native/clr/include/host/host.hh @@ -49,6 +49,8 @@ namespace xamarin::android { std::string_view const& assembly_name, std::string_view const& type_name, std::string_view const& method_name) noexcept -> void*; + static void preload_jni_libraries () noexcept; + private: static inline void *clr_host = nullptr; static inline unsigned int domain_id = 0; diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index 76f39ac6c18..7c774584a2e 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -348,6 +348,7 @@ extern "C" { [[gnu::visibility("default")]] extern AssemblyStoreRuntimeData assembly_store; [[gnu::visibility("default")]] extern DSOCacheEntry dso_cache[]; + [[gnu::visibility("default")]] extern const uint dso_jni_preloads_idx_stride; [[gnu::visibility("default")]] extern const uint dso_jni_preloads_idx_count; [[gnu::visibility("default")]] extern const uint dso_jni_preloads_idx[]; [[gnu::visibility("default")]] extern DSOCacheEntry aot_dso_cache[]; diff --git a/src/native/clr/xamarin-app-stub/application_dso_stub.cc b/src/native/clr/xamarin-app-stub/application_dso_stub.cc index 97afad72137..cbc59d8a99f 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -127,6 +127,7 @@ DSOCacheEntry dso_cache[] = { }, }; +const uint dso_jni_preloads_idx_stride = 1; const uint dso_jni_preloads_idx_count = 1; const uint dso_jni_preloads_idx[1] = { 0 diff --git a/src/native/mono/monodroid/monodroid-glue-internal.hh b/src/native/mono/monodroid/monodroid-glue-internal.hh index ed6a5fc032b..e1d1f446d07 100644 --- a/src/native/mono/monodroid/monodroid-glue-internal.hh +++ b/src/native/mono/monodroid/monodroid-glue-internal.hh @@ -162,6 +162,7 @@ namespace xamarin::android::internal static void monodroid_unhandled_exception (MonoObject *java_exception); static MonoClass* get_android_runtime_class () noexcept; + static void preload_jni_libraries () noexcept; static MonoDomain* create_domain (JNIEnv *env, jstring_array_wrapper &runtimeApks, bool is_root_domain, bool have_split_apks) noexcept; static MonoDomain* create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, jstring_array_wrapper &assemblies, jobjectArray assembliesBytes, jstring_array_wrapper &assembliesPaths, diff --git a/src/native/mono/monodroid/monodroid-glue.cc b/src/native/mono/monodroid/monodroid-glue.cc index dd943799ab6..194d5612423 100644 --- a/src/native/mono/monodroid/monodroid-glue.cc +++ b/src/native/mono/monodroid/monodroid-glue.cc @@ -1217,59 +1217,69 @@ monodroid_Mono_UnhandledException_internal ([[maybe_unused]] MonoException *ex) // Do nothing with it here, we let the exception naturally propagate on the managed side } -MonoDomain* -MonodroidRuntime::create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, - jstring_array_wrapper &assemblies, [[maybe_unused]] jobjectArray assembliesBytes, - [[maybe_unused]] jstring_array_wrapper &assembliesPaths, jobject loader, bool is_root_domain, - bool force_preload_assemblies, bool have_split_apks) noexcept +[[gnu::flatten, gnu::always_inline]] +void +MonodroidRuntime::preload_jni_libraries () noexcept { - MonoDomain* domain = create_domain (env, runtimeApks, is_root_domain, have_split_apks); + // NOTE: when fixing a bug here, fix also the CoreCLR code in src/native/clr/host/host.cc@preload_jni_libraries + if (application_config.number_of_shared_libraries == 0) [[unlikely]] { + return; + } - if (application_config.number_of_shared_libraries > 0) [[likely]] { - log_debug (LOG_ASSEMBLY, "DSO jni preloads index stride == {}", dso_jni_preloads_idx_stride); + log_debug (LOG_ASSEMBLY, "DSO jni preloads index stride == {}", dso_jni_preloads_idx_stride); - if ((dso_jni_preloads_idx_count % dso_jni_preloads_idx_stride) != 0) [[unlikely]] { - Helpers::abort_application ( - LOG_ASSEMBLY, - std::format ( - "DSO preload index is invalid, size ({}) is not a multiple of {}"sv, - dso_jni_preloads_idx_count, - dso_jni_preloads_idx_stride - ) - ); - } + if ((dso_jni_preloads_idx_count % dso_jni_preloads_idx_stride) != 0) [[unlikely]] { + Helpers::abort_application ( + LOG_ASSEMBLY, + std::format ( + "DSO preload index is invalid, size ({}) is not a multiple of {}"sv, + dso_jni_preloads_idx_count, + dso_jni_preloads_idx_stride + ) + ); + } - for (size_t i = 0; i < dso_jni_preloads_idx_count; i += dso_jni_preloads_idx_stride) { - const size_t entry_index = dso_jni_preloads_idx[i]; - DSOCacheEntry &entry = dso_cache[entry_index]; + for (size_t i = 0; i < dso_jni_preloads_idx_count; i += dso_jni_preloads_idx_stride) { + const size_t entry_index = dso_jni_preloads_idx[i]; + DSOCacheEntry &entry = dso_cache[entry_index]; - log_debug ( - LOG_ASSEMBLY, - "Preloading JNI shared library: {} (entry's index: {}; real name hash: {:x}; name hash: {:x})", - optional_string (entry.name), - entry_index, - entry.real_name_hash, - entry.hash - ); + log_debug ( + LOG_ASSEMBLY, + "Preloading JNI shared library: {} (entry's index: {}; real name hash: {:x}; name hash: {:x})", + optional_string (entry.name), + entry_index, + entry.real_name_hash, + entry.hash + ); - char *err = nullptr; - void *handle = MonodroidDl::monodroid_dlopen (&entry, entry.hash, entry.name, RTLD_NOW, &err); + char *err = nullptr; + void *handle = MonodroidDl::monodroid_dlopen (&entry, entry.hash, entry.name, RTLD_NOW, &err); - // Set handle in all the alias entries - for (size_t j = 1; j < dso_jni_preloads_idx_stride; j++) { - const size_t entry_alias_index = dso_jni_preloads_idx[i + j]; - DSOCacheEntry &entry_alias = dso_cache[entry_alias_index]; + // Set handle in all the alias entries + for (size_t j = 1; j < dso_jni_preloads_idx_stride; j++) { + const size_t entry_alias_index = dso_jni_preloads_idx[i + j]; + DSOCacheEntry &entry_alias = dso_cache[entry_alias_index]; - log_debug ( - LOG_ASSEMBLY, - "Putting JNI library handle in alias entry at index {}: {}", - entry_alias_index, - entry_alias.name - ); - entry_alias.handle = handle; - } + log_debug ( + LOG_ASSEMBLY, + "Putting JNI library handle in alias entry at index {}: {}", + entry_alias_index, + entry_alias.name + ); + entry_alias.handle = handle; } } +} + +MonoDomain* +MonodroidRuntime::create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks, + jstring_array_wrapper &assemblies, [[maybe_unused]] jobjectArray assembliesBytes, + [[maybe_unused]] jstring_array_wrapper &assembliesPaths, jobject loader, bool is_root_domain, + bool force_preload_assemblies, bool have_split_apks) noexcept +{ + MonoDomain* domain = create_domain (env, runtimeApks, is_root_domain, have_split_apks); + + preload_jni_libraries (); // Asserting this on desktop apparently breaks a Designer test abort_unless (domain != nullptr, "Failed to create AppDomain"); From 2b1fa40a43248b93ca01dba1341adb276f0d9e0e Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 26 Aug 2025 17:33:36 +0200 Subject: [PATCH 4/4] Finish the CoreCLR port --- ...icationConfigNativeAssemblyGeneratorCLR.cs | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index 5c9828b3300..a030b843761 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -243,8 +243,10 @@ sealed class DsoCacheState { public List> DsoCache = []; public List JniPreloadDSOs = []; + public List JniPreloadNames = []; public List> AotDsoCache = []; public LlvmIrStringBlob NamesBlob = null!; + public uint NameMutationsCount = 1; } // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh @@ -394,10 +396,14 @@ protected override void Construct (LlvmIrModule module) }; module.Add (dso_cache); + module.AddGlobalVariable ("dso_jni_preloads_idx_stride", dsoState.NameMutationsCount); + // This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) { Comment = " Indices into dso_cache[] of DSO libraries to preload because of JNI use", ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count, + GetArrayItemCommentCallback = GetPreloadIndicesLibraryName, + GetArrayItemCommentCallbackCallerState = dsoState, BeforeWriteCallback = PopulatePreloadIndices, BeforeWriteCallbackCallerState = dsoState, }; @@ -566,6 +572,21 @@ void AddAssemblyStores (LlvmIrModule module) module.Add (assembly_store); } + string? GetPreloadIndicesLibraryName (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState) + { + // Instead of throwing for such a triviality like a comment, we will return error messages as comments instead + var dsoState = callerState as DsoCacheState; + if (dsoState == null) { + return " Internal error: DSO state not present."; + } + + if (index >= (ulong)dsoState.JniPreloadNames.Count) { + return $" Invalid index {index}"; + } + + return $" {dsoState.JniPreloadNames[(int)index]}"; + } + void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) { var indices = variable.Value as List; @@ -578,6 +599,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, throw new InvalidOperationException ($"Internal error: DSO state not present."); } + var dsoNames = new List (); + + // Indices array MUST NOT be sorted, since it groups alias entries together with the main entry foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) { int dsoIdx = dsoState.DsoCache.FindIndex (entry => { if (entry.Instance == null) { @@ -592,8 +616,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, } indices.Add ((uint)dsoIdx); + dsoNames.Add (preload.HashedName ?? String.Empty); } - indices.Sort (); + dsoState.JniPreloadNames = dsoNames; } void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) @@ -648,6 +673,7 @@ DsoCacheState InitDSOCache () var aotDsoCache = new List> (); var nameMutations = new List (); var dsoNamesBlob = new LlvmIrStringBlob (); + int nameMutationsCount = -1; for (int i = 0; i < dsos.Count; i++) { string name = dsos[i].name; @@ -655,9 +681,14 @@ DsoCacheState InitDSOCache () bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec); bool ignore = dsos[i].ignore; + bool ignore_for_preload = !DsoCacheJniPreloadIgnore.Contains (name); nameMutations.Clear(); AddNameMutations (name); + if (nameMutationsCount == -1) { + nameMutationsCount = nameMutations.Count; + } + // All mutations point to the actual library name, but have hash of the mutated one foreach (string entryName in nameMutations) { var entry = new DSOCacheEntry { @@ -670,16 +701,19 @@ DsoCacheState InitDSOCache () name_index = (uint)nameOffset, }; - if (entry.is_jni_library && entry.HashedName == name && !DsoCacheJniPreloadIgnore.Contains (name)) { - jniPreloads.Add (entry); - } - var item = new StructureInstance (dsoCacheEntryStructureInfo, entry); if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) { aotDsoCache.Add (item); - } else { - dsoCache.Add (item); + continue; } + + // We must add all aliases to the preloads indices array so that all of them have their handle + // set when the library is preloaded. + if (entry.is_jni_library && ignore_for_preload) { + jniPreloads.Add (entry); + } + + dsoCache.Add (item); } } @@ -688,6 +722,7 @@ DsoCacheState InitDSOCache () JniPreloadDSOs = jniPreloads, AotDsoCache = aotDsoCache, NamesBlob = dsoNamesBlob, + NameMutationsCount = (uint)(nameMutationsCount <= 0 ? 1 : nameMutationsCount), }; void AddNameMutations (string name)