blob: 089dc34408d30cce02d816875060d8fdd3d587d2 [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.content.Context;
import android.os.SystemClock;
import android.util.Log;
import org.chromium.base.CommandLine;
import org.chromium.base.JNINamespace;
import org.chromium.base.TraceEvent;
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();
// One-way switch becomes true when the libraries are loaded.
private static boolean sLoaded = false;
// One-way switch becomes true when the Java command line is switched to
// native.
private static boolean sCommandLineSwitched = false;
// One-way switch becomes true when the libraries are initialized (
// by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
// library_loader_hooks.cc).
private static boolean sInitialized = false;
// One-way switches recording attempts to use Relro sharing in the browser.
// The flags are used to report UMA stats later.
private static boolean sIsUsingBrowserSharedRelros = false;
private static boolean sLoadAtFixedAddressFailed = false;
// One-way switch becomes true if the device supports memory mapping the
// APK file with executable permissions.
private static boolean sMapApkWithExecPermission = false;
// One-way switch becomes true if the Chromium library was loaded from the
// APK file directly.
private static boolean sLibraryWasLoadedFromApk = false;
// One-way switch becomes false if the Chromium library should be loaded
// directly from the APK file but it was not aligned.
private static boolean sLibraryWasAlignedInApk = true;
// One-way switch becomes true if the system library loading failed,
// and the right native library was found and loaded by the hack.
// The flag is used to report UMA stats later.
private static boolean sNativeLibraryHackWasUsed = false;
/**
* The same as ensureInitialized(null, false), should only be called
* by non-browser processes.
*
* @throws ProcessInitException
*/
public static 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, or it doesn't need to work around the issue
* http://b/13216167.
*
* When the context is not null and native library was not extracted
* by Android package manager, the LibraryLoader class
* will extract the native libraries from APK. This is a hack used to
* work around some Sony devices with the following platform bug:
* http://b/13216167.
*
* @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
* should delete the old workaround and fallback libraries or not.
*/
public static void ensureInitialized(
Context context, boolean shouldDeleteOldWorkaroundLibraries)
throws ProcessInitException {
synchronized (sLock) {
if (sInitialized) {
// Already initialized, nothing to do.
return;
}
loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
initializeAlreadyLocked();
}
}
/**
* Checks if library is fully loaded and initialized.
*/
public static boolean isInitialized() {
synchronized (sLock) {
return sInitialized;
}
}
/**
* The same as loadNow(null, false), should only be called by
* non-browser process.
*
* @throws ProcessInitException
*/
public static 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 shouldDeleteOldWorkaroundLibraries The flag tells whether the method
* should delete the old workaround and fallback libraries or not.
*
* @throws ProcessInitException if the native library failed to load.
*/
public static void loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries)
throws ProcessInitException {
synchronized (sLock) {
loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
}
}
/**
* 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 static void initialize() throws ProcessInitException {
synchronized (sLock) {
initializeAlreadyLocked();
}
}
// Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code
private static void loadAlreadyLocked(
Context context, boolean shouldDeleteOldWorkaroundLibraries)
throws ProcessInitException {
try {
if (!sLoaded) {
assert !sInitialized;
long startTime = SystemClock.uptimeMillis();
boolean useChromiumLinker = Linker.isUsed();
boolean fallbackWasUsed = false;
if (useChromiumLinker) {
String apkFilePath = null;
boolean useMapExecSupportFallback = false;
// Check if the device supports memory mapping the APK file
// with executable permissions.
if (context != null) {
apkFilePath = context.getApplicationInfo().sourceDir;
sMapApkWithExecPermission = Linker.checkMapExecSupport(apkFilePath);
if (!sMapApkWithExecPermission && 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.checkLibraryAlignedInApk(apkFilePath, libFilePath)) {
sLibraryWasAlignedInApk = false;
}
if (sLibraryWasAlignedInApk || 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 {
// Fallback.
Log.i(TAG, "Loading " + library
+ " using unpacking 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()) {
sIsUsingBrowserSharedRelros = 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();
sLoadAtFixedAddressFailed = true;
}
}
if (!isLoaded) {
loadLibrary(zipFilePath, libFilePath);
}
}
Linker.finishLibraryLoad();
} else {
// Load libraries using the system linker.
for (String library : NativeLibraries.LIBRARIES) {
try {
System.loadLibrary(library);
} catch (UnsatisfiedLinkError e) {
if (context != null
&& LibraryLoaderHelper.tryLoadLibraryUsingWorkaround(context,
library)) {
sNativeLibraryHackWasUsed = true;
} else {
throw e;
}
}
}
}
if (context != null && shouldDeleteOldWorkaroundLibraries) {
if (!sNativeLibraryHackWasUsed) {
LibraryLoaderHelper.deleteLibrariesAsynchronously(
context, LibraryLoaderHelper.PACKAGE_MANAGER_WORKAROUND_DIR);
}
if (!fallbackWasUsed) {
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));
sLoaded = 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 static void loadLibrary(@Nullable String zipFilePath, String libFilePath) {
Linker.loadLibrary(zipFilePath, libFilePath);
if (zipFilePath != null) {
sLibraryWasLoadedFromApk = 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 static 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 static void ensureCommandLineSwitchedAlreadyLocked() {
assert sLoaded;
if (sCommandLineSwitched) {
return;
}
nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull());
CommandLine.enableNativeProxy();
sCommandLineSwitched = true;
}
// Invoke base::android::LibraryLoaded in library_loader_hooks.cc
private static void initializeAlreadyLocked() throws ProcessInitException {
if (sInitialized) {
return;
}
// Setup the native command line if necessary.
if (!sCommandLineSwitched) {
nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull());
}
if (!nativeLibraryLoaded()) {
Log.e(TAG, "error calling nativeLibraryLoaded");
throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
}
// 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).
sInitialized = true;
// 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 (!sCommandLineSwitched) {
CommandLine.enableNativeProxy();
sCommandLineSwitched = true;
}
// From now on, keep tracing in sync with native.
TraceEvent.registerNativeEnabledObserver();
}
// Called after all native initializations are complete.
public static void onNativeInitializationComplete(Context context) {
recordBrowserProcessHistogram(context);
nativeRecordNativeLibraryHack(sNativeLibraryHackWasUsed);
}
// Record Chromium linker histogram state for the main browser process. Called from
// onNativeInitializationComplete().
private static void recordBrowserProcessHistogram(Context context) {
if (Linker.isUsed()) {
nativeRecordChromiumAndroidLinkerBrowserHistogram(sIsUsingBrowserSharedRelros,
sLoadAtFixedAddressFailed,
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 static int getLibraryLoadFromApkStatus(Context context) {
assert Linker.isUsed();
if (sLibraryWasLoadedFromApk) {
return sMapApkWithExecPermission
? LibraryLoadFromApkStatusCodes.SUCCESSFUL
: LibraryLoadFromApkStatusCodes.USED_NO_MAP_EXEC_SUPPORT_FALLBACK;
}
if (!sLibraryWasAlignedInApk) {
return LibraryLoadFromApkStatusCodes.NOT_ALIGNED;
}
if (context == null) {
Log.w(TAG, "Unknown APK filename due to null context");
return LibraryLoadFromApkStatusCodes.UNKNOWN;
}
return sMapApkWithExecPermission
? 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 static void registerRendererProcessHistogram(boolean requestedSharedRelro,
boolean loadAtFixedAddressFailed) {
if (Linker.isUsed()) {
nativeRegisterChromiumAndroidLinkerRendererHistogram(requestedSharedRelro,
loadAtFixedAddressFailed);
}
}
private static 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 static 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 static 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 static 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 static native String nativeGetVersionNumber();
private static native void nativeRecordNativeLibraryHack(boolean usedHack);
}