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.content.Context;
8import android.os.AsyncTask;
9import android.os.SystemClock;
10
11import org.chromium.base.CommandLine;
12import org.chromium.base.ContextUtils;
13import org.chromium.base.Log;
14import org.chromium.base.TraceEvent;
15import org.chromium.base.annotations.CalledByNative;
16import org.chromium.base.annotations.JNINamespace;
17import org.chromium.base.metrics.RecordHistogram;
18
19import java.util.concurrent.atomic.AtomicBoolean;
20
21import 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")
38public 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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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.
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
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.
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.
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).
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
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.
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().
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.
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.
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
430    public static int getLibraryProcessType() {
431        if (sInstance == null) return LibraryProcessType.PROCESS_UNINITIALIZED;
432        return sInstance.mLibraryProcessType;
433    }
434
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.
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.
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.
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.
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.
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.
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.
480    private static native boolean nativeForkAndPrefetchNativeLibrary();
481
482    // Returns the percentage of the native library code page that are currently reseident in
483    // memory.
484    private static native int nativePercentageOfResidentNativeLibraryCode();
485}
486