Bug 1042984 - Add extensive logging and descriptive crash data for library load errors. r=mfinkle, a=sledru
authorRichard Newman <rnewman@mozilla.com>
Thu, 24 Jul 2014 09:39:48 -0700
changeset 208180 3808d8fbe348
parent 208179 bce84b70da30
child 208181 b295f329dfd3
push id3754
push userryanvm@gmail.com
push date2014-07-28 15:24 +0000
treeherdermozilla-beta@61b30b605194 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, sledru
bugs1042984
milestone32.0
Bug 1042984 - Add extensive logging and descriptive crash data for library load errors. r=mfinkle, a=sledru
mobile/android/base/GeckoApplication.java
mobile/android/base/GeckoView.java
mobile/android/base/db/PasswordsProvider.java
mobile/android/base/mozglue/GeckoLoader.java.in
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -112,21 +112,23 @@ public class GeckoApplication extends Ap
         GeckoConnectivityReceiver.getInstance().start(applicationContext);
         GeckoNetworkManager.getInstance().start(applicationContext);
 
         mInBackground = false;
     }
 
     @Override
     public void onCreate() {
-        HardwareUtils.init(getApplicationContext());
-        Clipboard.init(getApplicationContext());
-        FilePicker.init(getApplicationContext());
-        GeckoLoader.loadMozGlue();
-        HomePanelsManager.getInstance().init(getApplicationContext());
+        final Context context = getApplicationContext();
+        HardwareUtils.init(context);
+        Clipboard.init(context);
+        FilePicker.init(context);
+        GeckoLoader.loadMozGlue(context);
+        HomePanelsManager.getInstance().init(context);
+
         super.onCreate();
     }
 
     public boolean isApplicationInBackground() {
         return mInBackground;
     }
 
     public LightweightTheme getLightweightTheme() {
--- a/mobile/android/base/GeckoView.java
+++ b/mobile/android/base/GeckoView.java
@@ -73,17 +73,17 @@ public class GeckoView extends LayerView
                 setGeckoInterface(new BaseGeckoInterface(context));
             }
 
             Clipboard.init(context);
             HardwareUtils.init(context);
 
             // If you want to use GeckoNetworkManager, start it.
 
-            GeckoLoader.loadMozGlue();
+            GeckoLoader.loadMozGlue(context);
             BrowserDB.setEnableContentProviders(false);
          }
 
         if (url != null) {
             GeckoThread.setUri(url);
             GeckoThread.setAction(Intent.ACTION_VIEW);
             GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(url));
         }
--- a/mobile/android/base/db/PasswordsProvider.java
+++ b/mobile/android/base/db/PasswordsProvider.java
@@ -73,24 +73,24 @@ public class PasswordsProvider extends S
         PASSWORDS_PROJECTION_MAP.put(Passwords.TIMES_USED, Passwords.TIMES_USED);
 
         URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "deleted-passwords", DELETED_PASSWORDS);
 
         DELETED_PASSWORDS_PROJECTION_MAP = new HashMap<String, String>();
         DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.ID, DeletedPasswords.ID);
         DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.GUID, DeletedPasswords.GUID);
         DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.TIME_DELETED, DeletedPasswords.TIME_DELETED);
-
-        // We don't use .loadMozGlue because we're in a different process,
-        // and we just want to reuse code rather than use the loader lock etc.
-        GeckoLoader.doLoadLibrary("mozglue");
     }
 
     public PasswordsProvider() {
         super(LOG_TAG);
+
+        // We don't use .loadMozGlue because we're in a different process,
+        // and we just want to reuse code rather than use the loader lock etc.
+        GeckoLoader.doLoadLibrary(getContext(), "mozglue");
     }
 
     @Override
     protected String getDBName(){
         return DB_FILENAME;
     }
 
     @Override
