| // 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 android.content.Context; |
| import android.os.AsyncTask; |
| import android.os.SystemClock; |
| import android.util.Log; |
| |
| import org.chromium.base.CalledByNative; |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.JNINamespace; |
| import org.chromium.base.TraceEvent; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.metrics.RecordHistogram; |
| |
| import java.util.Locale; |
| |
| 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. |
| */ |
| @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; |
| |
| // Guards all access to the libraries |
| private static final Object sLock = new Object(); |
| |
| // The singleton instance of LibraryLoader. |
| private static volatile LibraryLoader sInstance; |
| |
| // 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 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 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 device supports memory mapping the |
| // APK file with executable permissions. |
| private boolean mMapApkWithExecPermission; |
| |
| // One-way switch to indicate whether we probe for memory mapping the APK |
| // file with executable permissions. We suppress the probe under some |
| // conditions. |
| // For more, see: |
| // https://code.google.com/p/chromium/issues/detail?id=448084 |
| private boolean mProbeMapApkWithExecPermission = true; |
| |
| // One-way switch becomes true if the Chromium library was loaded from the |
| // APK file directly. |
| private boolean mLibraryWasLoadedFromApk; |
| |
| // One-way switch becomes false if the Chromium library should be loaded |
| // directly from the APK file but it was compressed or not aligned. |
| private boolean mLibraryIsMappableInApk = true; |
| |
| // The type of process the shared library is loaded in. |
| // This member can be accessed from multiple threads simultaneously, so it have to be |
| // final (like now) or be protected in some way (volatile of synchronized). |
| private final int mLibraryProcessType; |
| |
| /** |
| * @param libraryProcessType the process the shared library is loaded in. refer to |
| * LibraryProcessType for possible values. |
| * @return LibraryLoader if existing, otherwise create a new one. |
| */ |
| public static LibraryLoader get(int libraryProcessType) throws ProcessInitException { |
| synchronized (sLock) { |
| if (sInstance != null) { |
| if (sInstance.mLibraryProcessType == libraryProcessType) return sInstance; |
| throw new ProcessInitException( |
| LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED); |
| } |
| sInstance = new LibraryLoader(libraryProcessType); |
| return sInstance; |
| } |
| } |
| |
| private LibraryLoader(int libraryProcessType) { |
| mLibraryProcessType = libraryProcessType; |
| } |
| |
| /** |
| * The same as ensureInitialized(null, false), should only be called |
| * by non-browser processes. |
| * |
| * @throws ProcessInitException |
| */ |
| @VisibleForTesting |
| public void ensureInitialized() throws ProcessInitException { |
| ensureInitialized(null, false); |
| } |
| |
| /** |
| * This method blocks until the library is fully loaded and initialized. |
| * |
| * @param context The context in which the method is called, the caller |
| * may pass in a null context if it doesn't know in which context it |
| * is running. |
| * |
| * @param shouldDeleteFallbackLibraries The flag tells whether the method |
| * should delete the fallback libraries or not. |
| */ |
| public void ensureInitialized( |
| Context context, boolean shouldDeleteFallbackLibraries) |
| throws ProcessInitException { |
| synchronized (sLock) { |
| if (mInitialized) { |
| // Already initialized, nothing to do. |
| return; |
| } |
| loadAlreadyLocked(context, shouldDeleteFallbackLibraries); |
| initializeAlreadyLocked(); |
| } |
| } |
| |
| /** |
| * Checks if library is fully loaded and initialized. |
| */ |
| public static boolean isInitialized() { |
| return sInstance != null && sInstance.mInitialized; |
| } |
| |
| /** |
| * The same as loadNow(null, false), should only be called by |
| * non-browser process. |
| * |
| * @throws ProcessInitException |
| */ |
| public void loadNow() throws ProcessInitException { |
| loadNow(null, false); |
| } |
| |
| /** |
| * 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. |
| * |
| * @param context The context the code is running, or null if it doesn't have one. |
| * @param shouldDeleteFallbackLibraries The flag tells whether the method |
| * should delete the old fallback libraries or not. |
| * |
| * @throws ProcessInitException if the native library failed to load. |
| */ |
| public void loadNow(Context context, boolean shouldDeleteFallbackLibraries) |
| throws ProcessInitException { |
| synchronized (sLock) { |
| loadAlreadyLocked(context, shouldDeleteFallbackLibraries); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| public void initialize() throws ProcessInitException { |
| synchronized (sLock) { |
| initializeAlreadyLocked(); |
| } |
| } |
| |
| /** 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. |
| * |
| * @param context the application context. |
| */ |
| public void asyncPrefetchLibrariesToMemory(final Context context) { |
| new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... params) { |
| TraceEvent.begin("LibraryLoader.asyncPrefetchLibrariesToMemory"); |
| boolean success = nativeForkAndPrefetchNativeLibrary(); |
| if (!success) { |
| Log.w(TAG, "Forking a process to prefetch the native library failed."); |
| } |
| RecordHistogram.recordBooleanHistogram("LibraryLoader.PrefetchStatus", success); |
| TraceEvent.end("LibraryLoader.asyncPrefetchLibrariesToMemory"); |
| return null; |
| } |
| }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
| } |
| |
| // Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code |
| private void loadAlreadyLocked( |
| Context context, boolean shouldDeleteFallbackLibraries) |
| throws ProcessInitException { |
| try { |
| if (!mLoaded) { |
| assert !mInitialized; |
| |
| long startTime = SystemClock.uptimeMillis(); |
| boolean useChromiumLinker = Linker.isUsed(); |
| boolean fallbackWasUsed = false; |
| |
| if (useChromiumLinker) { |
| String apkFilePath = null; |
| boolean useMapExecSupportFallback = false; |
| |
| // If manufacturer is Samsung then skip the mmap exec check. |
| // |
| // For more, see: |
| // https://code.google.com/p/chromium/issues/detail?id=448084 |
| final String manufacturer = android.os.Build.MANUFACTURER; |
| if (manufacturer != null |
| && manufacturer.toLowerCase(Locale.ENGLISH).contains("samsung")) { |
| Log.w(TAG, "Suppressed load from APK support check on this device"); |
| mProbeMapApkWithExecPermission = false; |
| } |
| |
| // Check if the device supports memory mapping the APK file |
| // with executable permissions. |
| if (context != null) { |
| apkFilePath = context.getApplicationInfo().sourceDir; |
| if (mProbeMapApkWithExecPermission) { |
| mMapApkWithExecPermission = Linker.checkMapExecSupport(apkFilePath); |
| } |
| if (!mMapApkWithExecPermission && Linker.isInZipFile()) { |
| Log.w(TAG, "the no map executable support fallback will be used because" |
| + " memory mapping the APK file with executable permissions is" |
| + " not supported"); |
| Linker.enableNoMapExecSupportFallback(); |
| useMapExecSupportFallback = true; |
| } |
| } else { |
| Log.w(TAG, "could not check load from APK support due to null context"); |
| } |
| |
| // Load libraries using the Chromium linker. |
| Linker.prepareLibraryLoad(); |
| |
| 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 zipFilePath = null; |
| String libFilePath = System.mapLibraryName(library); |
| if (apkFilePath != null && Linker.isInZipFile()) { |
| // The library is in the APK file. |
| if (!Linker.checkLibraryIsMappableInApk(apkFilePath, libFilePath)) { |
| mLibraryIsMappableInApk = false; |
| } |
| if (mLibraryIsMappableInApk || useMapExecSupportFallback) { |
| // Load directly from the APK (or use the no map executable |
| // support fallback, see crazy_linker_elf_loader.cpp). |
| zipFilePath = apkFilePath; |
| Log.i(TAG, "Loading " + library + " " |
| + (useMapExecSupportFallback |
| ? "using no map executable support fallback" |
| : "directly") |
| + " from within " + apkFilePath); |
| } else { |
| // Unpack library fallback. |
| Log.i(TAG, "Loading " + library |
| + " using unpack library fallback from within " |
| + apkFilePath); |
| libFilePath = LibraryLoaderHelper.buildFallbackLibrary( |
| context, library); |
| fallbackWasUsed = true; |
| Log.i(TAG, "Built fallback library " + libFilePath); |
| } |
| } else { |
| // The library is in its own file. |
| Log.i(TAG, "Loading " + library); |
| } |
| |
| // Load the library. |
| boolean isLoaded = false; |
| if (Linker.isUsingBrowserSharedRelros()) { |
| mIsUsingBrowserSharedRelros = true; |
| try { |
| loadLibrary(zipFilePath, libFilePath); |
| isLoaded = true; |
| } catch (UnsatisfiedLinkError e) { |
| Log.w(TAG, "Failed to load native library with shared RELRO, " |
| + "retrying without"); |
| Linker.disableSharedRelros(); |
| mLoadAtFixedAddressFailed = true; |
| } |
| } |
| if (!isLoaded) { |
| loadLibrary(zipFilePath, libFilePath); |
| } |
| } |
| |
| Linker.finishLibraryLoad(); |
| } else { |
| // Load libraries using the system linker. |
| for (String library : NativeLibraries.LIBRARIES) { |
| System.loadLibrary(library); |
| } |
| } |
| |
| if (!fallbackWasUsed && context != null |
| && shouldDeleteFallbackLibraries) { |
| LibraryLoaderHelper.deleteLibrariesAsynchronously( |
| context, LibraryLoaderHelper.LOAD_FROM_APK_FALLBACK_DIR); |
| } |
| |
| long stopTime = SystemClock.uptimeMillis(); |
| Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)", |
| stopTime - startTime, |
| startTime % 10000, |
| stopTime % 10000)); |
| |
| mLoaded = true; |
| } |
| } catch (UnsatisfiedLinkError e) { |
| throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e); |
| } |
| // 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); |
| } |
| } |
| |
| // Load a native shared library with the Chromium linker. If the zip file |
| // path is not null, the library is loaded directly from the zip file. |
| private void loadLibrary(@Nullable String zipFilePath, String libFilePath) { |
| Linker.loadLibrary(zipFilePath, libFilePath); |
| if (zipFilePath != null) { |
| mLibraryWasLoadedFromApk = true; |
| } |
| } |
| |
| // 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 (sLock) { |
| 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; |
| } |
| nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull()); |
| CommandLine.enableNativeProxy(); |
| mCommandLineSwitched = true; |
| } |
| |
| // Invoke base::android::LibraryLoaded in library_loader_hooks.cc |
| private void initializeAlreadyLocked() throws ProcessInitException { |
| if (mInitialized) { |
| return; |
| } |
| |
| // Setup the native command line if necessary. |
| if (!mCommandLineSwitched) { |
| nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull()); |
| } |
| |
| if (!nativeLibraryLoaded()) { |
| Log.e(TAG, "error calling nativeLibraryLoaded"); |
| throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI); |
| } |
| |
| // The Chrome JNI is registered by now so we can switch the Java |
| // command line over to delegating to native if it's necessary. |
| if (!mCommandLineSwitched) { |
| CommandLine.enableNativeProxy(); |
| mCommandLineSwitched = true; |
| } |
| |
| // From now on, keep tracing in sync with native. |
| TraceEvent.registerNativeEnabledObserver(); |
| |
| // 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(Context context) { |
| recordBrowserProcessHistogram(context); |
| } |
| |
| // Record Chromium linker histogram state for the main browser process. Called from |
| // onNativeInitializationComplete(). |
| private void recordBrowserProcessHistogram(Context context) { |
| if (Linker.isUsed()) { |
| nativeRecordChromiumAndroidLinkerBrowserHistogram(mIsUsingBrowserSharedRelros, |
| mLoadAtFixedAddressFailed, |
| getLibraryLoadFromApkStatus(context)); |
| } |
| } |
| |
| // Returns the device's status for loading a library directly from the APK file. |
| // This method can only be called when the Chromium linker is used. |
| private int getLibraryLoadFromApkStatus(Context context) { |
| assert Linker.isUsed(); |
| |
| if (mLibraryWasLoadedFromApk) { |
| return mMapApkWithExecPermission |
| ? LibraryLoadFromApkStatusCodes.SUCCESSFUL |
| : LibraryLoadFromApkStatusCodes.USED_NO_MAP_EXEC_SUPPORT_FALLBACK; |
| } |
| |
| if (!mLibraryIsMappableInApk) { |
| return LibraryLoadFromApkStatusCodes.USED_UNPACK_LIBRARY_FALLBACK; |
| } |
| |
| if (context == null) { |
| Log.w(TAG, "Unknown APK filename due to null context"); |
| return LibraryLoadFromApkStatusCodes.UNKNOWN; |
| } |
| |
| if (!mProbeMapApkWithExecPermission) { |
| return LibraryLoadFromApkStatusCodes.UNKNOWN; |
| } |
| |
| return mMapApkWithExecPermission |
| ? LibraryLoadFromApkStatusCodes.SUPPORTED |
| : LibraryLoadFromApkStatusCodes.NOT_SUPPORTED; |
| } |
| |
| // 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) { |
| if (Linker.isUsed()) { |
| nativeRegisterChromiumAndroidLinkerRendererHistogram(requestedSharedRelro, |
| loadAtFixedAddressFailed); |
| } |
| } |
| |
| /** |
| * @return the process the shared library is loaded in, see the LibraryProcessType |
| * for possible values. |
| */ |
| @CalledByNative |
| public static int getLibraryProcessType() { |
| if (sInstance == null) return LibraryProcessType.PROCESS_UNINITIALIZED; |
| return sInstance.mLibraryProcessType; |
| } |
| |
| private native void nativeInitCommandLine(String[] initCommandLine); |
| |
| // 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(); |
| |
| // 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. |
| private native void nativeRecordChromiumAndroidLinkerBrowserHistogram( |
| boolean isUsingBrowserSharedRelros, |
| boolean loadAtFixedAddressFailed, |
| int libraryLoadFromApkStatus); |
| |
| // 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. |
| private native void nativeRegisterChromiumAndroidLinkerRendererHistogram( |
| boolean requestedSharedRelro, |
| boolean loadAtFixedAddressFailed); |
| |
| // 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 boolean nativeForkAndPrefetchNativeLibrary(); |
| } |