Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ protected override void OnCreate (Bundle savedInstanceState)
//${AFTER_FORMS_INIT}
LoadApplication (new App ());
}
//${AFTER_ONCREATE}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ sealed class DsoCacheState
{
public List<StructureInstance<DSOCacheEntry>> DsoCache = [];
public List<DSOCacheEntry> JniPreloadDSOs = [];
public List<string> JniPreloadNames = [];
public List<StructureInstance<DSOCacheEntry>> AotDsoCache = [];
public uint NameMutationsCount = 1;
}

// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
Expand Down Expand Up @@ -274,10 +276,14 @@ protected override void Construct (LlvmIrModule module)
};
module.Add (dso_cache);

module.AddGlobalVariable ("dso_jni_preloads_idx_stride", dsoState.NameMutationsCount);

// 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<uint> (), "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,
GetArrayItemCommentCallback = GetPreloadIndicesLibraryName,
GetArrayItemCommentCallbackCallerState = dsoState,
BeforeWriteCallback = PopulatePreloadIndices,
BeforeWriteCallbackCallerState = dsoState,
};
Expand Down Expand Up @@ -346,6 +352,21 @@ void AddAssemblyStores (LlvmIrModule module)
module.Add (assembly_store);
}

string? GetPreloadIndicesLibraryName (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState)
{
// Instead of throwing for such a triviality like a comment, we will return error messages as comments instead
var dsoState = callerState as DsoCacheState;
if (dsoState == null) {
return " Internal error: DSO state not present.";
}

if (index >= (ulong)dsoState.JniPreloadNames.Count) {
return $" Invalid index {index}";
}

return $" {dsoState.JniPreloadNames[(int)index]}";
}

void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
{
var indices = variable.Value as List<uint>;
Expand All @@ -358,6 +379,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target,
throw new InvalidOperationException ($"Internal error: DSO state not present.");
}

var dsoNames = new List<string> ();

// Indices array MUST NOT be sorted, since it groups alias entries together with the main entry
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
if (entry.Instance == null) {
Expand All @@ -372,8 +396,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target,
}

indices.Add ((uint)dsoIdx);
dsoNames.Add (preload.HashedName ?? String.Empty);
}
indices.Sort ();
dsoState.JniPreloadNames = dsoNames;
}

void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
Expand Down Expand Up @@ -427,15 +452,20 @@ DsoCacheState InitDSOCache ()
var jniPreloads = new List<DSOCacheEntry> ();
var aotDsoCache = new List<StructureInstance<DSOCacheEntry>> ();
var nameMutations = new List<string> ();
int nameMutationsCount = -1;

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;
bool ignore_for_preload = !ApplicationConfigNativeAssemblyGeneratorCLR.DsoCacheJniPreloadIgnore.Contains (name);

nameMutations.Clear();
AddNameMutations (name);
if (nameMutationsCount == -1) {
nameMutationsCount = nameMutations.Count;
}

// All mutations point to the actual library name, but have hash of the mutated one
foreach (string entryName in nameMutations) {
Expand All @@ -447,23 +477,27 @@ DsoCacheState InitDSOCache ()
name = name,
};

if (entry.is_jni_library && entry.HashedName == name && !ApplicationConfigNativeAssemblyGeneratorCLR.DsoCacheJniPreloadIgnore.Contains (name)) {
jniPreloads.Add (entry);
}

var item = new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry);
if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) {
aotDsoCache.Add (item);
} else {
dsoCache.Add (item);
continue;
}

// We must add all aliases to the preloads indices array so that all of them have their handle
// set when the library is preloaded.
if (entry.is_jni_library && ignore_for_preload) {
jniPreloads.Add (entry);
}

dsoCache.Add (item);
}
}

return new DsoCacheState {
DsoCache = dsoCache,
AotDsoCache = aotDsoCache,
JniPreloadDSOs = jniPreloads,
NameMutationsCount = (uint)(nameMutationsCount <= 0 ? 1 : nameMutationsCount),
};

