Skip to content

Commit

Permalink
[One .NET] Don't blindly load Mono components (#6507)
Browse files Browse the repository at this point in the history
Whenever a .NET SDK for Android app starts, MonoVM will probe for a
number of Mono components:

	I monodroid-assembly: Trying to load shared library '/data/app/~~5lwn5C40pkRva2-L4kilZA==/com.microsoft.net6.helloandroid-MrYUTOqX7yZPkmtLOPnuuw==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libmono-component-debugger.so'
	I monodroid-assembly: Failed to load shared library '/data/app/~~5lwn5C40pkRva2-L4kilZA==/com.microsoft.net6.helloandroid-MrYUTOqX7yZPkmtLOPnuuw==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libmono-component-debugger.so'.
	                      dlopen failed: library "/data/app/~~5lwn5C40pkRva2-L4kilZA==/com.microsoft.net6.helloandroid-MrYUTOqX7yZPkmtLOPnuuw==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libmono-component-debugger.so" not found

The above is repeated for, currently, 3 components.  Together, the
failed load attempts cost us around 1ms of startup time on Pixel 3 XL;
the time will depend on the speed of device's storage and CPU.

Since we know at the build time which components are included, we can
optimize the startup process by recording a flag indicating which
components can be loaded successfully.

Add a `mono_components_mask` field to `ApplicationConfig` which is set
to a bitmask indicating which components are packaged.  On application
startup, whenever `monodroid_dlopen` is called, we has the name of the
library passed to us by Mono and see if it matches one of the known
hashes for the various components.  If yes, we consult the mask stored
at build time and attempt to load the component only if its bit is set.

The above check is performed **only** during application startup since
that's when Mono probes for the components and it would be a waste of
time later in the application life.
  • Loading branch information
grendello committed Nov 19, 2021
1 parent f2cb33c commit 000cf5a
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 9 deletions.
Expand Up @@ -28,6 +28,8 @@ public class GeneratePackageManagerJava : AndroidTask
[Required]
public ITaskItem[] ResolvedUserAssemblies { get; set; }

public ITaskItem[] MonoComponents { get; set; }

public ITaskItem[] SatelliteAssemblies { get; set; }

public bool UseAssemblyStore { get; set; }
Expand Down Expand Up @@ -332,6 +334,19 @@ void AddEnvironment ()
assemblyNameWidth += abiNameLength + 2; // room for '/' and the terminating NUL
}

MonoComponent monoComponents = MonoComponent.None;
if (MonoComponents != null && MonoComponents.Length > 0) {
foreach (ITaskItem item in MonoComponents) {
if (String.Compare ("diagnostics_tracing", item.ItemSpec, StringComparison.OrdinalIgnoreCase) == 0) {
monoComponents |= MonoComponent.Tracing;
} else if (String.Compare ("hot_reload", item.ItemSpec, StringComparison.OrdinalIgnoreCase) == 0) {
monoComponents |= MonoComponent.HotReload;
} else if (String.Compare ("debugger", item.ItemSpec, StringComparison.OrdinalIgnoreCase) == 0) {
monoComponents |= MonoComponent.Debugger;
}
}
}

bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath);
var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ApplicationConfigTaskState> (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build);

Expand Down Expand Up @@ -359,6 +374,7 @@ void AddEnvironment ()
// and up to 4 other for arch-specific assemblies. Only **one** arch-specific store is ever loaded on the app
// runtime, thus the number 2 here. All architecture specific stores contain assemblies with the same names
// and in the same order.
MonoComponents = monoComponents,
HaveAssemblyStore = UseAssemblyStore,
};

