blob: 005ec7320d25398b1fd90446e2d5624c423647a6 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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.Bundle;
import android.system.Os;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeLibraryLoadedStatus;
import org.jni_zero.NativeLibraryLoadedStatus.NativeLibraryLoadedStatusProvider;
import org.jni_zero.NativeMethods;
import org.chromium.base.BaseSwitches;
import org.chromium.base.Callback;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ResettersForTesting;
import org.chromium.base.StrictModeContext;
import org.chromium.base.TimeUtils.CurrentThreadTimeMillisTimer;
import org.chromium.base.TimeUtils.UptimeMillisTimer;
import org.chromium.base.TraceEvent;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.UmaRecorderHolder;
import org.chromium.build.BuildConfig;
import org.chromium.build.NativeLibraries;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.concurrent.GuardedBy;
/**
* This class provides functionality to load and register the native libraries.
* Callers are allowed to separate loading the libraries from initializing
* them. When a zygote process is used (WebView or AppZygote) the per process
* initialization happens 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";
// Constant guarding debug logging in this class.
static final boolean DEBUG = false;
// 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";
// Compile time switch for sharing RELRO between the browser and the app zygote.
// TODO(crbug.com/1154224): remove when the issue is closed.
private static final boolean ALLOW_CHROMIUM_LINKER_IN_ZYGOTE = true;
// Default sampling interval for reached code profiler in microseconds.
private static final int DEFAULT_REACHED_CODE_SAMPLING_INTERVAL_US = 10000;
// Shared preferences key for the background thread pool setting.
private static final String BACKGROUND_THREAD_POOL_KEY = "background_thread_pool_enabled";
// The singleton instance of LibraryLoader. Never null (not final for tests).
private static LibraryLoader sInstance = new LibraryLoader();
private static boolean sBrowserStartupBlockedForTesting;
// Helps mInitializedForTesting and mLoadStateForTesting to be removed by R8.
private static boolean sEnableStateForTesting;
// 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;
// One way switch used by initInAppZygote() when the current platform does not support loading
// using a Chromium Linker in the App Zygote. Because of this limited usage it can avoid
// synchronization.
private boolean mFallbackToSystemLinker;
// 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;
// Tracks mLoadState, but can be reset to NOT_LOADED between tests to ensure that each test that
// requires native explicitly loads it.
private @LoadState int mLoadStateForTesting;
// Tracks mInitialized, but can be reset to false between tests to ensure that each test that
// requires native explicitly loads it.
private boolean mInitializedForTesting;
// Whether to use the Chromium linker vs. the system linker.
// Avoids locking: should be initialized very early.
private boolean mUseChromiumLinker = NativeLibraries.sUseLinker;
// 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;
// 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;
// Enumeration telling which init* methods were used, and therefore
// which process the library is loaded in.
@IntDef({CreatedIn.MAIN, CreatedIn.ZYGOTE, CreatedIn.CHILD_WITHOUT_ZYGOTE})
@Retention(RetentionPolicy.SOURCE)
private @interface CreatedIn {
int MAIN = 0;
int ZYGOTE = 1;
int CHILD_WITHOUT_ZYGOTE = 2;
}
// Used by tests to ensure that sLoadFailedCallback is called, also referenced by
// SplitCompatApplication.
@VisibleForTesting public static boolean sOverrideNativeLibraryCannotBeLoadedForTesting;
// Allow embedders to register a callback to handle native library load failures.
public static Callback<UnsatisfiedLinkError> sLoadFailedCallback;
// Returns true when sharing RELRO between the browser process and the app zygote should *not*
// be attempted.
public static boolean mainProcessIntendsToProvideRelroFd() {
return !ALLOW_CHROMIUM_LINKER_IN_ZYGOTE || Build.VERSION.SDK_INT <= Build.VERSION_CODES.R;
}
/**
* Inner class encapsulating points of communication between instances of LibraryLoader in
* different processes.
*
* Usage:
*
* 0. In the main (Browser) process this mediator can be bypassed by
* {@link LibraryLoader#ensureInitialized()}. It is convenient for targets that do not pay
* attention to RELRO sharing and load time statistics, but it is also more error prone. The
* {@link #ensureInitializedInMainProcess()} is recommended.
*
* 1. 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.
*
* 2. After the load address is received, the object needs to be initialized using one of
* {@link #ensureInitializedInMainProcess()}, {@link #initInChildProcess()} and
* {@link #initInAppZygote()}. For the main process the subsequent calls to initialization
* are ignored, primarily to simplify tests.
*
* 3. Later {@link #putLoadAddressToBundle(Bundle)} and
* {@link #takeLoadAddressFromBundle(Bundle)} should be called for passing the RELRO
* information between library loaders.
*
* Internally the {@link LibraryLoader} may ignore these messages because it can fall back to
* not sharing RELRO.
*
* In general the class is *not* thread safe. The client must guarantee that the steps 1-3 above
* happen sequentially in the memory model sense. After that the class is safe to use from
* multiple threads concurrently.
*/
public class MultiProcessMediator {
// Currently clients initialize |mLoadAddress| strictly before any other method can get
// executed on a different thread. Hence, synchronization is not required.
private long mLoadAddress;
// Only ever switched from false to true.
private volatile boolean mInitDone;
// How the mediator was created. The LibraryLoader.ensureInitialized() uses this default
// value.
private volatile @CreatedIn int mCreatedIn = CreatedIn.MAIN;
/**
* Extracts the load address as provided by another process.
* @param bundle The Bundle to extract from.
*/
public void takeLoadAddressFromBundle(Bundle bundle) {
assert !mInitDone;
mLoadAddress = Linker.extractLoadAddressFromBundle(bundle);
}
private long getLoadAddress() {
synchronized (mLock) {
return mLoadAddress;
}
}
/**
* Initializes the Main (Browser) process side of communication. This process coordinates
* creation of other processes. Can be called more than once, subsequent calls are ignored.
*/
public void ensureInitializedInMainProcess() {
if (mInitDone) return;
if (useChromiumLinker()) {
boolean attemptProduceRelro = mainProcessIntendsToProvideRelroFd();
// When the main process creates the shared region with relocations, it is faster
// to randomize the load address than to find the reserved one
// in /proc. When the main process relies on RELRO from the
// zygote, then it should scan /proc to find the reserved range
// because waiting for zygote to reveal its address would have
// delayed startup.
if (DEBUG) {
Log.i(
TAG,
"ensureInitializedInMainProcess, producing RELRO FD: %b",
attemptProduceRelro);
}
// For devices avoiding the App Zygote in
// ChildConnectionAllocator.createVariableSize()
// the FIND_RESERVED search can be avoided: a random region is sufficient.
// TODO(pasko):
// Investigate whether it is worth coordinating with the ChildConnectionAllocator.
// To
// speed up process creation.
int preferAddress =
attemptProduceRelro
? Linker.PreferAddress.RESERVE_RANDOM
: Linker.PreferAddress.FIND_RESERVED;
getLinker()
.ensureInitialized(
attemptProduceRelro, preferAddress, /* addressHint= */ 0);
}
mCreatedIn = CreatedIn.MAIN;
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 the App Zygote process. Will be followed by initInChildProcess() in all
* processes inheriting from the app zygote.
*/
public void initInAppZygote() {
assert !mInitDone;
if (useChromiumLinker() && !mainProcessIntendsToProvideRelroFd()) {
getLinker()
.ensureInitialized(
/* asRelroProducer= */ true, Linker.PreferAddress.FIND_RESERVED, 0);
} else {
// The main process will attempt to create RELRO FD without coordination. Fall back
// to loading with the system linker. Can happen in tests and on dev builds with
// forceSystemLinker(), should not happen in the field.
mFallbackToSystemLinker = true;
}
mCreatedIn = CreatedIn.ZYGOTE;
// The initInChildProcess() will set |mInitDone| to |true| after fork(2).
}
/**
* Initializes in processes other than "Main". Can be called only once in each non-main
* process.
*/
public void initInChildProcess() {
assert !mInitDone;
if (!useChromiumLinker()) {
mInitDone = true;
return;
}
if (mainProcessIntendsToProvideRelroFd()) {
if (DEBUG) {
Log.i(TAG, "initInChildProcess: RELRO FD not provided by App Zygote");
}
getLinker()
.ensureInitialized(
/* asRelroProducer= */ false,
Linker.PreferAddress.RESERVE_HINT,
getLoadAddress());
} else if (isLoadedByZygote()) {
if (DEBUG) {
Log.i(
TAG,
"initInChildProcess: already loaded by app zygote "
+ "(mFallbackToSystemLinker=%b)",
mFallbackToSystemLinker);
}
} else if (mCreatedIn == CreatedIn.ZYGOTE) {
if (DEBUG) {
Log.i(TAG, "initInChildProcess: the app zygote failed to produce RELRO FD");
}
getLinker()
.ensureInitialized(
/* asRelroProducer= */ false,
Linker.PreferAddress.RESERVE_HINT,
getLoadAddress());
} else {
// The main process expects the app zygote to provide the RELRO FD, but this process
// does not inherit from the app zygote. This could be because:
// 1. Running in a privileged process - very common
// 2. Running in a renderer process - App Zygote was disabled due to opt out on
// low end devices - somewhat common
// To cover both cases start with FIND_RESERVED, and proceed with fallbacks built
// into the Linker initialization.
//
// TODO(pasko): Investigate whether searching with FIND_RESERVED affects startup
// speed on Go devices.
if (DEBUG) {
Log.i(
TAG,
"initInChildProcess: child process not from app zygote, with address "
+ "hint: 0x%x",
getLoadAddress());
}
getLinker()
.ensureInitialized(
/* asRelroProducer= */ false,
Linker.PreferAddress.FIND_RESERVED,
getLoadAddress());
}
if (mCreatedIn != CreatedIn.ZYGOTE) mCreatedIn = CreatedIn.CHILD_WITHOUT_ZYGOTE;
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()) {
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);
}
}
private String creationAsString() {
switch (mCreatedIn) {
case CreatedIn.MAIN:
return "Browser";
case CreatedIn.ZYGOTE:
return "Zygote";
case CreatedIn.CHILD_WITHOUT_ZYGOTE:
return "Child";
default:
assert false : "Must initialize as one of {Browser,Zygote,Child}";
return "";
}
}
private static final String LINKER_HISTOGRAM_PREFIX = "ChromiumAndroidLinker.";
private void recordLoadTimeHistogram(long loadTimeMs) {
RecordHistogram.recordTimesHistogram(
LINKER_HISTOGRAM_PREFIX + creationAsString() + "LoadTime2", loadTimeMs);
}
public void recordLoadThreadTimeHistogram(long threadLoadTimeMs) {
RecordHistogram.recordTimesHistogram(
LINKER_HISTOGRAM_PREFIX + creationAsString() + "ThreadLoadTime",
threadLoadTimeMs);
}
}
public final MultiProcessMediator getMediator() {
return mMediator;
}
public static LibraryLoader getInstance() {
return sInstance;
}
@VisibleForTesting
protected LibraryLoader() {
if (DEBUG) {
logLinkerUsed();
}
if (BuildConfig.ENABLE_ASSERTS) {
NativeLibraryLoadedStatus.setProvider(
new NativeLibraryLoadedStatusProvider() {
@Override
public boolean areNativeMethodsReady() {
return isMainDexLoaded();
}
});
}
}
/**
* 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 and the Chromium linker is not used, the
* {@link NativeLibraryPreloader#loadLibrary(String)} ()} will be invoked before calling
* System.loadLibrary().
*
* @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.
*/
public void setLinkerImplementation(boolean useChromiumLinker) {
assert !mInitialized;
mUseChromiumLinker = useChromiumLinker;
if (DEBUG) logLinkerUsed();
}
private void logLinkerUsed() {
Log.i(TAG, "Configuration: useChromiumLinker() = %b", useChromiumLinker());
}
// Whether a Linker subclass replaces the system dynamic linker for loading. Even if returns
// |true|, when the Linker fails, the system dynamic linker is used as a fallback. Also it is
// common for App Zygote to choose loading with the system linker when sharing RELRO with the
// browser process is not supported.
private boolean useChromiumLinker() {
return mUseChromiumLinker;
}
/**
* Returns the singleton Linker instance.
*
* 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.
*
* More: docs/android_native_libraries.md
*
* @return the Linker implementation instance.
*/
private Linker getLinker() {
assert useChromiumLinker();
synchronized (mLock) {
if (mLinker == null) mLinker = new Linker();
return mLinker;
}
}
/** Return if library is already loaded successfully by the zygote. */
public boolean isLoadedByZygote() {
synchronized (mLock) {
return mLoadedByZygote;
}
}
/**
* Blocks until the library is fully loaded and initialized. When this method is used (without
* the {@link MultiProcessMediator}) the current process is treated as the Main process
* (w.r.t. how it shares RELRO and reports metrics) unless it was initialized before.
*/
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) {
if (DEBUG) logLinkerUsed();
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() {
preloadNowOverridePackageName(
ContextUtils.getApplicationContext().getApplicationInfo().packageName);
}
/** Similar to {@link #preloadNow}, but allows specifying app context to use. */
public void preloadNowOverridePackageName(String packageName) {
synchronized (mLock) {
if (useChromiumLinker()) return;
preloadAlreadyLocked(packageName, /* inZygote= */ false);
}
}
@GuardedBy("mLock")
private void preloadAlreadyLocked(String packageName, 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 && mainProcessIntendsToProvideRelroFd());
if (mLibraryPreloader != null && !mLibraryPreloaderCalled) {
mLibraryPreloader.loadLibrary(packageName);
mLibraryPreloaderCalled = true;
}
}
}
/**
* Checks whether the native library is fully loaded.
*
* @deprecated: please avoid using in new code:
* https://crsrc.org/c/base/android/jni_generator/README.md#testing-for-readiness-use-get
*/
@Deprecated
@VisibleForTesting
public boolean isLoaded() {
return mLoadState == LoadState.LOADED
&& (!sEnableStateForTesting || mLoadStateForTesting == LoadState.LOADED);
}
private boolean isMainDexLoaded() {
return mLoadState >= LoadState.MAIN_DEX_LOADED
&& (!sEnableStateForTesting || mLoadStateForTesting >= LoadState.MAIN_DEX_LOADED);
}
/**
* Checks whether the native library is fully loaded and initialized.
*
* @deprecated: please avoid using in new code:
* https://chromium.googlesource.com/chromium/src/+/main/base/android/jni_generator/README.md#testing-for-readiness_use
*/
@Deprecated
public boolean isInitialized() {
return mInitialized && isLoaded() && (!sEnableStateForTesting || mInitializedForTesting);
}
/**
* 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());
}
/** Causes LibraryLoader to pretend that native libraries have not yet been initialized. */
public void resetForTesting() {
mLoadStateForTesting = LoadState.NOT_LOADED;
mInitializedForTesting = false;
sEnableStateForTesting = true;
}
/**
* 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(), /* inZygote= */ false);
}
loadNonMainDex();
}
public void loadNowInZygote(ApplicationInfo appInfo) {
synchronized (mLock) {
assert mLoadState == LoadState.NOT_LOADED;
loadMainDexAlreadyLocked(appInfo, /* inZygote= */ true);
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);
}
}
/**
* Enables the background priority thread pool group. The value comes from the
* "BackgroundThreadPool" finch experiment, and is pushed on every run, to take effect on the
* subsequent run. I.e. the effect of the finch experiment lags by one run, which is the best we
* can do considering that the thread pool has to be configured 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 background priority thread pool group.
*/
public static void setBackgroundThreadPoolEnabledOnNextRuns(boolean enabled) {
SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
editor.putBoolean(BACKGROUND_THREAD_POOL_KEY, enabled).apply();
}
/**
* @return whether the background priority thread pool group should be enabled. (see
* setBackgroundThreadPoolEnabledOnNextRuns()).
*/
@VisibleForTesting
public static boolean isBackgroundThreadPoolEnabled() {
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
return ContextUtils.getAppSharedPreferences()
.getBoolean(BACKGROUND_THREAD_POOL_KEY, false);
}
}
private void loadWithChromiumLinker(ApplicationInfo appInfo, String library) {
Linker linker = getLinker();
String sourceDir = appInfo.sourceDir;
Log.i(TAG, "Loading %s from within %s", library, sourceDir);
linker.loadLibrary(library); // May throw UnsatisfiedLinkError.
}
@GuardedBy("mLock")
@SuppressLint({"UnsafeDynamicallyLoadedCode"})
private void loadWithSystemLinkerAlreadyLocked(ApplicationInfo appInfo, boolean inZygote) {
setEnvForNative();
preloadAlreadyLocked(appInfo.packageName, inZygote);
for (String library : NativeLibraries.LIBRARIES) {
System.loadLibrary(library);
}
}
// Invokes 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) {
if (sEnableStateForTesting && mLoadStateForTesting == LoadState.NOT_LOADED) {
mLoadStateForTesting = LoadState.MAIN_DEX_LOADED;
}
return;
}
try (TraceEvent te = TraceEvent.scoped("LibraryLoader.loadMainDexAlreadyLocked")) {
assert !mInitialized;
assert mLibraryProcessType != LibraryProcessType.PROCESS_UNINITIALIZED || inZygote;
UptimeMillisTimer uptimeTimer = new UptimeMillisTimer();
CurrentThreadTimeMillisTimer threadTimeTimer = new CurrentThreadTimeMillisTimer();
if (sOverrideNativeLibraryCannotBeLoadedForTesting) {
throw new UnsatisfiedLinkError();
}
if (useChromiumLinker() && !mFallbackToSystemLinker) {
if (DEBUG) Log.i(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 {
if (DEBUG) Log.i(TAG, "Loading with the System linker.");
loadWithSystemLinkerAlreadyLocked(appInfo, inZygote);
}
long loadTimeMs = uptimeTimer.getElapsedMillis();
if (DEBUG) Log.i(TAG, "Time to load native libraries: %d ms", loadTimeMs);
mLoadState = LoadState.MAIN_DEX_LOADED;
if (sEnableStateForTesting) {
mLoadStateForTesting = LoadState.MAIN_DEX_LOADED;
}
getMediator().recordLoadTimeHistogram(loadTimeMs);
getMediator().recordLoadThreadTimeHistogram(threadTimeTimer.getElapsedMillis());
} catch (UnsatisfiedLinkError e) {
if (sLoadFailedCallback != null) {
sLoadFailedCallback.onResult(e);
} else {
throw new ProcessInitException(LoaderErrors.NATIVE_LIBRARY_LOAD_FAILED, e);
}
}
}
// This used to actually do stuff, but now we have removed the concept of MainDex/non-MainDex
// JNI. However, entirely removing the "middle state" (LoadState.MAIN_DEX) causes issues with
// robolectric tests using GURL. See https://crbug.com/1371542#c13.
@VisibleForTesting
protected void loadNonMainDex() {
mLoadState = LoadState.LOADED;
if (sEnableStateForTesting) {
mLoadStateForTesting = LoadState.LOADED;
}
}
// 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 isMainDexLoaded();
if (mCommandLineSwitched) {
return;
}
CommandLine.enableNativeProxy();
mCommandLineSwitched = true;
}
/**
* Assert that library process type in the LibraryLoader is compatible with provided type.
*
* @param libraryProcessType a library process type to assert.
*/
public void assertCompatibleProcessType(@LibraryProcessType int libraryProcessType) {
assert libraryProcessType == mLibraryProcessType;
}
@GuardedBy("mLock")
private void initializeAlreadyLocked() {
if (mInitialized) {
if (sEnableStateForTesting) {
mInitializedForTesting = true;
}
return;
}
assert mLibraryProcessType != LibraryProcessType.PROCESS_UNINITIALIZED;
if (mLibraryProcessType == LibraryProcessType.PROCESS_BROWSER) {
// 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.
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));
}
// Similarly, append a switch to enable the background thread pool group if the cached
// preference indicates it should be enabled.
if (isBackgroundThreadPoolEnabled()) {
CommandLine.getInstance().appendSwitch(BaseSwitches.ENABLE_BACKGROUND_THREAD_POOL);
}
}
ensureCommandLineSwitchedAlreadyLocked();
// Invoke content::LibraryLoaded() in //content/app/android/library_loader_hooks.cc
// via a hook stored in //base/android/library_loader/library_loader_hooks.cc.
if (!LibraryLoaderJni.get().libraryLoaded(mLibraryProcessType)) {
Log.e(TAG, "error calling LibraryLoaderJni.get().libraryLoaded");
throw new ProcessInitException(LoaderErrors.FAILED_TO_REGISTER_JNI);
}
// The "Successfully loaded native library" string is used by
// tools/android/build_speed/benchmark.py. Please update that script if this log message is
// changed.
Log.i(TAG, "Successfully loaded native library");
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;
if (sEnableStateForTesting) {
mInitializedForTesting = true;
}
}
/**
* Overrides the library loader (normally with a mock) for testing.
*
* @deprecated: please avoid using in new code:
* https://chromium.googlesource.com/chromium/src/+/main/base/android/jni_generator/README.md#testing-for-readiness_use
*
* @param loader the mock library loader.
*/
@Deprecated
public static void setLibraryLoaderForTesting(LibraryLoader loader) {
var oldValue = sInstance;
sInstance = loader;
ResettersForTesting.register(() -> sInstance = oldValue);
}
/**
* 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) {
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.
*
* Don't use in new code. Tests that require this call should be migrated to
* NativeUnitTest.
* https://chromium.googlesource.com/chromium/src/+/main/base/android/jni_generator/README.md#testing-for-readiness_use
*/
protected static void setLibrariesLoadedForNativeTests() {
LibraryLoader self = getInstance();
self.mLoadState = LoadState.LOADED;
self.mInitialized = true;
if (sEnableStateForTesting) {
self.mInitializedForTesting = true;
self.mLoadStateForTesting = LoadState.LOADED;
}
}
public static void setOverrideNativeLibraryCannotBeLoadedForTesting() {
sOverrideNativeLibraryCannotBeLoadedForTesting = true;
ResettersForTesting.register(() -> sOverrideNativeLibraryCannotBeLoadedForTesting = false);
}
public static void setLoadFailedCallbackForTesting(Callback<UnsatisfiedLinkError> callback) {
sLoadFailedCallback = callback;
ResettersForTesting.register(() -> sLoadFailedCallback = null);
}
public static void setBrowserProcessStartupBlockedForTesting() {
sBrowserStartupBlockedForTesting = true;
}
public static boolean isBrowserProcessStartupBlockedForTesting() {
return sBrowserStartupBlockedForTesting;
}
// The native methods below are defined in library_loader_hooks.cc.
@NativeMethods
interface Natives {
// Performs auxiliary initialization useful right after the native library load. Returns
// true on success and false on failure.
boolean libraryLoaded(@LibraryProcessType int processType);
}
}