Skip to content

Commit

Permalink
[monodroid] Work around Android 9+ bug with extractNativeLibs flag (#…
Browse files Browse the repository at this point in the history
…4993)

Fixes: #4983

Context: https://developer.android.com/reference/android/content/pm/ApplicationInfo.html#FLAG_EXTRACT_NATIVE_LIBS

Android API-23 added support for
[`//application/@android:extractNativeLibs`][0], which allows an app
to state that native libs within the `.apk` should *not* be extracted
as part of app installation, to save on overall installation size.

Support for `extractNativeLibs` was added in 95ca102 and feb9ea2.

Unfortunately, we have determined that on some Android 9 devices and
Android 10 devices & emulators, the [`FLAG_EXTRACT_NATIVE_LIBS`][1]
check used in commit feb9ea2 doesn't work as intended or expected:

	boolean embeddedDSOsEnabled = (runtimePackage.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) == 0;

What we observe on the offending devices is that `embeddedDSOsEnabled`
is False when `//application/@extractNativeLibs` is false, which is
the *opposite* of what we expect.  Consequently, when we *should* be
looking for native libs within the `.apk`, we don't!

The result is that we can't find `libmonodroid.so` ('cause it's in
the `.apk`, not on disk):

	DllImport error loading library '__Internal': 'Could not load library: Library '/system/lib/libmonodroid.so' not found.'.

Note the *path* that is being checked: `/system/lib/libmonodroid.so`,
because we aren't checking within the `.apk`, so we "fallback" to
various other paths, including `/system/lib`, none of which have
`libmonodroid.so`.

The result is that, eventually, the app crashes during startup:

	Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x6f63736d in tid 7607 (…), pid 7607 (…)

It is possible that other Android versions are affected as well, which
means we can no longer trust the flag value and need to implement
another way of checking whether the libraries are on the filesystem or
in the `.apk`.

Work around this Android issue by checking for the presence of a known
library in a filesystem location, regardless of the API level.
Specifically, check for the presence of `libmonodroid.so`.
If `libmonodroid.so` is not found, assume that all libraries should be
loaded from the `.apk`, as if `//application/@extractNativeLibs`=false.

The check is performed once on application startup, thus minimizing the
performance impact.

[0]: https://developer.android.com/guide/topics/manifest/application-element#extractNativeLibs
[1]: https://developer.android.com/reference/android/content/pm/ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS

Co-Authored by: Jonathan Peppers (@jonathanpeppers)
  • Loading branch information
grendello authored and jonpryor committed Aug 12, 2020
1 parent aca845b commit 1b7890d
Show file tree
Hide file tree
Showing 11 changed files with 57 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/java-runtime/java/mono/android/DebugRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ public class DebugRuntime {
private DebugRuntime ()
{}

public static native void init (String[] apks, String runtimeLibDir, String[] appDirs, String[] externalStorageDirs, int androidApiLevel, boolean embeddedDSOsEnabled);
public static native void init (String[] apks, String runtimeLibDir, String[] appDirs, String[] externalStorageDirs);
}
7 changes: 1 addition & 6 deletions src/java-runtime/java/mono/android/MonoPackageManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@