Expand Down
Expand Up @@ -23,6 +23,66 @@ namespace Xamarin.Android.Build.Tests
[Parallelizable (ParallelScope.Children)]
public partial class BuildTest : BaseTest
{
static object [] MonoComponentMaskChecks () => new object [] {
new object[] {
true, // enableProfiler
true, // useInterpreter
true, // debugBuild
0x07U, // expectedMask
},

new object[] {
true, // enableProfiler
false, // useInterpreter
true, // debugBuild
0x05U, // expectedMask
},

new object[] {
false, // enableProfiler
false, // useInterpreter
true, // debugBuild
0x01U, // expectedMask
},

new object[] {
true, // enableProfiler
false, // useInterpreter
false, // debugBuild
0x04U, // expectedMask
},
};

[Test]
[TestCaseSource (nameof (MonoComponentMaskChecks))]
public void CheckMonoComponentsMask (bool enableProfiler, bool useInterpreter, bool debugBuild, uint expectedMask)
{
if (!Builder.UseDotNet) {
Assert.Ignore ("Valid only for NET6+ builds");
return;
}

var proj = new XamarinAndroidApplicationProject () {
IsRelease = !debugBuild,
};

proj.SetProperty (proj.ActiveConfigurationProperties, "AndroidEnableProfiler", enableProfiler.ToString ());
proj.SetProperty (proj.ActiveConfigurationProperties, "AndroidUseInterpreter", useInterpreter.ToString ());

var abis = new [] { "armeabi-v7a", "x86" };
proj.SetAndroidSupportedAbis (abis);

using (var b = CreateApkBuilder ()) {
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
string objPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath);

List<string> envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true);
EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles);
Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files");
Assert.IsTrue (app_config.mono_components_mask == expectedMask, "Expected Mono Components mask 0x{expectedMask:x}, got 0x{app_config.mono_components_mask:x}");
}
}

[Test]
[Category ("SmokeTests")]
public void SmokeTestBuildWithSpecialCharacters ([Values (false, true)] bool forms)
Expand Down
Expand Up @@ -34,9 +34,10 @@ public sealed class ApplicationConfig
public uint number_of_assemblies_in_apk;
public uint bundled_assembly_name_width;
public uint number_of_assembly_blobs;
public uint mono_components_mask;
public string android_package_name;
};
const uint ApplicationConfigFieldCount = 17;
const uint ApplicationConfigFieldCount = 18;

static readonly object ndkInitLock = new object ();
static readonly char[] readElfFieldSeparator = new [] { ' ', '\t' };
Expand Down Expand Up @@ -200,7 +201,12 @@ static ApplicationConfig ReadApplicationConfig (string envFile)
ret.number_of_assembly_blobs = ConvertFieldToUInt32 ("number_of_assembly_blobs", envFile, i, field [1]);
break;

case 16: // android_package_name: string / [pointer type]
case 16: // mono_components_mask: uint32_t / .word | .long
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile, i, field [1]);
break;

case 17: // android_package_name: string / [pointer type]
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}");
pointers.Add (field [1].Trim ());
break;
Expand Down
Expand Up @@ -6,6 +6,16 @@

