Skip to content

Commit 000cf5a

Browse files
authored
[One .NET] Don't blindly load Mono components (#6507)
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.
1 parent f2cb33c commit 000cf5a

15 files changed

+220
-9
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class GeneratePackageManagerJava : AndroidTask
2828
[Required]
2929
public ITaskItem[] ResolvedUserAssemblies { get; set; }
3030

31+
public ITaskItem[] MonoComponents { get; set; }
32+
3133
public ITaskItem[] SatelliteAssemblies { get; set; }
3234

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

337+
MonoComponent monoComponents = MonoComponent.None;
338+
if (MonoComponents != null && MonoComponents.Length > 0) {
339+
foreach (ITaskItem item in MonoComponents) {
340+
if (String.Compare ("diagnostics_tracing", item.ItemSpec, StringComparison.OrdinalIgnoreCase) == 0) {
341+
monoComponents |= MonoComponent.Tracing;
342+
} else if (String.Compare ("hot_reload", item.ItemSpec, StringComparison.OrdinalIgnoreCase) == 0) {
343+
monoComponents |= MonoComponent.HotReload;
344+
} else if (String.Compare ("debugger", item.ItemSpec, StringComparison.OrdinalIgnoreCase) == 0) {
345+
monoComponents |= MonoComponent.Debugger;
346+
}
347+
}
348+
}
349+
335350
bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath);
336351
var appConfState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ApplicationConfigTaskState> (ApplicationConfigTaskState.RegisterTaskObjectKey, RegisteredTaskObjectLifetime.Build);
337352

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

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,66 @@ namespace Xamarin.Android.Build.Tests
2323
[Parallelizable (ParallelScope.Children)]
2424
public partial class BuildTest : BaseTest
2525
{
26+
static object [] MonoComponentMaskChecks () => new object [] {
27+
new object[] {
28+
true, // enableProfiler
29+
true, // useInterpreter
30+
true, // debugBuild
31+
0x07U, // expectedMask
32+
},
33+
34+
new object[] {
35+
true, // enableProfiler
36+
false, // useInterpreter
37+
true, // debugBuild
38+
0x05U, // expectedMask
39+
},
40+
41+
new object[] {
42+
false, // enableProfiler
43+
false, // useInterpreter
44+
true, // debugBuild
45+
0x01U, // expectedMask
46+
},
47+
48+
new object[] {
49+
true, // enableProfiler
50+
false, // useInterpreter
51+
false, // debugBuild
52+
0x04U, // expectedMask
53+
},
54+
};
55+
56+
[Test]
57+
[TestCaseSource (nameof (MonoComponentMaskChecks))]
58+
public void CheckMonoComponentsMask (bool enableProfiler, bool useInterpreter, bool debugBuild, uint expectedMask)
59+
{
60+
if (!Builder.UseDotNet) {
61+
Assert.Ignore ("Valid only for NET6+ builds");
62+
return;
63+
}
64+
65+
var proj = new XamarinAndroidApplicationProject () {
66+
IsRelease = !debugBuild,
67+
};
68+
69+
proj.SetProperty (proj.ActiveConfigurationProperties, "AndroidEnableProfiler", enableProfiler.ToString ());
70+
proj.SetProperty (proj.ActiveConfigurationProperties, "AndroidUseInterpreter", useInterpreter.ToString ());
71+
72+
var abis = new [] { "armeabi-v7a", "x86" };
73+
proj.SetAndroidSupportedAbis (abis);
74+
75+
using (var b = CreateApkBuilder ()) {
76+
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
77+
string objPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath);
78+
79+
List<string> envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true);
80+
EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles);
81+
Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files");
82+
Assert.IsTrue (app_config.mono_components_mask == expectedMask, "Expected Mono Components mask 0x{expectedMask:x}, got 0x{app_config.mono_components_mask:x}");
83+
}
84+
}
85+
2686
[Test]
2787
[Category ("SmokeTests")]
2888
public void SmokeTestBuildWithSpecialCharacters ([Values (false, true)] bool forms)

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ public sealed class ApplicationConfig
3434
public uint number_of_assemblies_in_apk;
3535
public uint bundled_assembly_name_width;
3636
public uint number_of_assembly_blobs;
37+
public uint mono_components_mask;
3738
public string android_package_name;
3839
};
39-
const uint ApplicationConfigFieldCount = 17;
40+
const uint ApplicationConfigFieldCount = 18;
4041

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

203-
case 16: // android_package_name: string / [pointer type]
204+
case 16: // mono_components_mask: uint32_t / .word | .long
205+
Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile}:{i}': {field [0]}");
206+
ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile, i, field [1]);
207+
break;
208+
209+
case 17: // android_package_name: string / [pointer type]
204210
Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile}:{i}': {field [0]}");
205211
pointers.Add (field [1].Trim ());
206212
break;

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66

