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
5package org.chromium.base.library_loader;
6
7import android.annotation.SuppressLint;
8import android.content.Context;
9import android.os.AsyncTask;
10import android.os.SystemClock;
11
12import org.chromium.base.CommandLine;
13import org.chromium.base.ContextUtils;
14import org.chromium.base.Log;
15import org.chromium.base.TraceEvent;
16import org.chromium.base.VisibleForTesting;
17import org.chromium.base.annotations.CalledByNative;
18import org.chromium.base.annotations.JNINamespace;
19import org.chromium.base.annotations.MainDex;
20import org.chromium.base.metrics.RecordHistogram;
21
22import java.util.concurrent.atomic.AtomicBoolean;
23
24import 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
42public 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