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