Skip to content

Commit f90e889

Browse files
grendellojonathanpeppers
authored andcommitted
[native] Fix JNI preload cache generation (#10547)
Fixes: #10544 Context: 1a62af3 Context: cba39dc cba39dc introduced code to preload at startup native libraries which use JNI, to work around an issue in Android which prevents such libraries from being properly loaded at the later time of application life. Part of the workaround was support for updating handle of such a library in our shared library cache. Since every library has different entries in the cache (because we search the array using xxHash generated from various forms of the library name), after preloading it we had to update every entry in the DSO cache with the correct handle, so that the library is never loaded again. The code which generates the caches and indexes at application build time worked fine in my testing (using `dotnet build`) but it turns out that using `dotnet publish` instead breaks the code in a subtle, but nasty way. The issue is that the code which generated the index of shared libraries to preload reused an array which stored the indexes, while generating code for different RIDs. This resulted in the very first RID to process to contain valid indexes, the second one would **append** its own indexes to the preceding RID's data, the third RID would further append its own data etc. This would result in the following index code generated for the subsequend RIDs: ``` ; android-arm ; Indices into dso_cache[] of DSO libraries to preload because of JNI use @dso_jni_preloads_idx = dso_local local_unnamed_addr constant [4 x i32] [ i32 15, ; libSystem.Security.Cryptography.Native.Android.so i32 0, ; libSystem.Security.Cryptography.Native.Android i32 7, ; System.Security.Cryptography.Native.Android.so i32 8 ; System.Security.Cryptography.Native.Android ], align 4 ``` ``` ; android-arm64 ; Indices into dso_cache[] of DSO libraries to preload because of JNI use @dso_jni_preloads_idx = dso_local local_unnamed_addr constant [8 x i32] [ i32 15, ; libSystem.Security.Cryptography.Native.Android.so i32 0, ; libSystem.Security.Cryptography.Native.Android i32 7, ; System.Security.Cryptography.Native.Android.so i32 8, ; System.Security.Cryptography.Native.Android i32 10, ; Invalid index 4 i32 0, ; Invalid index 5 i32 1, ; Invalid index 6 i32 14 ; Invalid index 7 ], align 4 ``` ``` ; android-x64 ; Indices into dso_cache[] of DSO libraries to preload because of JNI use @dso_jni_preloads_idx = dso_local local_unnamed_addr constant [12 x i32] [ i32 15, ; libSystem.Security.Cryptography.Native.Android.so i32 0, ; libSystem.Security.Cryptography.Native.Android i32 7, ; System.Security.Cryptography.Native.Android.so i32 8, ; System.Security.Cryptography.Native.Android i32 10, ; Invalid index 4 i32 0, ; Invalid index 5 i32 1, ; Invalid index 6 i32 14, ; Invalid index 7 i32 10, ; Invalid index 8 i32 0, ; Invalid index 9 i32 1, ; Invalid index 10 i32 14 ; Invalid index 11 ], align 16 ``` In effect, when running on an arm64 device, we would try to load, and cache the handle, of an entirely different shared library, leading to further problems to find a requested symbol: ``` 10-17 11:25:18.062 27900 27900 F monodroid-assembly: Failed to load symbol 'AndroidCryptoNative_EcKeyCreateByKeyParameters' from shared library 'libSystem.Security.Cryptography.Native.Android' 10-17 11:25:18.110 1444 4298 I ActivityManager: Process com.companyname.test_jwt (pid 27900) has died: fg TOP 10-17 11:25:18.110 1444 1712 I libprocessgroup: Removed cgroup /sys/fs/cgroup/apps/uid_10361/pid_27900 10-17 11:25:18.111 913 913 I Zygote : Process 27900 exited cleanly (0) ``` Native code attempted to load the symbol from a library that happened to be stored at the index valid for `android-arm` but not for e.g. `android-arm64`, which was not `libSystem.Security.Cryptography.Native.Android`, leading to the above red herring error. Note: if **all** of RIDs enabled for the application are 32-bit or **all** of them are 64-bit, things would work even though the generated code would be technically incorrect. This is because all of the hashes and, thus, sort order of the `dso_cache` entries would be the same. Fix this by making sure that the index array is not shared between different RIDs when generating the DSO cache code.
1 parent 4860105 commit f90e889

File tree

4 files changed

+8
-14
lines changed

4 files changed

+8
-14
lines changed

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ protected override void Construct (LlvmIrModule module)
279279
module.AddGlobalVariable ("dso_jni_preloads_idx_stride", dsoState.NameMutationsCount);
280280

281281
// This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache
282-
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List<uint> (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
282+
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (typeof(List<uint>), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
283283
Comment = " Indices into dso_cache[] of DSO libraries to preload because of JNI use",
284284
ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count,
285285
GetArrayItemCommentCallback = GetPreloadIndicesLibraryName,
@@ -369,11 +369,6 @@ void AddAssemblyStores (LlvmIrModule module)
369369

370370
void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
371371
{
372-
var indices = variable.Value as List<uint>;
373-
if (indices == null) {
374-
throw new InvalidOperationException ($"Internal error: DSO preload indices list instance not present.");
375-
}
376-
377372
var dsoState = state as DsoCacheState;
378373
if (dsoState == null) {
379374
throw new InvalidOperationException ($"Internal error: DSO state not present.");
@@ -382,6 +377,8 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target,
382377
var dsoNames = new List<string> ();
383378

384379
// Indices array MUST NOT be sorted, since it groups alias entries together with the main entry
380+
var indices = new List<uint> ();
381+
variable.Value = indices;
385382
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
386383
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
387384
if (entry.Instance == null) {

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ protected override void Construct (LlvmIrModule module)
383383
module.AddGlobalVariable ("dso_jni_preloads_idx_stride", dsoState.NameMutationsCount);
384384

385385
// This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache
386-
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List<uint> (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
386+
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (typeof (List<uint>), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
387387
Comment = " Indices into dso_cache[] of DSO libraries to preload because of JNI use",
388388
ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count,
389389
GetArrayItemCommentCallback = GetPreloadIndicesLibraryName,
@@ -601,11 +601,6 @@ void PopulateDsoApkEntries (LlvmIrVariable variable, LlvmIrModuleTarget target,
601601

602602
void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
603603
{
604-
var indices = variable.Value as List<uint>;
605-
if (indices == null) {
606-
throw new InvalidOperationException ("Internal error: DSO preload indices list instance not present.");
607-
}
608-
609604
var dsoState = state as DsoCacheState;
610605
if (dsoState == null) {
611606
throw new InvalidOperationException ("Internal error: DSO state not present.");
@@ -614,6 +609,8 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target,
614609
var dsoNames = new List<string> ();
615610

616611
// Indices array MUST NOT be sorted, since it groups alias entries together with the main entry
612+
var indices = new List<uint> ();
613+
variable.Value = indices;
617614
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
618615
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
619616
if (entry.Instance == null) {

src/native/clr/include/host/pinvoke-override-impl.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ namespace xamarin::android {
5757

5858
void *entry_handle = MonodroidDl::monodroid_dlsym (lib_handle, symbol_name);
5959
if (entry_handle == nullptr) {
60-
log_warn (LOG_ASSEMBLY, "Symbol '{}' not found in shared library '{}', p/invoke may fail", library_name, symbol_name);
60+
log_warn (LOG_ASSEMBLY, "Symbol '{}' not found in shared library '{}', p/invoke may fail", symbol_name, library_name);
6161
return nullptr;
6262
}
6363

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ namespace xamarin::android {
3737

3838
void *entry_handle = internal::MonodroidDl::monodroid_dlsym (lib_handle, symbol_name, nullptr, nullptr);
3939
if (entry_handle == nullptr) {
40-
log_warn (LOG_ASSEMBLY, "Symbol '{}' not found in shared library '{}', p/invoke may fail", optional_string (library_name), optional_string (symbol_name));
40+
log_warn (LOG_ASSEMBLY, "Symbol '{}' not found in shared library '{}', p/invoke may fail", optional_string (symbol_name), optional_string (library_name));
4141
return nullptr;
4242
}
4343

0 commit comments

Comments
 (0)