diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs index 6bcc7108411..21c3651516d 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs @@ -61,6 +61,7 @@ sealed class DSOCacheEntry [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] public ulong real_name_hash; public bool ignore; + public bool is_jni_library; [NativeAssembler (UsesDataProvider = true)] public string? name; @@ -156,14 +157,19 @@ sealed class XamarinAndroidBundledAssembly } #pragma warning restore CS0649 + sealed class DsoCacheState + { + public List> DsoCache = []; + public List JniPreloadDSOs = []; + public List> AotDsoCache = []; + } + // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version SortedDictionary ? environmentVariables; SortedDictionary ? systemProperties; StructureInstance? application_config; - List>? dsoCache; - List>? aotDsoCache; List>? xamarinAndroidBundledAssemblies; StructureInfo? applicationConfigStructureInfo; @@ -229,7 +235,7 @@ protected override void Construct (LlvmIrModule module) }; module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs"); - (dsoCache, aotDsoCache) = InitDSOCache (); + DsoCacheState dsoState = InitDSOCache (); var app_cfg = new ApplicationConfig { uses_mono_llvm = UsesMonoLLVM, uses_mono_aot = UsesMonoAOT, @@ -249,8 +255,8 @@ protected override void Construct (LlvmIrModule module) number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk, number_of_shared_libraries = (uint)NativeLibraries.Count, bundled_assembly_name_width = (uint)BundledAssemblyNameWidth, - number_of_dso_cache_entries = (uint)dsoCache.Count, - number_of_aot_cache_entries = (uint)aotDsoCache.Count, + number_of_dso_cache_entries = (uint)dsoState.DsoCache.Count, + number_of_aot_cache_entries = (uint)dsoState.AotDsoCache.Count, android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken, jnienv_initialize_method_token = (uint)JNIEnvInitializeToken, jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken, @@ -262,13 +268,23 @@ protected override void Construct (LlvmIrModule module) application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); module.AddGlobalVariable ("application_config", application_config); - var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) { + var dso_cache = new LlvmIrGlobalVariable (dsoState.DsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) { Comment = " DSO cache entries", BeforeWriteCallback = HashAndSortDSOCache, }; module.Add (dso_cache); - var aot_dso_cache = new LlvmIrGlobalVariable (aotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) { + // 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, + BeforeWriteCallback = PopulatePreloadIndices, + BeforeWriteCallbackCallerState = dsoState, + }; + module.AddGlobalVariable ("dso_jni_preloads_idx_count", dso_jni_preloads_idx.ArrayItemCount); + module.Add (dso_jni_preloads_idx); + + var aot_dso_cache = new LlvmIrGlobalVariable (dsoState.AotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) { Comment = " AOT DSO cache entries", BeforeWriteCallback = HashAndSortDSOCache, }; @@ -330,6 +346,36 @@ void AddAssemblyStores (LlvmIrModule module) module.Add (assembly_store); } + void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) + { + var indices = variable.Value as List; + if (indices == null) { + throw new InvalidOperationException ($"Internal error: DSO preload indices list instance not present."); + } + + var dsoState = state as DsoCacheState; + if (dsoState == null) { + throw new InvalidOperationException ($"Internal error: DSO state not present."); + } + + foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) { + int dsoIdx = dsoState.DsoCache.FindIndex (entry => { + if (entry.Instance == null) { + return false; + } + + return entry.Instance.hash == preload.hash && entry.Instance.real_name_hash == preload.real_name_hash; + }); + + if (dsoIdx == -1) { + throw new InvalidOperationException ($"Internal error: DSO entry in JNI preload list not found in the DSO cache list."); + } + + indices.Add ((uint)dsoIdx); + } + indices.Sort (); + } + void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) { var cache = variable.Value as List>; @@ -358,9 +404,9 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob }); } - (List> dsoCache, List> aotDsoCache) InitDSOCache () + DsoCacheState InitDSOCache () { - var dsos = new List<(string name, string nameLabel, bool ignore)> (); + var dsos = new List<(string name, string nameLabel, bool ignore, ITaskItem item)> (); var nameCache = new HashSet (StringComparer.OrdinalIgnoreCase); foreach (ITaskItem item in NativeLibraries) { @@ -374,26 +420,37 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob continue; } - dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec))); + dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec), item)); } var dsoCache = new List> (); + var jniPreloads = new List (); var aotDsoCache = new List> (); var nameMutations = new List (); 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; + nameMutations.Clear(); AddNameMutations (name); + // All mutations point to the actual library name, but have hash of the mutated one foreach (string entryName in nameMutations) { var entry = new DSOCacheEntry { HashedName = entryName, hash = 0, // Hash is arch-specific, we compute it before writing - ignore = dsos[i].ignore, + ignore = ignore, + is_jni_library = isJniLibrary, 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); @@ -403,7 +460,11 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob } } - return (dsoCache, aotDsoCache); + return new DsoCacheState { + DsoCache = dsoCache, + AotDsoCache = aotDsoCache, + JniPreloadDSOs = jniPreloads, + }; void AddNameMutations (string name) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs index 1e11192ef9c..5c9828b3300 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs @@ -59,6 +59,7 @@ sealed class DSOCacheEntry [NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)] public ulong real_name_hash; public bool ignore; + public bool is_jni_library; [NativeAssembler (UsesDataProvider = true)] public uint name_index; @@ -238,15 +239,27 @@ sealed class XamarinAndroidBundledAssembly } #pragma warning restore CS0649 + sealed class DsoCacheState + { + public List> DsoCache = []; + public List JniPreloadDSOs = []; + public List> AotDsoCache = []; + public LlvmIrStringBlob NamesBlob = null!; + } + // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version + // List of library names to ignore when generating the list of JNI-using libraries to preload + internal static readonly HashSet DsoCacheJniPreloadIgnore = new (StringComparer.OrdinalIgnoreCase) { + "libmonodroid.so", + }; + SortedDictionary ? environmentVariables; SortedDictionary ? systemProperties; SortedDictionary ? runtimeProperties; StructureInstance? application_config; - List>? dsoCache; - List>? aotDsoCache; + #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value - assigned conditionally by build process List>? xamarinAndroidBundledAssemblies; #pragma warning restore CS0649 @@ -261,10 +274,10 @@ sealed class XamarinAndroidBundledAssembly StructureInfo? assemblyStoreRuntimeDataStructureInfo; StructureInfo? runtimePropertyStructureInfo; StructureInfo? runtimePropertyIndexEntryStructureInfo; -#pragma warning disable CS0169 // Field is never used - might be used in future versions +#pragma warning disable CS0169 // Field is never used - might be used in future versions StructureInfo? hostConfigurationPropertyStructureInfo; #pragma warning restore CS0169 -#pragma warning disable CS0169 // Field is never used - might be used in future versions +#pragma warning disable CS0169 // Field is never used - might be used in future versions StructureInfo? hostConfigurationPropertiesStructureInfo; #pragma warning restore CS0169 StructureInfo? appEnvironmentVariableStructureInfo; @@ -349,7 +362,7 @@ protected override void Construct (LlvmIrModule module) }; module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs"); - (dsoCache, aotDsoCache, LlvmIrStringBlob dsoNamesBlob) = InitDSOCache (); + DsoCacheState dsoState = InitDSOCache (); var app_cfg = new ApplicationConfigCLR { uses_assembly_preload = UsesAssemblyPreload, jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent, @@ -363,8 +376,8 @@ protected override void Construct (LlvmIrModule module) number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk, number_of_shared_libraries = (uint)NativeLibraries.Count, bundled_assembly_name_width = (uint)BundledAssemblyNameWidth, - number_of_dso_cache_entries = (uint)dsoCache.Count, - number_of_aot_cache_entries = (uint)aotDsoCache.Count, + number_of_dso_cache_entries = (uint)dsoState.DsoCache.Count, + number_of_aot_cache_entries = (uint)dsoState.AotDsoCache.Count, android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken, jnienv_initialize_method_token = (uint)JNIEnvInitializeToken, jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken, @@ -375,18 +388,28 @@ protected override void Construct (LlvmIrModule module) application_config = new StructureInstance (applicationConfigStructureInfo, app_cfg); module.AddGlobalVariable ("application_config", application_config); - var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) { + var dso_cache = new LlvmIrGlobalVariable (dsoState.DsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) { Comment = " DSO cache entries", BeforeWriteCallback = HashAndSortDSOCache, }; module.Add (dso_cache); - var aot_dso_cache = new LlvmIrGlobalVariable (aotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) { + // 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, + BeforeWriteCallback = PopulatePreloadIndices, + BeforeWriteCallbackCallerState = dsoState, + }; + module.AddGlobalVariable ("dso_jni_preloads_idx_count", dso_jni_preloads_idx.ArrayItemCount); + module.Add (dso_jni_preloads_idx); + + var aot_dso_cache = new LlvmIrGlobalVariable (dsoState.AotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) { Comment = " AOT DSO cache entries", BeforeWriteCallback = HashAndSortDSOCache, }; module.Add (aot_dso_cache); - module.AddGlobalVariable ("dso_names_data", dsoNamesBlob, LlvmIrVariableOptions.GlobalConstant); + module.AddGlobalVariable ("dso_names_data", dsoState.NamesBlob, LlvmIrVariableOptions.GlobalConstant); var dso_apk_entries = new LlvmIrGlobalVariable (typeof(List>), "dso_apk_entries") { ArrayItemCount = (ulong)NativeLibraries.Count, @@ -543,6 +566,36 @@ void AddAssemblyStores (LlvmIrModule module) module.Add (assembly_store); } + void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) + { + var indices = variable.Value as List; + if (indices == null) { + throw new InvalidOperationException ($"Internal error: DSO preload indices list instance not present."); + } + + var dsoState = state as DsoCacheState; + if (dsoState == null) { + throw new InvalidOperationException ($"Internal error: DSO state not present."); + } + + foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) { + int dsoIdx = dsoState.DsoCache.FindIndex (entry => { + if (entry.Instance == null) { + return false; + } + + return entry.Instance.hash == preload.hash && entry.Instance.real_name_hash == preload.real_name_hash; + }); + + if (dsoIdx == -1) { + throw new InvalidOperationException ($"Internal error: DSO entry in JNI preload list not found in the DSO cache list."); + } + + indices.Add ((uint)dsoIdx); + } + indices.Sort (); + } + void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state) { var cache = variable.Value as List>; @@ -571,10 +624,9 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob }); } - (List> dsoCache, List> aotDsoCache, LlvmIrStringBlob namesBlob) - InitDSOCache () + DsoCacheState InitDSOCache () { - var dsos = new List<(string name, string nameLabel, bool ignore)> (); + var dsos = new List<(string name, string nameLabel, bool ignore, ITaskItem item)> (); var nameCache = new HashSet (StringComparer.OrdinalIgnoreCase); foreach (ITaskItem item in NativeLibraries) { @@ -588,10 +640,11 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob continue; } - dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec))); + dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec), item)); } var dsoCache = new List> (); + var jniPreloads = new List (); var aotDsoCache = new List> (); var nameMutations = new List (); var dsoNamesBlob = new LlvmIrStringBlob (); @@ -600,6 +653,9 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob string name = dsos[i].name; (int nameOffset, _) = dsoNamesBlob.Add (name); + bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec); + bool ignore = dsos[i].ignore; + nameMutations.Clear(); AddNameMutations (name); // All mutations point to the actual library name, but have hash of the mutated one @@ -609,10 +665,15 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob RealName = name, hash = 0, // Hash is arch-specific, we compute it before writing - ignore = dsos[i].ignore, + ignore = ignore, + is_jni_library = isJniLibrary, 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); @@ -622,7 +683,12 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob } } - return (dsoCache, aotDsoCache, dsoNamesBlob); + return new DsoCacheState { + DsoCache = dsoCache, + JniPreloadDSOs = jniPreloads, + AotDsoCache = aotDsoCache, + NamesBlob = dsoNamesBlob, + }; void AddNameMutations (string name) { diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs index 94a363f1d0d..7dbb95287f0 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs @@ -112,7 +112,8 @@ public static bool IsEmptyAOTLibrary (TaskLoggingHelper log, string path) } try { - return IsEmptyAOTLibrary (log, path, ELFReader.Load (path)); + using IELF elf = ELFReader.Load (path); + return IsEmptyAOTLibrary (log, path, elf); } catch (Exception ex) { log.LogWarning ($"Attempt to check whether '{path}' is a valid ELF file failed with exception, ignoring AOT check for the file."); log.LogWarningFromException (ex, showStackTrace: true); @@ -126,7 +127,7 @@ public static bool ReferencesLibrary (string libraryPath, string referencedLibra return false; } - IELF elf = ELFReader.Load (libraryPath); + using IELF elf = ELFReader.Load (libraryPath); var dynstr = GetSection (elf, ".dynstr") as IStringTable; if (dynstr == null) { return false; @@ -143,6 +144,44 @@ public static bool ReferencesLibrary (string libraryPath, string referencedLibra return false; } + public static bool LibraryHasSymbol (TaskLoggingHelper log, string elfPath, string sectionName, string symbolName, ELFSymbolType symbolType = ELFSymbolType.NotSpecified) + { + if (elfPath.IsNullOrEmpty () || !File.Exists (elfPath)) { + return false; + } + + try { + using IELF elf = ELFReader.Load (elfPath); + return HasSymbol (elf, sectionName, symbolName, symbolType); + } catch (Exception ex) { + log.LogWarning ($"Attempt to check whether '{elfPath}' is a valid ELF file failed with exception, ignoring symbol '{symbolName}@{sectionName}' check for the file."); + log.LogWarningFromException (ex, showStackTrace: true); + return false; + } + } + + public static bool LibraryHasPublicSymbol (TaskLoggingHelper log, string elfPath, string symbolName, ELFSymbolType symbolType = ELFSymbolType.NotSpecified) => LibraryHasSymbol (log, elfPath, ".dynsym", symbolName, symbolType); + + public static bool HasSymbol (IELF elf, string sectionName, string symbolName, ELFSymbolType symbolType = ELFSymbolType.NotSpecified) + { + ISymbolTable? symtab = GetSymbolTable (elf, sectionName); + if (symtab == null) { + return false; + } + + foreach (var entry in symtab.Entries) { + if (MonoAndroidHelper.StringEquals (symbolName, entry.Name) && (symbolType == ELFSymbolType.NotSpecified || entry.Type == symbolType)) { + return true; + } + } + + return false; + } + + public static bool HasPublicSymbol (IELF elf, string symbolName, ELFSymbolType symbolType = ELFSymbolType.NotSpecified) => HasSymbol (elf, ".dynsym", symbolName, symbolType); + + public static bool IsJniLibrary (TaskLoggingHelper log, string elfPath) => LibraryHasPublicSymbol (log, elfPath, "JNI_OnLoad", ELFSymbolType.Function); + static bool IsLibraryReference (IStringTable stringTable, IDynamicEntry dynEntry, string referencedLibraryName) { if (dynEntry.Tag != DynamicTag.Needed) { @@ -163,26 +202,12 @@ static bool IsLibraryReference (IStringTable stringTable, IDynamicEntry dynEntry static bool IsEmptyAOTLibrary (TaskLoggingHelper log, string path, IELF elf) { - ISymbolTable? symtab = GetSymbolTable (elf, ".dynsym"); - if (symtab == null) { - // We can't be sure what the DSO is, play safe - return false; - } - - bool mono_aot_file_info_found = false; - foreach (var entry in symtab.Entries) { - if (MonoAndroidHelper.StringEquals ("mono_aot_file_info", entry.Name) && entry.Type == ELFSymbolType.Object) { - mono_aot_file_info_found = true; - break; - } - } - - if (!mono_aot_file_info_found) { + if (!HasPublicSymbol (elf, "mono_aot_file_info", ELFSymbolType.Object)) { // Not a MonoVM AOT assembly return false; } - symtab = GetSymbolTable (elf, ".symtab"); + ISymbolTable? symtab = GetSymbolTable (elf, ".symtab"); if (symtab == null) { // The DSO is stripped, we can't tell if there are any functions defined (.text will be present anyway) // We perhaps **can** take a look at the .text section size, but it's not a solid check... diff --git a/src/java-runtime/java/mono/android/MonoPackageManager.java b/src/java-runtime/java/mono/android/MonoPackageManager.java index db90e2092ef..6531bd2da3d 100644 --- a/src/java-runtime/java/mono/android/MonoPackageManager.java +++ b/src/java-runtime/java/mono/android/MonoPackageManager.java @@ -109,10 +109,6 @@ public static void LoadApplication (Context context) if (!BuildConfig.DotNetRuntime) { // .net5+ APKs don't contain `libmono-native.so` System.loadLibrary("mono-native"); - } else { - // for .net6 we temporarily need to load the SSL DSO - // see: https://github.com/dotnet/runtime/issues/51274#issuecomment-832963657 - System.loadLibrary("System.Security.Cryptography.Native.Android"); } System.loadLibrary("monodroid"); diff --git a/src/native/clr/host/CMakeLists.txt b/src/native/clr/host/CMakeLists.txt index 017a66918cf..2409f846dae 100644 --- a/src/native/clr/host/CMakeLists.txt +++ b/src/native/clr/host/CMakeLists.txt @@ -34,6 +34,7 @@ set(XAMARIN_MONODROID_SOURCES host-util.cc internal-pinvokes.cc os-bridge.cc + runtime-environment.cc runtime-util.cc typemap.cc xamarin_getifaddrs.cc @@ -153,6 +154,7 @@ macro(lib_target_options TARGET_NAME) xa::java-interop xa::pinvoke-override-precompiled xa::lz4 + -landroid -llog -lcoreclr ) diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index 3fd2e3ba959..5048d8eb32a 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -4,6 +4,9 @@ #include #include #include +#include + +#include #include @@ -17,8 +20,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -421,6 +426,20 @@ void Host::Java_mono_android_Runtime_initInternal ( } ); + DsoLoader::init ( + env, + RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "java_lang_System", true), + ALooper_forThread (), // main thread looper + 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); + } + struct JnienvInitializeArgs init = {}; init.javaVm = jvm; init.env = env; diff --git a/src/native/clr/host/runtime-environment.cc b/src/native/clr/host/runtime-environment.cc new file mode 100644 index 00000000000..41d94bfcf1d --- /dev/null +++ b/src/native/clr/host/runtime-environment.cc @@ -0,0 +1,9 @@ +#include +#include + +using namespace xamarin::android; + +auto RuntimeEnvironment::get_jnienv () noexcept -> JNIEnv* +{ + return OSBridge::ensure_jnienv (); +} diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index eff2961a62b..b60871a93c4 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -104,16 +104,15 @@ namespace xamarin::android { static void detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcept; static void setup_environment () noexcept; static void setup_app_library_directories (jstring_array_wrapper& runtimeApks, jstring_array_wrapper& appDirs, bool have_split_apks) noexcept; - static auto load_dso (std::string_view const& path, unsigned int dl_flags, bool skip_exists_check) noexcept -> void*; - static auto load_dso_from_any_directories (std::string_view const& name, unsigned int dl_flags) noexcept -> void*; + static auto load_dso_from_any_directories (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void*; private: static auto get_full_dso_path (std::string const& base_dir, std::string_view const& dso_path, dynamic_local_string& path) noexcept -> bool; template // TODO: replace with a concept - static auto load_dso_from_specified_dirs (TContainer directories, std::string_view const& dso_name, unsigned int dl_flags) noexcept -> void*; - static auto load_dso_from_app_lib_dirs (std::string_view const& name, unsigned int dl_flags) noexcept -> void*; - static auto load_dso_from_override_dirs (std::string_view const& name, unsigned int dl_flags) noexcept -> void*; + static auto load_dso_from_specified_dirs (TContainer directories, std::string_view const& dso_name, int dl_flags, bool is_jni) noexcept -> void*; + static auto load_dso_from_app_lib_dirs (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void*; + static auto load_dso_from_override_dirs (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void*; static auto lookup_system_property (std::string_view const &name, size_t &value_len) noexcept -> const char*; static auto monodroid__system_property_get (std::string_view const&, char *sp_value, size_t sp_value_len) noexcept -> int; static auto get_max_gref_count_from_system () noexcept -> long; diff --git a/src/native/clr/include/runtime-base/monodroid-dl.hh b/src/native/clr/include/runtime-base/monodroid-dl.hh index 0a479858813..0d339c82451 100644 --- a/src/native/clr/include/runtime-base/monodroid-dl.hh +++ b/src/native/clr/include/runtime-base/monodroid-dl.hh @@ -12,6 +12,7 @@ #include "../xamarin-app.hh" #include "android-system.hh" +#include #include #include "startup-aware-lock.hh" @@ -72,40 +73,7 @@ namespace xamarin::android return find_dso_cache_entry_common (hash); } - static auto monodroid_dlopen_log_and_return (void *handle, std::string_view const& full_name) -> void* - { - if (handle == nullptr) { - const char *load_error = dlerror (); - if (load_error == nullptr) { - load_error = "Unknown error"; - } - log_error ( - LOG_ASSEMBLY, - "Could not load library '{}'. {}", - full_name, - load_error - ); - } - - return handle; - } - - static auto monodroid_dlopen_ignore_component_or_load (std::string_view const& name, int flags) noexcept -> void* - { - // We first try to load the DSO using the passed name, it will cause `dlopen` to search our APK (or - // on-filesystem location), if necessary, so it's more efficient than trying to load from any specific - // directories first. - unsigned int dl_flags = static_cast(flags); - void *handle = AndroidSystem::load_dso (name, dl_flags, false /* skip_existing_check */); - if (handle != nullptr) { - return monodroid_dlopen_log_and_return (handle, name); - } - - handle = AndroidSystem::load_dso_from_any_directories (name, dl_flags); - return monodroid_dlopen_log_and_return (handle, name); - } - - private: + public: [[gnu::always_inline]] static auto get_dso_name (const DSOCacheEntry *const dso) -> std::string_view { @@ -116,43 +84,18 @@ namespace xamarin::android return &dso_names_data[dso->name_index]; } - public: - template [[gnu::flatten]] - static auto monodroid_dlopen (std::string_view const& name, int flags) noexcept -> void* + [[gnu::flatten]] + static auto monodroid_dlopen (DSOCacheEntry *dso, std::string_view const& name, int flags) noexcept -> void* { - if (name.empty ()) [[unlikely]] { - log_warn (LOG_ASSEMBLY, "monodroid_dlopen got a null name. This is not supported in NET+"sv); - return nullptr; - } - - hash_t name_hash = xxhash::hash (name.data (), name.size ()); - log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); - - DSOCacheEntry *dso = nullptr; - if constexpr (PREFER_AOT_CACHE) { - // This code isn't currently used by CoreCLR, but it's possible that in the future we will have separate - // .so files for AOT-d assemblies, similar to MonoVM, so let's keep it. - // - // If we're asked to look in the AOT DSO cache, do it first. This is because we're likely called from the - // MonoVM's dlopen fallback handler and it will not be a request to resolved a p/invoke, but most likely to - // find and load an AOT image for a managed assembly. Since there might be naming/hash conflicts in this - // scenario, we look at the AOT cache first. - // - // See: https://github.com/dotnet/android/issues/9081 - dso = find_only_aot_cache_entry (name_hash); - } - - if (dso == nullptr) { - dso = find_only_dso_cache_entry (name_hash); - } - log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash match {}found, DSO name is '{}'", dso == nullptr ? "not "sv : ""sv, get_dso_name (dso)); if (dso == nullptr) { - // DSO not known at build time, try to load it - return monodroid_dlopen_ignore_component_or_load (name, flags); + // DSO not known at build time, try to load it. Since we don't know whether or not the library uses + // JNI, we're going to assume it does and thus use System.loadLibrary eventually. + return DsoLoader::load (name, flags, true /* is_jni */); } else if (dso->handle != nullptr) { - return monodroid_dlopen_log_and_return (dso->handle, get_dso_name (dso)); + log_debug (LOG_ASSEMBLY, "monodroid_dlopen: library {} already loaded, returning handle {:p}", name, dso->handle); + return dso->handle; } if (dso->ignore) { @@ -171,29 +114,54 @@ namespace xamarin::android continue; } - android_dlextinfo dli; - dli.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET; - dli.library_fd = apk_entry->fd; - dli.library_fd_offset = apk_entry->offset; - - dso->handle = android_dlopen_ext (dso_name.data (), flags, &dli); - + dso->handle = DsoLoader::load (apk_entry->fd, apk_entry->offset, dso_name, flags, dso->is_jni_library); if (dso->handle != nullptr) { - return monodroid_dlopen_log_and_return (dso->handle, dso_name); + return dso->handle; } break; } } #endif - unsigned int dl_flags = static_cast(flags); - dso->handle = AndroidSystem::load_dso_from_any_directories (dso_name, dl_flags); + dso->handle = AndroidSystem::load_dso_from_any_directories (dso_name, flags, dso->is_jni_library); if (dso->handle != nullptr) { - return monodroid_dlopen_log_and_return (dso->handle, dso_name); + return dso->handle; + } + + dso->handle = AndroidSystem::load_dso_from_any_directories (name, flags, dso->is_jni_library); + return dso->handle; + } + + template [[gnu::flatten]] + static auto monodroid_dlopen (std::string_view const& name, int flags) noexcept -> void* + { + if (name.empty ()) [[unlikely]] { + log_warn (LOG_ASSEMBLY, "monodroid_dlopen got a null name. This is not supported in NET+"sv); + return nullptr; + } + + hash_t name_hash = xxhash::hash (name.data (), name.size ()); + log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); + + DSOCacheEntry *dso = nullptr; + if constexpr (PREFER_AOT_CACHE) { + // This code isn't currently used by CoreCLR, but it's possible that in the future we will have separate + // .so files for AOT-d assemblies, similar to MonoVM, so let's keep it. + // + // If we're asked to look in the AOT DSO cache, do it first. This is because we're likely called from the + // MonoVM's dlopen fallback handler and it will not be a request to resolved a p/invoke, but most likely to + // find and load an AOT image for a managed assembly. Since there might be naming/hash conflicts in this + // scenario, we look at the AOT cache first. + // + // See: https://github.com/dotnet/android/issues/9081 + dso = find_only_aot_cache_entry (name_hash); + } + + if (dso == nullptr) { + dso = find_only_dso_cache_entry (name_hash); } - dso->handle = AndroidSystem::load_dso_from_any_directories (name, dl_flags); - return monodroid_dlopen_log_and_return (dso->handle, name); + return monodroid_dlopen (dso, name, flags); } [[gnu::flatten]] diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh index 7fd7a7ab416..76f39ac6c18 100644 --- a/src/native/clr/include/xamarin-app.hh +++ b/src/native/clr/include/xamarin-app.hh @@ -258,6 +258,7 @@ struct DSOCacheEntry const uint64_t hash; const uint64_t real_name_hash; const bool ignore; + const bool is_jni_library; const uint32_t name_index; void *handle; }; @@ -347,6 +348,8 @@ 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_count; + [[gnu::visibility("default")]] extern const uint dso_jni_preloads_idx[]; [[gnu::visibility("default")]] extern DSOCacheEntry aot_dso_cache[]; [[gnu::visibility("default")]] extern const char dso_names_data[]; [[gnu::visibility("default")]] extern DSOApkEntry dso_apk_entries[]; diff --git a/src/native/clr/runtime-base/android-system.cc b/src/native/clr/runtime-base/android-system.cc index b6783827cb1..52790bfa97b 100644 --- a/src/native/clr/runtime-base/android-system.cc +++ b/src/native/clr/runtime-base/android-system.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -430,30 +431,8 @@ auto AndroidSystem::get_full_dso_path (std::string const& base_dir, std::string_ return true; } -auto AndroidSystem::load_dso (std::string_view const& path, unsigned int dl_flags, bool skip_exists_check) noexcept -> void* -{ - if (path.empty ()) [[unlikely]] { - return nullptr; - } - - log_info (LOG_ASSEMBLY, "Trying to load shared library '{}'", path); - if (!skip_exists_check && !is_embedded_dso_mode_enabled () && !Util::file_exists (path)) { - log_info (LOG_ASSEMBLY, "Shared library '{}' not found", path); - return nullptr; - } - - char *error = nullptr; - void *handle = java_interop_lib_load (path.data (), dl_flags, &error); - if (handle == nullptr && Util::should_log (LOG_ASSEMBLY)) { - log_info_nocheck_fmt (LOG_ASSEMBLY, "Failed to load shared library '{}'. {}", path, error); - } - java_interop_free (error); - - return handle; -} - template [[gnu::always_inline]] -auto AndroidSystem::load_dso_from_specified_dirs (TContainer directories, std::string_view const& dso_name, unsigned int dl_flags) noexcept -> void* +auto AndroidSystem::load_dso_from_specified_dirs (TContainer directories, std::string_view const& dso_name, int dl_flags, bool is_jni) noexcept -> void* { if (dso_name.empty ()) { return nullptr; @@ -465,7 +444,7 @@ auto AndroidSystem::load_dso_from_specified_dirs (TContainer directories, std::s continue; } - void *handle = load_dso (full_path.get (), dl_flags, false); + void *handle = DsoLoader::load (full_path.get (), dl_flags, is_jni); if (handle != nullptr) { return handle; } @@ -474,26 +453,26 @@ auto AndroidSystem::load_dso_from_specified_dirs (TContainer directories, std::s return nullptr; } -auto AndroidSystem::load_dso_from_app_lib_dirs (std::string_view const& name, unsigned int dl_flags) noexcept -> void* +auto AndroidSystem::load_dso_from_app_lib_dirs (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void* { - return load_dso_from_specified_dirs (app_lib_directories, name, dl_flags); + return load_dso_from_specified_dirs (app_lib_directories, name, dl_flags, is_jni); } -auto AndroidSystem::load_dso_from_override_dirs (std::string_view const& name, unsigned int dl_flags) noexcept -> void* +auto AndroidSystem::load_dso_from_override_dirs (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void* { if constexpr (Constants::is_release_build) { return nullptr; } else { - return load_dso_from_specified_dirs (AndroidSystem::override_dirs, name, dl_flags); + return load_dso_from_specified_dirs (AndroidSystem::override_dirs, name, dl_flags, is_jni); } } [[gnu::flatten]] -auto AndroidSystem::load_dso_from_any_directories (std::string_view const& name, unsigned int dl_flags) noexcept -> void* +auto AndroidSystem::load_dso_from_any_directories (std::string_view const& name, int dl_flags, bool is_jni) noexcept -> void* { - void *handle = load_dso_from_override_dirs (name, dl_flags); + void *handle = load_dso_from_override_dirs (name, dl_flags, is_jni); if (handle == nullptr) { - handle = load_dso_from_app_lib_dirs (name, dl_flags); + handle = load_dso_from_app_lib_dirs (name, dl_flags, is_jni); } return handle; } 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 56bd6e280ad..97afad72137 100644 --- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc +++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc @@ -112,6 +112,7 @@ DSOCacheEntry dso_cache[] = { .hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), .real_name_hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), .ignore = true, + .is_jni_library = false, .name_index = 1, .handle = nullptr, }, @@ -120,11 +121,17 @@ DSOCacheEntry dso_cache[] = { .hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1), .real_name_hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1), .ignore = true, + .is_jni_library = false, .name_index = 2, .handle = nullptr, }, }; +const uint dso_jni_preloads_idx_count = 1; +const uint dso_jni_preloads_idx[1] = { + 0 +}; + DSOCacheEntry aot_dso_cache[] = { { .hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), diff --git a/src/native/common/include/runtime-base/dso-loader.hh b/src/native/common/include/runtime-base/dso-loader.hh new file mode 100644 index 00000000000..f44947453b4 --- /dev/null +++ b/src/native/common/include/runtime-base/dso-loader.hh @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#if defined(XA_HOST_MONOVM) +using AndroidSystem = xamarin::android::internal::AndroidSystem; +#endif + +namespace xamarin::android { + class DsoLoader + { + public: + [[gnu::flatten]] + static void init (JNIEnv *env, jclass systemClass, ALooper *main_looper, pid_t _main_thread_id) noexcept + { + SystemLoadLibraryWrapper::init (env, systemClass); + MainThreadDsoLoader::init (env, main_looper); + main_thread_id = _main_thread_id; + } + + // Overload used to load libraries from the file system. + template + [[gnu::always_inline, gnu::flatten]] + static auto load (std::string_view const& path, int flags, bool is_jni) -> void* + { + if (is_jni) { + return load_jni (path, true /* name_is_path */); + } + + log_info (LOG_ASSEMBLY, "[filesystem] Trying to load shared library '{}'", path); + if constexpr (!SkipExistsCheck) { + if (!AndroidSystem::is_embedded_dso_mode_enabled () && !Util::file_exists (path)) { + log_info (LOG_ASSEMBLY, "Shared library '{}' not found", path); + return nullptr; + } + } + + return log_and_return (dlopen (path.data (), flags), path); + } + + // Overload used to load libraries from the APK. + [[gnu::always_inline, gnu::flatten]] + static auto load (int fd, off64_t offset, std::string_view const& name, int flags, bool is_jni) -> void* + { + if (is_jni) { + return load_jni (name, true /* name_is_path */); + } + + log_info (LOG_ASSEMBLY, "[apk] Trying to load shared library '{}', offset in the apk == {}", name, offset); + + android_dlextinfo dli; + dli.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET; + dli.library_fd = fd; + dli.library_fd_offset = offset; + + return log_and_return (android_dlopen_ext (name.data (), flags, &dli), name); + } + + private: + [[gnu::always_inline]] + static auto log_and_return (void *handle, std::string_view const& full_name) -> void* + { + if (handle != nullptr) [[likely]] { + log_debug (LOG_ASSEMBLY, "Shared library {} loaded (handle {:p})", full_name, handle); + return handle; + } + + const char *load_error = dlerror (); + if (load_error == nullptr) { + load_error = "Unknown error"; + } + log_error ( + LOG_ASSEMBLY, + "Could not load library '{}'. {}"sv, + full_name, + load_error + ); + + return nullptr; + } + + static auto load_jni (std::string_view const& name, bool name_is_path) -> void* + { + log_debug (LOG_ASSEMBLY, "Trying to load loading shared JNI library {} with System.loadLibrary", name); + + auto get_file_name = [](std::string_view const& full_name, bool is_path) -> std::string_view { + if (!is_path) { + return full_name; + } + + std::string_view name = full_name; + size_t last_slash = name.find_last_of ('/'); + if (last_slash != std::string_view::npos) [[likely]] { + last_slash++; + if (last_slash <= name.length ()) { + name.remove_prefix (last_slash); + } + } + + return name; + }; + + // System.loadLibrary call is going to be slow anyway, so we can spend some more time generating an + // undecorated library name here instead of at build time. This saves us a little bit of space in + // `libxamarin-app.so` and makes the build code less complicated. + auto get_undecorated_name = [&get_file_name](std::string_view const& full_name, bool is_path) -> std::string_view { + std::string_view name; + + if (!is_path) { + name = full_name; + } else { + name = get_file_name (full_name, is_path); + } + + constexpr std::string_view lib_prefix { "lib" }; + if (name.starts_with (lib_prefix) && name.length () > 3) { + if (lib_prefix.length () <= name.length ()) { + name.remove_prefix (lib_prefix.length ()); + } + } + + constexpr std::string_view lib_ext { ".so" }; + if (name.ends_with (lib_ext) && name.length () > 3) { + if (lib_ext.length () <= name.length ()) { + name.remove_suffix (lib_ext.length ()); + } + } + + return name; + }; + + // So, we have a rather nasty problem here. If we're on a thread other than the main one (or, to be more + // precise - one not created by Java), we will NOT have the special class loader Android uses in JVM and + // which knows about the special application-specific .so paths (like the one inside the APK itself). For + // that reason, `System.loadLibrary` will not be able to find the requested .so and we can't pass it a full + // path to it, since it accepts only the undecorated library name. + // We have to call `System.loadLibrary` on the main thread, so that the special class loader is available to + // it. At the same time, we have to do it synchronously, because we must be able to get the library handle + // **here**. We could call to a Java function here, but then synchronization might be an issue. So, instead, + // we use a wrapper around System.loadLibrary that uses the ALooper native Android interface. It's a bit + // clunky (as it requires using a fake pipe(2) to force the looper to call us on the main thread) but it + // should work across all the Android versions. + + // TODO: implement the above + if (gettid () == main_thread_id) { + if (!SystemLoadLibraryWrapper::load (RuntimeEnvironment::get_jnienv (), get_undecorated_name (name, name_is_path))) { + // We could abort, but let's let the managed land react to this library missing. We cannot continue + // with `dlopen` below, because without `JNI_OnLoad` etc invoked, we might have nasty crashes in the + // library code if e.g. it assumes that `JNI_OnLoad` initialized all the Java class, method etc + // pointers. + return nullptr; + } + } else { + MainThreadDsoLoader loader; + if (!loader.load (name, get_undecorated_name (name, name_is_path))) { + return nullptr; + } + } + + // This is unfortunate, but since `System.loadLibrary` doesn't return the class handle, we must get it this + // way :( + // We must use full name of the library, because dlopen won't accept an undecorated one without kicking up + // a fuss. + log_debug (LOG_ASSEMBLY, "Attempting to get library {} handle after System.loadLibrary. Will try to load using '{}'", name, get_file_name (name, name_is_path)); + return log_and_return (dlopen (get_file_name (name, name_is_path).data (), RTLD_NOLOAD), name); + } + + private: + static inline pid_t main_thread_id = 0; + }; +} diff --git a/src/native/common/include/runtime-base/mainthread-dso-loader.hh b/src/native/common/include/runtime-base/mainthread-dso-loader.hh new file mode 100644 index 00000000000..144ad67db81 --- /dev/null +++ b/src/native/common/include/runtime-base/mainthread-dso-loader.hh @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace xamarin::android { + // This class is **strictly** one-shot-per-instance! That is, the `load` method mustn't be called on the + // same object more than once. This is by design, to make the code simpler. + class MainThreadDsoLoader + { + public: + explicit MainThreadDsoLoader () noexcept + { + if (pipe (pipe_fds) != 0) { + Helpers::abort_application ( + LOG_ASSEMBLY, + std::format ( + "Failed to create a pipe for main thread DSO loader. {}"sv, + strerror (errno) + ) + ); + } + + int ret = ALooper_addFd ( + main_thread_looper, + pipe_fds[0], + ALOOPER_POLL_CALLBACK, + ALOOPER_EVENT_INPUT, + load_cb, + this + ); + + if (ret == -1) { + Helpers::abort_application ("Failed to init main looper with pipe file descriptors in the main thread DSO loader"sv); + } + } + + MainThreadDsoLoader (const MainThreadDsoLoader&) = delete; + MainThreadDsoLoader (MainThreadDsoLoader&&) = delete; + + virtual ~MainThreadDsoLoader () noexcept + { + if (pipe_fds[0] != -1) { + ALooper_removeFd (main_thread_looper, pipe_fds[0]); + close (pipe_fds[0]); + } + + if (pipe_fds[1] != -1) { + close (pipe_fds[1]); + } + + // No need to release the looper, it needs to stay acquired. + } + + MainThreadDsoLoader& operator=(const MainThreadDsoLoader&) = delete; + MainThreadDsoLoader& operator=(MainThreadDsoLoader&&) = delete; + + bool load (std::string_view const& full_name, std::string_view const& undecorated_name) noexcept + { + if (!undecorated_library_name.empty ()) [[unlikely]] { + Helpers::abort_application ("Main thread DSO loader object reused! DO NOT DO THAT!"sv); + } + log_debug (LOG_ASSEMBLY, "Running DSO loader on thread {}, dispatching to main thread"sv, gettid ()); + + undecorated_library_name = undecorated_name; + load_success = false; + constexpr std::array payload { 0xFF }; + ssize_t nbytes = write (pipe_fds[1], payload.data (), payload.size ()); + if (nbytes == -1) { + log_warn ( + LOG_ASSEMBLY, + "Write failure when posting a DSO load event to main thread. {}"sv, + strerror (errno) + ); + return false; + } + + // Wait for the callback to complete + using namespace std::literals; + + // We'll wait for up to 3s, it should be more than enough time for the library to load + bool success = load_complete_sem.try_acquire_for (3s); + if (!success) { + log_warn (LOG_ASSEMBLY, "Timeout while waiting for shared library '{}' to load."sv, full_name); + return false; + } + + return load_success; + } + + static void init (JNIEnv *main_jni_env, ALooper *main_looper) + { + if (main_thread_looper != nullptr) { + return; + } + + main_thread_looper = main_looper; + main_thread_jni_env = main_jni_env; + // This will keep the looper around for the lifetime of the application. + ALooper_acquire (main_looper); + } + + private: + + static auto load_cb ([[maybe_unused]] int fd, [[maybe_unused]] int events, void *data) noexcept -> int + { + auto self = reinterpret_cast (data); + if (self == nullptr) [[unlikely]] { + Helpers::abort_application ("MainThreadDsoLoader instance not passed to the looper callback."sv); + } + + auto over_and_out = [&self]() -> int { + // We're one-shot, 0 means just that + self->load_complete_sem.release (); + return 0; + }; + + if (self->undecorated_library_name.empty ()) { + log_warn (LOG_ASSEMBLY, "Library name not specified in main thread looper callback."sv); + return over_and_out (); + } + + log_debug ( + LOG_ASSEMBLY, + "Looper CB called on thread {}. Will attempt to load DSO '{}'"sv, + gettid (), + self->undecorated_library_name + ); + + self->load_success = SystemLoadLibraryWrapper::load (main_thread_jni_env /* RuntimeEnvironment::get_jnienv () */, self->undecorated_library_name); + return over_and_out (); + } + + private: + int pipe_fds[2] = {-1, -1}; + std::binary_semaphore load_complete_sem {0}; + std::string_view undecorated_library_name {}; + bool load_success = false; + + static inline ALooper *main_thread_looper = nullptr; + static inline JNIEnv *main_thread_jni_env = nullptr; + }; +} diff --git a/src/native/common/include/runtime-base/runtime-environment.hh b/src/native/common/include/runtime-base/runtime-environment.hh new file mode 100644 index 00000000000..c5bcbd2eb64 --- /dev/null +++ b/src/native/common/include/runtime-base/runtime-environment.hh @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace xamarin::android { + class RuntimeEnvironment + { + public: + static auto get_jnienv () noexcept -> JNIEnv*; + }; +} diff --git a/src/native/common/include/runtime-base/system-loadlibrary-wrapper.hh b/src/native/common/include/runtime-base/system-loadlibrary-wrapper.hh new file mode 100644 index 00000000000..577d6ba8e6f --- /dev/null +++ b/src/native/common/include/runtime-base/system-loadlibrary-wrapper.hh @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +namespace xamarin::android { + class SystemLoadLibraryWrapper + { + public: + [[gnu::flatten]] + static void init (JNIEnv *env, jclass systemClass) noexcept + { + systemKlass = systemClass; + System_loadLibrary = env->GetStaticMethodID (systemClass, "loadLibrary", "(Ljava/lang/String;)V"); + if (System_loadLibrary == nullptr) [[unlikely]] { + Helpers::abort_application ("Failed to look up the Java System.loadLibrary method."); + } + } + + [[gnu::flatten]] + static auto load (JNIEnv *jni_env, std::string_view const& undecorated_lib_name) noexcept -> bool + { + if (systemKlass == nullptr) [[unlikely]] { + Helpers::abort_application ("System.loadeLibrary wrapper class not initialized properly."sv); + } + + // std::string is needed because we must pass a NUL-terminated string to Java, otherwise + // strange things happen (and std::string_view is not necessarily such a string) + const std::string lib_name { undecorated_lib_name }; + log_debug (LOG_ASSEMBLY, "Undecorated library name: {}", lib_name); + + jstring java_lib_name = jni_env->NewStringUTF (lib_name.c_str ()); + if (java_lib_name == nullptr) [[unlikely]] { + // It's an OOM, there's nothing better we can do + Helpers::abort_application ("Java string allocation failed while loading a shared library."); + } + jni_env->CallStaticVoidMethod (systemKlass, System_loadLibrary, java_lib_name); + if (jni_env->ExceptionCheck ()) { + log_debug (LOG_ASSEMBLY, "System.loadLibrary threw a Java exception. Will attempt to log it."); + jni_env->ExceptionDescribe (); + jni_env->ExceptionClear (); + log_debug (LOG_ASSEMBLY, "Java exception cleared"); + return false; + } + + return true; + } + + private: + static inline jmethodID System_loadLibrary = nullptr; + static inline jclass systemKlass = nullptr; + }; +} diff --git a/src/native/mono/monodroid/CMakeLists.txt b/src/native/mono/monodroid/CMakeLists.txt index b8fffa6177c..df3a5b9b50e 100644 --- a/src/native/mono/monodroid/CMakeLists.txt +++ b/src/native/mono/monodroid/CMakeLists.txt @@ -103,6 +103,7 @@ set(XAMARIN_MONODROID_SOURCES monodroid-tracing.cc monovm-properties.cc osbridge.cc + runtime-environment.cc runtime-util.cc timezones.cc xamarin_getifaddrs.cc @@ -232,6 +233,7 @@ macro(lib_target_options TARGET_NAME) xa::pinvoke-override-precompiled xa::lz4 -lmonosgen-2.0 + -landroid -llog ) endmacro () diff --git a/src/native/mono/monodroid/debug.cc b/src/native/mono/monodroid/debug.cc index f97bf6ba408..d37ad8cf5ff 100644 --- a/src/native/mono/monodroid/debug.cc +++ b/src/native/mono/monodroid/debug.cc @@ -35,6 +35,7 @@ #include "util.hh" #include "globals.hh" #include +#include #include #include "java-interop-dlfcn.h" @@ -78,15 +79,18 @@ Debug::monodroid_profiler_load (const char *libmono_path, const char *desc, cons } std::unique_ptr mname {mname_ptr}; - unsigned int dlopen_flags = JAVA_INTEROP_LIB_LOAD_LOCALLY; + constexpr int dlopen_flags = RTLD_LOCAL; + constexpr bool IsJni = false; std::unique_ptr libname {Util::string_concat ("libmono-profiler-", mname.get (), ".so")}; bool found = false; - void *handle = AndroidSystem::load_dso_from_any_directories (libname.get (), dlopen_flags); + void *handle = AndroidSystem::load_dso_from_any_directories (libname.get (), dlopen_flags, IsJni); found = load_profiler_from_handle (handle, desc, mname.get ()); if (!found && libmono_path != nullptr) { std::unique_ptr full_path {Util::path_combine (libmono_path, libname.get ())}; - handle = AndroidSystem::load_dso (full_path.get (), dlopen_flags, FALSE); + + constexpr bool SkipExistsCheck = false; + handle = DsoLoader::load (full_path.get (), dlopen_flags, IsJni); found = load_profiler_from_handle (handle, desc, mname.get ()); } diff --git a/src/native/mono/monodroid/monodroid-glue.cc b/src/native/mono/monodroid/monodroid-glue.cc index 17a379cf19f..08cf4f78320 100644 --- a/src/native/mono/monodroid/monodroid-glue.cc +++ b/src/native/mono/monodroid/monodroid-glue.cc @@ -62,6 +62,7 @@ #include "monodroid-state.hh" #include "pinvoke-override-api.hh" #include +#include #include using namespace microsoft::java_interop; @@ -834,7 +835,6 @@ MonodroidRuntime::init_android_runtime (JNIEnv *env, jclass runtimeClass, jobjec init.marshalMethodsEnabled = application_config.marshal_methods_enabled; init.managedMarshalMethodsLookupEnabled = application_config.managed_marshal_methods_lookup_enabled; - java_System = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "java_lang_System", true); java_System_identityHashCode = env->GetStaticMethodID (java_System, "identityHashCode", "(Ljava/lang/Object;)I"); // GC threshold is 90% of the max GREF count @@ -1224,6 +1224,15 @@ MonodroidRuntime::create_and_initialize_domain (JNIEnv* env, jclass runtimeClass bool force_preload_assemblies, bool have_split_apks) noexcept { 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]]; + + 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); + } + // Asserting this on desktop apparently breaks a Designer test abort_unless (domain != nullptr, "Failed to create AppDomain"); @@ -1389,6 +1398,14 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl AndroidSystem::detect_embedded_dso_mode (applicationDirs); AndroidSystem::set_running_in_emulator (isEmulator); + java_System = RuntimeUtil::get_class_from_runtime_field (env, klass, "java_lang_System", true); + DsoLoader::init ( + env, + RuntimeUtil::get_class_from_runtime_field (env, klass, "java_lang_System", true), + ALooper_forThread (), // main thread looper + gettid () + ); + java_TimeZone = RuntimeUtil::get_class_from_runtime_field (env, klass, "java_util_TimeZone", true); jstring_wrapper jstr (env, lang); @@ -1431,6 +1448,7 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl log_debug (LOG_DEFAULT, "Using runtime path: {}", optional_string (AndroidSystem::get_runtime_libdir ())); } + AndroidSystem::setup_process_args (runtimeApks); mono_dl_fallback_register (MonodroidDl::monodroid_dlopen, MonodroidDl::monodroid_dlsym, nullptr, nullptr); diff --git a/src/native/mono/monodroid/osbridge.cc b/src/native/mono/monodroid/osbridge.cc index ca0151cc433..ad7a0cc24c0 100644 --- a/src/native/mono/monodroid/osbridge.cc +++ b/src/native/mono/monodroid/osbridge.cc @@ -1036,18 +1036,6 @@ OSBridge::register_gc_hooks (void) mono_gc_register_bridge_callbacks (&bridge_cbs); } -JNIEnv* -OSBridge::ensure_jnienv (void) -{ - JNIEnv *env; - jvm->GetEnv ((void**)&env, JNI_VERSION_1_6); - if (env == nullptr) { - mono_jit_thread_attach (Util::get_current_domain (/* attach_thread_if_needed */ false)); - jvm->GetEnv ((void**)&env, JNI_VERSION_1_6); - } - return env; -} - void OSBridge::initialize_on_onload (JavaVM *vm, JNIEnv *env) { diff --git a/src/native/mono/monodroid/osbridge.hh b/src/native/mono/monodroid/osbridge.hh index f439a57aedd..488448a0e71 100644 --- a/src/native/mono/monodroid/osbridge.hh +++ b/src/native/mono/monodroid/osbridge.hh @@ -6,6 +6,8 @@ #include #include +#include "util.hh" + namespace xamarin::android::internal { class OSBridge @@ -106,7 +108,7 @@ namespace xamarin::android::internal return mono_java_gc_bridge_info [index]; } - JavaVM *get_jvm () const + static JavaVM *get_jvm () noexcept { return jvm; } @@ -124,7 +126,18 @@ namespace xamarin::android::internal mono_bool gc_is_bridge_object (MonoObject *object); void gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs); int get_gref_gc_threshold (); - JNIEnv* ensure_jnienv (); + + static JNIEnv* ensure_jnienv () noexcept + { + JNIEnv *env; + jvm->GetEnv ((void**)&env, JNI_VERSION_1_6); + if (env == nullptr) { + mono_jit_thread_attach (Util::get_current_domain (/* attach_thread_if_needed */ false)); + jvm->GetEnv ((void**)&env, JNI_VERSION_1_6); + } + return env; + } + void initialize_on_onload (JavaVM *vm, JNIEnv *env); void initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass); void add_monodroid_domain (MonoDomain *domain); @@ -170,7 +183,7 @@ namespace xamarin::android::internal MonodroidGCTakeRefFunc take_global_ref = nullptr; MonodroidGCTakeRefFunc take_weak_global_ref = nullptr; - JavaVM *jvm; + static inline JavaVM *jvm = nullptr; jclass weakrefClass; jmethodID weakrefCtor; jmethodID weakrefGet; diff --git a/src/native/mono/monodroid/runtime-environment.cc b/src/native/mono/monodroid/runtime-environment.cc new file mode 100644 index 00000000000..917cd7cbd41 --- /dev/null +++ b/src/native/mono/monodroid/runtime-environment.cc @@ -0,0 +1,11 @@ +#include + +#include "osbridge.hh" + +using namespace xamarin::android; +using namespace xamarin::android::internal; + +auto RuntimeEnvironment::get_jnienv () noexcept -> JNIEnv* +{ + return OSBridge::ensure_jnienv (); +} diff --git a/src/native/mono/runtime-base/android-system.cc b/src/native/mono/runtime-base/android-system.cc index 0fcd825bead..94a5d39a63d 100644 --- a/src/native/mono/runtime-base/android-system.cc +++ b/src/native/mono/runtime-base/android-system.cc @@ -9,6 +9,7 @@ #include #include "java-interop-dlfcn.h" #include "java-interop.h" +#include #include #include "shared-constants.hh" #include @@ -301,27 +302,7 @@ AndroidSystem::get_full_dso_path (const char *base_dir, const char *dso_path, dy } void* -AndroidSystem::load_dso (const char *path, unsigned int dl_flags, bool skip_exists_check) noexcept -{ - if (path == nullptr || *path == '\0') - return nullptr; - - log_info (LOG_ASSEMBLY, "Trying to load shared library '{}'", path); - if (!skip_exists_check && !is_embedded_dso_mode_enabled () && !Util::file_exists (path)) { - log_info (LOG_ASSEMBLY, "Shared library '{}' not found", path); - return nullptr; - } - - char *error = nullptr; - void *handle = java_interop_lib_load (path, dl_flags, &error); - if (handle == nullptr && Util::should_log (LOG_ASSEMBLY)) - log_info_nocheck_fmt (LOG_ASSEMBLY, "Failed to load shared library '{}'. {}", path, error); - java_interop_free (error); - return handle; -} - -void* -AndroidSystem::load_dso_from_specified_dirs (const char **directories, size_t num_entries, const char *dso_name, unsigned int dl_flags) noexcept +AndroidSystem::load_dso_from_specified_dirs (const char **directories, size_t num_entries, const char *dso_name, int dl_flags, bool is_jni) noexcept { abort_if_invalid_pointer_argument (directories, "directories"); if (dso_name == nullptr) @@ -332,7 +313,7 @@ AndroidSystem::load_dso_from_specified_dirs (const char **directories, size_t nu if (!get_full_dso_path (directories [i], dso_name, full_path)) { continue; } - void *handle = load_dso (full_path.get (), dl_flags, false); + void *handle = DsoLoader::load (full_path.get (), dl_flags, is_jni); if (handle != nullptr) return handle; } @@ -341,27 +322,27 @@ AndroidSystem::load_dso_from_specified_dirs (const char **directories, size_t nu } void* -AndroidSystem::load_dso_from_app_lib_dirs (const char *name, unsigned int dl_flags) noexcept +AndroidSystem::load_dso_from_app_lib_dirs (const char *name, int dl_flags, bool is_jni) noexcept { - return load_dso_from_specified_dirs (app_lib_directories.data (), app_lib_directories.size (), name, dl_flags); + return load_dso_from_specified_dirs (app_lib_directories.data (), app_lib_directories.size (), name, dl_flags, is_jni); } void* -AndroidSystem::load_dso_from_override_dirs ([[maybe_unused]] const char *name, [[maybe_unused]] unsigned int dl_flags) noexcept +AndroidSystem::load_dso_from_override_dirs ([[maybe_unused]] const char *name, [[maybe_unused]] int dl_flags, [[maybe_unused]] bool is_jni) noexcept { #ifdef RELEASE return nullptr; #else // def RELEASE - return load_dso_from_specified_dirs (const_cast (AndroidSystem::override_dirs.data ()), AndroidSystem::override_dirs.size (), name, dl_flags); + return load_dso_from_specified_dirs (const_cast (AndroidSystem::override_dirs.data ()), AndroidSystem::override_dirs.size (), name, dl_flags, is_jni); #endif // ndef RELEASE } void* -AndroidSystem::load_dso_from_any_directories (const char *name, unsigned int dl_flags) noexcept +AndroidSystem::load_dso_from_any_directories (const char *name, int dl_flags, bool is_jni) noexcept { - void *handle = load_dso_from_override_dirs (name, dl_flags); + void *handle = load_dso_from_override_dirs (name, dl_flags, is_jni); if (handle == nullptr) - handle = load_dso_from_app_lib_dirs (name, dl_flags); + handle = load_dso_from_app_lib_dirs (name, dl_flags, is_jni); return handle; } diff --git a/src/native/mono/runtime-base/android-system.hh b/src/native/mono/runtime-base/android-system.hh index 814323d4773..0cb6b081589 100644 --- a/src/native/mono/runtime-base/android-system.hh +++ b/src/native/mono/runtime-base/android-system.hh @@ -88,8 +88,7 @@ namespace xamarin::android::internal { static char* get_bundled_app (JNIEnv *env, jstring dir) noexcept; static int count_override_assemblies () noexcept; static long get_gref_gc_threshold () noexcept; - static void* load_dso (const char *path, unsigned int dl_flags, bool skip_exists_check) noexcept; - static void* load_dso_from_any_directories (const char *name, unsigned int dl_flags) noexcept; + static void* load_dso_from_any_directories (const char *name, int dl_flags, bool is_jni) noexcept; static bool get_full_dso_path_on_disk (const char *dso_name, dynamic_local_string& path) noexcept; static void setup_environment () noexcept; static void setup_process_args (jstring_array_wrapper &runtimeApks) noexcept; @@ -208,9 +207,9 @@ namespace xamarin::android::internal { static size_t _monodroid_get_system_property_from_file (const char *path, char **value) noexcept; #endif static bool get_full_dso_path (const char *base_dir, const char *dso_path, dynamic_local_string& path) noexcept; - static void* load_dso_from_specified_dirs (const char **directories, size_t num_entries, const char *dso_name, unsigned int dl_flags) noexcept; - static void* load_dso_from_app_lib_dirs (const char *name, unsigned int dl_flags) noexcept; - static void* load_dso_from_override_dirs (const char *name, unsigned int dl_flags) noexcept; + static void* load_dso_from_specified_dirs (const char **directories, size_t num_entries, const char *dso_name, int dl_flags, bool is_jni) noexcept; + static void* load_dso_from_app_lib_dirs (const char *name, int dl_flags, bool is_jni) noexcept; + static void* load_dso_from_override_dirs (const char *name, int dl_flags, bool is_jni) noexcept; static bool get_existing_dso_path_on_disk (const char *base_dir, const char *dso_name, dynamic_local_string& path) noexcept; private: diff --git a/src/native/mono/runtime-base/monodroid-dl.hh b/src/native/mono/runtime-base/monodroid-dl.hh index 93cb8b70b48..c730723297a 100644 --- a/src/native/mono/runtime-base/monodroid-dl.hh +++ b/src/native/mono/runtime-base/monodroid-dl.hh @@ -10,6 +10,7 @@ #include "android-system.hh" #include "monodroid-state.hh" +#include #include #include "shared-constants.hh" #include "startup-aware-lock.hh" @@ -30,12 +31,9 @@ namespace xamarin::android::internal static inline xamarin::android::mutex dso_handle_write_lock; - static unsigned int convert_dl_flags (int flags) noexcept + constexpr static int convert_dl_flags (int flags) noexcept { - unsigned int lflags = (flags & static_cast (MONO_DL_LOCAL)) - ? microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_LOCALLY - : microsoft::java_interop::JAVA_INTEROP_LIB_LOAD_GLOBALLY; - return lflags; + return (flags & static_cast (MONO_DL_LOCAL)) ? RTLD_LOCAL : RTLD_GLOBAL; } template @@ -130,43 +128,23 @@ namespace xamarin::android::internal } } - unsigned int dl_flags = convert_dl_flags (flags); - void * handle = AndroidSystem::load_dso_from_any_directories (name, dl_flags); + int dl_flags = convert_dl_flags (flags); + constexpr bool IsJniLib = false; // Mono components won't be using JNI + void *handle = AndroidSystem::load_dso_from_any_directories (name, dl_flags, IsJniLib); if (handle != nullptr) { return monodroid_dlopen_log_and_return (handle, err, name, false /* name_needs_free */); } - handle = AndroidSystem::load_dso (name, dl_flags, false /* skip_existing_check */); + constexpr bool SkipExistingCheck = false; + handle = DsoLoader::load (name, dl_flags, IsJniLib); + return monodroid_dlopen_log_and_return (handle, err, name, false /* name_needs_free */); } public: [[gnu::flatten]] - static void* monodroid_dlopen (const char *name, int flags, char **err, bool prefer_aot_cache) noexcept + static void* monodroid_dlopen (DSOCacheEntry *dso, hash_t name_hash, const char *name, int flags, char **err) noexcept { - if (name == nullptr) { - log_warn (LOG_ASSEMBLY, "monodroid_dlopen got a null name. This is not supported in NET+"sv); - return nullptr; - } - - hash_t name_hash = xxhash::hash (name, strlen (name)); - log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); - - DSOCacheEntry *dso = nullptr; - if (prefer_aot_cache) { - // If we're asked to look in the AOT DSO cache, do it first. This is because we're likely called from the - // MonoVM's dlopen fallback handler and it will not be a request to resolved a p/invoke, but most likely to - // find and load an AOT image for a managed assembly. Since there might be naming/hash conflicts in this - // scenario, we look at the AOT cache first. - // - // See: https://github.com/dotnet/android/issues/9081 - dso = find_only_aot_cache_entry (name_hash); - } - - if (dso == nullptr) { - dso = find_only_dso_cache_entry (name_hash); - } - log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash match {}found, DSO name is '{}'", dso == nullptr ? "not "sv : ""sv, dso == nullptr ? ""sv : dso->name); if (dso == nullptr) { @@ -181,6 +159,7 @@ namespace xamarin::android::internal return nullptr; } + int dl_flags = convert_dl_flags (flags); StartupAwareLock lock (dso_handle_write_lock); #if defined (RELEASE) if (AndroidSystem::is_embedded_dso_mode_enabled ()) { @@ -191,12 +170,7 @@ namespace xamarin::android::internal continue; } - android_dlextinfo dli; - dli.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET; - dli.library_fd = apk_entry->fd; - dli.library_fd_offset = apk_entry->offset; - dso->handle = android_dlopen_ext (dso->name, flags, &dli); - + dso->handle = DsoLoader::load (apk_entry->fd, apk_entry->offset, name, dl_flags, dso->is_jni_library); if (dso->handle != nullptr) { return monodroid_dlopen_log_and_return (dso->handle, err, dso->name, false /* name_needs_free */); } @@ -204,17 +178,45 @@ namespace xamarin::android::internal } } #endif - unsigned int dl_flags = convert_dl_flags (flags); - dso->handle = AndroidSystem::load_dso_from_any_directories (dso->name, dl_flags); + dso->handle = AndroidSystem::load_dso_from_any_directories (dso->name, dl_flags, dso->is_jni_library); if (dso->handle != nullptr) { return monodroid_dlopen_log_and_return (dso->handle, err, dso->name, false /* name_needs_free */); } - dso->handle = AndroidSystem::load_dso_from_any_directories (name, dl_flags); + dso->handle = AndroidSystem::load_dso_from_any_directories (name, dl_flags, dso->is_jni_library); return monodroid_dlopen_log_and_return (dso->handle, err, name, false /* name_needs_free */); } + [[gnu::flatten]] + static void* monodroid_dlopen (const char *name, int flags, char **err, bool prefer_aot_cache) noexcept + { + if (name == nullptr) { + log_warn (LOG_ASSEMBLY, "monodroid_dlopen got a null name. This is not supported in NET+"sv); + return nullptr; + } + + hash_t name_hash = xxhash::hash (name, strlen (name)); + log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '{}' is {:x}", name, name_hash); + + DSOCacheEntry *dso = nullptr; + if (prefer_aot_cache) { + // If we're asked to look in the AOT DSO cache, do it first. This is because we're likely called from the + // MonoVM's dlopen fallback handler and it will not be a request to resolved a p/invoke, but most likely to + // find and load an AOT image for a managed assembly. Since there might be naming/hash conflicts in this + // scenario, we look at the AOT cache first. + // + // See: https://github.com/dotnet/android/issues/9081 + dso = find_only_aot_cache_entry (name_hash); + } + + if (dso == nullptr) { + dso = find_only_dso_cache_entry (name_hash); + } + + return monodroid_dlopen (dso, name_hash, name, flags, err); + } + [[gnu::flatten]] static void* monodroid_dlopen (const char *name, int flags, char **err, [[maybe_unused]] void *user_data) noexcept { diff --git a/src/native/mono/runtime-base/util.hh b/src/native/mono/runtime-base/util.hh index 2f8c18caa40..8f4e6882bfe 100644 --- a/src/native/mono/runtime-base/util.hh +++ b/src/native/mono/runtime-base/util.hh @@ -30,6 +30,8 @@ static inline constexpr int FALSE = 0; #include #include +#include + #include #include @@ -78,6 +80,11 @@ namespace xamarin::android static bool directory_exists (const char *directory); static bool file_copy (const char *to, const char *from); + static bool file_exists (std::string_view const& path) noexcept + { + return file_exists (path.data ()); + } + static std::optional get_file_size_at (int dirfd, const char *file_name) noexcept { struct stat sbuf; 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 1028e8132ab..3d937692528 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,11 @@ DSOCacheEntry dso_cache[] = { }, }; +const uint dso_jni_preloads_idx_count = 1; +const uint dso_jni_preloads_idx[1] = { + 0 +}; + DSOCacheEntry aot_dso_cache[] = { { .hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1), diff --git a/src/native/mono/xamarin-app-stub/xamarin-app.hh b/src/native/mono/xamarin-app-stub/xamarin-app.hh index d6e9ef89a8a..f42cb460b32 100644 --- a/src/native/mono/xamarin-app-stub/xamarin-app.hh +++ b/src/native/mono/xamarin-app-stub/xamarin-app.hh @@ -269,6 +269,7 @@ struct DSOCacheEntry uint64_t hash; uint64_t real_name_hash; bool ignore; + const bool is_jni_library; const char *name; void *handle; }; @@ -340,6 +341,8 @@ 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_count; +MONO_API MONO_API_EXPORT const uint dso_jni_preloads_idx[]; MONO_API MONO_API_EXPORT DSOCacheEntry aot_dso_cache[]; MONO_API MONO_API_EXPORT DSOApkEntry dso_apk_entries[]; diff --git a/src/native/mono/xamarin-debug-app-helper/CMakeLists.txt b/src/native/mono/xamarin-debug-app-helper/CMakeLists.txt index 33adc3c0bde..0a6c6170f02 100644 --- a/src/native/mono/xamarin-debug-app-helper/CMakeLists.txt +++ b/src/native/mono/xamarin-debug-app-helper/CMakeLists.txt @@ -2,6 +2,7 @@ set(LIB_NAME xamarin-debug-app-helper) set(LIB_ALIAS xa::debug-app-helper) set(XAMARIN_DEBUG_HELPER_SOURCES + ../monodroid/runtime-environment.cc debug-app-helper.cc ) add_clang_check_sources("${XAMARIN_DEBUG_HELPER_SOURCES}") @@ -19,6 +20,7 @@ target_include_directories( SYSTEM PRIVATE ${SYSROOT_CXX_INCLUDE_DIR} ${RUNTIME_INCLUDE_DIR} + ../monodroid/ ) target_compile_definitions( @@ -53,6 +55,7 @@ target_link_libraries( xa::runtime-base xa::java-interop -ldl + -landroid -llog -lmonosgen-2.0 ) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj index 0f5005eedd8..5ada197c0f5 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj @@ -31,7 +31,7 @@ NetworkInterfaces excluded: https://github.com/dotnet/runtime/issues/75155 --> - $(ExcludeCategories):CoreCLRIgnore:SSL:NTLM:RuntimeConfig + $(ExcludeCategories):CoreCLRIgnore:NTLM:RuntimeConfig $(ExcludeCategories):NativeAOTIgnore:SSL:NTLM:GCBridge:AndroidClientHandler:Export:NativeTypeMap