Home | History | Annotate | Download | only in library_loader
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.base.library_loader;
      6 
      7 import android.annotation.SuppressLint;
      8 import android.content.Context;
      9 import android.os.AsyncTask;
     10 import android.os.SystemClock;
     11 
     12 import org.chromium.base.CommandLine;
     13 import org.chromium.base.ContextUtils;
     14 import org.chromium.base.Log;
     15 import org.chromium.base.TraceEvent;
     16 import org.chromium.base.VisibleForTesting;
     17 import org.chromium.base.annotations.CalledByNative;
     18 import org.chromium.base.annotations.JNINamespace;
     19 import org.chromium.base.annotations.MainDex;
     20 import org.chromium.base.metrics.RecordHistogram;
     21 
     22 import java.util.concurrent.atomic.AtomicBoolean;
     23 
     24 import javax.annotation.Nullable;
     25 
     26 /**
     27  * This class provides functionality to load and register the native libraries.
     28  * Callers are allowed to separate loading the libraries from initializing them.
     29  * This may be an advantage for Android Webview, where the libraries can be loaded
     30  * by the zygote process, but then needs per process initialization after the
     31  * application processes are forked from the zygote process.
     32  *
     33  * The libraries may be loaded and initialized from any thread. Synchronization
     34  * primitives are used to ensure that overlapping requests from different
     35  * threads are handled sequentially.
     36  *
     37  * See also base/android/library_loader/library_loader_hooks.cc, which contains
     38  * the native counterpart to this class.
     39  */
     40 @JNINamespace("base::android")
     41 @MainDex
     42 public class LibraryLoader {
     43     private static final String TAG = "LibraryLoader";
     44 
     45     // Set to true to enable debug logs.
     46     private static final boolean DEBUG = false;
     47 
     48     // Guards all access to the libraries
     49     private static final Object sLock = new Object();
     50 
     51     // The singleton instance of NativeLibraryPreloader.
     52     private static NativeLibraryPreloader sLibraryPreloader;
     53 
     54     // The singleton instance of LibraryLoader.
     55     private static volatile LibraryLoader sInstance;
     56 
     57     // One-way switch becomes true when the libraries are loaded.
     58     private boolean mLoaded;
     59 
     60     // One-way switch becomes true when the Java command line is switched to
     61     // native.
     62     private boolean mCommandLineSwitched;
     63 
     64     // One-way switch becomes true when the libraries are initialized (
     65     // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
     66     // library_loader_hooks.cc).
     67     // Note that this member should remain a one-way switch, since it accessed from multiple
     68     // threads without a lock.
     69     private volatile boolean mInitialized;
     70 
     71     // One-way switches recording attempts to use Relro sharing in the browser.
     72     // The flags are used to report UMA stats later.
     73     private boolean mIsUsingBrowserSharedRelros;
     74     private boolean mLoadAtFixedAddressFailed;
     75 
     76     // One-way switch becomes true if the Chromium library was loaded from the
     77     // APK file directly.
     78     private boolean mLibraryWasLoadedFromApk;
     79 
     80     // The type of process the shared library is loaded in.
     81     // This member can be accessed from multiple threads simultaneously, so it have to be
     82     // final (like now) or be protected in some way (volatile of synchronized).
     83     private final int mLibraryProcessType;
     84 
     85     // One-way switch that becomes true once
     86     // {@link asyncPrefetchLibrariesToMemory} has been called.
     87     private final AtomicBoolean mPrefetchLibraryHasBeenCalled;
     88 
     89     // The number of milliseconds it took to load all the native libraries, which
     90     // will be reported via UMA. Set once when the libraries are done loading.
     91     private long mLibraryLoadTimeMs;
     92 
     93     // The return value of NativeLibraryPreloader.loadLibrary(), which will be reported
     94     // via UMA, it is initialized to the invalid value which shouldn't showup in UMA
     95     // report.
     96     private int mLibraryPreloaderStatus = -1;
     97 
     98     /**
     99      * Set native library preloader, if set, the NativeLibraryPreloader.loadLibrary will be invoked
    100      * before calling System.loadLibrary, this only applies when not using the chromium linker.
    101      *
    102      * @param loader the NativeLibraryPreloader, it shall only be set once and before the
    103      *               native library loaded.
    104      */
    105     public static void setNativeLibraryPreloader(NativeLibraryPreloader loader) {
    106         synchronized (sLock) {
    107             assert sLibraryPreloader == null && (sInstance == null || !sInstance.mLoaded);
    108             sLibraryPreloader = loader;
    109         }
    110     }
    111 
    112     /**
    113      * @param libraryProcessType the process the shared library is loaded in. refer to
    114      *                           LibraryProcessType for possible values.
    115      * @return LibraryLoader if existing, otherwise create a new one.
    116      */
    117     public static LibraryLoader get(int libraryProcessType) throws ProcessInitException {
    118         synchronized (sLock) {
    119             if (sInstance != null) {
    120                 if (sInstance.mLibraryProcessType == libraryProcessType) return sInstance;
    121                 throw new ProcessInitException(
    122                         LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED);
    123             }
    124             sInstance = new LibraryLoader(libraryProcessType);
    125             return sInstance;
    126         }
    127     }
    128 
    129     private LibraryLoader(int libraryProcessType) {
    130         mLibraryProcessType = libraryProcessType;
    131         mPrefetchLibraryHasBeenCalled = new AtomicBoolean();
    132     }
    133 
    134     /**
    135      *  This method blocks until the library is fully loaded and initialized.
    136      */
    137     public void ensureInitialized() throws ProcessInitException {
    138         synchronized (sLock) {
    139             if (mInitialized) {
    140                 // Already initialized, nothing to do.
    141                 return;
    142             }
    143             loadAlreadyLocked(ContextUtils.getApplicationContext());
    144             initializeAlreadyLocked();
    145         }
    146     }
    147 
    148     /**
    149      * Checks if library is fully loaded and initialized.
    150      */
    151     public static boolean isInitialized() {
    152         return sInstance != null && sInstance.mInitialized;
    153     }
    154 
    155     /**
    156      * Loads the library and blocks until the load completes. The caller is responsible
    157      * for subsequently calling ensureInitialized().
    158      * May be called on any thread, but should only be called once. Note the thread
    159      * this is called on will be the thread that runs the native code's static initializers.
    160      * See the comment in doInBackground() for more considerations on this.
    161      *
    162      * @throws ProcessInitException if the native library failed to load.
    163      */
    164     public void loadNow() throws ProcessInitException {
    165         loadNowOverrideApplicationContext(ContextUtils.getApplicationContext());
    166     }
    167 
    168     /**
    169      * Override kept for callers that need to load from a different app context. Do not use unless
    170      * specifically required to load from another context that is not the current process's app
    171      * context.
    172      *
    173      * @param appContext The overriding app context to be used to load libraries.
    174      * @throws ProcessInitException if the native library failed to load with this context.
    175      */
    176     public void loadNowOverrideApplicationContext(Context appContext) throws ProcessInitException {
    177         synchronized (sLock) {
    178             if (mLoaded && appContext != ContextUtils.getApplicationContext()) {
    179                 throw new IllegalStateException("Attempt to load again from alternate context.");
    180             }
    181             loadAlreadyLocked(appContext);
    182         }
    183     }
    184 
    185     /**
    186      * initializes the library here and now: must be called on the thread that the
    187      * native will call its "main" thread. The library must have previously been
    188      * loaded with loadNow.
    189      */
    190     public void initialize() throws ProcessInitException {
    191         synchronized (sLock) {
    192             initializeAlreadyLocked();
    193         }
    194     }
    195 
    196     /** Prefetches the native libraries in a background thread.
    197      *
    198      * Launches an AsyncTask that, through a short-lived forked process, reads a
    199      * part of each page of the native library.  This is done to warm up the
    200      * page cache, turning hard page faults into soft ones.
    201      *
    202      * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is
    203      * detrimental to the startup time.
    204      */
    205     public void asyncPrefetchLibrariesToMemory() {
    206         final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true);
    207         new AsyncTask<Void, Void, Void>() {
    208             @Override
    209             protected Void doInBackground(Void... params) {
    210                 TraceEvent.begin("LibraryLoader.asyncPrefetchLibrariesToMemory");
    211                 int percentage = nativePercentageOfResidentNativeLibraryCode();
    212                 boolean success = false;
    213                 // Arbitrary percentage threshold. If most of the native library is already
    214                 // resident (likely with monochrome), don't bother creating a prefetch process.
    215                 boolean prefetch = coldStart && percentage < 90;
    216                 if (prefetch) {
    217                     success = nativeForkAndPrefetchNativeLibrary();
    218                     if (!success) {
    219                         Log.w(TAG, "Forking a process to prefetch the native library failed.");
    220                     }
    221                 }
    222                 // As this runs in a background thread, it can be called before histograms are
    223                 // initialized. In this instance, histograms are dropped.
    224                 RecordHistogram.initialize();
    225                 if (prefetch) {
    226                     RecordHistogram.recordBooleanHistogram("LibraryLoader.PrefetchStatus", success);
    227                 }
    228                 if (percentage != -1) {
    229                     String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch"
    230                             + (coldStart ? ".ColdStartup" : ".WarmStartup");
    231                     RecordHistogram.recordPercentageHistogram(histogram, percentage);
    232                 }
    233                 TraceEvent.end("LibraryLoader.asyncPrefetchLibrariesToMemory");
    234                 return null;
    235             }
    236         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    237     }
    238 
    239     // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker.
    240     // Sets UMA flags depending on the results of loading.
    241     private void loadLibrary(Linker linker, @Nullable String zipFilePath, String libFilePath) {
    242         if (linker.isUsingBrowserSharedRelros()) {
    243             // If the browser is set to attempt shared RELROs then we try first with shared
    244             // RELROs enabled, and if that fails then retry without.
    245             mIsUsingBrowserSharedRelros = true;
    246             try {
    247                 linker.loadLibrary(zipFilePath, libFilePath);
    248             } catch (UnsatisfiedLinkError e) {
    249                 Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
    250                 mLoadAtFixedAddressFailed = true;
    251                 linker.loadLibraryNoFixedAddress(zipFilePath, libFilePath);
    252             }
    253         } else {
    254             // No attempt to use shared RELROs in the browser, so load as normal.
    255             linker.loadLibrary(zipFilePath, libFilePath);
    256         }
    257 
    258         // Loaded successfully, so record if we loaded directly from an APK.
    259         if (zipFilePath != null) {
    260             mLibraryWasLoadedFromApk = true;
    261         }
    262     }
    263 
    264     // Invoke either Linker.loadLibrary(...) or System.loadLibrary(...), triggering
    265     // JNI_OnLoad in native code
    266     // TODO(crbug.com/635567): Fix this properly.
    267     @SuppressLint("DefaultLocale")
    268     private void loadAlreadyLocked(Context appContext) throws ProcessInitException {
    269         try {
    270             if (!mLoaded) {
    271                 assert !mInitialized;
    272 
    273                 long startTime = SystemClock.uptimeMillis();
    274 
    275                 if (Linker.isUsed()) {
    276                     // Load libraries using the Chromium linker.
    277                     Linker linker = Linker.getInstance();
    278                     linker.prepareLibraryLoad();
    279 
    280                     for (String library : NativeLibraries.LIBRARIES) {
    281                         // Don't self-load the linker. This is because the build system is
    282                         // not clever enough to understand that all the libraries packaged
    283                         // in the final .apk don't need to be explicitly loaded.
    284                         if (linker.isChromiumLinkerLibrary(library)) {
    285                             if (DEBUG) Log.i(TAG, "ignoring self-linker load");
    286                             continue;
    287                         }
    288 
    289                         // Determine where the library should be loaded from.
    290                         String zipFilePath = null;
    291                         String libFilePath = System.mapLibraryName(library);
    292                         if (Linker.isInZipFile()) {
    293                             // Load directly from the APK.
    294                             zipFilePath = appContext.getApplicationInfo().sourceDir;
    295                             Log.i(TAG, "Loading " + library + " from within " + zipFilePath);
    296                         } else {
    297                             // The library is in its own file.
    298                             Log.i(TAG, "Loading " + library);
    299                         }
    300 
    301                         try {
    302                             // Load the library using this Linker. May throw UnsatisfiedLinkError.
    303                             loadLibrary(linker, zipFilePath, libFilePath);
    304                         } catch (UnsatisfiedLinkError e) {
    305                             Log.e(TAG, "Unable to load library: " + library);
    306                             throw(e);
    307                         }
    308                     }
    309 
    310                     linker.finishLibraryLoad();
    311                 } else {
    312                     if (sLibraryPreloader != null) {
    313                         mLibraryPreloaderStatus = sLibraryPreloader.loadLibrary(appContext);
    314                     }
    315                     // Load libraries using the system linker.
    316                     for (String library : NativeLibraries.LIBRARIES) {
    317                         try {
    318                             System.loadLibrary(library);
    319                         } catch (UnsatisfiedLinkError e) {
    320                             Log.e(TAG, "Unable to load library: " + library);
    321                             throw(e);
    322                         }
    323                     }
    324                 }
    325 
    326                 long stopTime = SystemClock.uptimeMillis();
    327                 mLibraryLoadTimeMs = stopTime - startTime;
    328                 Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)",
    329                         mLibraryLoadTimeMs,
    330                         startTime % 10000,
    331                         stopTime % 10000));
    332 
    333                 mLoaded = true;
    334             }
    335         } catch (UnsatisfiedLinkError e) {
    336             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e);
    337         }
    338     }
    339 
    340     // The WebView requires the Command Line to be switched over before
    341     // initialization is done. This is okay in the WebView's case since the
    342     // JNI is already loaded by this point.
    343     public void switchCommandLineForWebView() {
    344         synchronized (sLock) {
    345             ensureCommandLineSwitchedAlreadyLocked();
    346         }
    347     }
    348 
    349     // Switch the CommandLine over from Java to native if it hasn't already been done.
    350     // This must happen after the code is loaded and after JNI is ready (since after the
    351     // switch the Java CommandLine will delegate all calls the native CommandLine).
    352     private void ensureCommandLineSwitchedAlreadyLocked() {
    353         assert mLoaded;
    354         if (mCommandLineSwitched) {
    355             return;
    356         }
    357         nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull());
    358         CommandLine.enableNativeProxy();
    359         mCommandLineSwitched = true;
    360 
    361         // Ensure that native side application context is loaded and in sync with java side. Must do
    362         // this here so webview also gets its application context set before fully initializing.
    363         ContextUtils.initApplicationContextForNative();
    364     }
    365 
    366     // Invoke base::android::LibraryLoaded in library_loader_hooks.cc
    367     private void initializeAlreadyLocked() throws ProcessInitException {
    368         if (mInitialized) {
    369             return;
    370         }
    371 
    372         ensureCommandLineSwitchedAlreadyLocked();
    373 
    374         if (!nativeLibraryLoaded()) {
    375             Log.e(TAG, "error calling nativeLibraryLoaded");
    376             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
    377         }
    378 
    379         // Check that the version of the library we have loaded matches the version we expect
    380         Log.i(TAG, String.format("Expected native library version number \"%s\", "
    381                                    + "actual native library version number \"%s\"",
    382                            NativeLibraries.sVersionNumber, nativeGetVersionNumber()));
    383         if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) {
    384             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION);
    385         }
    386 
    387         // From now on, keep tracing in sync with native.
    388         TraceEvent.registerNativeEnabledObserver();
    389 
    390         // From this point on, native code is ready to use and checkIsReady()
    391         // shouldn't complain from now on (and in fact, it's used by the
    392         // following calls).
    393         // Note that this flag can be accessed asynchronously, so any initialization
    394         // must be performed before.
    395         mInitialized = true;
    396     }
    397 
    398     // Called after all native initializations are complete.
    399     public void onNativeInitializationComplete() {
    400         recordBrowserProcessHistogram();
    401     }
    402 
    403     // Record Chromium linker histogram state for the main browser process. Called from
    404     // onNativeInitializationComplete().
    405     private void recordBrowserProcessHistogram() {
    406         if (Linker.getInstance().isUsed()) {
    407             nativeRecordChromiumAndroidLinkerBrowserHistogram(
    408                     mIsUsingBrowserSharedRelros,
    409                     mLoadAtFixedAddressFailed,
    410                     getLibraryLoadFromApkStatus(),
    411                     mLibraryLoadTimeMs);
    412         }
    413         if (sLibraryPreloader != null) {
    414             nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus);
    415         }
    416     }
    417 
    418     // Returns the device's status for loading a library directly from the APK file.
    419     // This method can only be called when the Chromium linker is used.
    420     private int getLibraryLoadFromApkStatus() {
    421         assert Linker.getInstance().isUsed();
    422 
    423         if (mLibraryWasLoadedFromApk) {
    424             return LibraryLoadFromApkStatusCodes.SUCCESSFUL;
    425         }
    426 
    427         // There were no libraries to be loaded directly from the APK file.
    428         return LibraryLoadFromApkStatusCodes.UNKNOWN;
    429     }
    430 
    431     // Register pending Chromium linker histogram state for renderer processes. This cannot be
    432     // recorded as a histogram immediately because histograms and IPC are not ready at the
    433     // time it are captured. This function stores a pending value, so that a later call to
    434     // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly.
    435     public void registerRendererProcessHistogram(boolean requestedSharedRelro,
    436                                                  boolean loadAtFixedAddressFailed) {
    437         if (Linker.getInstance().isUsed()) {
    438             nativeRegisterChromiumAndroidLinkerRendererHistogram(requestedSharedRelro,
    439                                                                  loadAtFixedAddressFailed,
    440                                                                  mLibraryLoadTimeMs);
    441         }
    442         if (sLibraryPreloader != null) {
    443             nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus);
    444         }
    445     }
    446 
    447     /**
    448      * @return the process the shared library is loaded in, see the LibraryProcessType
    449      *         for possible values.
    450      */
    451     @CalledByNative
    452     public static int getLibraryProcessType() {
    453         if (sInstance == null) return LibraryProcessType.PROCESS_UNINITIALIZED;
    454         return sInstance.mLibraryProcessType;
    455     }
    456 
    457     /**
    458      * Override the library loader (normally with a mock) for testing.
    459      * @param loader the mock library loader.
    460      */
    461     @VisibleForTesting
    462     public static void setLibraryLoaderForTesting(LibraryLoader loader) {
    463         sInstance = loader;
    464     }
    465 
    466     private native void nativeInitCommandLine(String[] initCommandLine);
    467 
    468     // Only methods needed before or during normal JNI registration are during System.OnLoad.
    469     // nativeLibraryLoaded is then called to register everything else.  This process is called
    470     // "initialization".  This method will be mapped (by generated code) to the LibraryLoaded
    471     // definition in base/android/library_loader/library_loader_hooks.cc.
    472     //
    473     // Return true on success and false on failure.
    474     private native boolean nativeLibraryLoaded();
    475 
    476     // Method called to record statistics about the Chromium linker operation for the main
    477     // browser process. Indicates whether the linker attempted relro sharing for the browser,
    478     // and if it did, whether the library failed to load at a fixed address. Also records
    479     // support for loading a library directly from the APK file, and the number of milliseconds
    480     // it took to load the libraries.
    481     private native void nativeRecordChromiumAndroidLinkerBrowserHistogram(
    482             boolean isUsingBrowserSharedRelros,
    483             boolean loadAtFixedAddressFailed,
    484             int libraryLoadFromApkStatus,
    485             long libraryLoadTime);
    486 
    487     // Method called to record the return value of NativeLibraryPreloader.loadLibrary for the main
    488     // browser process.
    489     private native void nativeRecordLibraryPreloaderBrowserHistogram(int status);
    490 
    491     // Method called to register (for later recording) statistics about the Chromium linker
    492     // operation for a renderer process. Indicates whether the linker attempted relro sharing,
    493     // and if it did, whether the library failed to load at a fixed address. Also records the
    494     // number of milliseconds it took to load the libraries.
    495     private native void nativeRegisterChromiumAndroidLinkerRendererHistogram(
    496             boolean requestedSharedRelro,
    497             boolean loadAtFixedAddressFailed,
    498             long libraryLoadTime);
    499 
    500     // Method called to register (for later recording) the return value of
    501     // NativeLibraryPreloader.loadLibrary for a renderer process.
    502     private native void nativeRegisterLibraryPreloaderRendererHistogram(int status);
    503 
    504     // Get the version of the native library. This is needed so that we can check we
    505     // have the right version before initializing the (rest of the) JNI.
    506     private native String nativeGetVersionNumber();
    507 
    508     // Finds the ranges corresponding to the native library pages, forks a new
    509     // process to prefetch these pages and waits for it. The new process then
    510     // terminates. This is blocking.
    511     private static native boolean nativeForkAndPrefetchNativeLibrary();
    512 
    513     // Returns the percentage of the native library code page that are currently reseident in
    514     // memory.
    515     private static native int nativePercentageOfResidentNativeLibraryCode();
    516 }
    517