77
namespace Xamarin.Android.Tasks
88
{
9+
// Must match the MonoComponent enum in src/monodroid/jni/xamarin-app.hh
10+
[Flags]
11+
enum MonoComponent
12+
{
13+
None = 0x00,
14+
Debugger = 0x01,
15+
HotReload = 0x02,
16+
Tracing = 0x04,
17+
}
18+
919
class ApplicationConfigNativeAssemblyGenerator : NativeAssemblyGenerator
1020
{
1121
SortedDictionary <string, string> environmentVariables;
@@ -28,6 +38,7 @@ class ApplicationConfigNativeAssemblyGenerator : NativeAssemblyGenerator
2838
public int NumberOfAssembliesInApk { get; set; }
2939
public int NumberOfAssemblyStoresInApks { get; set; }
3040
public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL
41+
public MonoComponent MonoComponents { get; set; }
3142

3243
public PackageNamingPolicy PackageNamingPolicy { get; set; }
3344

@@ -103,6 +114,9 @@ protected override void WriteSymbols (StreamWriter output)
103114
WriteCommentLine (output, "number_of_assembly_store_files");
104115
size += WriteData (output, NumberOfAssemblyStoresInApks);
105116

117+
WriteCommentLine (output, "mono_components_mask");
118+
size += WriteData (output, (uint)MonoComponents);
119+
106120
WriteCommentLine (output, "android_package_name");
107121
size += WritePointer (output, MakeLocalLabel (stringLabel));
108122

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,7 @@ because xbuild doesn't support framework reference assemblies.
15661566
_PrepareAssemblies;
15671567
_PrepareEnvironmentAssemblySources;
15681568
_GenerateEnvironmentFiles;
1569+
_IncludeNativeSystemLibraries;
15691570
</_GeneratePackageManagerJavaDependsOn>
15701571
</PropertyGroup>
15711572

@@ -1578,6 +1579,7 @@ because xbuild doesn't support framework reference assemblies.
15781579
ResolvedAssemblies="@(_ResolvedAssemblies)"
15791580
ResolvedUserAssemblies="@(_ResolvedUserAssemblies)"
15801581
SatelliteAssemblies="@(_AndroidResolvedSatellitePaths)"
1582+
MonoComponents="@(_MonoComponent)"
15811583
MainAssembly="$(TargetPath)"
15821584
OutputDirectory="$(_AndroidIntermediateJavaSourceDirectory)mono"
15831585
EnvironmentOutputDirectory="$(IntermediateOutputPath)android"

src/monodroid/jni/application_dso_stub.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ ApplicationConfig application_config = {
5555
.number_of_assemblies_in_apk = 2,
5656
.bundled_assembly_name_width = 0,
5757
.number_of_assembly_store_files = 2,
58+
.mono_components_mask = MonoComponent::None,
5859
.android_package_name = "com.xamarin.test",
5960
};
6061

src/monodroid/jni/cpp-util.hh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cstdarg>
66
#include <cstdlib>
77
#include <memory>
8+
#include <type_traits>
89

910
#include <semaphore.h>
1011

@@ -123,5 +124,50 @@ namespace xamarin::android
123124
return ret;
124125
};
125126

127+
template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
128+
constexpr TEnum operator & (TEnum l, TEnum r) noexcept
129+
{
130+
using etype = std::underlying_type_t<TEnum>;
131+
return static_cast<TEnum>(static_cast<etype>(l) & static_cast<etype>(r));
132+
}
133+
134+
template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
135+
constexpr TEnum& operator &= (TEnum& l, TEnum r) noexcept
136+
{
137+
return l = (l & r);
138+
}
139+
140+
template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
141+
constexpr TEnum operator | (TEnum l, TEnum r) noexcept
142+
{
143+
using etype = std::underlying_type_t<TEnum>;
144+
return static_cast<TEnum>(static_cast<etype>(l) | static_cast<etype>(r));
145+
}
146+
147+
template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
148+
constexpr TEnum& operator |= (TEnum& l, TEnum r) noexcept
149+
{
150+
return l = (l | r);
151+
}
152+
153+
template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
154+
constexpr TEnum operator ~ (TEnum r) noexcept
155+
{
156+
using etype = std::underlying_type_t<TEnum>;
157+
return static_cast<TEnum> (~static_cast<etype>(r));
158+
}
159+
160+
template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
161+
constexpr TEnum operator ^ (TEnum l, TEnum r) noexcept
162+
{
163+
using etype = std::underlying_type_t<TEnum>;
164+
return static_cast<TEnum>(static_cast<etype>(l) ^ static_cast<etype>(r));
165+
}
166+
167+
template <typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
168+
constexpr TEnum& operator ^= (TEnum& l, TEnum r) noexcept
169+
{
170+
return l = (l ^ r);
171+
}
126172
}
127173
#endif // !def __CPP_UTIL_HH

src/monodroid/jni/embedded-assemblies-zip.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus
297297
.prefix = get_assemblies_prefix (),
298298
.prefix_len = get_assemblies_prefix_length (),
299299
.buf_offset = 0,
300+
.compression_method = 0,
300301
.local_header_offset = 0,
301302
.data_offset = 0,
302303
.file_size = 0,

src/monodroid/jni/embedded-assemblies.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ force_inline void
189189
EmbeddedAssemblies::map_runtime_file (XamarinAndroidBundledAssembly& file) noexcept
190190
{
191191
md_mmap_info map_info = md_mmap_apk_file (file.apk_fd, file.data_offset, file.data_size, file.name);
192-
if (monodroidRuntime.is_startup_in_progress ()) {
192+
if (MonodroidRuntime::is_startup_in_progress ()) {
193193
file.data = static_cast<uint8_t*>(map_info.area);
194194
} else {
195195
uint8_t *expected_null = nullptr;

src/monodroid/jni/generate-pinvoke-tables.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -824,14 +824,14 @@ int main (int argc, char **argv)
824824
print (output, "64-bit internal p/invoke table", "internal_pinvokes", internal_pinvokes64);
825825
print (output, "64-bit DotNet p/invoke table", "dotnet_pinvokes", dotnet_pinvokes64);
826826
output << std::endl;
827-
write_library_name_hashes (xxhash64::hash, output);
827+
write_library_name_hashes<uint64_t> (xxhash64::hash, output);
828828

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

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

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

0 commit comments

Comments
 (0)