public class MonoPackageManager {

// Exists only since API23 onwards, we must define it here
static final int FLAG_EXTRACT_NATIVE_LIBS = 0x10000000;

static Object lock = new Object ();
static boolean initialized;

Expand Down Expand Up @@ -51,7 +48,6 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack
String externalLegacyDir = new java.io.File (
external0,
"../legacy/Android/data/" + context.getPackageName () + "/files/.__override__").getAbsolutePath ();
boolean embeddedDSOsEnabled = android.os.Build.VERSION.SDK_INT >= 23 && (runtimePackage.flags & FLAG_EXTRACT_NATIVE_LIBS) == 0;
String runtimeDir = getNativeLibraryPath (runtimePackage);
String[] appDirs = new String[] {filesDir, cacheDir, dataDir};
String[] externalStorageDirs = new String[] {externalDir, externalLegacyDir};
Expand Down Expand Up @@ -85,7 +81,7 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack
//
if (BuildConfig.Debug) {
System.loadLibrary ("xamarin-debug-app-helper");
DebugRuntime.init (apks, runtimeDir, appDirs, externalStorageDirs, android.os.Build.VERSION.SDK_INT, embeddedDSOsEnabled);
DebugRuntime.init (apks, runtimeDir, appDirs, externalStorageDirs);
} else {
System.loadLibrary("monosgen-2.0");
}
Expand All @@ -111,7 +107,6 @@ public static void LoadApplication (Context context, ApplicationInfo runtimePack
externalStorageDirs,
MonoPackageManager_Resources.Assemblies,
Build.VERSION.SDK_INT,
embeddedDSOsEnabled,
isEmulator ()
);

Expand Down
2 changes: 1 addition & 1 deletion src/java-runtime/java/mono/android/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class Runtime {
}

public static native void init (String lang, String[] runtimeApks, String runtimeDataDir, String[] appDirs, ClassLoader loader, String[] externalStorageDirs, String[] assemblies, String packageName, int apiLevel, String[] environmentVariables);
public static native void initInternal (String lang, String[] runtimeApks, String runtimeDataDir, String[] appDirs, ClassLoader loader, String[] externalStorageDirs, String[] assemblies, int apiLevel, boolean embeddedDSOsEnabled, boolean isEmulator);
public static native void initInternal (String lang, String[] runtimeApks, String runtimeDataDir, String[] appDirs, ClassLoader loader, String[] externalStorageDirs, String[] assemblies, int apiLevel, boolean isEmulator);
public static native void register (String managedType, java.lang.Class nativeClass, String methods);
public static native void notifyTimeZoneChanged ();
public static native int createNewContext (String[] runtimeApks, String[] assemblies, ClassLoader loader);
Expand Down
22 changes: 20 additions & 2 deletions src/monodroid/jni/basic-android-system.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <cerrno>

#include "basic-android-system.hh"
#include "cpp-util.hh"
#include "globals.hh"

using namespace xamarin::android;
Expand All @@ -11,13 +12,29 @@ const char **BasicAndroidSystem::app_lib_directories;
size_t BasicAndroidSystem::app_lib_directories_size = 0;

void
BasicAndroidSystem::setup_app_library_directories (jstring_array_wrapper& runtimeApks, jstring_array_wrapper& appDirs, int androidApiLevel)
BasicAndroidSystem::detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcept
{
if (androidApiLevel < 23 || !is_embedded_dso_mode_enabled ()) {
// appDirs[2] points to the native library directory
simple_pointer_guard<char[]> libmonodroid_path = utils.path_combine (appDirs[2].get_cstr (), "libmonodroid.so");
log_debug (LOG_ASSEMBLY, "Checking if libmonodroid was unpacked to %s", libmonodroid_path.get ());
if (!utils.file_exists (libmonodroid_path)) {
log_debug (LOG_ASSEMBLY, "%s not found, assuming application/android:extractNativeLibs == false", libmonodroid_path.get ());
set_embedded_dso_mode_enabled (true);
} else {
log_debug (LOG_ASSEMBLY, "Native libs extracted to %s, assuming application/android:extractNativeLibs == true", appDirs[2].get_cstr ());
set_embedded_dso_mode_enabled (false);
}
}

void
BasicAndroidSystem::setup_app_library_directories (jstring_array_wrapper& runtimeApks, jstring_array_wrapper& appDirs)
{
if (!is_embedded_dso_mode_enabled ()) {
log_info (LOG_DEFAULT, "Setting up for DSO lookup in app data directories");
BasicAndroidSystem::app_lib_directories_size = 1;
BasicAndroidSystem::app_lib_directories = new const char*[app_lib_directories_size]();
BasicAndroidSystem::app_lib_directories [0] = utils.strdup_new (appDirs[2].get_cstr ());
log_debug (LOG_ASSEMBLY, "Added filesystem DSO lookup location: %s", appDirs[2].get_cstr ());
} else {
log_info (LOG_DEFAULT, "Setting up for DSO lookup directly in the APK");
BasicAndroidSystem::app_lib_directories_size = runtimeApks.get_length ();
Expand Down Expand Up @@ -47,6 +64,7 @@ BasicAndroidSystem::add_apk_libdir (const char *apk, size_t index, [[maybe_unuse
assert (user_data != nullptr);
assert (index < app_lib_directories_size);
app_lib_directories [index] = utils.string_concat (apk, "!/lib/", static_cast<const char*>(user_data));
log_debug (LOG_ASSEMBLY, "Added APK DSO lookup location: %s", app_lib_directories[index]);
}

void
Expand Down
12 changes: 7 additions & 5 deletions src/monodroid/jni/basic-android-system.hh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ namespace xamarin::android::internal
static size_t app_lib_directories_size;

public:
void setup_app_library_directories (jstring_array_wrapper& runtimeApks, jstring_array_wrapper& appDirs, int androidApiLevel);
void setup_app_library_directories (jstring_array_wrapper& runtimeApks, jstring_array_wrapper& appDirs);
void setup_apk_directories (unsigned short running_on_cpu, jstring_array_wrapper &runtimeApks);

const char* get_override_dir (size_t index) const
Expand All @@ -77,10 +77,7 @@ namespace xamarin::android::internal
return embedded_dso_mode_enabled;
}

void set_embedded_dso_mode_enabled (bool yesno)
{
embedded_dso_mode_enabled = yesno;
}
void detect_embedded_dso_mode (jstring_array_wrapper& appDirs) noexcept;

char *get_runtime_libdir () const
{
Expand Down Expand Up @@ -109,6 +106,11 @@ namespace xamarin::android::internal
private:
char* determine_primary_override_dir (jstring_wrapper &home);

void set_embedded_dso_mode_enabled (bool yesno) noexcept
{
embedded_dso_mode_enabled = yesno;
}

private:
bool embedded_dso_mode_enabled = false;
char *runtime_libdir = nullptr;
Expand Down
22 changes: 16 additions & 6 deletions src/monodroid/jni/debug-app-helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ bool maybe_load_library (const char *path);
#define ANDROID_LOG_WARN 2
#define ANDROID_LOG_ERROR 3
#define ANDROID_LOG_FATAL 4
#define ANDROID_LOG_DEBUG 5

static void
__android_log_vprint (int prio, const char* tag, const char* fmt, va_list ap)
Expand All @@ -56,7 +57,7 @@ __android_log_vprint (int prio, const char* tag, const char* fmt, va_list ap)

static constexpr char TAG[] = "debug-app-helper";

unsigned int log_categories = LOG_DEFAULT;
unsigned int log_categories = LOG_DEFAULT | LOG_ASSEMBLY;
BasicUtilities utils;
BasicAndroidSystem androidSystem;

Expand All @@ -69,17 +70,15 @@ JNI_OnLoad ([[maybe_unused]] JavaVM *vm, [[maybe_unused]] void *reserved)
JNIEXPORT void JNICALL
Java_mono_android_DebugRuntime_init (JNIEnv *env, [[maybe_unused]] jclass klass, jobjectArray runtimeApksJava,
jstring runtimeNativeLibDir, jobjectArray appDirs,
jobjectArray externalStorageDirs, jint androidApiLevel,
jboolean embeddedDSOsEnabled)
jobjectArray externalStorageDirs)
{
androidSystem.set_embedded_dso_mode_enabled ((bool) embeddedDSOsEnabled);

jstring_array_wrapper applicationDirs (env, appDirs);
jstring_array_wrapper runtimeApks (env, runtimeApksJava);

androidSystem.detect_embedded_dso_mode (applicationDirs);
androidSystem.set_primary_override_dir (applicationDirs [0]);
androidSystem.set_override_dir (0, androidSystem.get_primary_override_dir ());
androidSystem.setup_app_library_directories (runtimeApks, applicationDirs, androidApiLevel);
androidSystem.setup_app_library_directories (runtimeApks, applicationDirs);

jstring_wrapper jstr (env);
jstr = env->GetObjectArrayElement (externalStorageDirs, 0);
Expand Down Expand Up @@ -298,6 +297,17 @@ get_libmonosgen_path ()
return libmonoso;
}

void
log_debug_nocheck ([[maybe_unused]] LogCategories category, const char *format, ...)
{
va_list args;

if ((log_categories & category) == 0)
return;

DO_LOG (ANDROID_LOG_DEBUG, TAG, format, args);
}

void
log_info ([[maybe_unused]] LogCategories category, const char *format, ...)
{
Expand Down
2 changes: 1 addition & 1 deletion src/monodroid/jni/debug-app-helper.hh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ extern "C" {
* Signature: ([Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String);[Ljava/lang/String);Ljava/lang/String;IZ)V
*/
JNIEXPORT void JNICALL Java_mono_android_DebugRuntime_init
(JNIEnv *, jclass, jobjectArray, jstring, jobjectArray, jobjectArray, jint, jboolean);
(JNIEnv *, jclass, jobjectArray, jstring, jobjectArray, jobjectArray);
}
#endif // _Included_mono_android_DebugRuntime
2 changes: 1 addition & 1 deletion src/monodroid/jni/mono_android_Runtime.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/monodroid/jni/monodroid-glue-internal.hh
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ namespace xamarin::android::internal
void Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava,
jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader,
jobjectArray externalStorageDirs, jobjectArray assembliesJava,
jint apiLevel, jboolean embeddedDSOsEnabled, jboolean isEmulator);
jint apiLevel, jboolean isEmulator);
jint Java_mono_android_Runtime_createNewContextWithData (JNIEnv *env, jclass klass, jobjectArray runtimeApksJava, jobjectArray assembliesJava,
jobjectArray assembliesBytes, jobjectArray assembliesPaths, jobject loader, jboolean force_preload_assemblies);
void Java_mono_android_Runtime_switchToContext (JNIEnv *env, jint contextID);
Expand Down
13 changes: 6 additions & 7 deletions src/monodroid/jni/monodroid-glue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1422,7 +1422,7 @@ inline void
MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava,
jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader,
[[maybe_unused]] jobjectArray externalStorageDirs, jobjectArray assembliesJava,
jint apiLevel, jboolean embeddedDSOsEnabled, jboolean isEmulator)
jint apiLevel, jboolean isEmulator)
{
init_logging_categories ();

Expand All @@ -1432,8 +1432,10 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl
total_time.mark_start ();
}

jstring_array_wrapper applicationDirs (env, appDirs);

android_api_level = apiLevel;
androidSystem.set_embedded_dso_mode_enabled ((bool) embeddedDSOsEnabled);
androidSystem.detect_embedded_dso_mode (applicationDirs);
androidSystem.set_running_in_emulator (isEmulator);

java_TimeZone = utils.get_class_from_runtime_field (env, klass, "java_util_TimeZone", true);
Expand All @@ -1445,7 +1447,6 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl

androidSystem.setup_environment ();

jstring_array_wrapper applicationDirs (env, appDirs);
jstring_wrapper &home = applicationDirs[0];
set_environment_variable_for_directory ("TMPDIR", applicationDirs[1]);
set_environment_variable_for_directory ("HOME", home);
Expand All @@ -1455,7 +1456,7 @@ MonodroidRuntime::Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass kl
disable_external_signal_handlers ();

jstring_array_wrapper runtimeApks (env, runtimeApksJava);
androidSystem.setup_app_library_directories (runtimeApks, applicationDirs, apiLevel);
androidSystem.setup_app_library_directories (runtimeApks, applicationDirs);

init_reference_logging (androidSystem.get_primary_override_dir ());
androidSystem.create_update_dir (androidSystem.get_primary_override_dir ());
Expand Down Expand Up @@ -1632,7 +1633,6 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
externalStorageDirs,
assembliesJava,
apiLevel,
/* embeddedDSOsEnabled */ JNI_FALSE,
/* isEmulator */ JNI_FALSE
);
}
Expand All @@ -1641,7 +1641,7 @@ JNIEXPORT void JNICALL
Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApksJava,
jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader,
jobjectArray externalStorageDirs, jobjectArray assembliesJava,
jint apiLevel, jboolean embeddedDSOsEnabled, jboolean isEmulator)
jint apiLevel, jboolean isEmulator)
{
monodroidRuntime.Java_mono_android_Runtime_initInternal (
env,
Expand All @@ -1654,7 +1654,6 @@ Java_mono_android_Runtime_initInternal (JNIEnv *env, jclass klass, jstring lang,
externalStorageDirs,
assembliesJava,
apiLevel,
embeddedDSOsEnabled,
isEmulator
);
}
Expand Down
3 changes: 2 additions & 1 deletion tests/MSBuildDeviceIntegration/Tests/DebuggingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void SetTargetFrameworkAndManifest(XamarinAndroidApplicationProject proj, Builde

[Test]
[Retry (1)]
public void ApplicationRunsWithoutDebugger ([Values (false, true)] bool isRelease)
public void ApplicationRunsWithoutDebugger ([Values (false, true)] bool isRelease, [Values (false, true)] bool extractNativeLibs)
{
if (!HasDevices) {
Assert.Ignore ("Test needs a device attached.");
Expand All @@ -49,6 +49,7 @@ public void ApplicationRunsWithoutDebugger ([Values (false, true)] bool isReleas
proj.SetDefaultTargetDevice ();
using (var b = CreateApkBuilder (Path.Combine ("temp", TestName))) {
SetTargetFrameworkAndManifest (proj, b);
proj.AndroidManifest = proj.AndroidManifest.Replace ("<application ", $"<application android:extractNativeLibs=\"{extractNativeLibs}\" ");
Assert.True (b.Install (proj), "Project should have installed.");
ClearAdbLogcat ();
if (CommercialBuildAvailable)
Expand Down

0 comments on commit 1b7890d

Please sign in to comment.