• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
setNativeLibraryPreloader(NativeLibraryPreloader loader)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      */
get(int libraryProcessType)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 
LibraryLoader(int libraryProcessType)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      */
ensureInitialized()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      */
isInitialized()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      */
loadNow()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      */
loadNowOverrideApplicationContext(Context appContext)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      */
initialize()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      */
asyncPrefetchLibrariesToMemory()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