Skip to content

Commit 4cf5d8f

Browse files
committed
Managed part of JNI DSO preloading done
1 parent 8ba9f03 commit 4cf5d8f

File tree

2 files changed

+145
-24
lines changed

2 files changed

+145
-24
lines changed

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

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,19 @@ sealed class XamarinAndroidBundledAssembly
157157
}
158158
#pragma warning restore CS0649
159159

160+
sealed class DsoCacheState
161+
{
162+
public List<StructureInstance<DSOCacheEntry>> DsoCache = [];
163+
public List<DSOCacheEntry> JniPreloadDSOs = [];
164+
public List<StructureInstance<DSOCacheEntry>> AotDsoCache = [];
165+
}
166+
160167
// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
161168
const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
162169

163170
SortedDictionary <string, string>? environmentVariables;
164171
SortedDictionary <string, string>? systemProperties;
165172
StructureInstance? application_config;
166-
List<StructureInstance<DSOCacheEntry>>? dsoCache;
167-
List<StructureInstance<DSOCacheEntry>>? aotDsoCache;
168173
List<StructureInstance<XamarinAndroidBundledAssembly>>? xamarinAndroidBundledAssemblies;
169174

170175
StructureInfo? applicationConfigStructureInfo;
@@ -230,7 +235,7 @@ protected override void Construct (LlvmIrModule module)
230235
};
231236
module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs");
232237

233-
(dsoCache, aotDsoCache) = InitDSOCache ();
238+
DsoCacheState dsoState = InitDSOCache ();
234239
var app_cfg = new ApplicationConfig {
235240
uses_mono_llvm = UsesMonoLLVM,
236241
uses_mono_aot = UsesMonoAOT,
@@ -250,8 +255,8 @@ protected override void Construct (LlvmIrModule module)
250255
number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
251256
number_of_shared_libraries = (uint)NativeLibraries.Count,
252257
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
253-
number_of_dso_cache_entries = (uint)dsoCache.Count,
254-
number_of_aot_cache_entries = (uint)aotDsoCache.Count,
258+
number_of_dso_cache_entries = (uint)dsoState.DsoCache.Count,
259+
number_of_aot_cache_entries = (uint)dsoState.AotDsoCache.Count,
255260
android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken,
256261
jnienv_initialize_method_token = (uint)JNIEnvInitializeToken,
257262
jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken,
@@ -263,13 +268,22 @@ protected override void Construct (LlvmIrModule module)
263268
application_config = new StructureInstance<ApplicationConfig> (applicationConfigStructureInfo, app_cfg);
264269
module.AddGlobalVariable ("application_config", application_config);
265270

266-
var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
271+
var dso_cache = new LlvmIrGlobalVariable (dsoState.DsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
267272
Comment = " DSO cache entries",
268273
BeforeWriteCallback = HashAndSortDSOCache,
269274
};
270275
module.Add (dso_cache);
271276

272-
var aot_dso_cache = new LlvmIrGlobalVariable (aotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
277+
// This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache
278+
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List<uint> (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
279+
Comment = "Indices of DSO libraries to preload because of JNI use",
280+
ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count,
281+
BeforeWriteCallback = PopulatePreloadIndices,
282+
BeforeWriteCallbackCallerState = dsoState,
283+
};
284+
module.Add (dso_jni_preloads_idx);
285+
286+
var aot_dso_cache = new LlvmIrGlobalVariable (dsoState.AotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
273287
Comment = " AOT DSO cache entries",
274288
BeforeWriteCallback = HashAndSortDSOCache,
275289
};
@@ -331,6 +345,36 @@ void AddAssemblyStores (LlvmIrModule module)
331345
module.Add (assembly_store);
332346
}
333347

348+
void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
349+
{
350+
var indices = variable.Value as List<uint>;
351+
if (indices == null) {
352+
throw new InvalidOperationException ($"Internal error: DSO preload indices list instance not present.");
353+
}
354+
355+
var dsoState = state as DsoCacheState;
356+
if (dsoState == null) {
357+
throw new InvalidOperationException ($"Internal error: DSO state not present.");
358+
}
359+
360+
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
361+
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
362+
if (entry.Instance == null) {
363+
return false;
364+
}
365+
366+
return entry.Instance.hash == preload.hash && entry.Instance.real_name_hash == preload.real_name_hash;
367+
});
368+
369+
if (dsoIdx == -1) {
370+
throw new InvalidOperationException ($"Internal error: DSO entry in JNI preload list not found in the DSO cache list.");
371+
}
372+
373+
indices.Add ((uint)dsoIdx);
374+
}
375+
indices.Sort ();
376+
}
377+
334378
void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
335379
{
336380
var cache = variable.Value as List<StructureInstance<DSOCacheEntry>>;
@@ -359,7 +403,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
359403
});
360404
}
361405

