Skip to content

Conversation

@grendello
Copy link
Contributor

@grendello grendello commented Oct 17, 2025

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.

@jonathanpeppers jonathanpeppers merged commit 267e129 into main Oct 17, 2025
59 checks passed
@jonathanpeppers jonathanpeppers deleted the dev/grendel/pinvokes-wth branch October 17, 2025 17:47
jonathanpeppers pushed a commit that referenced this pull request Oct 17, 2025
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.
@github-actions github-actions bot locked and limited conversation to collaborators Nov 17, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[mono] App hangs at startup when using dotnet publish -t:Run and different combination of RIDs

3 participants