--- a/mobile/android/base/mozglue/GeckoLoader.java.in
+++ b/mobile/android/base/mozglue/GeckoLoader.java.in
@@ -159,17 +159,17 @@ public final class GeckoLoader {
         putenv("EXTERNAL_STORAGE=" + f.getPath());
 
         // setup the app-specific cache path
         f = context.getCacheDir();
         putenv("CACHE_DIRECTORY=" + f.getPath());
 
         /* We really want to use this code, but it requires bumping up the SDK to 17 so for now
            we will use reflection. See https://bugzilla.mozilla.org/show_bug.cgi?id=811763#c11
-        
+
         if (Build.VERSION.SDK_INT >= 17) {
             android.os.UserManager um = (android.os.UserManager)context.getSystemService(Context.USER_SERVICE);
             if (um != null) {
                 putenv("MOZ_ANDROID_USER_SERIAL_NUMBER=" + um.getSerialNumberForUser(android.os.Process.myUserHandle()));
             } else {
                 Log.d(LOGTAG, "Unable to obtain user manager service on a device with SDK version " + Build.VERSION.SDK_INT);
             }
         }
@@ -231,74 +231,172 @@ public final class GeckoLoader {
             cacheDir.setWritable(true, false);
             cacheDir.setExecutable(true, false);
             cacheDir.setReadable(true, false);
         }
 #endif
     }
 
     @RobocopTarget
-    public static void loadSQLiteLibs(Context context, String apkName) {
+    public static void loadSQLiteLibs(final Context context, final String apkName) {
         synchronized (sLibLoadingLock) {
             if (sSQLiteLibsLoaded) {
                 return;
             }
             sSQLiteLibsLoaded = true;
         }
 
-        loadMozGlue();
-        // the extract libs parameter is being removed in bug 732069
+        loadMozGlue(context);
         loadLibsSetup(context);
         loadSQLiteLibsNative(apkName, false);
     }
 
-    public static void loadNSSLibs(Context context, String apkName) {
+    public static void loadNSSLibs(final Context context, final String apkName) {
         synchronized (sLibLoadingLock) {
             if (sNSSLibsLoaded) {
                 return;
             }
             sNSSLibsLoaded = true;
         }
 
-        loadMozGlue();
+        loadMozGlue(context);
         loadLibsSetup(context);
         loadNSSLibsNative(apkName, false);
     }
 
-    public static void doLoadLibrary(final String lib) {
+    private static String getLoadDiagnostics(final Context context, final String lib) {
+        final StringBuilder message = new StringBuilder("LOAD ");
+        message.append(lib);
+
+        message.append(": Data: " + context.getApplicationInfo().dataDir);
+        try {
+            final boolean appLibExists = new File("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so").exists();
+            final boolean dataDataExists = new File("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so").exists();
+            message.append(", ax=" + appLibExists);
+            message.append(", ddx=" + dataDataExists);
+        } catch (Throwable e) {
+            message.append(": ax/ddx fail, ");
+        }
+
+        try {
+            final String dashOne = "/data/data/" + ANDROID_PACKAGE_NAME + "-1";
+            final String dashTwo = "/data/data/" + ANDROID_PACKAGE_NAME + "-2";
+            final boolean dashOneExists = new File(dashOne).exists();
+            final boolean dashTwoExists = new File(dashTwo).exists();
+            message.append(", -1x=" + dashOneExists);
+            message.append(", -2x=" + dashTwoExists);
+        } catch (Throwable e) {
+            message.append(", dash fail, ");
+        }
+
         try {
+            if (Build.VERSION.SDK_INT >= 9) {
+                final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir;
+                final boolean nativeLibDirExists = new File(nativeLibPath).exists();
+                final boolean nativeLibLibExists = new File(nativeLibPath + "/lib" + lib + ".so").exists();
+
+                message.append(", nativeLib: " + nativeLibPath);
+                message.append(", dirx=" + nativeLibDirExists);
+                message.append(", libx=" + nativeLibLibExists);
+            } else {
+                message.append(", <pre-9>");
+            }
+        } catch (Throwable e) {
+            message.append(", nativeLib fail.");
+        }
+
+        return message.toString();
+    }
+
+    private static final boolean attemptLoad(final String path) {
+        try {
+            System.load(path);
+            return true;
+        } catch (Throwable e) {
+            Log.wtf(LOGTAG, "Couldn't load " + path + ": " + e);
+        }
+
+        return false;
+    }
+
+    /**
+     * The first two attempts at loading a library: directly, and
+     * then using the app library path.
+     *
+     * Returns null or the cause exception.
+     */
+    private static final Throwable doLoadLibraryExpected(final Context context, final String lib) {
+        try {
+            // Attempt 1: the way that should work.
             System.loadLibrary(lib);
-        } catch (UnsatisfiedLinkError e) {
-            Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying /data/app-lib path.");
-            try {
-                System.load("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so");
-            } catch (Throwable ee) {
-                try {
-                    Log.wtf(LOGTAG, "Couldn't load " + lib + ": " + ee + ". Trying /data/data path.");
-                    System.load("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so");
-                } catch (Throwable eee) {
-                    Log.wtf(LOGTAG, "Failed every attempt to load " + lib + ". Giving up.");
-                    throw new RuntimeException("Unable to load " + lib, eee);
-                }
+            return null;
+        } catch (Throwable e) {
+            Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying native library dir.");
+
+            if (Build.VERSION.SDK_INT < 9) {
+                // We can't use nativeLibraryDir.
+                return e;
             }
+
+            // Attempt 2: use nativeLibraryDir, which should also work.
+            final String libDir = context.getApplicationInfo().nativeLibraryDir;
+            if (attemptLoad(libDir + "/lib" + lib + ".so")) {
+                // Success!
+                return null;
+            }
+
+            // We failed. Return the original cause.
+            return e;
         }
     }
 
-    public static void loadMozGlue() {
+    public static void doLoadLibrary(final Context context, final String lib) {
+        final Throwable e = doLoadLibraryExpected(context, lib);
+        if (e == null) {
+            // Success.
+            return;
+        }
+
+        // Attempt 3: try finding the path the pseudo-supported way using .dataDir.
+        final String dataLibPath = context.getApplicationInfo().dataDir + "/lib/lib" + lib + ".so";
+        if (attemptLoad(dataLibPath)) {
+            return;
+        }
+
+        // Attempt 4: use /data/app-lib directly. This is a last-ditch effort.
+        if (attemptLoad("/data/app-lib/" + ANDROID_PACKAGE_NAME + "/lib" + lib + ".so")) {
+            return;
+        }
+
+        // Attempt 5: even more optimistic.
+        if (attemptLoad("/data/data/" + ANDROID_PACKAGE_NAME + "/lib/lib" + lib + ".so")) {
+            return;
+        }
+
+        // Give up loudly, leaking information to debug the failure.
+        final String message = getLoadDiagnostics(context, lib);
+        Log.e(LOGTAG, "Load diagnostics: " + message);
+
+        // Throw the descriptive message, using the original library load
+        // failure as the cause.
+        throw new RuntimeException(message, e);
+    }
+
+    public static void loadMozGlue(final Context context) {
         synchronized (sLibLoadingLock) {
             if (sMozGlueLoaded) {
                 return;
             }
             sMozGlueLoaded = true;
         }
 
-        doLoadLibrary("mozglue");
+        doLoadLibrary(context, "mozglue");
     }
 
-    public static void loadGeckoLibs(Context context, String apkName) {
+    public static void loadGeckoLibs(final Context context, final String apkName) {
         loadLibsSetup(context);
         loadGeckoLibsNative(apkName);
     }
 
     private static void setupLocaleEnvironment() {
         putenv("LANG=" + Locale.getDefault().toString());
         NumberFormat nf = NumberFormat.getInstance();
         if (nf instanceof DecimalFormat) {