362-
(List<StructureInstance<DSOCacheEntry>> dsoCache, List<StructureInstance<DSOCacheEntry>> aotDsoCache) InitDSOCache ()
406+
DsoCacheState InitDSOCache ()
363407
{
364408
var dsos = new List<(string name, string nameLabel, bool ignore, ITaskItem item)> ();
365409
var nameCache = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
@@ -379,23 +423,33 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
379423
}
380424

381425
var dsoCache = new List<StructureInstance<DSOCacheEntry>> ();
426+
var jniPreloads = new List<DSOCacheEntry> ();
382427
var aotDsoCache = new List<StructureInstance<DSOCacheEntry>> ();
383428
var nameMutations = new List<string> ();
384429

385430
for (int i = 0; i < dsos.Count; i++) {
386431
string name = dsos[i].name;
432+
433+
bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec);
434+
bool ignore = dsos[i].ignore;
435+
387436
nameMutations.Clear();
388437
AddNameMutations (name);
438+
389439
// All mutations point to the actual library name, but have hash of the mutated one
390440
foreach (string entryName in nameMutations) {
391441
var entry = new DSOCacheEntry {
392442
HashedName = entryName,
393443
hash = 0, // Hash is arch-specific, we compute it before writing
394-
ignore = dsos[i].ignore,
395-
is_jni_library = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec),
444+
ignore = ignore,
445+
is_jni_library = isJniLibrary,
396446
name = name,
397447
};
398448

449+
if (entry.is_jni_library && entry.HashedName == name && !ApplicationConfigNativeAssemblyGeneratorCLR.DsoCacheJniPreloadIgnore.Contains (name)) {
450+
jniPreloads.Add (entry);
451+
}
452+
399453
var item = new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry);
400454
if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) {
401455
aotDsoCache.Add (item);
@@ -405,7 +459,11 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
405459
}
406460
}
407461

408-
return (dsoCache, aotDsoCache);
462+
return new DsoCacheState {
463+
DsoCache = dsoCache,
464+
AotDsoCache = aotDsoCache,
465+
JniPreloadDSOs = jniPreloads,
466+
};
409467

410468
void AddNameMutations (string name)
411469
{

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

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,27 @@ sealed class XamarinAndroidBundledAssembly
239239
}
240240
#pragma warning restore CS0649
241241

242+
sealed class DsoCacheState
243+
{
244+
public List<StructureInstance<DSOCacheEntry>> DsoCache = [];
245+
public List<DSOCacheEntry> JniPreloadDSOs = [];
246+
public List<StructureInstance<DSOCacheEntry>> AotDsoCache = [];
247+
public LlvmIrStringBlob NamesBlob = null!;
248+
}
249+
242250
// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
243251
const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
244252

253+
// List of library names to ignore when generating the list of JNI-using libraries to preload
254+
internal static readonly HashSet<string> DsoCacheJniPreloadIgnore = new (StringComparer.OrdinalIgnoreCase) {
255+
"libmonodroid.so",
256+
};
257+
245258
SortedDictionary <string, string>? environmentVariables;
246259
SortedDictionary <string, string>? systemProperties;
247260
SortedDictionary <string, string>? runtimeProperties;
248261
StructureInstance? application_config;
249-
List<StructureInstance<DSOCacheEntry>>? dsoCache;
250-
List<StructureInstance<DSOCacheEntry>>? aotDsoCache;
262+
251263
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value - assigned conditionally by build process
252264
List<StructureInstance<XamarinAndroidBundledAssembly>>? xamarinAndroidBundledAssemblies;
253265
#pragma warning restore CS0649
@@ -350,7 +362,7 @@ protected override void Construct (LlvmIrModule module)
350362
};
351363
module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs");
352364

