Commit 51ca0822 authored by petrcermak's avatar petrcermak Committed by Commit bot

Library loading fallback.

This patch adds support for copying the Chromium library from the APK file
to a separate file.

This patch is largly based on https://codereview.chromium.org/673093005/.

BUG=390618

Review URL: https://codereview.chromium.org/684163002

Cr-Commit-Position: refs/heads/master@{#302456}
parent 04f14479
......@@ -12,6 +12,8 @@ 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.
......@@ -53,8 +55,12 @@ public class LibraryLoader {
private static boolean sIsUsingBrowserSharedRelros = false;
private static boolean sLoadAtFixedAddressFailed = false;
// One-way switch becomes true if the library was loaded from the APK file
// directly.
// One-way switch becomes true if the device supports loading the Chromium
// library directly from the APK.
private static boolean sLibraryLoadFromApkSupported = 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
......@@ -91,7 +97,7 @@ public class LibraryLoader {
* http://b/13216167.
*
* @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
* should delete the old workaround libraries or not.
* should delete the old workaround and fallback libraries or not.
*/
public static void ensureInitialized(
Context context, boolean shouldDeleteOldWorkaroundLibraries)
......@@ -134,7 +140,7 @@ public class LibraryLoader {
*
* @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 libraries or not.
* should delete the old workaround and fallback libraries or not.
*
* @throws ProcessInitException if the native library failed to load.
*/
......@@ -166,8 +172,20 @@ public class LibraryLoader {
long startTime = SystemClock.uptimeMillis();
boolean useChromiumLinker = Linker.isUsed();
boolean fallbackWasUsed = false;
if (useChromiumLinker) {
String apkFilePath = null;
// Check if the device supports loading a library directly from the APK file.
if (context != null) {
apkFilePath = context.getApplicationInfo().sourceDir;
sLibraryLoadFromApkSupported = Linker.checkLibraryLoadFromApkSupport(
apkFilePath);
} else {
Log.w(TAG, "could not check load from APK support due to null context");
}
// Load libraries using the Chromium linker.
Linker.prepareLibraryLoad();
......@@ -180,26 +198,40 @@ public class LibraryLoader {
continue;
}
String zipfile = null;
if (Linker.isInZipFile()) {
zipfile = context.getApplicationInfo().sourceDir;
sLibraryWasAlignedInApk = Linker.checkLibraryAlignedInApk(
zipfile, System.mapLibraryName(library));
Log.i(TAG, "Loading " + library + " from within " + zipfile);
// 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 (!sLibraryLoadFromApkSupported || sLibraryWasAlignedInApk) {
// Load directly from the APK (or use memory fallback, see
// crazy_linker_elf_loader.cpp).
Log.i(TAG, "Loading " + library + " directly from within "
+ apkFilePath);
zipFilePath = apkFilePath;
} else {
// Fallback.
Log.i(TAG, "Loading " + library + " using fallback from within "
+ apkFilePath);
libFilePath = LibraryLoaderHelper.buildFallbackLibrary(
context, library);
fallbackWasUsed = true;
Log.i(TAG, "Built fallback library " + libFilePath);
}
} else {
Log.i(TAG, "Loading: " + library);
// The library is in its own file.
Log.i(TAG, "Loading " + library);
}
// Load the library.
boolean isLoaded = false;
if (Linker.isUsingBrowserSharedRelros()) {
sIsUsingBrowserSharedRelros = true;
try {
if (zipfile != null) {
Linker.loadLibraryInZipFile(zipfile, library);
sLibraryWasLoadedFromApk = true;
} else {
Linker.loadLibrary(library);
}
loadLibrary(zipFilePath, libFilePath);
isLoaded = true;
} catch (UnsatisfiedLinkError e) {
Log.w(TAG, "Failed to load native library with shared RELRO, "
......@@ -209,12 +241,7 @@ public class LibraryLoader {
}
}
if (!isLoaded) {
if (zipfile != null) {
Linker.loadLibraryInZipFile(zipfile, library);
sLibraryWasLoadedFromApk = true;
} else {
Linker.loadLibrary(library);
}
loadLibrary(zipFilePath, libFilePath);
}
}
......@@ -227,7 +254,7 @@ public class LibraryLoader {
} catch (UnsatisfiedLinkError e) {
if (context != null
&& LibraryLoaderHelper.tryLoadLibraryUsingWorkaround(context,
library)) {
library)) {
sNativeLibraryHackWasUsed = true;
} else {
throw e;
......@@ -236,11 +263,15 @@ public class LibraryLoader {
}
}
if (context != null
&& shouldDeleteOldWorkaroundLibraries
&& !sNativeLibraryHackWasUsed) {
LibraryLoaderHelper.deleteWorkaroundLibrariesAsynchronously(
context);
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();
......@@ -265,6 +296,15 @@ public class LibraryLoader {
}
}
// 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) {
if (zipFilePath != null) {
sLibraryWasLoadedFromApk = true;
}
Linker.loadLibrary(zipFilePath, libFilePath);
}
// 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.
......
......@@ -15,6 +15,7 @@ import org.chromium.base.CalledByNative;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
......@@ -699,33 +700,17 @@ public class Linker {
}
/**
* Load a native shared library with the Chromium linker.
* The shared library is uncompressed and page aligned inside the zipfile.
* Note the crazy linker treats libraries and files as equivalent,
* so you can only open one library in a given zip file. The library must
* not be the Chromium linker library.
* Load a native shared library with the Chromium linker. If the zip file
* is not null, the shared library must be uncompressed and page aligned
* inside the zipfile. Note the crazy linker treats libraries and files as
* equivalent, so you can only open one library in a given zip file. The
* library must not be the Chromium linker library.
*
* @param zipfile The filename of the zipfile contain the library.
* @param library The library's base name.
*/
public static void loadLibraryInZipFile(String zipfile, String library) {
loadLibraryMaybeInZipFile(zipfile, library);
}
/**
* Load a native shared library with the Chromium linker. The library must
* not be the Chromium linker library.
*
* @param library The library's base name.
* @param zipFilePath The path of the zip file containing the library (or null).
* @param libFilePath The path of the library (possibly in the zip file).
*/
public static void loadLibrary(String library) {
loadLibraryMaybeInZipFile(null, library);
}
private static void loadLibraryMaybeInZipFile(
@Nullable String zipFile, String library) {
if (DEBUG) Log.i(TAG, "loadLibrary: " + library);
assert !isChromiumLinkerLibrary(library);
public static void loadLibrary(@Nullable String zipFilePath, String libFilePath) {
if (DEBUG) Log.i(TAG, "loadLibrary: " + zipFilePath + ", " + libFilePath);
synchronized (Linker.class) {
ensureInitializedLocked();
......@@ -736,12 +721,10 @@ public class Linker {
// that wrap all calls to loadLibrary() in the library loader.
assert sPrepareLibraryLoadCalled;
String libName = System.mapLibraryName(library);
if (sLoadedLibraries == null) sLoadedLibraries = new HashMap<String, LibInfo>();
if (sLoadedLibraries.containsKey(libName)) {
if (DEBUG) Log.i(TAG, "Not loading " + libName + " twice");
if (sLoadedLibraries.containsKey(libFilePath)) {
if (DEBUG) Log.i(TAG, "Not loading " + libFilePath + " twice");
return;
}
......@@ -752,18 +735,18 @@ public class Linker {
loadAddress = sCurrentLoadAddress;
}
String sharedRelRoName = libName;
if (zipFile != null) {
if (!nativeLoadLibraryInZipFile(zipFile, libName, loadAddress, libInfo)) {
String errorMessage = "Unable to load library: " + libName
+ ", in: " + zipFile;
String sharedRelRoName = libFilePath;
if (zipFilePath != null) {
if (!nativeLoadLibraryInZipFile(zipFilePath, libFilePath, loadAddress, libInfo)) {
String errorMessage = "Unable to load library: " + libFilePath
+ ", in: " + zipFilePath;
Log.e(TAG, errorMessage);
throw new UnsatisfiedLinkError(errorMessage);
}
sharedRelRoName = zipFile;
sharedRelRoName = zipFilePath;
} else {
if (!nativeLoadLibrary(libName, loadAddress, libInfo)) {
String errorMessage = "Unable to load library: " + libName;
if (!nativeLoadLibrary(libFilePath, loadAddress, libInfo)) {
String errorMessage = "Unable to load library: " + libFilePath;
Log.e(TAG, errorMessage);
throw new UnsatisfiedLinkError(errorMessage);
}
......@@ -780,7 +763,7 @@ public class Linker {
Locale.US,
"%s_LIBRARY_ADDRESS: %s %x",
sInBrowserProcess ? "BROWSER" : "RENDERER",
libName,
libFilePath,
libInfo.mLoadAddress));
}
......@@ -788,7 +771,7 @@ public class Linker {
// Create a new shared RELRO section at the 'current' fixed load address.
if (!nativeCreateSharedRelro(sharedRelRoName, sCurrentLoadAddress, libInfo)) {
Log.w(TAG, String.format(Locale.US,
"Could not create shared RELRO for %s at %x", libName,
"Could not create shared RELRO for %s at %x", libFilePath,
sCurrentLoadAddress));
} else {
if (DEBUG) Log.i(TAG,
......@@ -822,6 +805,21 @@ public class Linker {
return library.equals(TAG) || library.equals(TAG + ".cr");
}
/**
* Get the full library path in zip file (lib/<abi>/crazy.<lib_name>).
*
* @param library The library's base name.
* @return the library path.
*/
public static String getLibraryFilePathInZipFile(String library) throws FileNotFoundException {
String path = nativeGetLibraryFilePathInZipFile(library);
if (path.equals("")) {
throw new FileNotFoundException(
"Failed to retrieve path in zip file for library " + library);
}
return path;
}
/**
* Check whether the device supports loading a library directly from the APK file.
*
......@@ -854,8 +852,8 @@ public class Linker {
if (DEBUG) Log.i(TAG, "checkLibraryAlignedInApk: " + apkFile + ", " + library);
boolean aligned = nativeCheckLibraryAlignedInApk(apkFile, library);
if (DEBUG) Log.i(TAG, library + " is " + (aligned ? "" : "NOT ") +
"page aligned in " + apkFile);
if (DEBUG) Log.i(TAG, library + " is " + (aligned ? "" : "NOT ")
+ "page aligned in " + apkFile);
return aligned;
}
}
......@@ -956,6 +954,15 @@ public class Linker {
*/
private static native long nativeGetRandomBaseLoadAddress(long sizeBytes);
/**
* Native method used to get the full library path in zip file
* (lib/<abi>/crazy.<lib_name>).
*
* @param library The library's base name.
* @return the library path (or empty string on failure).
*/
private static native String nativeGetLibraryFilePathInZipFile(String library);
/**
* Native method which checks whether the device supports loading a library
* directly from the APK file.
......
......@@ -21,7 +21,8 @@ public class LibraryLoaderHelperTest extends InstrumentationTestCase {
@Override
public void setUp() throws Exception {
Context context = getInstrumentation().getTargetContext();
LibraryLoaderHelper.deleteWorkaroundLibrariesSynchronously(context);
LibraryLoaderHelper.deleteLibrariesSynchronously(
context, LibraryLoaderHelper.PACKAGE_MANAGER_WORKAROUND_DIR);
}
@MediumTest
......@@ -30,13 +31,13 @@ public class LibraryLoaderHelperTest extends InstrumentationTestCase {
@Override
public void run() {
Context context = getInstrumentation().getTargetContext();
File libDir = LibraryLoaderHelper.getWorkaroundLibDir(context);
File libDir = LibraryLoaderHelper.getLibDir(context,
LibraryLoaderHelper.PACKAGE_MANAGER_WORKAROUND_DIR);
assertTrue(libDir.exists());
assertTrue(libDir.isDirectory());
assertEquals(libDir.list().length, 0);
assertTrue(
LibraryLoaderHelper.loadNativeLibrariesUsingWorkaroundForTesting(
assertTrue(LibraryLoaderHelper.loadNativeLibrariesUsingWorkaroundForTesting(
context));
assertTrue(libDir.list().length > 0);
......@@ -47,7 +48,8 @@ public class LibraryLoaderHelperTest extends InstrumentationTestCase {
@Override
public void tearDown() throws Exception {
Context context = getInstrumentation().getTargetContext();
LibraryLoaderHelper.deleteWorkaroundLibrariesSynchronously(context);
LibraryLoaderHelper.deleteLibrariesSynchronously(
context, LibraryLoaderHelper.PACKAGE_MANAGER_WORKAROUND_DIR);
super.tearDown();
}
}
......@@ -576,6 +576,28 @@ jlong GetRandomBaseLoadAddress(JNIEnv* env, jclass clazz, jlong bytes) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(address));
}
// Get the full path of a library in the zip file
// (lib/<abi>/crazy.<lib_name>).
//
// |env| is the current JNI environment handle.
// |clazz| is the static class handle which is not used here.
// |lib_name| is the library base name.
// Returns the full path (or empty string on failure).
jstring GetLibraryFilePathInZipFile(JNIEnv* env,
jclass clazz,
jstring lib_name) {
String lib_name_str(env, lib_name);
const char* lib_name_c_str = lib_name_str.c_str();
char buffer[kMaxFilePathLengthInZip + 1];
if (crazy_library_file_path_in_zip_file(
lib_name_c_str, buffer, sizeof(buffer)) == CRAZY_STATUS_FAILURE) {
LOG_ERROR("%s: Failed to get full filename for library '%s'",
__FUNCTION__, lib_name_c_str);
buffer[0] = '\0';
}
return env->NewStringUTF(buffer);
}
// Check whether the device supports loading a library directly from the APK
// file.
//
......@@ -686,19 +708,25 @@ const JNINativeMethod kNativeMethods[] = {
")"
"J",
reinterpret_cast<void*>(&GetRandomBaseLoadAddress)},
{"nativeCheckLibraryLoadFromApkSupport",
"("
"Ljava/lang/String;"
")"
"Z",
reinterpret_cast<void*>(&CheckLibraryLoadFromApkSupport)},
{"nativeCheckLibraryAlignedInApk",
"("
"Ljava/lang/String;"
"Ljava/lang/String;"
")"
"Z",
reinterpret_cast<void*>(&CheckLibraryAlignedInApk)}, };
{"nativeGetLibraryFilePathInZipFile",
"("
"Ljava/lang/String;"
")"
"Ljava/lang/String;",
reinterpret_cast<void*>(&GetLibraryFilePathInZipFile)},
{"nativeCheckLibraryLoadFromApkSupport",
"("
"Ljava/lang/String;"
")"
"Z",
reinterpret_cast<void*>(&CheckLibraryLoadFromApkSupport)},
{"nativeCheckLibraryAlignedInApk",
"("
"Ljava/lang/String;"
"Ljava/lang/String;"
")"
"Z",
reinterpret_cast<void*>(&CheckLibraryAlignedInApk)}, };
} // namespace
......
......@@ -32,6 +32,9 @@ extern "C" {
// the library.
#define _CRAZY_PUBLIC __attribute__((__visibility__("default")))
// Maximum path length of a file in a zip file.
const size_t kMaxFilePathLengthInZip = 256;
// Status values returned by crazy linker functions to indicate
// success or failure. They were chosen to match boolean values,
// this allows one to test for failures with:
......@@ -335,6 +338,15 @@ crazy_status_t crazy_library_find_from_address(
void* address,
crazy_library_t** library) _CRAZY_PUBLIC;
// Return the full path of |lib_name| in the zip file
// (lib/<abi>/crazy.<lib_name>). The result is returned in
// |buffer[0..buffer_size - 1]|. If |buffer_size| is too small,
// CRAZY_STATUS_FAILURE is returned.
crazy_status_t crazy_library_file_path_in_zip_file(const char* lib_name,
char* buffer,
size_t buffer_size)
_CRAZY_PUBLIC;
// Check whether |lib_name| is page aligned in |zipfile_name|.
crazy_status_t crazy_linker_check_library_aligned_in_zip_file(
const char* zipfile_name,
......
......@@ -372,6 +372,20 @@ crazy_status_t crazy_library_find_from_address(void* address,
}
}
crazy_status_t crazy_library_file_path_in_zip_file(const char* lib_name,
char* buffer,
size_t buffer_size) {
crazy::String path = crazy::LibraryList::GetLibraryFilePathInZipFile(
lib_name);
if (path.size() >= buffer_size) {
return CRAZY_STATUS_FAILURE;
}
memcpy(buffer, path.c_str(), path.size());
buffer[path.size()] = '\0';
return CRAZY_STATUS_SUCCESS;
}
crazy_status_t crazy_linker_check_library_aligned_in_zip_file(
const char* zipfile_name,
const char* lib_name) {
......
......@@ -5,6 +5,7 @@
#include "crazy_linker_library_list.h"
#include <assert.h>
#include <crazy_linker.h>
#include <dlfcn.h>
#include "crazy_linker_debug.h"
......@@ -20,9 +21,6 @@ namespace crazy {
namespace {
// Maximum filename length of a file in a zip file.
const size_t kMaxFilenameInZip = 256;
// Page size for alignment in a zip file.
const size_t kZipAlignmentPageSize = 4096;
static_assert(kZipAlignmentPageSize % PAGE_SIZE == 0,
......@@ -401,24 +399,28 @@ LibraryView* LibraryList::LoadLibrary(const char* lib_name,
#error "Unsupported target abi"
#endif
String LibraryList::GetLibraryFilePathInZipFile(const char* lib_name) {
String path;
path.Reserve(kMaxFilePathLengthInZip);
path = "lib/";
path += CURRENT_ABI;
path += "/crazy.";
path += lib_name;
return path;
}
int LibraryList::FindAlignedLibraryInZipFile(
const char* zip_file_path,
const char* lib_name,
Error* error) {
String fullname;
fullname.Reserve(kMaxFilenameInZip);
fullname = "lib/";
fullname += CURRENT_ABI;
fullname += "/crazy.";
fullname += lib_name;
if (fullname.size() + 1 > kMaxFilenameInZip) {
String path = GetLibraryFilePathInZipFile(lib_name);
if (path.size() >= kMaxFilePathLengthInZip) {
error->Format("Filename too long for a file in a zip file %s\n",
fullname.c_str());
path.c_str());
return CRAZY_OFFSET_FAILED;
}
int offset = FindStartOffsetOfFileInZipFile(zip_file_path, fullname.c_str());
int offset = FindStartOffsetOfFileInZipFile(zip_file_path, path.c_str());
if (offset == CRAZY_OFFSET_FAILED) {
return CRAZY_OFFSET_FAILED;
}
......
......@@ -71,6 +71,10 @@ class LibraryList {
SearchPathList* search_path_list,
Error* error);
// Return the full path of |lib_name| in the zip file
// (lib/<abi>/crazy.<lib_name>).
static String GetLibraryFilePathInZipFile(const char* lib_name);
// Find the location of a library in the zip file. If the name of the library
// is too long, an error occurs during the search or the library is not
// page aligned in the zip file, CRAZY_OFFSET_FAILED is returned. Otherwise,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment