Skip to content

Commit 47a3b33

Browse files
authored
[native] Split up "regular" and AOT DSO caches (#9117)
Fixes: #9081 Context: dotnet/runtime#104397 Context: dotnet/runtime#106026 Context: #9081 (comment) Context: c227042 ("Compatibility is fun") Consider a P/Invoke method declaration: [DllImport("libSkiaSharp")] static extern void gr_backendrendertarget_delete(IntPtr rendertarget); Historically, when attempting to resolve this method, Mono would try loading the following native libraries: * `libSkiaSharp` (original name) * `libSkiaSharp.so` (add `.so` suffix) * `liblibSkiaSharp` (add `lib` prefix) * `liblibSkiaSharp.so` (`lib` prefix + `.so` suffix) .NET for Android would further permute these names, *removing* the `lib` prefix, for attempted compatibility in case there is a P/Invoke into `"SkiaSharp"`. The unfortunate occasional result would be an *ambiguity*: when told to resolve "SkiaSharp", what should we return? The information for `libSkiaSharp.so`, or for the *AOT'd image* of the assembly `SkiaSharp.dll`, by way of `libaot-SkiaSharp.dll.so`? %struct.DSOCacheEntry { i64 u0x12e73d483788709d, ; from name: SkiaSharp.so i64 u0x3cb282562b838c95, ; uint64_t real_name_hash i1 false, ; bool ignore ptr @.DSOCacheEntry.23_name, ; name: libaot-SkiaSharp.dll.so ptr null; void* handle }, ; 71 %struct.DSOCacheEntry { i64 u0x12e73d483788709d, ; from name: SkiaSharp.so i64 u0x43db119dcc3147fa, ; uint64_t real_name_hash i1 false, ; bool ignore ptr @.DSOCacheEntry.7_name, ; name: libSkiaSharp.so ptr null; void* handle }, ; 72 If we return the wrong thing, then the app may crash or otherwise behave incorrectly. Fix this by: * Splitting up the DSO cache into AOT-related `.so` files and everything else. * Updating `PinvokeOverride::load_library_symbol()` so that the AOT files are *not* consulted when resolving P/Invoke libraries. * Updating `MonodroidDl::monodroid_dlopen()` -- which is called by MonoVM via `mono_dl_fallback_register()` -- so that the AOT files *are* consulted *first* when resolving AOT images. When dotnet/runtime#104397 is fixed, it will make the AOT side of the split more efficient as we won't have to permute the shared library name as many times as now.
1 parent 73f948e commit 47a3b33

File tree

9 files changed

+130
-42
lines changed

9 files changed

+130
-42
lines changed

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public sealed class ApplicationConfig
5858
public uint bundled_assembly_name_width;
5959
public uint number_of_assembly_store_files;
6060
public uint number_of_dso_cache_entries;
61+
public uint number_of_aot_cache_entries;
6162
public uint android_runtime_jnienv_class_token;
6263
public uint jnienv_initialize_method_token;
6364
public uint jnienv_registerjninatives_method_token;
@@ -67,7 +68,7 @@ public sealed class ApplicationConfig
6768
public string android_package_name = String.Empty;
6869
}
6970

70-
const uint ApplicationConfigFieldCount = 26;
71+
const uint ApplicationConfigFieldCount = 27;
7172

7273
const string ApplicationConfigSymbolName = "application_config";
7374
const string AppEnvironmentVariablesSymbolName = "app_environment_variables";
@@ -301,37 +302,42 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile)
301302
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
302303
break;
303304

304-
case 19: // android_runtime_jnienv_class_token: uint32_t / .word | .long
305+
case 19: // number_of_aot_cache_entries: uint32_t / .word | .long
305306
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
306-
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
307+
ret.number_of_aot_cache_entries = ConvertFieldToUInt32 ("number_of_aot_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
307308
break;
308309

309-
case 20: // jnienv_initialize_method_token: uint32_t / .word | .long
310+
case 20: // android_runtime_jnienv_class_token: uint32_t / .word | .long
310311
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
311-
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
312+
ret.android_runtime_jnienv_class_token = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
312313
break;
313314

314-
case 21: // jnienv_registerjninatives_method_token: uint32_t / .word | .long
315+
case 21: // jnienv_initialize_method_token: uint32_t / .word | .long
315316
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
316-
ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
317+
ret.jnienv_initialize_method_token = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
317318
break;
318319

319-
case 22: // jni_remapping_replacement_type_count: uint32_t / .word | .long
320+
case 22: // jnienv_registerjninatives_method_token: uint32_t / .word | .long
321+
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
322+
ret.jnienv_registerjninatives_method_token = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
323+
break;
324+
325+
case 23: // jni_remapping_replacement_type_count: uint32_t / .word | .long
320326
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
321327
ret.jni_remapping_replacement_type_count = ConvertFieldToUInt32 ("jni_remapping_replacement_type_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
322328
break;
323329

324-
case 23: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long
330+
case 24: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long
325331
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
326332
ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
327333
break;
328334

329-
case 24: // mono_components_mask: uint32_t / .word | .long
335+
case 25: // mono_components_mask: uint32_t / .word | .long
330336
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
331337
ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
332338
break;
333339

334-
case 25: // android_package_name: string / [pointer type]
340+
case 26: // android_package_name: string / [pointer type]
335341
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
336342
pointers.Add (field [1].Trim ());
337343
break;

src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ sealed class ApplicationConfig
4242
public uint number_of_assemblies_in_apk;
4343
public uint bundled_assembly_name_width;
4444
public uint number_of_dso_cache_entries;
45+
public uint number_of_aot_cache_entries;
4546
public uint number_of_shared_libraries;
4647

4748
[NativeAssembler (NumberFormat = LLVMIR.LlvmIrVariableNumberFormat.Hexadecimal)]

src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ sealed class XamarinAndroidBundledAssembly
155155
SortedDictionary <string, string>? systemProperties;
156156
StructureInstance? application_config;
157157
List<StructureInstance<DSOCacheEntry>>? dsoCache;
158+
List<StructureInstance<DSOCacheEntry>>? aotDsoCache;
158159
List<StructureInstance<XamarinAndroidBundledAssembly>>? xamarinAndroidBundledAssemblies;
159160

160161
StructureInfo? applicationConfigStructureInfo;
@@ -218,7 +219,7 @@ protected override void Construct (LlvmIrModule module)
218219
};
219220
module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs");
220221

221-
dsoCache = InitDSOCache ();
222+
(dsoCache, aotDsoCache) = InitDSOCache ();
222223
var app_cfg = new ApplicationConfig {
223224
uses_mono_llvm = UsesMonoLLVM,
224225
uses_mono_aot = UsesMonoAOT,
@@ -239,6 +240,7 @@ protected override void Construct (LlvmIrModule module)
239240
number_of_shared_libraries = (uint)NativeLibraries.Count,
240241
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
241242
number_of_dso_cache_entries = (uint)dsoCache.Count,
243+
number_of_aot_cache_entries = (uint)aotDsoCache.Count,
242244
android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken,
243245
jnienv_initialize_method_token = (uint)JNIEnvInitializeToken,
244246
jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken,
@@ -256,6 +258,12 @@ protected override void Construct (LlvmIrModule module)
256258
};
257259
module.Add (dso_cache);
258260

261+
var aot_dso_cache = new LlvmIrGlobalVariable (aotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
262+
Comment = " AOT DSO cache entries",
263+
BeforeWriteCallback = HashAndSortDSOCache,
264+
};
265+
module.Add (aot_dso_cache);
266+
259267
var dso_apk_entries = new LlvmIrGlobalVariable (typeof(List<StructureInstance<DSOApkEntry>>), "dso_apk_entries") {
260268
ArrayItemCount = (ulong)NativeLibraries.Count,
261269
Options = LlvmIrVariableOptions.GlobalWritable,
@@ -337,7 +345,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
337345
cache.Sort ((StructureInstance<DSOCacheEntry> a, StructureInstance<DSOCacheEntry> b) => a.Instance.hash.CompareTo (b.Instance.hash));
338346
}
339347

340-
List<StructureInstance<DSOCacheEntry>> InitDSOCache ()
348+
(List<StructureInstance<DSOCacheEntry>> dsoCache, List<StructureInstance<DSOCacheEntry>> aotDsoCache) InitDSOCache ()
341349
{
342350
var dsos = new List<(string name, string nameLabel, bool ignore)> ();
343351
var nameCache = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
@@ -357,6 +365,7 @@ List<StructureInstance<DSOCacheEntry>> InitDSOCache ()
357365
}
358366

359367
var dsoCache = new List<StructureInstance<DSOCacheEntry>> ();
368+
var aotDsoCache = new List<StructureInstance<DSOCacheEntry>> ();
360369
var nameMutations = new List<string> ();
361370

362371
for (int i = 0; i < dsos.Count; i++) {
@@ -372,11 +381,16 @@ List<StructureInstance<DSOCacheEntry>> InitDSOCache ()
372381
name = name,
373382
};
374383

375-
dsoCache.Add (new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry));
384+
var item = new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry);
385+
if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) {
386+
aotDsoCache.Add (item);
387+
} else {
388+
dsoCache.Add (item);
389+
}
376390
}
377391
}
378392

379-
return dsoCache;
393+
return (dsoCache, aotDsoCache);
380394

