diff options
Diffstat (limited to 'base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java')
-rw-r--r-- | base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java | 829 |
1 files changed, 829 insertions, 0 deletions
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java new file mode 100644 index 0000000000..5bc62042d4 --- /dev/null +++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java @@ -0,0 +1,829 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.library_loader; + +import static org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.Process; +import android.os.StrictMode; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.system.Os; + +import org.chromium.base.AsyncTask; +import org.chromium.base.BuildConfig; +import org.chromium.base.BuildInfo; +import org.chromium.base.CommandLine; +import org.chromium.base.ContextUtils; +import org.chromium.base.FileUtils; +import org.chromium.base.Log; +import org.chromium.base.SysUtils; +import org.chromium.base.TraceEvent; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.base.metrics.RecordHistogram; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.ZipFile; + +import javax.annotation.Nullable; + +/** + * This class provides functionality to load and register the native libraries. + * Callers are allowed to separate loading the libraries from initializing them. + * This may be an advantage for Android Webview, where the libraries can be loaded + * by the zygote process, but then needs per process initialization after the + * application processes are forked from the zygote process. + * + * The libraries may be loaded and initialized from any thread. Synchronization + * primitives are used to ensure that overlapping requests from different + * threads are handled sequentially. + * + * See also base/android/library_loader/library_loader_hooks.cc, which contains + * the native counterpart to this class. + */ +@MainDex +@JNINamespace("base::android") +public class LibraryLoader { + private static final String TAG = "LibraryLoader"; + + // Set to true to enable debug logs. + private static final boolean DEBUG = false; + + // Experience shows that on some devices, the PackageManager fails to properly extract + // native shared libraries to the /data partition at installation or upgrade time, + // which creates all kind of chaos (https://crbug.com/806998). + // + // We implement a fallback when we detect the issue by manually extracting the library + // into Chromium's own data directory, then retrying to load the new library from here. + // + // This will work for any device running K-. Starting with Android L, render processes + // cannot access the file system anymore, and extraction will always fail for them. + // However, the issue doesn't seem to appear in the field for Android L. + // + // Also, starting with M, the issue doesn't exist if shared libraries are stored + // uncompressed in the APK (as Chromium does), because the system linker can access them + // directly, and the PackageManager will thus never extract them in the first place. + static public final boolean PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION = + Build.VERSION.SDK_INT <= VERSION_CODES.KITKAT; + + // Location of extracted native libraries. + private static final String LIBRARY_DIR = "native_libraries"; + + // SharedPreferences key for "don't prefetch libraries" flag + private static final String DONT_PREFETCH_LIBRARIES_KEY = "dont_prefetch_libraries"; + + private static final EnumeratedHistogramSample sRelinkerCountHistogram = + new EnumeratedHistogramSample("ChromiumAndroidLinker.RelinkerFallbackCount", 2); + + // The singleton instance of LibraryLoader. Never null (not final for tests). + private static LibraryLoader sInstance = new LibraryLoader(); + + // One-way switch becomes true when the libraries are initialized ( + // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in + // library_loader_hooks.cc). + // Note that this member should remain a one-way switch, since it accessed from multiple + // threads without a lock. + private volatile boolean mInitialized; + + // One-way switch that becomes true once + // {@link asyncPrefetchLibrariesToMemory} has been called. + private final AtomicBoolean mPrefetchLibraryHasBeenCalled = new AtomicBoolean(); + + // Guards all fields below. + private final Object mLock = new Object(); + + private NativeLibraryPreloader mLibraryPreloader; + private boolean mLibraryPreloaderCalled; + + // One-way switch becomes true when the libraries are loaded. + private boolean mLoaded; + + // One-way switch becomes true when the Java command line is switched to + // native. + private boolean mCommandLineSwitched; + + // One-way switches recording attempts to use Relro sharing in the browser. + // The flags are used to report UMA stats later. + private boolean mIsUsingBrowserSharedRelros; + private boolean mLoadAtFixedAddressFailed; + + // One-way switch becomes true if the Chromium library was loaded from the + // APK file directly. + private boolean mLibraryWasLoadedFromApk; + + // The type of process the shared library is loaded in. + private @LibraryProcessType int mLibraryProcessType; + + // The number of milliseconds it took to load all the native libraries, which + // will be reported via UMA. Set once when the libraries are done loading. + private long mLibraryLoadTimeMs; + + // The return value of NativeLibraryPreloader.loadLibrary(), which will be reported + // via UMA, it is initialized to the invalid value which shouldn't showup in UMA + // report. + private int mLibraryPreloaderStatus = -1; + + /** + * Call this method to determine if this chromium project must + * use this linker. If not, System.loadLibrary() should be used to load + * libraries instead. + */ + public static boolean useCrazyLinker() { + // TODO(digit): Remove this early return GVR is loadable. + // A non-monochrome APK (such as ChromePublic.apk or ChromeModernPublic.apk) on N+ cannot + // use the Linker because the latter is incompatible with the GVR library. Fall back + // to using System.loadLibrary() or System.load() at the cost of no RELRO sharing. + // + // A non-monochrome APK (such as ChromePublic.apk) can be installed on N+ in these + // circumstances: + // * installing APK manually + // * after OTA from M to N + // * side-installing Chrome (possibly from another release channel) + // * Play Store bugs leading to incorrect APK flavor being installed + // + if (Build.VERSION.SDK_INT >= VERSION_CODES.N) return false; + + // The auto-generated NativeLibraries.sUseLinker variable will be true if the + // build has not explicitly disabled Linker features. + return NativeLibraries.sUseLinker; + } + + /** + * Call this method to determine if the chromium project must load the library + * directly from a zip file. + */ + private static boolean isInZipFile() { + // The auto-generated NativeLibraries.sUseLibraryInZipFile variable will be true + // iff the library remains embedded in the APK zip file on the target. + return NativeLibraries.sUseLibraryInZipFile; + } + + /** + * Set native library preloader, if set, the NativeLibraryPreloader.loadLibrary will be invoked + * before calling System.loadLibrary, this only applies when not using the chromium linker. + * + * @param loader the NativeLibraryPreloader, it shall only be set once and before the + * native library loaded. + */ + public void setNativeLibraryPreloader(NativeLibraryPreloader loader) { + synchronized (mLock) { + assert mLibraryPreloader == null && !mLoaded; + mLibraryPreloader = loader; + } + } + + public static LibraryLoader getInstance() { + return sInstance; + } + + private LibraryLoader() {} + + /** + * This method blocks until the library is fully loaded and initialized. + * + * @param processType the process the shared library is loaded in. + */ + public void ensureInitialized(@LibraryProcessType int processType) throws ProcessInitException { + synchronized (mLock) { + if (mInitialized) { + // Already initialized, nothing to do. + return; + } + loadAlreadyLocked(ContextUtils.getApplicationContext()); + initializeAlreadyLocked(processType); + } + } + + /** + * Calls native library preloader (see {@link #setNativeLibraryPreloader}) with the app + * context. If there is no preloader set, this function does nothing. + * Preloader is called only once, so calling it explicitly via this method means + * that it won't be (implicitly) called during library loading. + */ + public void preloadNow() { + preloadNowOverrideApplicationContext(ContextUtils.getApplicationContext()); + } + + /** + * Similar to {@link #preloadNow}, but allows specifying app context to use. + */ + public void preloadNowOverrideApplicationContext(Context appContext) { + synchronized (mLock) { + if (!useCrazyLinker()) { + preloadAlreadyLocked(appContext); + } + } + } + + private void preloadAlreadyLocked(Context appContext) { + try (TraceEvent te = TraceEvent.scoped("LibraryLoader.preloadAlreadyLocked")) { + // Preloader uses system linker, we shouldn't preload if Chromium linker is used. + assert !useCrazyLinker(); + if (mLibraryPreloader != null && !mLibraryPreloaderCalled) { + mLibraryPreloaderStatus = mLibraryPreloader.loadLibrary(appContext); + mLibraryPreloaderCalled = true; + } + } + } + + /** + * Checks if library is fully loaded and initialized. + */ + public boolean isInitialized() { + return mInitialized; + } + + /** + * Loads the library and blocks until the load completes. The caller is responsible + * for subsequently calling ensureInitialized(). + * May be called on any thread, but should only be called once. Note the thread + * this is called on will be the thread that runs the native code's static initializers. + * See the comment in doInBackground() for more considerations on this. + * + * @throws ProcessInitException if the native library failed to load. + */ + public void loadNow() throws ProcessInitException { + loadNowOverrideApplicationContext(ContextUtils.getApplicationContext()); + } + + /** + * Override kept for callers that need to load from a different app context. Do not use unless + * specifically required to load from another context that is not the current process's app + * context. + * + * @param appContext The overriding app context to be used to load libraries. + * @throws ProcessInitException if the native library failed to load with this context. + */ + public void loadNowOverrideApplicationContext(Context appContext) throws ProcessInitException { + synchronized (mLock) { + if (mLoaded && appContext != ContextUtils.getApplicationContext()) { + throw new IllegalStateException("Attempt to load again from alternate context."); + } + loadAlreadyLocked(appContext); + } + } + + /** + * Initializes the library here and now: must be called on the thread that the + * native will call its "main" thread. The library must have previously been + * loaded with loadNow. + * + * @param processType the process the shared library is loaded in. + */ + public void initialize(@LibraryProcessType int processType) throws ProcessInitException { + synchronized (mLock) { + initializeAlreadyLocked(processType); + } + } + + /** + * Disables prefetching for subsequent runs. The value comes from "DontPrefetchLibraries" + * finch experiment, and is pushed on every run. I.e. the effect of the finch experiment + * lags by one run, which is the best we can do considering that prefetching happens way + * before finch is initialized. Note that since LibraryLoader is in //base, it can't depend + * on ChromeFeatureList, and has to rely on external code pushing the value. + * + * @param dontPrefetch whether not to prefetch libraries + */ + public static void setDontPrefetchLibrariesOnNextRuns(boolean dontPrefetch) { + ContextUtils.getAppSharedPreferences() + .edit() + .putBoolean(DONT_PREFETCH_LIBRARIES_KEY, dontPrefetch) + .apply(); + } + + /** + * @return whether not to prefetch libraries (see setDontPrefetchLibrariesOnNextRun()). + */ + private static boolean isNotPrefetchingLibraries() { + // This might be the first time getAppSharedPreferences() is used, so relax strict mode + // to allow disk reads. + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + return ContextUtils.getAppSharedPreferences().getBoolean( + DONT_PREFETCH_LIBRARIES_KEY, false); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + /** Prefetches the native libraries in a background thread. + * + * Launches an AsyncTask that, through a short-lived forked process, reads a + * part of each page of the native library. This is done to warm up the + * page cache, turning hard page faults into soft ones. + * + * This is done this way, as testing shows that fadvise(FADV_WILLNEED) is + * detrimental to the startup time. + */ + public void asyncPrefetchLibrariesToMemory() { + SysUtils.logPageFaultCountToTracing(); + if (isNotPrefetchingLibraries()) return; + + final boolean coldStart = mPrefetchLibraryHasBeenCalled.compareAndSet(false, true); + + // Collection should start close to the native library load, but doesn't have + // to be simultaneous with it. Also, don't prefetch in this case, as this would + // skew the results. + if (coldStart && CommandLine.getInstance().hasSwitch("log-native-library-residency")) { + // nativePeriodicallyCollectResidency() sleeps, run it on another thread, + // and not on the AsyncTask thread pool. + new Thread(LibraryLoader::nativePeriodicallyCollectResidency).start(); + return; + } + + new LibraryPrefetchTask(coldStart).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private static class LibraryPrefetchTask extends AsyncTask<Void, Void, Void> { + private final boolean mColdStart; + + public LibraryPrefetchTask(boolean coldStart) { + mColdStart = coldStart; + } + + @Override + protected Void doInBackground(Void... params) { + try (TraceEvent e = TraceEvent.scoped("LibraryLoader.asyncPrefetchLibrariesToMemory")) { + int percentage = nativePercentageOfResidentNativeLibraryCode(); + // Arbitrary percentage threshold. If most of the native library is already + // resident (likely with monochrome), don't bother creating a prefetch process. + boolean prefetch = mColdStart && percentage < 90; + if (prefetch) { + nativeForkAndPrefetchNativeLibrary(); + } + if (percentage != -1) { + String histogram = "LibraryLoader.PercentageOfResidentCodeBeforePrefetch" + + (mColdStart ? ".ColdStartup" : ".WarmStartup"); + RecordHistogram.recordPercentageHistogram(histogram, percentage); + } + } + return null; + } + } + + // Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker. + // Sets UMA flags depending on the results of loading. + private void loadLibraryWithCustomLinkerAlreadyLocked( + Linker linker, @Nullable String zipFilePath, String libFilePath) { + assert Thread.holdsLock(mLock); + if (linker.isUsingBrowserSharedRelros()) { + // If the browser is set to attempt shared RELROs then we try first with shared + // RELROs enabled, and if that fails then retry without. + mIsUsingBrowserSharedRelros = true; + try { + linker.loadLibrary(libFilePath); + } catch (UnsatisfiedLinkError e) { + Log.w(TAG, "Failed to load native library with shared RELRO, retrying without"); + mLoadAtFixedAddressFailed = true; + linker.loadLibraryNoFixedAddress(libFilePath); + } + } else { + // No attempt to use shared RELROs in the browser, so load as normal. + linker.loadLibrary(libFilePath); + } + + // Loaded successfully, so record if we loaded directly from an APK. + if (zipFilePath != null) { + mLibraryWasLoadedFromApk = true; + } + } + + static void incrementRelinkerCountHitHistogram() { + sRelinkerCountHistogram.record(1); + } + + static void incrementRelinkerCountNotHitHistogram() { + sRelinkerCountHistogram.record(0); + } + + // Experience shows that on some devices, the system sometimes fails to extract native libraries + // at installation or update time from the APK. This function will extract the library and + // return the extracted file path. + static String getExtractedLibraryPath(Context appContext, String libName) { + assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION; + Log.w(TAG, "Failed to load libName %s, attempting fallback extraction then trying again", + libName); + String libraryEntry = LibraryLoader.makeLibraryPathInZipFile(libName, false, false); + return extractFileIfStale(appContext, libraryEntry, makeLibraryDirAndSetPermission()); + } + + // Invoke either Linker.loadLibrary(...), System.loadLibrary(...) or System.load(...), + // triggering JNI_OnLoad in native code. + // TODO(crbug.com/635567): Fix this properly. + @SuppressLint({"DefaultLocale", "NewApi", "UnsafeDynamicallyLoadedCode"}) + private void loadAlreadyLocked(Context appContext) throws ProcessInitException { + try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadAlreadyLocked")) { + if (!mLoaded) { + assert !mInitialized; + + long startTime = SystemClock.uptimeMillis(); + + if (useCrazyLinker()) { + // Load libraries using the Chromium linker. + Linker linker = Linker.getInstance(); + + String apkFilePath = + isInZipFile() ? appContext.getApplicationInfo().sourceDir : null; + linker.prepareLibraryLoad(apkFilePath); + + for (String library : NativeLibraries.LIBRARIES) { + // Don't self-load the linker. This is because the build system is + // not clever enough to understand that all the libraries packaged + // in the final .apk don't need to be explicitly loaded. + if (linker.isChromiumLinkerLibrary(library)) { + if (DEBUG) Log.i(TAG, "ignoring self-linker load"); + continue; + } + + // Determine where the library should be loaded from. + String libFilePath = System.mapLibraryName(library); + if (apkFilePath != null) { + Log.i(TAG, " Loading " + library + " from within " + apkFilePath); + } else { + Log.i(TAG, "Loading " + library); + } + + try { + // Load the library using this Linker. May throw UnsatisfiedLinkError. + loadLibraryWithCustomLinkerAlreadyLocked( + linker, apkFilePath, libFilePath); + incrementRelinkerCountNotHitHistogram(); + } catch (UnsatisfiedLinkError e) { + if (!isInZipFile() + && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) { + loadLibraryWithCustomLinkerAlreadyLocked( + linker, null, getExtractedLibraryPath(appContext, library)); + incrementRelinkerCountHitHistogram(); + } else { + Log.e(TAG, "Unable to load library: " + library); + throw(e); + } + } + } + + linker.finishLibraryLoad(); + } else { + setEnvForNative(); + preloadAlreadyLocked(appContext); + + // If the libraries are located in the zip file, assert that the device API + // level is M or higher. On devices lower than M, the libraries should + // always be loaded by Linker. + assert !isInZipFile() || Build.VERSION.SDK_INT >= VERSION_CODES.M; + + // Load libraries using the system linker. + for (String library : NativeLibraries.LIBRARIES) { + try { + if (!isInZipFile()) { + // The extract and retry logic isn't needed because this path is + // used only for local development. + System.loadLibrary(library); + } else { + // Load directly from the APK. + boolean is64Bit = Process.is64Bit(); + String zipFilePath = appContext.getApplicationInfo().sourceDir; + // In API level 23 and above, it’s possible to open a .so file + // directly from the APK of the path form + // "my_zip_file.zip!/libs/libstuff.so". See: + // https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md#opening-shared-libraries-directly-from-an-apk + String libraryName = zipFilePath + "!/" + + makeLibraryPathInZipFile(library, true, is64Bit); + Log.i(TAG, "libraryName: " + libraryName); + System.load(libraryName); + } + } catch (UnsatisfiedLinkError e) { + Log.e(TAG, "Unable to load library: " + library); + throw(e); + } + } + } + + long stopTime = SystemClock.uptimeMillis(); + mLibraryLoadTimeMs = stopTime - startTime; + Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)", + mLibraryLoadTimeMs, + startTime % 10000, + stopTime % 10000)); + + mLoaded = true; + } + } catch (UnsatisfiedLinkError e) { + throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e); + } + } + + /** + * @param library The library name that is looking for. + * @param crazyPrefix true iff adding crazy linker prefix to the file name. + * @param is64Bit true if the caller think it's run on a 64 bit device. + * @return the library path name in the zip file. + */ + @NonNull + public static String makeLibraryPathInZipFile( + String library, boolean crazyPrefix, boolean is64Bit) { + // Determine the ABI string that Android uses to find native libraries. Values are described + // in: https://developer.android.com/ndk/guides/abis.html + // The 'armeabi' is omitted here because it is not supported in Chrome/WebView, while Cronet + // and Cast load the native library via other paths. + String cpuAbi; + switch (NativeLibraries.sCpuFamily) { + case NativeLibraries.CPU_FAMILY_ARM: + cpuAbi = is64Bit ? "arm64-v8a" : "armeabi-v7a"; + break; + case NativeLibraries.CPU_FAMILY_X86: + cpuAbi = is64Bit ? "x86_64" : "x86"; + break; + case NativeLibraries.CPU_FAMILY_MIPS: + cpuAbi = is64Bit ? "mips64" : "mips"; + break; + default: + throw new RuntimeException("Unknown CPU ABI for native libraries"); + } + + // When both the Chromium linker and zip-uncompressed native libraries are used, + // the build system renames the native shared libraries with a 'crazy.' prefix + // (e.g. "/lib/armeabi-v7a/libfoo.so" -> "/lib/armeabi-v7a/crazy.libfoo.so"). + // + // This prevents the package manager from extracting them at installation/update time + // to the /data directory. The libraries can still be accessed directly by the Chromium + // linker from the APK. + String crazyPart = crazyPrefix ? "crazy." : ""; + return String.format("lib/%s/%s%s", cpuAbi, crazyPart, System.mapLibraryName(library)); + } + + // The WebView requires the Command Line to be switched over before + // initialization is done. This is okay in the WebView's case since the + // JNI is already loaded by this point. + public void switchCommandLineForWebView() { + synchronized (mLock) { + ensureCommandLineSwitchedAlreadyLocked(); + } + } + + // Switch the CommandLine over from Java to native if it hasn't already been done. + // This must happen after the code is loaded and after JNI is ready (since after the + // switch the Java CommandLine will delegate all calls the native CommandLine). + private void ensureCommandLineSwitchedAlreadyLocked() { + assert mLoaded; + if (mCommandLineSwitched) { + return; + } + CommandLine.enableNativeProxy(); + mCommandLineSwitched = true; + } + + // Invoke base::android::LibraryLoaded in library_loader_hooks.cc + private void initializeAlreadyLocked(@LibraryProcessType int processType) + throws ProcessInitException { + if (mInitialized) { + if (mLibraryProcessType != processType) { + throw new ProcessInitException( + LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED); + } + return; + } + mLibraryProcessType = processType; + + ensureCommandLineSwitchedAlreadyLocked(); + + if (!nativeLibraryLoaded(mLibraryProcessType)) { + Log.e(TAG, "error calling nativeLibraryLoaded"); + throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI); + } + + // Check that the version of the library we have loaded matches the version we expect + Log.i(TAG, String.format("Expected native library version number \"%s\", " + + "actual native library version number \"%s\"", + NativeLibraries.sVersionNumber, nativeGetVersionNumber())); + if (!NativeLibraries.sVersionNumber.equals(nativeGetVersionNumber())) { + throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION); + } + + // From now on, keep tracing in sync with native. + TraceEvent.registerNativeEnabledObserver(); + + if (processType == LibraryProcessType.PROCESS_BROWSER + && PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION) { + // Perform the detection and deletion of obsolete native libraries on a background + // background thread. + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + final String suffix = BuildInfo.getInstance().extractedFileSuffix; + final File[] files = getLibraryDir().listFiles(); + if (files == null) return; + + for (File file : files) { + // NOTE: Do not simply look for <suffix> at the end of the file. + // + // Extracted library files have names like 'libfoo.so<suffix>', but + // extractFileIfStale() will use FileUtils.copyFileStreamAtomicWithBuffer() + // to create them, and this method actually uses a transient temporary file + // named like 'libfoo.so<suffix>.tmp' to do that. These temporary files, if + // detected here, should be preserved; hence the reason why contains() is + // used below. + if (!file.getName().contains(suffix)) { + String fileName = file.getName(); + if (!file.delete()) { + Log.w(TAG, "Unable to remove %s", fileName); + } else { + Log.i(TAG, "Removed obsolete file %s", fileName); + } + } + } + } + }); + } + + // From this point on, native code is ready to use and checkIsReady() + // shouldn't complain from now on (and in fact, it's used by the + // following calls). + // Note that this flag can be accessed asynchronously, so any initialization + // must be performed before. + mInitialized = true; + } + + // Called after all native initializations are complete. + public void onNativeInitializationComplete() { + synchronized (mLock) { + recordBrowserProcessHistogramAlreadyLocked(); + } + } + + // Record Chromium linker histogram state for the main browser process. Called from + // onNativeInitializationComplete(). + private void recordBrowserProcessHistogramAlreadyLocked() { + assert Thread.holdsLock(mLock); + if (useCrazyLinker()) { + nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros, + mLoadAtFixedAddressFailed, + mLibraryWasLoadedFromApk ? LibraryLoadFromApkStatusCodes.SUCCESSFUL + : LibraryLoadFromApkStatusCodes.UNKNOWN, + mLibraryLoadTimeMs); + } + if (mLibraryPreloader != null) { + nativeRecordLibraryPreloaderBrowserHistogram(mLibraryPreloaderStatus); + } + } + + // Register pending Chromium linker histogram state for renderer processes. This cannot be + // recorded as a histogram immediately because histograms and IPC are not ready at the + // time it are captured. This function stores a pending value, so that a later call to + // RecordChromiumAndroidLinkerRendererHistogram() will record it correctly. + public void registerRendererProcessHistogram(boolean requestedSharedRelro, + boolean loadAtFixedAddressFailed) { + synchronized (mLock) { + if (useCrazyLinker()) { + nativeRegisterChromiumAndroidLinkerRendererHistogram( + requestedSharedRelro, loadAtFixedAddressFailed, mLibraryLoadTimeMs); + } + if (mLibraryPreloader != null) { + nativeRegisterLibraryPreloaderRendererHistogram(mLibraryPreloaderStatus); + } + } + } + + /** + * Override the library loader (normally with a mock) for testing. + * @param loader the mock library loader. + */ + @VisibleForTesting + public static void setLibraryLoaderForTesting(LibraryLoader loader) { + sInstance = loader; + } + + /** + * Configure ubsan using $UBSAN_OPTIONS. This function needs to be called before any native + * libraries are loaded because ubsan reads its configuration from $UBSAN_OPTIONS when the + * native library is loaded. + */ + public static void setEnvForNative() { + // The setenv API was added in L. On older versions of Android, we should still see ubsan + // reports, but they will not have stack traces. + if (BuildConfig.IS_UBSAN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + try { + // This value is duplicated in build/android/pylib/constants/__init__.py. + Os.setenv("UBSAN_OPTIONS", + "print_stacktrace=1 stack_trace_format='#%n pc %o %m' " + + "handle_segv=0 handle_sigbus=0 handle_sigfpe=0", + true); + } catch (Exception e) { + Log.w(TAG, "failed to set UBSAN_OPTIONS", e); + } + } + } + + // Android system sometimes fails to extract libraries from APK (https://crbug.com/806998). + // This function manually extract libraries as a fallback. + @SuppressLint({"SetWorldReadable"}) + private static String extractFileIfStale( + Context appContext, String pathWithinApk, File destDir) { + assert PLATFORM_REQUIRES_NATIVE_FALLBACK_EXTRACTION; + + String apkPath = appContext.getApplicationInfo().sourceDir; + String fileName = + (new File(pathWithinApk)).getName() + BuildInfo.getInstance().extractedFileSuffix; + File libraryFile = new File(destDir, fileName); + + if (!libraryFile.exists()) { + try (ZipFile zipFile = new ZipFile(apkPath); + InputStream inputStream = + zipFile.getInputStream(zipFile.getEntry(pathWithinApk))) { + if (zipFile.getEntry(pathWithinApk) == null) + throw new RuntimeException("Cannot find ZipEntry" + pathWithinApk); + + FileUtils.copyFileStreamAtomicWithBuffer( + inputStream, libraryFile, new byte[16 * 1024]); + libraryFile.setReadable(true, false); + libraryFile.setExecutable(true, false); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return libraryFile.getAbsolutePath(); + } + + // Ensure the extracted native libraries is created with the right permissions. + private static File makeLibraryDirAndSetPermission() { + if (!ContextUtils.isIsolatedProcess()) { + File cacheDir = ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()); + File libDir = new File(cacheDir, LIBRARY_DIR); + cacheDir.mkdir(); + cacheDir.setExecutable(true, false); + libDir.mkdir(); + libDir.setExecutable(true, false); + } + return getLibraryDir(); + } + + // Return File object for the directory containing extracted native libraries. + private static File getLibraryDir() { + return new File( + ContextCompat.getCodeCacheDir(ContextUtils.getApplicationContext()), LIBRARY_DIR); + } + + // Only methods needed before or during normal JNI registration are during System.OnLoad. + // nativeLibraryLoaded is then called to register everything else. This process is called + // "initialization". This method will be mapped (by generated code) to the LibraryLoaded + // definition in base/android/library_loader/library_loader_hooks.cc. + // + // Return true on success and false on failure. + private native boolean nativeLibraryLoaded(@LibraryProcessType int processType); + + // Method called to record statistics about the Chromium linker operation for the main + // browser process. Indicates whether the linker attempted relro sharing for the browser, + // and if it did, whether the library failed to load at a fixed address. Also records + // support for loading a library directly from the APK file, and the number of milliseconds + // it took to load the libraries. + private native void nativeRecordChromiumAndroidLinkerBrowserHistogram( + boolean isUsingBrowserSharedRelros, + boolean loadAtFixedAddressFailed, + int libraryLoadFromApkStatus, + long libraryLoadTime); + + // Method called to record the return value of NativeLibraryPreloader.loadLibrary for the main + // browser process. + private native void nativeRecordLibraryPreloaderBrowserHistogram(int status); + + // Method called to register (for later recording) statistics about the Chromium linker + // operation for a renderer process. Indicates whether the linker attempted relro sharing, + // and if it did, whether the library failed to load at a fixed address. Also records the + // number of milliseconds it took to load the libraries. + private native void nativeRegisterChromiumAndroidLinkerRendererHistogram( + boolean requestedSharedRelro, + boolean loadAtFixedAddressFailed, + long libraryLoadTime); + + // Method called to register (for later recording) the return value of + // NativeLibraryPreloader.loadLibrary for a renderer process. + private native void nativeRegisterLibraryPreloaderRendererHistogram(int status); + + // Get the version of the native library. This is needed so that we can check we + // have the right version before initializing the (rest of the) JNI. + private native String nativeGetVersionNumber(); + + // Finds the ranges corresponding to the native library pages, forks a new + // process to prefetch these pages and waits for it. The new process then + // terminates. This is blocking. + private static native void nativeForkAndPrefetchNativeLibrary(); + + // Returns the percentage of the native library code page that are currently reseident in + // memory. + private static native int nativePercentageOfResidentNativeLibraryCode(); + + // Periodically logs native library residency from this thread. + private static native void nativePeriodicallyCollectResidency(); +} |