Skip to content

Commit 42c0887

Browse files
authored
[CoreCLR] Add support to load assemblies from storage (#9917)
Context: ec0cbcf Context: ea38c0c Before Android 6.0 (API-23), native libraries within a `.apk` would be compressed. They would be extracted at installation time "duplicating" the data. (One set of `.so` files would be on-disk, while the original set would remain within the `.apk`, compressed.) API-23 introduced the [`//application/@android:extractNativeLibs`][0] attribute, which allowed this behavior to change: when set to `false`, native libraries within the `.apk` would be *stored uncompressed*, and native libraries would *not* be extracted at install time. This increased `.apk` size, but *decreased* post-install app sizes, as there was no duplication of native library data. The initial support for CoreCLR (ec0cbcf, ea38c0c, many others) only supported `/application[@android:extractNativeLibs='false']`. If this attribute was set to `true`, *or* you ran on a device older than Android 6.0, then the app would abort during startup: A Abort message: 'Filesystem mode not supported yet.' Implement support for CoreCLR host to load assemblies from storage. This adds the missing bits to load assembly store from device's filesystem instead of from the APK when running application which has the `android:extractNativeLibs` attribute set to `true` in its manifest. [0]: https://developer.android.com/guide/topics/manifest/application-element#extractNativeLibs
1 parent d31a6bf commit 42c0887

File tree

14 files changed

+267
-21
lines changed

14 files changed

+267
-21
lines changed

src/native/clr/host/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ set(XAMARIN_MONODROID_SOURCES
3333
host-jni.cc
3434
host-util.cc
3535
internal-pinvokes.cc
36+
jni-remapping.cc
3637
os-bridge.cc
3738
pinvoke-override.cc
3839
typemap.cc

src/native/clr/host/generate-pinvoke-tables.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ const std::vector<std::string> internal_pinvoke_names = {
5858
"_monodroid_gref_log_new",
5959
"monodroid_log",
6060
// "monodroid_log_traces",
61-
// "_monodroid_lookup_replacement_type",
62-
// "_monodroid_lookup_replacement_method_info",
61+
"_monodroid_lookup_replacement_type",
62+
"_monodroid_lookup_replacement_method_info",
6363
// "_monodroid_lref_log_delete",
6464
// "_monodroid_lref_log_new",
6565
// "_monodroid_max_gref_get",

src/native/clr/host/host.cc

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
#include <sys/types.h>
2+
#include <dirent.h>
3+
4+
#include <cerrno>
15
#include <cstdio>
6+
#include <cstring>
27

38
#include <coreclrhost.h>
49

@@ -62,10 +67,98 @@ auto Host::zip_scan_callback (std::string_view const& apk_path, int apk_fd, dyna
6267
return false;
6368
}
6469

70+
[[gnu::always_inline]]
71+
void Host::scan_filesystem_for_assemblies_and_libraries () noexcept
72+
{
73+
std::string const& native_lib_dir = AndroidSystem::get_native_libraries_dir ();
74+
log_debug (LOG_ASSEMBLY, "Looking for assemblies in '{}'", native_lib_dir);
75+
76+
DIR *lib_dir = opendir (native_lib_dir.c_str ());
77+
if (lib_dir == nullptr) [[unlikely]] {
78+
Helpers::abort_application (
79+
LOG_ASSEMBLY,
80+
std::format (
81+
"Unable to open native library directory '{}'. {}",
82+
native_lib_dir,
83+
std::strerror (errno)
84+
)
85+
);
86+
}
87+
88+
int dir_fd = dirfd (lib_dir);
89+
if (dir_fd < 0) [[unlikely]] {
90+
Helpers::abort_application (
91+
LOG_ASSEMBLY,
92+
std::format (
93+
"Unable to obtain file descriptor for opened directory '{}'. {}",
94+
native_lib_dir,
95+
std::strerror (errno)
96+
)
97+
);
98+
}
99+
100+
do {
101+
errno = 0;
102+
dirent *cur = readdir (lib_dir);
103+
if (cur == nullptr) {
104+
if (errno != 0) {
105+
log_warn (LOG_ASSEMBLY, "Failed to open a directory entry from '{}': {}", native_lib_dir, std::strerror (errno));
106+
continue; // No harm, keep going
107+
}
108+
break; // we're done
109+
}
110+
111+
// We can ignore the obvious entries
112+
if (cur->d_name[0] == '.') {
113+
continue;
114+
}
115+
116+
if (!found_assembly_store) {
117+
found_assembly_store = Constants::assembly_store_file_name.compare (cur->d_name) == 0;
118+
if (!found_assembly_store) {
119+
continue;
120+
}
121+
122+
log_debug (LOG_ASSEMBLY, "Found assembly store in '{}/{}'", native_lib_dir, Constants::assembly_store_file_name);
123+
int store_fd = openat (dir_fd, cur->d_name, O_RDONLY);
124+
if (store_fd < 0) {
125+
Helpers::abort_application (
126+
LOG_ASSEMBLY,
127+
std::format (
128+
"Unable to open assembly store '{}/{}' for reading. {}",
129+
native_lib_dir,
130+
Constants::assembly_store_file_name,
131+
std::strerror (errno)
132+
)
133+
);
134+
}
135+
136+
auto file_size = Util::get_file_size_at (dir_fd, cur->d_name);
137+
if (!file_size) {
138+
// get_file_size_at logged errno for us
139+
Helpers::abort_application (
140+
LOG_ASSEMBLY,
141+
std::format (
142+
"Unable to map assembly store '{}/{}'",
143+
native_lib_dir,
144+
Constants::assembly_store_file_name
145+
)
146+
);
147+
}
148+
149+
AssemblyStore::map (store_fd, cur->d_name, 0, static_cast<uint32_t>(file_size.value ()));
150+
close (store_fd);
151+
break; // we've found all we need
152+
}
153+
} while (true);
154+
closedir (lib_dir);
155+
}
156+
65157
void Host::gather_assemblies_and_libraries (jstring_array_wrapper& runtimeApks, bool have_split_apks)
66158
{
67159
if (!AndroidSystem::is_embedded_dso_mode_enabled ()) {
68-
Helpers::abort_application ("Filesystem mode not supported yet.");
160+
scan_filesystem_for_assemblies_and_libraries ();
161+
return;
69162
}
70163

71164
int64_t apk_count = static_cast<int64_t>(runtimeApks.get_length ());

src/native/clr/host/internal-pinvokes.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <host/os-bridge.hh>
33
#include <host/typemap.hh>
44
#include <runtime-base/internal-pinvokes.hh>
5+
#include <runtime-base/jni-remapping.hh>
56

67
using namespace xamarin::android;
78

@@ -75,3 +76,15 @@ void monodroid_free (void *ptr) noexcept
7576
{
7677
free (ptr);
7778
}
79+
80+
const char*
81+
_monodroid_lookup_replacement_type (const char *jniSimpleReference)
82+
{
83+
return JniRemapping::lookup_replacement_type (jniSimpleReference);
84+
}
85+
86+
const JniRemappingReplacementMethod*
87+
_monodroid_lookup_replacement_method_info (const char *jniSourceType, const char *jniMethodName, const char *jniMethodSignature)
88+
{
89+
return JniRemapping::lookup_replacement_method_info (jniSourceType, jniMethodName, jniMethodSignature);
90+
}

src/native/clr/host/jni-remapping.cc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include <cstring>
2+
3+
#include <runtime-base/logger.hh>
4+
#include <runtime-base/jni-remapping.hh>
5+
6+
#include "xamarin-app.hh"
7+
8+
using namespace xamarin::android;
9+
10+
[[gnu::always_inline]]
11+
auto JniRemapping::equal (JniRemappingString const& left, const char *right, size_t right_len) noexcept -> bool
12+
{
13+
if (left.length != static_cast<uint32_t>(right_len) || left.str[0] != *right) {
14+
return false;
15+
}
16+
17+
if (memcmp (left.str, right, right_len) == 0) {
18+
return true;
19+
}
20+
21+
return false;
22+
}
23+
24+
auto JniRemapping::lookup_replacement_type (const char *jniSimpleReference) noexcept -> const char*
25+
{
26+
if (application_config.jni_remapping_replacement_type_count == 0 || jniSimpleReference == nullptr || *jniSimpleReference == '\0') {
27+
return nullptr;
28+
}
29+
30+
size_t ref_len = strlen (jniSimpleReference);
31+
for (size_t i = 0uz; i < application_config.jni_remapping_replacement_type_count; i++) {
32+
JniRemappingTypeReplacementEntry const& entry = jni_remapping_type_replacements[i];
33+
34+
if (equal (entry.name, jniSimpleReference, ref_len)) {
35+
return entry.replacement;
36+
}
37+
}
38+
39+
return nullptr;
40+
}
41+
42+
auto JniRemapping::lookup_replacement_method_info (const char *jniSourceType, const char *jniMethodName, const char *jniMethodSignature) noexcept -> const JniRemappingReplacementMethod*
43+
{
44+
if (application_config.jni_remapping_replacement_method_index_entry_count == 0 ||
45+
jniSourceType == nullptr || *jniSourceType == '\0' ||
46+
jniMethodName == nullptr || *jniMethodName == '\0') {
47+
return nullptr;
48+
}
49+
50+
size_t source_type_len = strlen (jniSourceType);
51+
52+
const JniRemappingIndexTypeEntry *type = nullptr;
53+
for (size_t i = 0uz; i < application_config.jni_remapping_replacement_method_index_entry_count; i++) {
54+
JniRemappingIndexTypeEntry const& entry = jni_remapping_method_replacement_index[i];
55+
56+
if (!equal (entry.name, jniSourceType, source_type_len)) {
57+
continue;
58+
}
59+
60+
type = &jni_remapping_method_replacement_index[i];
61+
break;
62+
}
63+
64+
if (type == nullptr || type->method_count == 0 || type->methods == nullptr) {
65+
return nullptr;
66+
}
67+
68+
size_t method_name_len = strlen (jniMethodName);
69+
size_t signature_len = jniMethodSignature == nullptr ? 0uz : strlen (jniMethodSignature);
70+
71+
for (size_t i = 0uz; i < type->method_count; i++) {
72+
JniRemappingIndexMethodEntry const& entry = type->methods[i];
73+
74+
if (!equal (entry.name, jniMethodName, method_name_len)) {
75+
continue;
76+
}
77+
78+
if (entry.signature.length == 0 || equal (entry.signature, jniMethodSignature, signature_len)) {
79+
return &type->methods[i].replacement;
80+
}
81+
82+
const char *sig_end = jniMethodSignature + signature_len;
83+
if (*sig_end == ')') {
84+
continue;
85+
}
86+
87+
while (sig_end != jniMethodSignature && *sig_end != ')') {
88+
sig_end--;
89+
}
90+
91+
if (equal (entry.signature, jniMethodSignature, static_cast<size_t>(sig_end - jniMethodSignature) + 1uz)) {
92+
return &type->methods[i].replacement;
93+
}
94+
}
95+
96+
return nullptr;
97+
}

src/native/clr/host/pinvoke-tables.include

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
namespace {
1212
#if INTPTR_MAX == INT64_MAX
1313
//64-bit internal p/invoke table
14-
std::array<PinvokeEntry, 11> internal_pinvokes {{
14+
std::array<PinvokeEntry, 13> internal_pinvokes {{
15+
{0x423c8f539a2c56d2, "_monodroid_lookup_replacement_type", reinterpret_cast<void*>(&_monodroid_lookup_replacement_type)},
1516
{0x4310c1531ddddc14, "__android_log_print", reinterpret_cast<void*>(&__android_log_print)},
1617
{0x4b1956138764939a, "_monodroid_gref_log_new", reinterpret_cast<void*>(&_monodroid_gref_log_new)},
1718
{0x9187e6bc6294cacf, "clr_typemap_managed_to_java", reinterpret_cast<void*>(&clr_typemap_managed_to_java)},
@@ -20,6 +21,7 @@ namespace {
2021
{0xae3df96dda0143bd, "_monodroid_gref_log", reinterpret_cast<void*>(&_monodroid_gref_log)},
2122
{0xb8306f71b963cd3d, "monodroid_log", reinterpret_cast<void*>(&monodroid_log)},
2223
{0xb9bae9c43fb05089, "xamarin_app_init", reinterpret_cast<void*>(&xamarin_app_init)},
24+
{0xc2a21d3f6c8ccc24, "_monodroid_lookup_replacement_method_info", reinterpret_cast<void*>(&_monodroid_lookup_replacement_method_info)},
2325
{0xd1e121b94ea63f2e, "_monodroid_gref_get", reinterpret_cast<void*>(&_monodroid_gref_get)},
2426
{0xd5151b00eb33d85e, "monodroid_TypeManager_get_java_class_name", reinterpret_cast<void*>(&monodroid_TypeManager_get_java_class_name)},
2527
{0xf41c48df6f9be476, "monodroid_free", reinterpret_cast<void*>(&monodroid_free)},
@@ -516,14 +518,16 @@ constexpr hash_t system_security_cryptography_native_android_library_hash = 0x18
516518
constexpr hash_t system_globalization_native_library_hash = 0x28b5c8fca080abd5;
517519
#else
518520
//32-bit internal p/invoke table
519-
std::array<PinvokeEntry, 11> internal_pinvokes {{
521+
std::array<PinvokeEntry, 13> internal_pinvokes {{
520522
{0xb7a486a, "monodroid_TypeManager_get_java_class_name", reinterpret_cast<void*>(&monodroid_TypeManager_get_java_class_name)},
523+
{0x333d4835, "_monodroid_lookup_replacement_method_info", reinterpret_cast<void*>(&_monodroid_lookup_replacement_method_info)},
521524
{0x39e5b5d4, "__android_log_print", reinterpret_cast<void*>(&__android_log_print)},
522525
{0x656e00bd, "clr_typemap_managed_to_java", reinterpret_cast<void*>(&clr_typemap_managed_to_java)},
523526
{0xa04e5d1c, "monodroid_free", reinterpret_cast<void*>(&monodroid_free)},
524527
{0xb02468aa, "_monodroid_gref_get", reinterpret_cast<void*>(&_monodroid_gref_get)},
525528
{0xb6431f9a, "clr_typemap_java_to_managed", reinterpret_cast<void*>(&clr_typemap_java_to_managed)},
526529
{0xbe8d7701, "_monodroid_gref_log_new", reinterpret_cast<void*>(&_monodroid_gref_log_new)},
530+
{0xc439b5d7, "_monodroid_lookup_replacement_type", reinterpret_cast<void*>(&_monodroid_lookup_replacement_type)},
527531
{0xc5146c54, "_monodroid_gref_log_delete", reinterpret_cast<void*>(&_monodroid_gref_log_delete)},
528532
{0xe7e77ca5, "_monodroid_gref_log", reinterpret_cast<void*>(&_monodroid_gref_log)},
529533
{0xeac7f6e3, "xamarin_app_init", reinterpret_cast<void*>(&xamarin_app_init)},
@@ -1021,6 +1025,6 @@ constexpr hash_t system_security_cryptography_native_android_library_hash = 0x93
10211025
constexpr hash_t system_globalization_native_library_hash = 0xa66f1e5a;
10221026
#endif
10231027

1024-
constexpr size_t internal_pinvokes_count = 11;
1028+
constexpr size_t internal_pinvokes_count = 13;
10251029
constexpr size_t dotnet_pinvokes_count = 477;
10261030
} // end of anonymous namespace

src/native/clr/include/constants.hh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ namespace xamarin::android {
3131
static constexpr bool is_debug_build = true;
3232
#endif
3333
static constexpr std::string_view MANGLED_ASSEMBLY_NAME_EXT { ".so" };
34+
static constexpr std::string_view dso_suffix { ".so" };
3435

3536
private:
3637
static constexpr std::string_view RUNTIME_CONFIG_BLOB_BASE_NAME { "libarc.bin" };
@@ -94,7 +95,15 @@ namespace xamarin::android {
9495
private:
9596
static constexpr size_t split_config_abi_apk_name_size = calc_size (split_config_prefix, android_abi, split_config_extension);
9697

98+
static constexpr std::string_view assembly_store_prefix { "libassemblies." };
99+
static constexpr std::string_view assembly_store_extension { ".blob" };
100+
static constexpr size_t assembly_store_file_name_size = calc_size (assembly_store_prefix, android_lib_abi, assembly_store_extension, dso_suffix);
101+
static constexpr auto assembly_store_file_name_array = concat_string_views<assembly_store_file_name_size> (assembly_store_prefix, android_lib_abi, assembly_store_extension, dso_suffix);
102+
97103
public:
104+
// .data() must be used otherwise string_view length will include the trailing \0 in the array
105+
static constexpr std::string_view assembly_store_file_name { assembly_store_file_name_array.data () };
106+
98107
static constexpr auto split_config_abi_apk_name = concat_string_views<split_config_abi_apk_name_size> (split_config_prefix, android_abi, split_config_extension);
99108

100109
//

src/native/clr/include/host/host.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace xamarin::android {
3333
static void create_xdg_directories_and_environment (jstring_wrapper &homeDir) noexcept;
3434
static auto zip_scan_callback (std::string_view const& apk_path, int apk_fd, dynamic_local_string<SENSIBLE_PATH_MAX> const& entry_name, uint32_t offset, uint32_t size) -> bool;
3535
static void gather_assemblies_and_libraries (jstring_array_wrapper& runtimeApks, bool have_split_apks);
36+
static void scan_filesystem_for_assemblies_and_libraries () noexcept;
3637

3738
static size_t clr_get_runtime_property (const char *key, char *value_buffer, size_t value_buffer_size, void *contract_context) noexcept;
3839
static bool clr_external_assembly_probe (const char *path, void **data_start, int64_t *size) noexcept;

src/native/clr/include/runtime-base/android-system.hh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ namespace xamarin::android {
7070
primary_override_dir = determine_primary_override_dir (home);
7171
}
7272

73+
static auto get_native_libraries_dir () noexcept -> std::string const&
74+
{
75+
return native_libraries_dir;
76+
}
77+
7378
static void create_update_dir (std::string const& override_dir) noexcept
7479
{
7580
if constexpr (Constants::is_release_build) {
@@ -141,6 +146,7 @@ namespace xamarin::android {
141146
static inline bool running_in_emulator = false;
142147
static inline bool embedded_dso_mode_enabled = false;
143148
static inline std::string primary_override_dir;
149+
static inline std::string native_libraries_dir;
144150

145151
#if defined (DEBUG)
146152
static inline std::unordered_map<std::string, std::string> bundled_properties;

src/native/clr/include/runtime-base/internal-pinvokes.hh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <jni.h>
44

5+
#include <xamarin-app.hh>
56
#include "logger.hh"
67

78
int _monodroid_gref_get () noexcept;
@@ -13,3 +14,5 @@ bool clr_typemap_java_to_managed (const char *java_type_name, char const** assem
1314
void monodroid_log (xamarin::android::LogLevel level, LogCategories category, const char *message) noexcept;
1415
char* monodroid_TypeManager_get_java_class_name (jclass klass) noexcept;
1516
void monodroid_free (void *ptr) noexcept;
17+
const char* _monodroid_lookup_replacement_type (const char *jniSimpleReference);
18+
const JniRemappingReplacementMethod* _monodroid_lookup_replacement_method_info (const char *jniSourceType, const char *jniMethodName, const char *jniMethodSignature);

0 commit comments

Comments
 (0)