381395
void AddNameMutations (string name)
382396
{

src/native/monodroid/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ set(XAMARIN_MONODROID_SOURCES
6060
monodroid-tracing.cc
6161
monovm-properties.cc
6262
osbridge.cc
63-
pinvoke-override-api.cc
6463
runtime-util.cc
6564
timezones.cc
6665
xamarin_getifaddrs.cc

src/native/monodroid/pinvoke-override-api.cc

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/native/pinvoke-override/pinvoke-override-api-impl.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ namespace xamarin::android {
1919
void *lib_handle = dso_handle == nullptr ? nullptr : *dso_handle;
2020

2121
if (lib_handle == nullptr) {
22-
lib_handle = internal::MonodroidDl::monodroid_dlopen (library_name, MONO_DL_LOCAL, nullptr, nullptr);
22+
// We're being called as part of the p/invoke mechanism, we don't need to look in the AOT cache
23+
constexpr bool PREFER_AOT_CACHE = false;
24+
lib_handle = internal::MonodroidDl::monodroid_dlopen (library_name, MONO_DL_LOCAL, nullptr, PREFER_AOT_CACHE);
2325
if (lib_handle == nullptr) {
2426
log_warn (LOG_ASSEMBLY, "Shared library '%s' not loaded, p/invoke '%s' may fail", library_name, symbol_name);
2527
return nullptr;

src/native/runtime-base/monodroid-dl.hh

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ namespace xamarin::android::internal
1919
{
2020
class MonodroidDl
2121
{
22+
enum class CacheKind
23+
{
24+
// Access AOT cache
25+
AOT,
26+
27+
// Access DSO cache
28+
DSO,
29+
};
30+
2231
static inline xamarin::android::mutex dso_handle_write_lock;
2332

2433
static unsigned int convert_dl_flags (int flags) noexcept
@@ -29,18 +38,48 @@ namespace xamarin::android::internal
2938
return lflags;
3039
}
3140

32-
static DSOCacheEntry* find_dso_cache_entry (hash_t hash) noexcept
41+
template<CacheKind WhichCache>
42+
[[gnu::always_inline, gnu::flatten]]
43+
static DSOCacheEntry* find_dso_cache_entry_common (hash_t hash) noexcept
3344
{
45+
static_assert (WhichCache == CacheKind::AOT || WhichCache == CacheKind::DSO, "Unknown cache type specified");
46+
47+
DSOCacheEntry *arr;
48+
size_t arr_size;
49+
50+
if constexpr (WhichCache == CacheKind::AOT) {
51+
log_debug (LOG_ASSEMBLY, "Looking for hash 0x%x in AOT cache", hash);
52+
arr = aot_dso_cache;
53+
arr_size = application_config.number_of_aot_cache_entries;
54+
} else if constexpr (WhichCache == CacheKind::DSO) {
55+
log_debug (LOG_ASSEMBLY, "Looking for hash 0x%x in DSO cache", hash);
56+
arr = dso_cache;
57+
arr_size = application_config.number_of_dso_cache_entries;
58+
}
59+
3460
auto equal = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash == key; };
3561
auto less_than = [](DSOCacheEntry const& entry, hash_t key) -> bool { return entry.hash < key; };
36-
ssize_t idx = Search::binary_search<DSOCacheEntry, equal, less_than> (hash, dso_cache, application_config.number_of_dso_cache_entries);
62+
ssize_t idx = Search::binary_search<DSOCacheEntry, equal, less_than> (hash, arr, arr_size);
63+
3764
if (idx >= 0) {
38-
return &dso_cache[idx];
65+
return &arr[idx];
3966
}
4067

4168
return nullptr;
4269
}
4370

71+
[[gnu::always_inline, gnu::flatten]]
72+
static DSOCacheEntry* find_only_aot_cache_entry (hash_t hash) noexcept
73+
{
74+
return find_dso_cache_entry_common<CacheKind::AOT> (hash);
75+
}
76+
77+
[[gnu::always_inline, gnu::flatten]]
78+
static DSOCacheEntry* find_only_dso_cache_entry (hash_t hash) noexcept
79+
{
80+
return find_dso_cache_entry_common<CacheKind::DSO> (hash);
81+
}
82+
4483
static void* monodroid_dlopen_log_and_return (void *handle, char **err, const char *full_name, bool free_memory)
4584
{
4685
if (handle == nullptr && err != nullptr) {
@@ -103,7 +142,7 @@ namespace xamarin::android::internal
103142

104143
public:
105144
[[gnu::flatten]]
106-
static void* monodroid_dlopen (const char *name, int flags, char **err, [[maybe_unused]] void *user_data) noexcept
145+
static void* monodroid_dlopen (const char *name, int flags, char **err, bool prefer_aot_cache) noexcept
107146
{
108147
if (name == nullptr) {
109148
log_warn (LOG_ASSEMBLY, "monodroid_dlopen got a null name. This is not supported in NET+");
@@ -112,7 +151,22 @@ namespace xamarin::android::internal
112151

113152
hash_t name_hash = xxhash::hash (name, strlen (name));
114153
log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash for name '%s' is 0x%zx", name, name_hash);
115-
DSOCacheEntry *dso = find_dso_cache_entry (name_hash);
154+
155+
DSOCacheEntry *dso = nullptr;
156+
if (prefer_aot_cache) {
157+
// If we're asked to look in the AOT DSO cache, do it first. This is because we're likely called from the
158+
// MonoVM's dlopen fallback handler and it will not be a request to resolved a p/invoke, but most likely to
159+
// find and load an AOT image for a managed assembly. Since there might be naming/hash conflicts in this
160+
// scenario, we look at the AOT cache first.
161+
//
162+
// See: https://github.com/dotnet/android/issues/9081
163+
dso = find_only_aot_cache_entry (name_hash);
164+
}
165+
166+
if (dso == nullptr) {
167+
dso = find_only_dso_cache_entry (name_hash);
168+
}
169+
116170
log_debug (LOG_ASSEMBLY, "monodroid_dlopen: hash match %sfound, DSO name is '%s'", dso == nullptr ? "not " : "", dso == nullptr ? "<unknown>" : dso->name);
117171

118172
if (dso == nullptr) {
@@ -161,6 +215,15 @@ namespace xamarin::android::internal
161215
return monodroid_dlopen_log_and_return (dso->handle, err, name, false /* name_needs_free */);
162216
}
163217

218+
[[gnu::flatten]]
219+
static void* monodroid_dlopen (const char *name, int flags, char **err, [[maybe_unused]] void *user_data) noexcept
220+
{
221+
// We're called by MonoVM via a callback, we might need to return an AOT DSO.
222+
// See: https://github.com/dotnet/android/issues/9081
223+
constexpr bool PREFER_AOT_CACHE = true;
224+
return monodroid_dlopen (name, flags, err, PREFER_AOT_CACHE);
225+
}
226+
164227
[[gnu::flatten]]
165228
static void* monodroid_dlsym (void *handle, const char *name, char **err, [[maybe_unused]] void *user_data)
166229
{

src/native/xamarin-app-stub/application_dso_stub.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,24 @@ DSOCacheEntry dso_cache[] = {
145145
},
146146
};
147147

148+
DSOCacheEntry aot_dso_cache[] = {
149+
{
150+
.hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1),
151+
.real_name_hash = xamarin::android::xxhash::hash (fake_dso_name, sizeof(fake_dso_name) - 1),
152+
.ignore = true,
153+
.name = fake_dso_name,
154+
.handle = nullptr,
155+
},
156+
157+
{
158+
.hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1),
159+
.real_name_hash = xamarin::android::xxhash::hash (fake_dso_name2, sizeof(fake_dso_name2) - 1),
160+
.ignore = true,
161+
.name = fake_dso_name2,
162+
.handle = nullptr,
163+
},
164+
};
165+
148166
DSOApkEntry dso_apk_entries[2] {};
149167

150168
//

src/native/xamarin-app-stub/xamarin-app.hh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ enum class MonoComponent : uint32_t
227227
Tracing = 0x04,
228228
};
229229

230+
// Keep in strict sync with:
231+
// src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
232+
// src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs
230233
struct ApplicationConfig
231234
{
232235
bool uses_mono_llvm;
@@ -247,6 +250,7 @@ struct ApplicationConfig
247250
uint32_t number_of_assemblies_in_apk;
248251
uint32_t bundled_assembly_name_width;
249252
uint32_t number_of_dso_cache_entries;
253+
uint32_t number_of_aot_cache_entries;
250254
uint32_t number_of_shared_libraries;
251255
uint32_t android_runtime_jnienv_class_token;
252256
uint32_t jnienv_initialize_method_token;
@@ -336,6 +340,7 @@ MONO_API MONO_API_EXPORT AssemblyStoreSingleAssemblyRuntimeData assembly_store_b
336340
MONO_API MONO_API_EXPORT AssemblyStoreRuntimeData assembly_store;
337341

338342
MONO_API MONO_API_EXPORT DSOCacheEntry dso_cache[];
343+
MONO_API MONO_API_EXPORT DSOCacheEntry aot_dso_cache[];
339344
MONO_API MONO_API_EXPORT DSOApkEntry dso_apk_entries[];
340345

341346
//

0 commit comments

Comments
 (0)