void AddNameMutations (string name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,10 @@ sealed class DsoCacheState
{
public List<StructureInstance<DSOCacheEntry>> DsoCache = [];
public List<DSOCacheEntry> JniPreloadDSOs = [];
public List<string> JniPreloadNames = [];
public List<StructureInstance<DSOCacheEntry>> AotDsoCache = [];
public LlvmIrStringBlob NamesBlob = null!;
public uint NameMutationsCount = 1;
}

// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
Expand Down Expand Up @@ -394,10 +396,14 @@ protected override void Construct (LlvmIrModule module)
};
module.Add (dso_cache);

module.AddGlobalVariable ("dso_jni_preloads_idx_stride", dsoState.NameMutationsCount);

// 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<uint> (), "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,
GetArrayItemCommentCallback = GetPreloadIndicesLibraryName,
GetArrayItemCommentCallbackCallerState = dsoState,
BeforeWriteCallback = PopulatePreloadIndices,
BeforeWriteCallbackCallerState = dsoState,
};
Expand Down Expand Up @@ -566,6 +572,21 @@ void AddAssemblyStores (LlvmIrModule module)
module.Add (assembly_store);
}

string? GetPreloadIndicesLibraryName (LlvmIrVariable v, LlvmIrModuleTarget target, ulong index, object? value, object? callerState)
{
// Instead of throwing for such a triviality like a comment, we will return error messages as comments instead
var dsoState = callerState as DsoCacheState;
if (dsoState == null) {
return " Internal error: DSO state not present.";
}

if (index >= (ulong)dsoState.JniPreloadNames.Count) {
return $" Invalid index {index}";
}

return $" {dsoState.JniPreloadNames[(int)index]}";
}

void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
{
var indices = variable.Value as List<uint>;
Expand All @@ -578,6 +599,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target,
throw new InvalidOperationException ($"Internal error: DSO state not present.");
}

var dsoNames = new List<string> ();

// Indices array MUST NOT be sorted, since it groups alias entries together with the main entry
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
if (entry.Instance == null) {
Expand All @@ -592,8 +616,9 @@ void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target,
}

indices.Add ((uint)dsoIdx);
dsoNames.Add (preload.HashedName ?? String.Empty);
}
indices.Sort ();
dsoState.JniPreloadNames = dsoNames;
}

void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
Expand Down Expand Up @@ -648,16 +673,22 @@ DsoCacheState InitDSOCache ()
var aotDsoCache = new List<StructureInstance<DSOCacheEntry>> ();
var nameMutations = new List<string> ();
var dsoNamesBlob = new LlvmIrStringBlob ();
int nameMutationsCount = -1;

for (int i = 0; i < dsos.Count; i++) {
string name = dsos[i].name;
(int nameOffset, _) = dsoNamesBlob.Add (name);

bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec);
bool ignore = dsos[i].ignore;
bool ignore_for_preload = !DsoCacheJniPreloadIgnore.Contains (name);

nameMutations.Clear();
AddNameMutations (name);
if (nameMutationsCount == -1) {
nameMutationsCount = nameMutations.Count;
}

// All mutations point to the actual library name, but have hash of the mutated one
foreach (string entryName in nameMutations) {
var entry = new DSOCacheEntry {
Expand All @@ -670,16 +701,19 @@ DsoCacheState InitDSOCache ()
name_index = (uint)nameOffset,
};

if (entry.is_jni_library && entry.HashedName == name && !DsoCacheJniPreloadIgnore.Contains (name)) {
jniPreloads.Add (entry);
}

var item = new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry);
if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) {
aotDsoCache.Add (item);
} else {
dsoCache.Add (item);
continue;
}

// We must add all aliases to the preloads indices array so that all of them have their handle
// set when the library is preloaded.
if (entry.is_jni_library && ignore_for_preload) {
jniPreloads.Add (entry);
}

dsoCache.Add (item);
}
}

Expand All @@ -688,6 +722,7 @@ DsoCacheState InitDSOCache ()
JniPreloadDSOs = jniPreloads,
AotDsoCache = aotDsoCache,
NamesBlob = dsoNamesBlob,
NameMutationsCount = (uint)(nameMutationsCount <= 0 ? 1 : nameMutationsCount),
};

void AddNameMutations (string name)
Expand Down
61 changes: 55 additions & 6 deletions src/native/clr/host/host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,60 @@ auto Host::create_delegate (
return delegate;
}

