blob: 0f403f92e9ea66b9885b79e08a5292daa36dd5d0 [file] [log] [blame]
// 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.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.SystemClock;
import android.system.Os;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.BaseSwitches;
import org.chromium.base.BuildConfig;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.JNIUtils;
import org.chromium.base.Log;
import org.chromium.base.NativeLibraryLoadedStatus;
import org.chromium.base.NativeLibraryLoadedStatus.NativeLibraryLoadedStatusProvider;
import org.chromium.base.StrictModeContext;
import org.chromium.base.TraceEvent;
import org.chromium.base.annotations.CheckDiscard;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.MainDex;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.compat.ApiHelperForM;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.UmaRecorderHolder;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
* 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";
// Shared preferences key for the reached code profiler.
private static final String DEPRECATED_REACHED_CODE_PROFILER_KEY =
"reached_code_profiler_enabled";
private static final String REACHED_CODE_SAMPLING_INTERVAL_KEY =
"reached_code_sampling_interval";
// Default sampling interval for reached code profiler in microseconds.
private static final int DEFAULT_REACHED_CODE_SAMPLING_INTERVAL_US = 10000;
// 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
// LibraryLoaderJni.get().libraryLoaded, 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;
// State that only transitions one-way from 0->1->2. Volatile for the same reasons as
// mInitialized.
@IntDef({LoadState.NOT_LOADED, LoadState.MAIN_DEX_LOADED, LoadState.LOADED})
@Retention(RetentionPolicy.SOURCE)
private @interface LoadState {
int NOT_LOADED = 0;
int MAIN_DEX_LOADED = 1;
int LOADED = 2;
}
private volatile @LoadState int mLoadState;
// Whether to use the Chromium linker vs. the system linker.
// Avoids locking: should be initialized very early.
private boolean mUseChromiumLinker;
// Whether to use ModernLinker vs. LegacyLinker.
// Avoids locking: should be initialized very early.
private boolean mUseModernLinker;
// Whether the |mUseChromiumLinker| and |mUseModernLinker| configuration has been set.
// Avoids locking: should be initialized very early.
private boolean mConfigurationSet;
// The type of process the shared library is loaded in. Gets passed to native after loading.
// Avoids locking: should be initialized very early.
private @LibraryProcessType int mLibraryProcessType;
// Makes sure non-Main Dex initialization happens only once. Does not use any class members
// except the volatile |mLoadState|.
private final Object mNonMainDexLock = new Object();
// Mediates all communication between Linker instances in different processes.
private final MultiProcessMediator mMediator = new MultiProcessMediator();
// Guards all the fields below.
private final Object mLock = new Object();
// When a Chromium linker is used, this field represents the concrete class serving as a Linker.
// Always accessed via getLinker() because the choice of the class can be influenced by
// public setLinkerImplementation() below.
@GuardedBy("mLock")
private Linker mLinker;
@GuardedBy("mLock")
private NativeLibraryPreloader mLibraryPreloader;
@GuardedBy("mLock")
private boolean mLibraryPreloaderCalled;
// Similar to |mLoadState| but is limited case of being loaded in app zygote.
// This is exposed to clients.
@GuardedBy("mLock")
private boolean mLoadedByZygote;
// One-way switch becomes true when the Java command line is switched to
// native.
@GuardedBy("mLock")
private boolean mCommandLineSwitched;
// 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.
@GuardedBy("mLock")
private long mLibraryLoadTimeMs;
/**
* Inner class encapsulating points of communication between instances of LibraryLoader in
* different processes.
*
* Usage:
*
* - For a {@link LibraryLoader} requiring the knowledge of the load address before
* initialization, {@link #takeLoadAddressFromBundle(Bundle)} should be called first. It is
* done very early after establishing a Binder connection.
*
* - To initialize the object, one of {@link #ensureInitializedInMainProcess()} and
* {@link #initInChildProcess()} must be called. Subsequent calls to initialization are
* ignored.
*
* - Later {@link #putLoadAddressToBundle(Bundle)} and
* {@link #takeLoadAddressFromBundle(Bundle)} should be called for passing the RELRO
* information between library loaders.
*
* Internally the {@LibraryLoader} may ignore these messages because it can fall back to not
* sharing RELRO.
*/
@ThreadSafe
public class MultiProcessMediator {
@GuardedBy("mLock")
private long mLoadAddress;
// Used only for asserts, and only ever switched from false to true.
private volatile boolean mInitDone;
/**
* Extracts the load address as provided by another process.
* @param bundle The Bundle to extract from.
*/
public void takeLoadAddressFromBundle(Bundle bundle) {
// Currently clients call this method strictly before any other method can get executed
// on a different thread. Hence, synchronization is not required, but verification of
// correctness is still non-trivial, and over-synchronization is cheap compared to
// library loading.
synchronized (mLock) {
mLoadAddress = Linker.extractLoadAddressFromBundle(bundle);
}
}
/**
* Initializes the Browser process side of communication, the one that coordinates creation
* of other processes. Can be called more than once, subsequent calls are ignored.
*/
public void ensureInitializedInMainProcess() {
if (mInitDone) return;
if (useChromiumLinker()) {
getLinker().initAsRelroProducer();
}
mInitDone = true;
}
/**
* Serializes the load address for communication, if any was determined during
* initialization. Must be called after the library has been loaded in this process.
* @param bundle Bundle to put the address to.
*/
public void putLoadAddressToBundle(Bundle bundle) {
assert mInitDone;
if (useChromiumLinker()) {
getLinker().putLoadAddressToBundle(bundle);
}
}
/**
* Initializes in processes other than "Main".
*/
public void initInChildProcess() {
if (useChromiumLinker()) {
synchronized (mLock) {
getLinker().initAsRelroConsumer(mLoadAddress);
}
}
mInitDone = true;
}
/**
* Optionally extracts RELRO and saves it for replacing the RELRO section in this process.
* Can be invoked before initialization.
* @param bundle Where to deserialize from.
*/
public void takeSharedRelrosFromBundle(Bundle bundle) {
if (useChromiumLinker() && !isLoadedByZygote()) {
getLinker().takeSharedRelrosFromBundle(bundle);
}
}
/**
* Optionally puts the RELRO section information so that it can be memory-mapped in another
* process reading the bundle.
* @param bundle Where to serialize.
*/
public void putSharedRelrosToBundle(Bundle bundle) {
assert mInitDone;
if (useChromiumLinker()) {
getLinker().putSharedRelrosToBundle(bundle);
}
}
}
public final MultiProcessMediator getMediator() {
return mMediator;
}
/**
* 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;
}
public static LibraryLoader getInstance() {
return sInstance;
}
@VisibleForTesting
protected LibraryLoader() {}
/**
* Set the {@Link LibraryProcessType} for this process.
*
* Since this function is called extremely early on in startup, locking is not required.
*
* @param type the process type.
*/
public void setLibraryProcessType(@LibraryProcessType int type) {
assert type != LibraryProcessType.PROCESS_UNINITIALIZED;
if (type == mLibraryProcessType) return;
if (mLibraryProcessType != LibraryProcessType.PROCESS_UNINITIALIZED) {
throw new IllegalStateException(
String.format("Trying to change the LibraryProcessType from %d to %d",
mLibraryProcessType, type));
}
mLibraryProcessType = type;
}
/**
* 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 is loaded.
*/
public void setNativeLibraryPreloader(NativeLibraryPreloader loader) {
synchronized (mLock) {
assert mLibraryPreloader == null;
assert mLoadState == LoadState.NOT_LOADED;
mLibraryPreloader = loader;
}
}
/**
* Sets the configuration for library loading.
*
* Must be called before loading the library. Since this function is called extremely early on
* in startup, locking is not required.
*
* @param useChromiumLinker Whether to use a chromium linker.
* @param useModernLinker Given that one of the Chromium linkers is used, whether to use
* ModernLinker instea of the LegacyLinker.
*/
public void setLinkerImplementation(boolean useChromiumLinker, boolean useModernLinker) {
assert !mInitialized;
mUseChromiumLinker = useChromiumLinker;
mUseModernLinker = useModernLinker;
Log.d(TAG, "Configuration, useChromiumLinker = %b, useModernLinker = %b",
mUseChromiumLinker, mUseModernLinker);
mConfigurationSet = true;
}
@GuardedBy("mLock")
private void setLinkerImplementationIfNeededAlreadyLocked() {
if (mConfigurationSet) return;
// Cannot use initializers for the fields below, as this makes roboelectric tests fail,
// since they don't have a NativeLibraries class.
mUseChromiumLinker = NativeLibraries.sUseLinker;
mUseModernLinker = NativeLibraries.sUseModernLinker;
mConfigurationSet = true;
}
// LegacyLinker is buggy on Android 10, causing crashes (see crbug.com/980304).
//
// Rather than preventing people from running chrome_public_apk on Android 10, fallback to the
// system linker on this platform. We lose relocation sharing as a side-effect, but this
// configuration does not ship to users (since we only use LegacyLinker for APKs targeted at
// pre-N users).
//
// Note: This cannot be done in the build configuration, as otherwise chrome_public_apk cannot
// both be used as the basis to ship on L, and the default APK used by developers on 10+.
private boolean forceSystemLinker() {
boolean result =
mUseChromiumLinker && !mUseModernLinker && Build.VERSION.SDK_INT >= VERSION_CODES.Q;
if (result) {
Log.d(TAG,
"Forcing system linker, relocations will not be shared. "
+ "This negatively impacts memory usage.");
}
return result;
}
private boolean useChromiumLinker() {
return mUseChromiumLinker && !forceSystemLinker();
}
/**
* Returns either a LegacyLinker or a ModernLinker.
*
* ModernLinker requires OS features from Android M and later: a system linker that handles
* packed relocations and load from APK, and |android_dlopen_ext()| for shared RELRO support. It
* cannot run on Android releases earlier than M.
*
* LegacyLinker runs on all Android releases but it is slower and more complex than
* ModernLinker. The LegacyLinker is used on M as it avoids writing the relocation to disk.
*
* On N, O and P Monochrome is selected by Play Store. With Monochrome this code is not used,
* instead Chrome asks the WebView to provide the library (and the shared RELRO). If the WebView
* fails to provide the library, the system linker is used as a fallback.
*
* LegacyLinker can run on all Android releases, but is unused on P+ as it may cause issues.
* LegacyLinker is preferred on M- because it does not write the shared RELRO to disk at
* almost every cold startup.
*
* Finally, ModernLinker is used on Android Q+ with Trichrome.
*
* More: docs/android_native_libraries.md
*
* @return the Linker implementation instance.
*/
private Linker getLinker(ApplicationInfo info) {
// 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
// * installing other Chromium-based browsers
//
// For Chrome builds regularly shipped to users on N+, the system linker (or the Android
// Framework) provides the necessary functionality to load without crazylinker. The
// LegacyLinker is risky to auto-enable on newer Android releases, as it may interfere with
// regular library loading. See http://crbug.com/980304 as example.
//
// This is only called if LibraryLoader.useChromiumLinker() returns true, meaning this is
// either Chrome{,Modern} or Trichrome.
synchronized (mLock) {
if (mLinker == null) {
// With incremental install, it's important to fall back to the "normal"
// library loading path in order for the libraries to be found.
String appClass = info.className;
boolean isIncrementalInstall =
appClass != null && appClass.contains("incrementalinstall");
if (mUseModernLinker && !isIncrementalInstall) {
mLinker = new ModernLinker();
} else {
mLinker = new LegacyLinker();
}
Log.i(TAG, "Using linker: %s", mLinker.getClass().getName());
}
return mLinker;
}
}
private Linker getLinker() {
return getLinker(ContextUtils.getApplicationContext().getApplicationInfo());
}
@CheckDiscard("Can't use @RemovableInRelease because Release build with DCHECK_IS_ON needs it")
public void enableJniChecks() {
if (!BuildConfig.DCHECK_IS_ON) return;
NativeLibraryLoadedStatus.setProvider(new NativeLibraryLoadedStatusProvider() {
@Override
public boolean areMainDexNativeMethodsReady() {
return mLoadState >= LoadState.MAIN_DEX_LOADED;
}
@Override
public boolean areNativeMethodsReady() {
return isInitialized();
}
});
}
/**
* Return if library is already loaded successfully by the zygote.
*/
public boolean isLoadedByZygote() {
synchronized (mLock) {
return mLoadedByZygote;
}
}
/**
* This method blocks until the library is fully loaded and initialized.
*/
public void ensureInitialized() {
if (isInitialized()) return;
ensureMainDexInitialized();
loadNonMainDex();
}
/**
* This method blocks until the native library is initialized, and the Main Dex is loaded
* (MainDex JNI is registered).
*
* You should use this if you would like to use isolated parts of the native library that don't
* depend on content initialization, and only use MainDex classes with JNI.
*
* However, you should be careful not to call this too early in startup on the UI thread, or you
* may significantly increase the time to first draw.
*/
public void ensureMainDexInitialized() {
synchronized (mLock) {
loadMainDexAlreadyLocked(
ContextUtils.getApplicationContext().getApplicationInfo(), false);
initializeAlreadyLocked();
}
}
/**
* 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) {
setLinkerImplementationIfNeededAlreadyLocked();
if (mUseChromiumLinker) return;
preloadAlreadyLocked(appContext.getApplicationInfo(), false /* inZygote */);
}
}
@GuardedBy("mLock")
private void preloadAlreadyLocked(ApplicationInfo appInfo, boolean inZygote) {
try (TraceEvent te = TraceEvent.scoped("LibraryLoader.preloadAlreadyLocked")) {
// Preloader uses system linker, we shouldn't preload if Chromium linker is used.
assert !useChromiumLinker() || inZygote;
if (mLibraryPreloader != null && !mLibraryPreloaderCalled) {
mLibraryPreloader.loadLibrary(appInfo);
mLibraryPreloaderCalled = true;
}
}
}
/**
* Checks whether the native library is fully loaded and initialized.
*/
public boolean isInitialized() {
return mInitialized && mLoadState == LoadState.LOADED;
}
/**
* 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.
*/
public void loadNow() {
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.
*/
public void loadNowOverrideApplicationContext(Context appContext) {
synchronized (mLock) {
if (mLoadState != LoadState.NOT_LOADED
&& appContext != ContextUtils.getApplicationContext()) {
throw new IllegalStateException("Attempt to load again from alternate context.");
}
loadMainDexAlreadyLocked(appContext.getApplicationInfo(), false /* inZygote */);
}
loadNonMainDex();
}
public void loadNowInZygote(ApplicationInfo appInfo) {
synchronized (mLock) {
assert mLoadState == LoadState.NOT_LOADED;
loadMainDexAlreadyLocked(appInfo, true /* inZygote */);
loadNonMainDex();
mLoadedByZygote = true;
}
}
/**
* Initializes the native library: must be called on the thread that the
* native will call its "main" thread. The library must have previously been
* loaded with one of the loadNow*() variants.
*/
public void initialize() {
synchronized (mLock) {
initializeAlreadyLocked();
}
}
/**
* Enables the reached code profiler. The value comes from "ReachedCodeProfiler"
* 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 the profiler has to be enabled
* 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 enabled whether to enable the reached code profiler.
* @param samplingIntervalUs the sampling interval for reached code profiler.
*/
public static void setReachedCodeProfilerEnabledOnNextRuns(
boolean enabled, int samplingIntervalUs) {
// Store 0 if the profiler is not enabled, otherwise store the sampling interval in
// microseconds.
if (enabled && samplingIntervalUs == 0) {
samplingIntervalUs = DEFAULT_REACHED_CODE_SAMPLING_INTERVAL_US;
} else if (!enabled) {
samplingIntervalUs = 0;
}
SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
editor.remove(DEPRECATED_REACHED_CODE_PROFILER_KEY);
editor.putInt(REACHED_CODE_SAMPLING_INTERVAL_KEY, samplingIntervalUs).apply();
}
/**
* @return sampling interval for reached code profiler, or 0 when the profiler is disabled. (see
* setReachedCodeProfilerEnabledOnNextRuns()).
*/
@VisibleForTesting
public static int getReachedCodeSamplingIntervalUs() {
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
if (ContextUtils.getAppSharedPreferences().getBoolean(
DEPRECATED_REACHED_CODE_PROFILER_KEY, false)) {
return DEFAULT_REACHED_CODE_SAMPLING_INTERVAL_US;
}
return ContextUtils.getAppSharedPreferences().getInt(
REACHED_CODE_SAMPLING_INTERVAL_KEY, 0);
}
}
// Helper for loadAlreadyLocked(). Load a native shared library with the Chromium linker.
private void loadLibraryWithCustomLinker(Linker linker, String library) {
// Attempt shared RELROs, and if that fails then retry without.
try {
linker.loadLibrary(library, true /* isFixedAddressPermitted */);
} catch (UnsatisfiedLinkError e) {
Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
linker.loadLibrary(library, false /* isFixedAddressPermitted */);
}
}
private void loadWithChromiumLinker(ApplicationInfo appInfo, String library) {
Linker linker = getLinker(appInfo);
if (isInZipFile()) {
String sourceDir = appInfo.sourceDir;
linker.setApkFilePath(sourceDir);
Log.i(TAG, " Loading %s from within %s", library, sourceDir);
} else {
Log.i(TAG, "Loading %s", library);
}
// Load the library using this Linker. May throw UnsatisfiedLinkError.
loadLibraryWithCustomLinker(linker, library);
}
@GuardedBy("mLock")
@SuppressLint("UnsafeDynamicallyLoadedCode")
private void loadWithSystemLinkerAlreadyLocked(ApplicationInfo appInfo, boolean inZygote) {
setEnvForNative();
preloadAlreadyLocked(appInfo, inZygote);
// If the libraries are located in the zip file, assert that the device API level is M or
// higher. On devices <=M, the libraries should always be loaded by LegacyLinker.
assert !isInZipFile() || Build.VERSION.SDK_INT >= VERSION_CODES.M;
// Load libraries using the system linker.
for (String library : NativeLibraries.LIBRARIES) {
if (!isInZipFile()) {
System.loadLibrary(library);
} else {
// Load directly from the APK.
boolean is64Bit = ApiHelperForM.isProcess64Bit();
String zipFilePath = appInfo.sourceDir;
boolean crazyPrefix = forceSystemLinker(); // See comment in this function.
String fullPath = zipFilePath + "!/"
+ makeLibraryPathInZipFile(library, crazyPrefix, is64Bit);
Log.i(TAG, "libraryName: %s", fullPath);
System.load(fullPath);
}
}
}
// Invoke either Linker.loadLibrary(...), System.loadLibrary(...) or System.load(...),
// triggering JNI_OnLoad in native code.
@GuardedBy("mLock")
@VisibleForTesting
protected void loadMainDexAlreadyLocked(ApplicationInfo appInfo, boolean inZygote) {
if (mLoadState >= LoadState.MAIN_DEX_LOADED) return;
try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadMainDexAlreadyLocked")) {
assert !mInitialized;
assert mLibraryProcessType != LibraryProcessType.PROCESS_UNINITIALIZED || inZygote;
setLinkerImplementationIfNeededAlreadyLocked();
long startTime = SystemClock.uptimeMillis();
if (useChromiumLinker() && !inZygote) {
Log.d(TAG, "Loading with the Chromium linker.");
// See base/android/linker/config.gni, the chromium linker is only enabled when
// we have a single library.
assert NativeLibraries.LIBRARIES.length == 1;
String library = NativeLibraries.LIBRARIES[0];
loadWithChromiumLinker(appInfo, library);
} else {
Log.d(TAG, "Loading with the System linker.");
loadWithSystemLinkerAlreadyLocked(appInfo, inZygote);
}
long stopTime = SystemClock.uptimeMillis();
mLibraryLoadTimeMs = stopTime - startTime;
Log.d(TAG, "Time to load native libraries: %d ms", mLibraryLoadTimeMs);
mLoadState = LoadState.MAIN_DEX_LOADED;
} catch (UnsatisfiedLinkError e) {
throw new ProcessInitException(LoaderErrors.NATIVE_LIBRARY_LOAD_FAILED, e);
}
}
@VisibleForTesting
// After Android M, this function is likely a no-op.
protected void loadNonMainDex() {
if (mLoadState == LoadState.LOADED) return;
synchronized (mNonMainDexLock) {
assert mLoadState != LoadState.NOT_LOADED;
if (mLoadState == LoadState.LOADED) return;
try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadNonMainDex")) {
if (!JNIUtils.isSelectiveJniRegistrationEnabled()) {
LibraryLoaderJni.get().registerNonMainDexJni();
}
mLoadState = LoadState.LOADED;
}
}
}
/**
* @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(
Locale.US, "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).
@GuardedBy("mLock")
private void ensureCommandLineSwitchedAlreadyLocked() {
assert mLoadState >= LoadState.MAIN_DEX_LOADED;
if (mCommandLineSwitched) {
return;
}
CommandLine.enableNativeProxy();
mCommandLineSwitched = true;
}
/**
* Assert that library process type in the LibraryLoarder is compatible with provided type.
*
* @param libraryProcessType a library process type to assert.
*/
public void assertCompatibleProcessType(@LibraryProcessType int libraryProcessType) {
assert libraryProcessType == mLibraryProcessType;
}
// Invoke base::android::LibraryLoaded in library_loader_hooks.cc
@GuardedBy("mLock")
private void initializeAlreadyLocked() {
if (mInitialized) return;
assert mLibraryProcessType != LibraryProcessType.PROCESS_UNINITIALIZED;
// Add a switch for the reached code profiler as late as possible since it requires a read
// from the shared preferences. At this point the shared preferences are usually warmed up.
if (mLibraryProcessType == LibraryProcessType.PROCESS_BROWSER) {
int reachedCodeSamplingIntervalUs = getReachedCodeSamplingIntervalUs();
if (reachedCodeSamplingIntervalUs > 0) {
CommandLine.getInstance().appendSwitch(BaseSwitches.ENABLE_REACHED_CODE_PROFILER);
CommandLine.getInstance().appendSwitchWithValue(
BaseSwitches.REACHED_CODE_SAMPLING_INTERVAL_US,
Integer.toString(reachedCodeSamplingIntervalUs));
}
}
ensureCommandLineSwitchedAlreadyLocked();
if (!LibraryLoaderJni.get().libraryLoaded(mLibraryProcessType)) {
Log.e(TAG, "error calling LibraryLoaderJni.get().libraryLoaded");
throw new ProcessInitException(LoaderErrors.FAILED_TO_REGISTER_JNI);
}
// Check that the version of the library we have loaded matches the version we expect
if (!NativeLibraries.sVersionNumber.equals(LibraryLoaderJni.get().getVersionNumber())) {
Log.e(TAG,
"Expected native library version number \"%s\", "
+ "actual native library version number \"%s\"",
NativeLibraries.sVersionNumber, LibraryLoaderJni.get().getVersionNumber());
throw new ProcessInitException(LoaderErrors.NATIVE_LIBRARY_WRONG_VERSION);
} else {
// Log the version anyway as this is often helpful, but word it differently so it's
// clear that it isn't an error.
Log.i(TAG, "Loaded native library version number \"%s\"",
NativeLibraries.sVersionNumber);
}
UmaRecorderHolder.onLibraryLoaded();
// From now on, keep tracing in sync with native.
TraceEvent.onNativeTracingReady();
// From this point on, native code is ready to use, but non-MainDex JNI may not yet have
// been registered. Check isInitialized() to be sure that initialization is fully complete.
// 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 onBrowserNativeInitializationComplete() {
synchronized (mLock) {
if (mUseChromiumLinker) {
RecordHistogram.recordTimesHistogram(
"ChromiumAndroidLinker.BrowserLoadTime", mLibraryLoadTimeMs);
}
}
}
// Records pending Chromium linker histogram state for renderer process. This cannot be
// recorded as a histogram immediately because histograms and IPCs are not ready at the
// time they are captured. This function stores a pending value, so that a later call to
// RecordChromiumAndroidLinkerRendererHistogram() will record it correctly.
public void registerRendererProcessHistogram() {
if (!mUseChromiumLinker) return;
synchronized (mLock) {
LibraryLoaderJni.get().recordRendererLibraryLoadTime(mLibraryLoadTimeMs);
}
}
/**
* Overrides 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);
}
}
}
/**
* This sets the LibraryLoader internal state to its fully initialized state and should *only*
* be used by clients like NativeTests which manually load their native libraries without using
* the LibraryLoader.
*/
public void setLibrariesLoadedForNativeTests() {
mLoadState = LoadState.LOADED;
mInitialized = true;
}
@NativeMethods
interface Natives {
// 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.
boolean libraryLoaded(@LibraryProcessType int processType);
void registerNonMainDexJni();
// Records the number of milliseconds it took to load the libraries in the renderer.
void recordRendererLibraryLoadTime(long libraryLoadTime);
// 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.
String getVersionNumber();
}
}