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 static org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
8 
9 import android.annotation.SuppressLint;
10 import android.content.Context;
11 import android.os.Build;
12 import android.os.Build.VERSION_CODES;
13 import android.os.Process;
14 import android.os.StrictMode;
15 import android.os.SystemClock;
16 import android.support.annotation.NonNull;
17 import android.support.v4.content.ContextCompat;
18 import android.system.Os;
19 
20 import org.chromium.base.AsyncTask;
21 import org.chromium.base.BuildConfig;
22 import org.chromium.base.BuildInfo;
23 import org.chromium.base.CommandLine;
24 import org.chromium.base.ContextUtils;
25 import org.chromium.base.FileUtils;
26 import org.chromium.base.Log;
27 import org.chromium.base.SysUtils;
28 import org.chromium.base.TraceEvent;
29 import org.chromium.base.VisibleForTesting;
30 import org.chromium.base.annotations.JNINamespace;
31 import org.chromium.base.annotations.MainDex;
32 import org.chromium.base.metrics.RecordHistogram;
33 
34 import java.io.File;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.util.concurrent.atomic.AtomicBoolean;
38 import java.util.zip.ZipFile;
39 
40 import javax.annotation.Nullable;
41 
42 /**
43  * This class provides functionality to load and register the native libraries.
44  * Callers are allowed to separate loading the libraries from initializing them.
45  * This may be an advantage for Android Webview, where the libraries can be loaded
46  * by the zygote process, but then needs per process initialization after the
47  * application processes are forked from the zygote process.
48  *
49  * The libraries may be loaded and initialized from any thread. Synchronization
50  * primitives are used to ensure that overlapping requests from different
51  * threads are handled sequentially.
52  *
53  * See also base/android/library_loader/library_loader_hooks.cc, which contains
54  * the native counterpart to this class.
55  */
56 @MainDex
57 @JNINamespace("base::android")
58 public class LibraryLoader {
59     private static final String TAG = "LibraryLoader";
60 
61     // Set to true to enable debug logs.
62     private static final boolean DEBUG = false;
63 
64     // Experience shows that on some devices, the PackageManager fails to properly extract
65     // native shared libraries to the /data partition at installation or upgrade time,
66     // which creates all kind of chaos (https://crbug.com/806998).
67     //
68     // We implement a fallback when we detect the issue by manually extracting the library
69     // into Chromium's own data directory, then retrying to load the new library from here.
70     //
71     // This will work for any device running K-. Starting with Android L, render processes
72     // cannot access the file system anymore, and extraction will always fail for them.
73     // However, the issue doesn't seem to appear in the field for Android L.
74     //
75     // Also, starting with M, the issue doesn't exist if shared libraries are stored
76     // uncompressed in the APK (as Chromium does), because the system linker can access them
77     // directly, and the PackageManager will thus never extract them in the first place.
78     static public final boolean PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION =
79             Build.VERSION.SDK_INT <= VERSION_CODES.KITKAT;
80 
81     // Location of extracted native libraries.
82     private static final String LIBRARY_DIR = "native_libraries";
83 
84     // SharedPreferences key for "don't prefetch libraries" flag
85     private static final String DONT_PREFETCH_LIBRARIES_KEY = "dont_prefetch_libraries";
86 
87     private static final EnumeratedHistogramSample sRelinkerCountHistogram =
88             new EnumeratedHistogramSample("ChromiumAndroidLinker.RelinkerFallbackCount", 2);
89 
90     // The singleton instance of LibraryLoader. Never null (not final for tests).
91     private static LibraryLoader sInstance = new LibraryLoader();
92 
93     // One-way switch becomes true when the libraries are initialized (
94     // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
95     // library_loader_hooks.cc).
96     // Note that this member should remain a one-way switch, since it accessed from multiple
97     // threads without a lock.
98     private volatile boolean mInitialized;
99 
100     // One-way switch that becomes true once
101     // {@link asyncPrefetchLibrariesToMemory} has been called.
102     private final AtomicBoolean mPrefetchLibraryHasBeenCalled = new AtomicBoolean();
103 
104     // Guards all fields below.
105     private final Object mLock = new Object();
106 
107     private NativeLibraryPreloader mLibraryPreloader;
108     private boolean mLibraryPreloaderCalled;
109 
110     // One-way switch becomes true when the libraries are loaded.
111     private boolean mLoaded;
112 
113     // One-way switch becomes true when the Java command line is switched to
114     // native.
115     private boolean mCommandLineSwitched;
116 
117     // One-way switches recording attempts to use Relro sharing in the browser.
118     // The flags are used to report UMA stats later.
119     private boolean mIsUsingBrowserSharedRelros;
120     private boolean mLoadAtFixedAddressFailed;
121 
122     // One-way switch becomes true if the Chromium library was loaded from the
123     // APK file directly.
124     private boolean mLibraryWasLoadedFromApk;
125 
126     // The type of process the shared library is loaded in.
127     private @LibraryProcessType int mLibraryProcessType;
128 
129     // The number of milliseconds it took to load all the native libraries, which
130     // will be reported via UMA. Set once when the libraries are done loading.
131     private long mLibraryLoadTimeMs;
132 
133     // The return value of NativeLibraryPreloader.loadLibrary(), which will be reported
134     // via UMA, it is initialized to the invalid value which shouldn't showup in UMA
135     // report.
136     private int mLibraryPreloaderStatus = -1;
137 
138     /**
139      * Call this method to determine if this chromium project must
140      * use this linker. If not, System.loadLibrary() should be used to load
141      * libraries instead.
142      */
useCrazyLinker()143     public static boolean useCrazyLinker() {
144         // TODO(digit): Remove this early return GVR is loadable.
145         // A non-monochrome APK (such as ChromePublic.apk or ChromeModernPublic.apk) on N+ cannot
146         // use the Linker because the latter is incompatible with the GVR library. Fall back
147         // to using System.loadLibrary() or System.load() at the cost of no RELRO sharing.
148         //
149         // A non-monochrome APK (such as ChromePublic.apk) can be installed on N+ in these
150         // circumstances:
151         // * installing APK manually
152         // * after OTA from M to N
153         // * side-installing Chrome (possibly from another release channel)
154         // * Play Store bugs leading to incorrect APK flavor being installed
155         //
156         if (Build.VERSION.SDK_INT >= VERSION_CODES.N) return false;
157 
158         // The auto-generated NativeLibraries.sUseLinker variable will be true if the
159         // build has not explicitly disabled Linker features.
160         return NativeLibraries.sUseLinker;
161     }
162 
163     /**
164      * Call this method to determine if the chromium project must load the library
165      * directly from a zip file.
166      */
isInZipFile()167     private static boolean isInZipFile() {
168         // The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true
169         // iff the library remains embedded in the APK zip file on the target.
170         return NativeLibraries.sUseLibraryInZipFile;
171     }
172 
173     /**
174      * Set native library preloader, if set, the NativeLibraryPreloader.loadLibrary will be invoked
175      * before calling System.loadLibrary, this only applies when not using the chromium linker.
176      *
177      * @param loader the NativeLibraryPreloader, it shall only be set once and before the
178      *               native library loaded.
179      */
setNativeLibraryPreloader(NativeLibraryPreloader loader)180     public void setNativeLibraryPreloader(NativeLibraryPreloader loader) {
181         synchronized (mLock) {
182             assert mLibraryPreloader == null && !mLoaded;
183             mLibraryPreloader = loader;
184         }
185     }
186 
getInstance()187     public static LibraryLoader getInstance() {
188         return sInstance;
189     }
190 
LibraryLoader()191     private LibraryLoader() {}
192 
193     /**
194      *  This method blocks until the library is fully loaded and initialized.
195      *
196      * @param processType the process the shared library is loaded in.
197      */
ensureInitialized(@ibraryProcessType int processType)198     public void ensureInitialized(@LibraryProcessType int processType) throws ProcessInitException {
199         synchronized (mLock) {
200             if (mInitialized) {
201                 // Already initialized, nothing to do.
202                 return;
203             }
204             loadAlreadyLocked(ContextUtils.getApplicationContext());
205             initializeAlreadyLocked(processType);
206         }
207     }
208 
209     /**
210      * Calls native library preloader (see {@link #setNativeLibraryPreloader}) with the app
211      * context. If there is no preloader set, this function does nothing.
212      * Preloader is called only once, so calling it explicitly via this method means
213      * that it won't be (implicitly) called during library loading.
214      */
preloadNow()215     public void preloadNow() {
216         preloadNowOverrideApplicationContext(ContextUtils.getApplicationContext());
217     }
218 
219     /**
220      * Similar to {@link #preloadNow}, but allows specifying app context to use.
221      */
preloadNowOverrideApplicationContext(Context appContext)222     public void preloadNowOverrideApplicationContext(Context appContext) {
223         synchronized (mLock) {
224             if (!useCrazyLinker()) {
225                 preloadAlreadyLocked(appContext);
226             }
227         }
228     }
229 
preloadAlreadyLocked(Context appContext)230     private void preloadAlreadyLocked(Context appContext) {
231         try (TraceEvent te = TraceEvent.scoped("LibraryLoader.preloadAlreadyLocked")) {
232             // Preloader uses system linker, we shouldn't preload if Chromium linker is used.
233             assert !useCrazyLinker();
234             if (mLibraryPreloader != null && !mLibraryPreloaderCalled) {
235                 mLibraryPreloaderStatus = mLibraryPreloader.loadLibrary(appContext);
236                 mLibraryPreloaderCalled = true;
237             }
238         }
239     }
240 
241     /**
242      * Checks if library is fully loaded and initialized.
243      */
isInitialized()244     public boolean isInitialized() {
245         return mInitialized;
246     }
247 
248     /**
249      * Loads the library and blocks until the load completes. The caller is responsible
250      * for subsequently calling ensureInitialized().
251      * May be called on any thread, but should only be called once. Note the thread
252      * this is called on will be the thread that runs the native code's static initializers.
253      * See the comment in doInBackground() for more considerations on this.
254      *
255      * @throws ProcessInitException if the native library failed to load.
256      */
loadNow()257     public void loadNow() throws ProcessInitException {
258         loadNowOverrideApplicationContext(ContextUtils.getApplicationContext());
259     }
260 
261     /**
262      * Override kept for callers that need to load from a different app context. Do not use unless
263      * specifically required to load from another context that is not the current process's app
264      * context.
265      *
266      * @param appContext The overriding app context to be used to load libraries.
267      * @throws ProcessInitException if the native library failed to load with this context.
268      */
loadNowOverrideApplicationContext(Context appContext)269     public void loadNowOverrideApplicationContext(Context appContext) throws ProcessInitException {
270         synchronized (mLock) {
271             if (mLoaded && appContext != ContextUtils.getApplicationContext()) {
272                 throw new IllegalStateException("Attempt to load again from alternate context.");
273             }
274             loadAlreadyLocked(appContext);
275         }
276     }
277 
278     /**
279      * Initializes the library here and now: must be called on the thread that the
280      * native will call its "main" thread. The library must have previously been
281      * loaded with loadNow.
282      *
283      * @param processType the process the shared library is loaded in.
284      */
initialize(@ibraryProcessType int processType)285     public void initialize(@LibraryProcessType int processType) throws ProcessInitException {
286         synchronized (mLock) {
287             initializeAlreadyLocked(processType);
288         }
289     }
290 
291     /**
292      * Disables prefetching for subsequent runs. The value comes from "DontPrefetchLibraries"
293      * finch experiment, and is pushed on every run. I.e. the effect of the finch experiment
294      * lags by one run, which is the best we can do considering that prefetching happens way
295      * before finch is initialized. Note that since LibraryLoader is in //base, it can't depend
296      * on ChromeFeatureList, and has to rely on external code pushing the value.
297      *
298      * @param dontPrefetch whether not to prefetch libraries
299      */
setDontPrefetchLibrariesOnNextRuns(boolean dontPrefetch)300     public static void setDontPrefetchLibrariesOnNextRuns(boolean dontPrefetch) {
301         ContextUtils.getAppSharedPreferences()
302                 .edit()
303                 .putBoolean(DONT_PREFETCH_LIBRARIES_KEY, dontPrefetch)
304                 .apply();
305     }
306 
307     /**
308      * @return whether not to prefetch libraries (see setDontPrefetchLibrariesOnNextRun()).
309      */
isNotPrefetchingLibraries()310     private static boolean isNotPrefetchingLibraries() {
311         // This might be the first time getAppSharedPreferences() is used, so relax strict mode
312         // to allow disk reads.
313         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
314         try {
315             return ContextUtils.getAppSharedPreferences().getBoolean(
316                     DONT_PREFETCH_LIBRARIES_KEY, false);
317         } finally {
318             StrictMode.setThreadPolicy(oldPolicy);
319         }
320     }
321 
322     /** Prefetches the native libraries in a background thread.
323      *
324      * Launches an AsyncTask that, through a short-lived forked process, reads a
325      * part of each page of the native library.  This is done to warm up the
326      * page cache, turning hard page faults into soft ones.
327      *
328      * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is
329      * detrimental to the startup time.
330      */
asyncPrefetchLibrariesToMemory()331     public void asyncPrefetchLibrariesToMemory() {
332         SysUtils.logPageFaultCountToTracing();
333         if (isNotPrefetchingLibraries()) return;
334 
335         final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true);
336 
337         // Collection should start close to the native library load, but doesn't have
338         // to be simultaneous with it. Also, don't prefetch in this case, as this would
339         // skew the results.
340         if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) {
341             // nativePeriodicallyCollectResidency() sleeps, run it on another thread,
342             // and not on the AsyncTask thread pool.
343             new Thread(LibraryLoader::nativePeriodicallyCollectResidency).start();
344             return;
345         }
346 
347         new LibraryPrefetchTask(coldStart).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
348     }
349 
350     private static class LibraryPrefetchTask extends AsyncTask<Void, Void, Void> {
351         private final boolean mColdStart;
352 
LibraryPrefetchTask(boolean coldStart)353         public LibraryPrefetchTask(boolean coldStart) {
354             mColdStart = coldStart;
355         }
356 
357         @Override
doInBackground(Void... params)358         protected Void doInBackground(Void... params) {
359             try (TraceEvent e = TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory")) {
360                 int percentage = nativePercentageOfResidentNativeLibraryCode();
361                 // Arbitrary percentage threshold. If most of the native library is already
362                 // resident (likely with monochrome), don't bother creating a prefetch process.
363                 boolean prefetch = mColdStart && percentage < 90;
364                 if (prefetch) {
365                     nativeForkAndPrefetchNativeLibrary();
366                 }
367                 if (percentage != -1) {
368                     String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch"
369                             + (mColdStart ? ".ColdStartup" : ".WarmStartup");
370                     RecordHistogram.recordPercentageHistogram(histogram, percentage);
371                 }
372             }
373             return null;
374         }
375     }
376 
377     // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker.
378     // Sets UMA flags depending on the results of loading.
379     private void loadLibraryWithCustomLinkerAlreadyLocked(
380             Linker linker, @Nullable String zipFilePath, String libFilePath) {
381         assert Thread.holdsLock(mLock);
382         if (linker.isUsingBrowserSharedRelros()) {
383             // If the browser is set to attempt shared RELROs then we try first with shared
384             // RELROs enabled, and if that fails then retry without.
385             mIsUsingBrowserSharedRelros = true;
386             try {
387                 linker.loadLibrary(libFilePath);
388             } catch (UnsatisfiedLinkError e) {
389                 Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
390                 mLoadAtFixedAddressFailed = true;
391                 linker.loadLibraryNoFixedAddress(libFilePath);
392             }
393         } else {
394             // No attempt to use shared RELROs in the browser, so load as normal.
395             linker.loadLibrary(libFilePath);
396         }
397 
398         // Loaded successfully, so record if we loaded directly from an APK.
399         if (zipFilePath != null) {
400             mLibraryWasLoadedFromApk = true;
401         }
402     }
403 
404     static void incrementRelinkerCountHitHistogram() {
405         sRelinkerCountHistogram.record(1);
406     }
407 
408     static void incrementRelinkerCountNotHitHistogram() {
409         sRelinkerCountHistogram.record(0);
410     }
411 
412     // Experience shows that on some devices, the system sometimes fails to extract native libraries
413     // at installation or update time from the APK. This function will extract the library and
414     // return the extracted file path.
415     static String getExtractedLibraryPath(Context appContext, String libName) {
416         assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION;
417         Log.w(TAG, "Failed to load libName %s, attempting fallback extraction then trying again",
418                 libName);
419         String libraryEntry = LibraryLoader.makeLibraryPathInZipFile(libName, false, false);
420         return extractFileIfStale(appContext, libraryEntry, makeLibraryDirAndSetPermission());
421     }
422 
423     // Invoke either Linker.loadLibrary(...), System.loadLibrary(...) or System.load(...),
424     // triggering JNI_OnLoad in native code.
425     // TODO(crbug.com/635567): Fix this properly.
426     @SuppressLint({"DefaultLocale", "NewApi", "UnsafeDynamicallyLoadedCode"})
427     private void loadAlreadyLocked(Context appContext) throws ProcessInitException {
428         try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadAlreadyLocked")) {
429             if (!mLoaded) {
430                 assert !mInitialized;
431 
432                 long startTime = SystemClock.uptimeMillis();
433 
434                 if (useCrazyLinker()) {
435                     // Load libraries using the Chromium linker.
436                     Linker linker = Linker.getInstance();
437 
438                     String apkFilePath =
439                             isInZipFile() ? appContext.getApplicationInfo().sourceDir : null;
440                     linker.prepareLibraryLoad(apkFilePath);
441 
442                     for (String library : NativeLibraries.LIBRARIES) {
443                         // Don't self-load the linker. This is because the build system is
444                         // not clever enough to understand that all the libraries packaged
445                         // in the final .apk don't need to be explicitly loaded.
446                         if (linker.isChromiumLinkerLibrary(library)) {
447                             if (DEBUG) Log.i(TAG, "ignoring self-linker load");
448                             continue;
449                         }
450 
451                         // Determine where the library should be loaded from.
452                         String libFilePath = System.mapLibraryName(library);
453                         if (apkFilePath != null) {
454                             Log.i(TAG, " Loading " + library + " from within " + apkFilePath);
455                         } else {
456                             Log.i(TAG, "Loading " + library);
457                         }
458 
459                         try {
460                             // Load the library using this Linker. May throw UnsatisfiedLinkError.
461                             loadLibraryWithCustomLinkerAlreadyLocked(
462                                     linker, apkFilePath, libFilePath);
463                             incrementRelinkerCountNotHitHistogram();
464                         } catch (UnsatisfiedLinkError e) {
465                             if (!isInZipFile()
466                                     && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) {
467                                 loadLibraryWithCustomLinkerAlreadyLocked(
468                                         linker, null, getExtractedLibraryPath(appContext, library));
469                                 incrementRelinkerCountHitHistogram();
470                             } else {
471                                 Log.e(TAG, "Unable to load library: " + library);
472                                 throw(e);
473                             }
474                         }
475                     }
476 
477                     linker.finishLibraryLoad();
478                 } else {
479                     setEnvForNative();
480                     preloadAlreadyLocked(appContext);
481 
482                     // If the libraries are located in the zip file, assert that the device API
483                     // level is M or higher. On devices lower than M, the libraries should
484                     // always be loaded by Linker.
485                     assert !isInZipFile() || Build.VERSION.SDK_INT >= VERSION_CODES.M;
486 
487                     // Load libraries using the system linker.
488                     for (String library : NativeLibraries.LIBRARIES) {
489                         try {
490                             if (!isInZipFile()) {
491                                 // The extract and retry logic isn't needed because this path is
492                                 // used only for local development.
493                                 System.loadLibrary(library);
494                             } else {
495                                 // Load directly from the APK.
496                                 boolean is64Bit = Process.is64Bit();
497                                 String zipFilePath = appContext.getApplicationInfo().sourceDir;
498                                 // In API level 23 and above, it’s possible to open a .so file
499                                 // directly from the APK of the path form
500                                 // "my_zip_file.zip!/libs/libstuff.so". See:
501                                 // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#opening-shared-libraries-directly-from-an-apk
502                                 String libraryName = zipFilePath + "!/"
503                                         + makeLibraryPathInZipFile(library, true, is64Bit);
504                                 Log.i(TAG, "libraryName: " + libraryName);
505                                 System.load(libraryName);
506                             }
507                         } catch (UnsatisfiedLinkError e) {
508                             Log.e(TAG, "Unable to load library: " + library);
509                             throw(e);
510                         }
511                     }
512                 }
513 
514                 long stopTime = SystemClock.uptimeMillis();
515                 mLibraryLoadTimeMs = stopTime - startTime;
516                 Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)",
517                         mLibraryLoadTimeMs,
518                         startTime % 10000,
519                         stopTime % 10000));
520 
521                 mLoaded = true;
522             }
523         } catch (UnsatisfiedLinkError e) {
524             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e);
525         }
526     }
527 
528     /**
529      * @param library The library name that is looking for.
530      * @param crazyPrefix true iff adding crazy linker prefix to the file name.
531      * @param is64Bit true if the caller think it's run on a 64 bit device.
532      * @return the library path name in the zip file.
533      */
534     @NonNull
makeLibraryPathInZipFile( String library, boolean crazyPrefix, boolean is64Bit)535     public static String makeLibraryPathInZipFile(
536             String library, boolean crazyPrefix, boolean is64Bit) {
537         // Determine the ABI string that Android uses to find native libraries. Values are described
538         // in: https://developer.android.com/ndk/guides/abis.html
539         // The 'armeabi' is omitted here because it is not supported in Chrome/WebView, while Cronet
540         // and Cast load the native library via other paths.
541         String cpuAbi;
542         switch (NativeLibraries.sCpuFamily) {
543             case NativeLibraries.CPU_FAMILY_ARM:
544                 cpuAbi = is64Bit ? "arm64-v8a" : "armeabi-v7a";
545                 break;
546             case NativeLibraries.CPU_FAMILY_X86:
547                 cpuAbi = is64Bit ? "x86_64" : "x86";
548                 break;
549             case NativeLibraries.CPU_FAMILY_MIPS:
550                 cpuAbi = is64Bit ? "mips64" : "mips";
551                 break;
552             default:
553                 throw new RuntimeException("Unknown CPU ABI for native libraries");
554         }
555 
556         // When both the Chromium linker and zip-uncompressed native libraries are used,
557         // the build system renames the native shared libraries with a 'crazy.' prefix
558         // (e.g. "/lib/armeabi-v7a/libfoo.so" -> "/lib/armeabi-v7a/crazy.libfoo.so").
559         //
560         // This prevents the package manager from extracting them at installation/update time
561         // to the /data directory. The libraries can still be accessed directly by the Chromium
562         // linker from the APK.
563         String crazyPart = crazyPrefix ? "crazy." : "";
564         return String.format("lib/%s/%s%s", cpuAbi, crazyPart, System.mapLibraryName(library));
565     }
566 
567     // The WebView requires the Command Line to be switched over before
568     // initialization is done. This is okay in the WebView's case since the
569     // JNI is already loaded by this point.
switchCommandLineForWebView()570     public void switchCommandLineForWebView() {
571         synchronized (mLock) {
572             ensureCommandLineSwitchedAlreadyLocked();
573         }
574     }
575 
576     // Switch the CommandLine over from Java to native if it hasn't already been done.
577     // This must happen after the code is loaded and after JNI is ready (since after the
578     // switch the Java CommandLine will delegate all calls the native CommandLine).
ensureCommandLineSwitchedAlreadyLocked()579     private void ensureCommandLineSwitchedAlreadyLocked() {
580         assert mLoaded;
581         if (mCommandLineSwitched) {
582             return;
583         }
584         CommandLine.enableNativeProxy();
585         mCommandLineSwitched = true;
586     }
587 
588     // Invoke base::android::LibraryLoaded in library_loader_hooks.cc
initializeAlreadyLocked(@ibraryProcessType int processType)589     private void initializeAlreadyLocked(@LibraryProcessType int processType)
590             throws ProcessInitException {
591         if (mInitialized) {
592             if (mLibraryProcessType != processType) {
593                 throw new ProcessInitException(
594                         LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED);
595             }
596             return;
597         }
598         mLibraryProcessType = processType;
599 
600         ensureCommandLineSwitchedAlreadyLocked();
601 
602         if (!nativeLibraryLoaded(mLibraryProcessType)) {
603             Log.e(TAG, "error calling nativeLibraryLoaded");
604             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
605         }
606 
607         // Check that the version of the library we have loaded matches the version we expect
608         Log.i(TAG, String.format("Expected native library version number \"%s\", "
609                                    + "actual native library version number \"%s\"",
610                            NativeLibraries.sVersionNumber, nativeGetVersionNumber()));
611         if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) {
612             throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION);
613         }
614 
615         // From now on, keep tracing in sync with native.
616         TraceEvent.registerNativeEnabledObserver();
617 
618         if (processType == LibraryProcessType.PROCESS_BROWSER
619                 && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) {
620             // Perform the detection and deletion of obsolete native libraries on a background
621             // background thread.
622             AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
623                 @Override
624                 public void run() {
625                     final String suffix = BuildInfo.getInstance().extractedFileSuffix;
626                     final File[] files = getLibraryDir().listFiles();
627                     if (files == null) return;
628 
629                     for (File file : files) {
630                         // NOTE: Do not simply look for <suffix> at the end of the file.
631                         //
632                         // Extracted library files have names like 'libfoo.so<suffix>', but
633                         // extractFileIfStale() will use FileUtils.copyFileStreamAtomicWithBuffer()
634                         // to create them, and this method actually uses a transient temporary file
635                         // named like 'libfoo.so<suffix>.tmp' to do that. These temporary files, if
636                         // detected here, should be preserved; hence the reason why contains() is
637                         // used below.
638                         if (!file.getName().contains(suffix)) {
639                             String fileName = file.getName();
640                             if (!file.delete()) {
641                                 Log.w(TAG, "Unable to remove %s", fileName);
642                             } else {
643                                 Log.i(TAG, "Removed obsolete file %s", fileName);
644                             }
645                         }
646                     }
647                 }
648             });
649         }
650 
651         // From this point on, native code is ready to use and checkIsReady()
652         // shouldn't complain from now on (and in fact, it's used by the
653         // following calls).
654         // Note that this flag can be accessed asynchronously, so any initialization
655         // must be performed before.
656         mInitialized = true;
657     }
658 
659     // Called after all native initializations are complete.
onNativeInitializationComplete()660     public void onNativeInitializationComplete() {
661         synchronized (mLock) {
662             recordBrowserProcessHistogramAlreadyLocked();
663         }
664     }
665 
666     // Record Chromium linker histogram state for the main browser process. Called from
667     // onNativeInitializationComplete().
recordBrowserProcessHistogramAlreadyLocked()668     private void recordBrowserProcessHistogramAlreadyLocked() {
669         assert Thread.holdsLock(mLock);
670         if (useCrazyLinker()) {
671             nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros,
672                     mLoadAtFixedAddressFailed,
673                     mLibraryWasLoadedFromApk ? LibraryLoadFromApkStatusCodes.SUCCESSFUL
674                                              : LibraryLoadFromApkStatusCodes.UNKNOWN,
675                     mLibraryLoadTimeMs);
676         }
677         if (mLibraryPreloader != null) {
678             nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus);
679         }
680     }
681 
682     // Register pending Chromium linker histogram state for renderer processes. This cannot be
683     // recorded as a histogram immediately because histograms and IPC are not ready at the
684     // time it are captured. This function stores a pending value, so that a later call to
685     // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly.
registerRendererProcessHistogram(boolean requestedSharedRelro, boolean loadAtFixedAddressFailed)686     public void registerRendererProcessHistogram(boolean requestedSharedRelro,
687                                                  boolean loadAtFixedAddressFailed) {
688         synchronized (mLock) {
689             if (useCrazyLinker()) {
690                 nativeRegisterChromiumAndroidLinkerRendererHistogram(
691                         requestedSharedRelro, loadAtFixedAddressFailed, mLibraryLoadTimeMs);
692             }
693             if (mLibraryPreloader != null) {
694                 nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus);
695             }
696         }
697     }
698 
699     /**
700      * Override the library loader (normally with a mock) for testing.
701      * @param loader the mock library loader.
702      */
703     @VisibleForTesting
setLibraryLoaderForTesting(LibraryLoader loader)704     public static void setLibraryLoaderForTesting(LibraryLoader loader) {
705         sInstance = loader;
706     }
707 
708     /**
709      * Configure ubsan using $UBSAN_OPTIONS. This function needs to be called before any native
710      * libraries are loaded because ubsan reads its configuration from $UBSAN_OPTIONS when the
711      * native library is loaded.
712      */
setEnvForNative()713     public static void setEnvForNative() {
714         // The setenv API was added in L. On older versions of Android, we should still see ubsan
715         // reports, but they will not have stack traces.
716         if (BuildConfig.IS_UBSAN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
717             try {
718                 // This value is duplicated in build/android/pylib/constants/__init__.py.
719                 Os.setenv("UBSAN_OPTIONS",
720                         "print_stacktrace=1 stack_trace_format='#%n pc %o %m' "
721                                 + "handle_segv=0 handle_sigbus=0 handle_sigfpe=0",
722                         true);
723             } catch (Exception e) {
724                 Log.w(TAG, "failed to set UBSAN_OPTIONS", e);
725             }
726         }
727     }
728 
729     // Android system sometimes fails to extract libraries from APK (https://crbug.com/806998).
730     // This function manually extract libraries as a fallback.
731     @SuppressLint({"SetWorldReadable"})
extractFileIfStale( Context appContext, String pathWithinApk, File destDir)732     private static String extractFileIfStale(
733             Context appContext, String pathWithinApk, File destDir) {
734         assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION;
735 
736         String apkPath = appContext.getApplicationInfo().sourceDir;
737         String fileName =
738                 (new File(pathWithinApk)).getName() + BuildInfo.getInstance().extractedFileSuffix;
739         File libraryFile = new File(destDir, fileName);
740 
741         if (!libraryFile.exists()) {
742             try (ZipFile zipFile = new ZipFile(apkPath);
743                     InputStream inputStream =
744                             zipFile.getInputStream(zipFile.getEntry(pathWithinApk))) {
745                 if (zipFile.getEntry(pathWithinApk) == null)
746                     throw new RuntimeException("Cannot find ZipEntry" + pathWithinApk);
747 
748                 FileUtils.copyFileStreamAtomicWithBuffer(
749                         inputStream, libraryFile, new byte[16 * 1024]);
750                 libraryFile.setReadable(true, false);
751                 libraryFile.setExecutable(true, false);
752             } catch (IOException e) {
753                 throw new RuntimeException(e);
754             }
755         }
756         return libraryFile.getAbsolutePath();
757     }
758 
759     // Ensure the extracted native libraries is created with the right permissions.
makeLibraryDirAndSetPermission()760     private static File makeLibraryDirAndSetPermission() {
761         if (!ContextUtils.isIsolatedProcess()) {
762             File cacheDir = ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext());
763             File libDir = new File(cacheDir, LIBRARY_DIR);
764             cacheDir.mkdir();
765             cacheDir.setExecutable(true, false);
766             libDir.mkdir();
767             libDir.setExecutable(true, false);
768         }
769         return getLibraryDir();
770     }
771 
772     // Return File object for the directory containing extracted native libraries.
getLibraryDir()773     private static File getLibraryDir() {
774         return new File(
775                 ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()), LIBRARY_DIR);
776     }
777 
778     // Only methods needed before or during normal JNI registration are during System.OnLoad.
779     // nativeLibraryLoaded is then called to register everything else.  This process is called
780     // "initialization".  This method will be mapped (by generated code) to the LibraryLoaded
781     // definition in base/android/library_loader/library_loader_hooks.cc.
782     //
783     // Return true on success and false on failure.
nativeLibraryLoaded(@ibraryProcessType int processType)784     private native boolean nativeLibraryLoaded(@LibraryProcessType int processType);
785 
786     // Method called to record statistics about the Chromium linker operation for the main
787     // browser process. Indicates whether the linker attempted relro sharing for the browser,
788     // and if it did, whether the library failed to load at a fixed address. Also records
789     // support for loading a library directly from the APK file, and the number of milliseconds
790     // it took to load the libraries.
nativeRecordChromiumAndroidLinkerBrowserHistogram( boolean isUsingBrowserSharedRelros, boolean loadAtFixedAddressFailed, int libraryLoadFromApkStatus, long libraryLoadTime)791     private native void nativeRecordChromiumAndroidLinkerBrowserHistogram(
792             boolean isUsingBrowserSharedRelros,
793             boolean loadAtFixedAddressFailed,
794             int libraryLoadFromApkStatus,
795             long libraryLoadTime);
796 
797     // Method called to record the return value of NativeLibraryPreloader.loadLibrary for the main
798     // browser process.
nativeRecordLibraryPreloaderBrowserHistogram(int status)799     private native void nativeRecordLibraryPreloaderBrowserHistogram(int status);
800 
801     // Method called to register (for later recording) statistics about the Chromium linker
802     // operation for a renderer process. Indicates whether the linker attempted relro sharing,
803     // and if it did, whether the library failed to load at a fixed address. Also records the
804     // number of milliseconds it took to load the libraries.
nativeRegisterChromiumAndroidLinkerRendererHistogram( boolean requestedSharedRelro, boolean loadAtFixedAddressFailed, long libraryLoadTime)805     private native void nativeRegisterChromiumAndroidLinkerRendererHistogram(
806             boolean requestedSharedRelro,
807             boolean loadAtFixedAddressFailed,
808             long libraryLoadTime);
809 
810     // Method called to register (for later recording) the return value of
811     // NativeLibraryPreloader.loadLibrary for a renderer process.
nativeRegisterLibraryPreloaderRendererHistogram(int status)812     private native void nativeRegisterLibraryPreloaderRendererHistogram(int status);
813 
814     // Get the version of the native library. This is needed so that we can check we
815     // have the right version before initializing the (rest of the) JNI.
nativeGetVersionNumber()816     private native String nativeGetVersionNumber();
817 
818     // Finds the ranges corresponding to the native library pages, forks a new
819     // process to prefetch these pages and waits for it. The new process then
820     // terminates. This is blocking.
nativeForkAndPrefetchNativeLibrary()821     private static native void nativeForkAndPrefetchNativeLibrary();
822 
823     // Returns the percentage of the native library code page that are currently reseident in
824     // memory.
nativePercentageOfResidentNativeLibraryCode()825     private static native int nativePercentageOfResidentNativeLibraryCode();
826 
827     // Periodically logs native library residency from this thread.
nativePeriodicallyCollectResidency()828     private static native void nativePeriodicallyCollectResidency();
829 }
830