[[gnu::flatten, gnu::always_inline]]
void Host::preload_jni_libraries () noexcept
{
// NOTE: when fixing a bug here, fix also the MonoVM code in src/native/mono/monodroid-glue.cc@preload_jni_libraries
if (application_config.number_of_shared_libraries == 0) [[unlikely]] {
return;
}

log_debug (LOG_ASSEMBLY, "DSO jni preloads index stride == {}", dso_jni_preloads_idx_stride);

if ((dso_jni_preloads_idx_count % dso_jni_preloads_idx_stride) != 0) [[unlikely]] {
Helpers::abort_application (
LOG_ASSEMBLY,
std::format (
"DSO preload index is invalid, size ({}) is not a multiple of {}"sv,
dso_jni_preloads_idx_count,
dso_jni_preloads_idx_stride
)
);
}

for (size_t i = 0; i < dso_jni_preloads_idx_count; i += dso_jni_preloads_idx_stride) {
const size_t entry_index = dso_jni_preloads_idx[i];
DSOCacheEntry &entry = dso_cache[entry_index];
const std::string_view dso_name = MonodroidDl::get_dso_name (&entry);

log_debug (
LOG_ASSEMBLY,
"Preloading JNI shared library: {} (entry's index: {}; real name hash: {:x}; name hash: {:x})",
dso_name,
entry_index,
entry.real_name_hash,
entry.hash
);

void *handle = MonodroidDl::monodroid_dlopen (&entry, dso_name, RTLD_NOW);

// Set handle in all the alias entries
for (size_t j = 1; j < dso_jni_preloads_idx_stride; j++) {
const size_t entry_alias_index = dso_jni_preloads_idx[i + j];
DSOCacheEntry &entry_alias = dso_cache[entry_alias_index];
const std::string_view entry_alias_name = MonodroidDl::get_dso_name (&entry);

log_debug (
LOG_ASSEMBLY,
"Putting JNI library handle in alias entry at index {}: {}",
entry_alias_index,
entry_alias_name
);
entry_alias.handle = handle;
}
}
}

void Host::Java_mono_android_Runtime_initInternal (
JNIEnv *env, jclass runtimeClass, jstring lang, jobjectArray runtimeApksJava,

Expand Down Expand Up @@ -433,12 +487,7 @@ void Host::Java_mono_android_Runtime_initInternal (
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);
}
preload_jni_libraries ();

struct JnienvInitializeArgs init = {};
init.javaVm = jvm;
Expand Down
2 changes: 2 additions & 0 deletions src/native/clr/include/host/host.hh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ namespace xamarin::android {
std::string_view const& assembly_name, std::string_view const& type_name,
std::string_view const& method_name) noexcept -> void*;

static void preload_jni_libraries () noexcept;

private:
static inline void *clr_host = nullptr;
static inline unsigned int domain_id = 0;
Expand Down
1 change: 1 addition & 0 deletions src/native/clr/include/xamarin-app.hh
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ 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_stride;
[[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[];
Expand Down
1 change: 1 addition & 0 deletions src/native/clr/xamarin-app-stub/application_dso_stub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ DSOCacheEntry dso_cache[] = {
},
};

const uint dso_jni_preloads_idx_stride = 1;
const uint dso_jni_preloads_idx_count = 1;
const uint dso_jni_preloads_idx[1] = {
0
Expand Down
1 change: 1 addition & 0 deletions src/native/mono/monodroid/monodroid-glue-internal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ namespace xamarin::android::internal
static void monodroid_unhandled_exception (MonoObject *java_exception);
static MonoClass* get_android_runtime_class () noexcept;

static void preload_jni_libraries () noexcept;
static MonoDomain* create_domain (JNIEnv *env, jstring_array_wrapper &runtimeApks, bool is_root_domain, bool have_split_apks) noexcept;
static MonoDomain* create_and_initialize_domain (JNIEnv* env, jclass runtimeClass, jstring_array_wrapper &runtimeApks,
jstring_array_wrapper &assemblies, jobjectArray assembliesBytes, jstring_array_wrapper &assembliesPaths,
Expand Down
Loading
Loading