353-
(dsoCache, aotDsoCache, LlvmIrStringBlob dsoNamesBlob) = InitDSOCache ();
365+
DsoCacheState dsoState = InitDSOCache ();
354366
var app_cfg = new ApplicationConfigCLR {
355367
uses_assembly_preload = UsesAssemblyPreload,
356368
jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent,
@@ -364,8 +376,8 @@ protected override void Construct (LlvmIrModule module)
364376
number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
365377
number_of_shared_libraries = (uint)NativeLibraries.Count,
366378
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
367-
number_of_dso_cache_entries = (uint)dsoCache.Count,
368-
number_of_aot_cache_entries = (uint)aotDsoCache.Count,
379+
number_of_dso_cache_entries = (uint)dsoState.DsoCache.Count,
380+
number_of_aot_cache_entries = (uint)dsoState.AotDsoCache.Count,
369381
android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken,
370382
jnienv_initialize_method_token = (uint)JNIEnvInitializeToken,
371383
jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken,
@@ -376,18 +388,27 @@ protected override void Construct (LlvmIrModule module)
376388
application_config = new StructureInstance<ApplicationConfigCLR> (applicationConfigStructureInfo, app_cfg);
377389
module.AddGlobalVariable ("application_config", application_config);
378390

379-
var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
391+
var dso_cache = new LlvmIrGlobalVariable (dsoState.DsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
380392
Comment = " DSO cache entries",
381393
BeforeWriteCallback = HashAndSortDSOCache,
382394
};
383395
module.Add (dso_cache);
384396

385-
var aot_dso_cache = new LlvmIrGlobalVariable (aotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
397+
// This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache
398+
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List<uint> (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
399+
Comment = "Indices of DSO libraries to preload because of JNI use",
400+
ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count,
401+
BeforeWriteCallback = PopulatePreloadIndices,
402+
BeforeWriteCallbackCallerState = dsoState,
403+
};
404+
module.Add (dso_jni_preloads_idx);
405+
406+
var aot_dso_cache = new LlvmIrGlobalVariable (dsoState.AotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
386407
Comment = " AOT DSO cache entries",
387408
BeforeWriteCallback = HashAndSortDSOCache,
388409
};
389410
module.Add (aot_dso_cache);
390-
module.AddGlobalVariable ("dso_names_data", dsoNamesBlob, LlvmIrVariableOptions.GlobalConstant);
411+
module.AddGlobalVariable ("dso_names_data", dsoState.NamesBlob, LlvmIrVariableOptions.GlobalConstant);
391412

392413
var dso_apk_entries = new LlvmIrGlobalVariable (typeof(List<StructureInstance<DSOApkEntry>>), "dso_apk_entries") {
393414
ArrayItemCount = (ulong)NativeLibraries.Count,
@@ -544,6 +565,36 @@ void AddAssemblyStores (LlvmIrModule module)
544565
module.Add (assembly_store);
545566
}
546567

568+
void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
569+
{
570+
var indices = variable.Value as List<uint>;
571+
if (indices == null) {
572+
throw new InvalidOperationException ($"Internal error: DSO preload indices list instance not present.");
573+
}
574+
575+
var dsoState = state as DsoCacheState;
576+
if (dsoState == null) {
577+
throw new InvalidOperationException ($"Internal error: DSO state not present.");
578+
}
579+
580+
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
581+
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
582+
if (entry.Instance == null) {
583+
return false;
584+
}
585+
586+
return entry.Instance.hash == preload.hash && entry.Instance.real_name_hash == preload.real_name_hash;
587+
});
588+
589+
if (dsoIdx == -1) {
590+
throw new InvalidOperationException ($"Internal error: DSO entry in JNI preload list not found in the DSO cache list.");
591+
}
592+
593+
indices.Add ((uint)dsoIdx);
594+
}
595+
indices.Sort ();
596+
}
597+
547598
void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
548599
{
549600
var cache = variable.Value as List<StructureInstance<DSOCacheEntry>>;
@@ -572,8 +623,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
572623
});
573624
}
574625

575-
(List<StructureInstance<DSOCacheEntry>> dsoCache, List<StructureInstance<DSOCacheEntry>> aotDsoCache, LlvmIrStringBlob namesBlob)
576-
InitDSOCache ()
626+
DsoCacheState InitDSOCache ()
577627
{
578628
var dsos = new List<(string name, string nameLabel, bool ignore, ITaskItem item)> ();
579629
var nameCache = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
@@ -593,6 +643,7 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
593643
}
594644

595645
var dsoCache = new List<StructureInstance<DSOCacheEntry>> ();
646+
var jniPreloads = new List<DSOCacheEntry> ();
596647
var aotDsoCache = new List<StructureInstance<DSOCacheEntry>> ();
597648
var nameMutations = new List<string> ();
598649
var dsoNamesBlob = new LlvmIrStringBlob ();
@@ -601,6 +652,9 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
601652
string name = dsos[i].name;
602653
(int nameOffset, _) = dsoNamesBlob.Add (name);
603654

655+
bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec);
656+
bool ignore = dsos[i].ignore;
657+
604658
nameMutations.Clear();
605659
AddNameMutations (name);
606660
// All mutations point to the actual library name, but have hash of the mutated one
@@ -610,11 +664,15 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
610664
RealName = name,
611665

612666
hash = 0, // Hash is arch-specific, we compute it before writing
613-
ignore = dsos[i].ignore,
614-
is_jni_library = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec),
667+
ignore = ignore,
668+
is_jni_library = isJniLibrary,
615669
name_index = (uint)nameOffset,
616670
};
617671

672+
if (entry.is_jni_library && entry.HashedName == name && !DsoCacheJniPreloadIgnore.Contains (name)) {
673+
jniPreloads.Add (entry);
674+
}
675+
618676
var item = new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry);
619677
if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) {
620678
aotDsoCache.Add (item);
@@ -624,7 +682,12 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
624682
}
625683
}
626684

627-
return (dsoCache, aotDsoCache, dsoNamesBlob);
685+
return new DsoCacheState {
686+
DsoCache = dsoCache,
687+
JniPreloadDSOs = jniPreloads,
688+
AotDsoCache = aotDsoCache,
689+
NamesBlob = dsoNamesBlob,
690+
};
628691

629692
void AddNameMutations (string name)
630693
{

0 commit comments

Comments
 (0)