namespace Xamarin.Android.Tasks
{
// Must match the MonoComponent enum in src/monodroid/jni/xamarin-app.hh
[Flags]
enum MonoComponent
{
None = 0x00,
Debugger = 0x01,
HotReload = 0x02,
Tracing = 0x04,
}

class ApplicationConfigNativeAssemblyGenerator : NativeAssemblyGenerator
{
SortedDictionary <string, string> environmentVariables;
Expand All @@ -28,6 +38,7 @@ class ApplicationConfigNativeAssemblyGenerator : NativeAssemblyGenerator
public int NumberOfAssembliesInApk { get; set; }
public int NumberOfAssemblyStoresInApks { get; set; }
public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL
public MonoComponent MonoComponents { get; set; }

public PackageNamingPolicy PackageNamingPolicy { get; set; }

Expand Down Expand Up @@ -103,6 +114,9 @@ protected override void WriteSymbols (StreamWriter output)
WriteCommentLine (output, "number_of_assembly_store_files");
size += WriteData (output, NumberOfAssemblyStoresInApks);
WriteCommentLine (output, "mono_components_mask");
size += WriteData (output, (uint)MonoComponents);
WriteCommentLine (output, "android_package_name");
size += WritePointer (output, MakeLocalLabel (stringLabel));
Expand Down
Expand Up @@ -1566,6 +1566,7 @@ because xbuild doesn't support framework reference assemblies.
_PrepareAssemblies;
_PrepareEnvironmentAssemblySources;
_GenerateEnvironmentFiles;
_IncludeNativeSystemLibraries;
</_GeneratePackageManagerJavaDependsOn>
</PropertyGroup>

Expand All @@ -1578,6 +1579,7 @@ because xbuild doesn't support framework reference assemblies.
ResolvedAssemblies="@(_ResolvedAssemblies)"
ResolvedUserAssemblies="@(_ResolvedUserAssemblies)"
SatelliteAssemblies="@(_AndroidResolvedSatellitePaths)"
MonoComponents="@(_MonoComponent)"
MainAssembly="$(TargetPath)"
OutputDirectory="$(_AndroidIntermediateJavaSourceDirectory)mono"
EnvironmentOutputDirectory="$(IntermediateOutputPath)android"
Expand Down
1 change: 1 addition & 0 deletions src/monodroid/jni/application_dso_stub.cc
Expand Up @@ -55,6 +55,7 @@ ApplicationConfig application_config = {
.number_of_assemblies_in_apk = 2,
.bundled_assembly_name_width = 0,
.number_of_assembly_store_files = 2,
.mono_components_mask = MonoComponent::None,
.android_package_name = "com.xamarin.test",
};

Expand Down
46 changes: 46 additions & 0 deletions src/monodroid/jni/cpp-util.hh
Expand Up @@ -5,6 +5,7 @@
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <type_traits>

#include <semaphore.h>

Expand Down Expand Up @@ -123,5 +124,50 @@ namespace xamarin::android
return ret;
};

template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum operator & (TEnum l, TEnum r) noexcept
{
using etype = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(static_cast<etype>(l) & static_cast<etype>(r));
}

template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum& operator &= (TEnum& l, TEnum r) noexcept
{
return l = (l & r);
}

template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum operator | (TEnum l, TEnum r) noexcept
{
using etype = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(static_cast<etype>(l) | static_cast<etype>(r));
}

template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum& operator |= (TEnum& l, TEnum r) noexcept
{
return l = (l | r);
}

template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum operator ~ (TEnum r) noexcept
{
using etype = std::underlying_type_t<TEnum>;
return static_cast<TEnum> (~static_cast<etype>(r));
}

template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum operator ^ (TEnum l, TEnum r) noexcept
{
using etype = std::underlying_type_t<TEnum>;
return static_cast<TEnum>(static_cast<etype>(l) ^ static_cast<etype>(r));
}

template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
constexpr TEnum& operator ^= (TEnum& l, TEnum r) noexcept
{
return l = (l ^ r);
}
}
#endif // !def __CPP_UTIL_HH
1 change: 1 addition & 0 deletions src/monodroid/jni/embedded-assemblies-zip.cc
Expand Up @@ -297,6 +297,7 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus
.prefix = get_assemblies_prefix (),
.prefix_len = get_assemblies_prefix_length (),
.buf_offset = 0,
.compression_method = 0,
.local_header_offset = 0,
.data_offset = 0,
.file_size = 0,
Expand Down
2 changes: 1 addition & 1 deletion src/monodroid/jni/embedded-assemblies.cc
Expand Up @@ -189,7 +189,7 @@ force_inline void
EmbeddedAssemblies::map_runtime_file (XamarinAndroidBundledAssembly& file) noexcept
{
md_mmap_info map_info = md_mmap_apk_file (file.apk_fd, file.data_offset, file.data_size, file.name);
if (monodroidRuntime.is_startup_in_progress ()) {
if (MonodroidRuntime::is_startup_in_progress ()) {
file.data = static_cast<uint8_t*>(map_info.area);
} else {
uint8_t *expected_null = nullptr;
Expand Down
4 changes: 2 additions & 2 deletions src/monodroid/jni/generate-pinvoke-tables.cc
Expand Up @@ -824,14 +824,14 @@ int main (int argc, char **argv)
print (output, "64-bit internal p/invoke table", "internal_pinvokes", internal_pinvokes64);
print (output, "64-bit DotNet p/invoke table", "dotnet_pinvokes", dotnet_pinvokes64);
output << std::endl;
write_library_name_hashes (xxhash64::hash, output);
write_library_name_hashes<uint64_t> (xxhash64::hash, output);

output << "#else" << std::endl;

print (output, "32-bit internal p/invoke table", "internal_pinvokes", internal_pinvokes32);
print (output, "32-bit DotNet p/invoke table", "dotnet_pinvokes", dotnet_pinvokes32);
output << std::endl;
write_library_name_hashes (xxhash32::hash, output);
write_library_name_hashes<uint32_t> (xxhash32::hash, output);

output << "#endif" << std::endl << std::endl;

Expand Down
13 changes: 11 additions & 2 deletions src/monodroid/jni/monodroid-glue-internal.hh
Expand Up @@ -136,6 +136,15 @@ namespace xamarin::android::internal
true;
#endif

static constexpr char mono_component_debugger_name[] = "libmono-component-debugger.so";
static constexpr hash_t mono_component_debugger_hash = xxhash::hash (mono_component_debugger_name);

static constexpr char mono_component_hot_reload_name[] = "libmono-component-hot_reload.so";
static constexpr hash_t mono_component_hot_reload_hash = xxhash::hash (mono_component_hot_reload_name);

static constexpr char mono_component_diagnostics_tracing_name[] = "libmono-component-diagnostics_tracing.so";
static constexpr hash_t mono_component_diagnostics_tracing_hash = xxhash::hash (mono_component_diagnostics_tracing_name);

#if !defined (NET6)
#define MAKE_API_DSO_NAME(_ext_) "libxa-internal-api." # _ext_
#if defined (WINDOWS)
Expand Down Expand Up @@ -164,7 +173,7 @@ namespace xamarin::android::internal
#endif
jint Java_JNI_OnLoad (JavaVM *vm, void *reserved);

bool is_startup_in_progress () const noexcept
static bool is_startup_in_progress () noexcept
{
return startup_in_progress;
}
Expand Down Expand Up @@ -360,7 +369,7 @@ namespace xamarin::android::internal
* able to switch our different contexts from different threads.
*/
int current_context_id = -1;
bool startup_in_progress = true;
static bool startup_in_progress;

#if defined (NET6)
MonoAssemblyLoadContextGCHandle default_alc = nullptr;
Expand Down
35 changes: 35 additions & 0 deletions src/monodroid/jni/monodroid-glue.cc
Expand Up @@ -117,6 +117,8 @@ MonoCoreRuntimeProperties MonodroidRuntime::monovm_core_properties = {

#endif // def NET6

bool MonodroidRuntime::startup_in_progress = true;

#ifdef WINDOWS
static const char* get_xamarin_android_msbuild_path (void);
const char *BasicAndroidSystem::SYSTEM_LIB_PATH = get_xamarin_android_msbuild_path();
Expand Down Expand Up @@ -1302,6 +1304,39 @@ MonodroidRuntime::monodroid_dlopen_log_and_return (void *handle, char **err, con
force_inline void*
MonodroidRuntime::monodroid_dlopen (const char *name, int flags, char **err)
{
if (startup_in_progress) {
hash_t name_hash = xxhash::hash (name, strlen (name));

auto ignore_component = [&](const char *label, MonoComponent component) -> bool {
if ((application_config.mono_components_mask & component) != component) {
log_info (LOG_ASSEMBLY, "Mono '%s' component requested but not packaged, ignoring", label);
return true;
}

return false;
};

switch (name_hash) {
case mono_component_debugger_hash:
if (ignore_component ("Debugger", MonoComponent::Debugger)) {
return nullptr;
}
break;

case mono_component_hot_reload_hash:
if (ignore_component ("Hot Reload", MonoComponent::HotReload)) {
return nullptr;
}
break;

case mono_component_diagnostics_tracing_hash:
if (ignore_component ("Diagnostics Tracing", MonoComponent::Tracing)) {
return nullptr;
}
break;
}
}

unsigned int dl_flags = monodroidRuntime.convert_dl_flags (flags);
void *h = androidSystem.load_dso_from_any_directories (name, dl_flags);
if (h != nullptr) {
Expand Down
4 changes: 2 additions & 2 deletions src/monodroid/jni/startup-aware-lock.hh
Expand Up @@ -11,7 +11,7 @@ namespace xamarin::android::internal
explicit StartupAwareLock (std::mutex &m)
: lock (m)
{
if (monodroidRuntime.is_startup_in_progress ()) {
if (MonodroidRuntime::is_startup_in_progress ()) {
// During startup we run without threads, do nothing
return;
}
Expand All @@ -21,7 +21,7 @@ namespace xamarin::android::internal

~StartupAwareLock ()
{
if (monodroidRuntime.is_startup_in_progress ()) {
if (MonodroidRuntime::is_startup_in_progress ()) {
return;
}

Expand Down

0 comments on commit 000cf5a

Please sign in to comment.