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.
......
......@@ -6,17 +6,41 @@
package org.chromium.base.library_loader;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* Class representing an exception which occured during the unpacking process.
*/
class UnpackingException extends Exception {
public UnpackingException(String message, Throwable cause) {
super(message, cause);
}
public UnpackingException(String message) {
super(message);
}
}
/**
* The class provides helper functions to extract native libraries from APK,
* and load libraries from there.
......@@ -27,7 +51,12 @@ import java.util.zip.ZipFile;
public class LibraryLoaderHelper {
private static final String TAG = "LibraryLoaderHelper";
private static final String LIB_DIR = "lib";
// Workaround and fallback directories.
// TODO(petrcermak): Merge the directories once refactored.
public static final String PACKAGE_MANAGER_WORKAROUND_DIR = "lib";
public static final String LOAD_FROM_APK_FALLBACK_DIR = "fallback";
private static final int BUFFER_SIZE = 16384;
/**
* One-way switch becomes true if native libraries were unpacked
......@@ -71,7 +100,7 @@ public class LibraryLoaderHelper {
* recreate one to the new directory. However, on some devices (e.g. Sony Xperia),
* the symlink was updated, but fails to extract new native libraries from
* the new apk.
*
* We make the following changes to alleviate the issue:
* 1) name the native library with apk version code, e.g.,
* libchrome.1750.136.so, 1750.136 is Chrome version number;
......@@ -89,8 +118,9 @@ public class LibraryLoaderHelper {
*/
static boolean tryLoadLibraryUsingWorkaround(Context context, String library) {
assert context != null;
File libFile = getWorkaroundLibFile(context, library);
if (!libFile.exists() && !unpackLibrariesOnce(context)) {
String libName = System.mapLibraryName(library);
File libFile = new File(getLibDir(context, PACKAGE_MANAGER_WORKAROUND_DIR), libName);
if (!libFile.exists() && !unpackWorkaroundLibrariesOnce(context)) {
return false;
}
try {
......@@ -105,16 +135,18 @@ public class LibraryLoaderHelper {
* Returns the directory for holding extracted native libraries.
* It may create the directory if it doesn't exist.
*
* @param context
* @return the directory file object
* @param context The context the code is running.
* @param dirName The name of the directory containing the libraries.
* @return The directory file object.
*/
public static File getWorkaroundLibDir(Context context) {
return context.getDir(LIB_DIR, Context.MODE_PRIVATE);
public static File getLibDir(Context context, String dirName) {
return context.getDir(dirName, Context.MODE_PRIVATE);
}
private static File getWorkaroundLibFile(Context context, String library) {
String libName = System.mapLibraryName(library);
return new File(getWorkaroundLibDir(context), libName);
@SuppressWarnings("deprecation")
private static String getJniNameInApk(String libName) {
// TODO(aurimas): Build.CPU_ABI has been deprecated. Replace it when final L SDK is public.
return "lib/" + Build.CPU_ABI + "/" + libName;
}
/**
......@@ -126,128 +158,306 @@ public class LibraryLoaderHelper {
* @return true when unpacking was successful, false when failed or called
* more than once.
*/
private static boolean unpackLibrariesOnce(Context context) {
private static boolean unpackWorkaroundLibrariesOnce(Context context) {
if (sLibrariesWereUnpacked) {
return false;
}
sLibrariesWereUnpacked = true;
File libDir = getWorkaroundLibDir(context);
deleteDirectorySync(libDir);
deleteLibrariesSynchronously(context, PACKAGE_MANAGER_WORKAROUND_DIR);
File libDir = getLibDir(context, PACKAGE_MANAGER_WORKAROUND_DIR);
try {
ApplicationInfo appInfo = context.getApplicationInfo();
ZipFile file = new ZipFile(new File(appInfo.sourceDir), ZipFile.OPEN_READ);
for (String libName : NativeLibraries.LIBRARIES) {
String jniNameInApk = getJniNameInApk(libName);
final ZipEntry entry = file.getEntry(jniNameInApk);
if (entry == null) {
Log.e(TAG, appInfo.sourceDir + " doesn't have file " + jniNameInApk);
file.close();
deleteDirectorySync(libDir);
return false;
}
File outputFile = getWorkaroundLibFile(context, libName);
Log.i(TAG, "Extracting native libraries into " + outputFile.getAbsolutePath());
assert !outputFile.exists();
try {
if (!outputFile.createNewFile()) {
throw new IOException();
}
InputStream is = null;
FileOutputStream os = null;
try {
is = file.getInputStream(entry);
os = new FileOutputStream(outputFile);
int count = 0;
byte[] buffer = new byte[16 * 1024];
while ((count = is.read(buffer)) > 0) {
os.write(buffer, 0, count);
}
} finally {
try {
if (is != null) is.close();
} finally {
if (os != null) os.close();
}
}
// Change permission to rwxr-xr-x
outputFile.setReadable(true, false);
outputFile.setExecutable(true, false);
outputFile.setWritable(true);
} catch (IOException e) {
if (outputFile.exists()) {
if (!outputFile.delete()) {
Log.e(TAG, "Failed to delete " + outputFile.getAbsolutePath());
}
}
file.close();
throw e;
}
Map<String, File> dstFiles = new HashMap<String, File>();
for (String library : NativeLibraries.LIBRARIES) {
String libName = System.mapLibraryName(library);
String pathInZipFile = getJniNameInApk(libName);
dstFiles.put(pathInZipFile, new File(libDir, libName));
}
file.close();
unpackLibraries(context, dstFiles);
return true;
} catch (IOException e) {
} catch (UnpackingException e) {
Log.e(TAG, "Failed to unpack native libraries", e);
deleteDirectorySync(libDir);
deleteLibrariesSynchronously(context, PACKAGE_MANAGER_WORKAROUND_DIR);
return false;
}
}
/**
* Delete old library files in the backup directory.
* The actual deletion is done in a background thread.
* Delete libraries and their directory synchronously.
* For testing purpose only.
*
* @param context
*/
public static void deleteLibrariesSynchronously(Context context, String dirName) {
File libDir = getLibDir(context, dirName);
deleteObsoleteLibraries(libDir, Collections.<File>emptyList());
}
/**
* Delete libraries and their directory asynchronously.
* The actual deletion is done in a background thread.
*
* @param context
*/
static void deleteWorkaroundLibrariesAsynchronously(final Context context) {
static void deleteLibrariesAsynchronously(
final Context context, final String dirName) {
// Child process should not reach here.
new Thread() {
@Override
public void run() {
deleteWorkaroundLibrariesSynchronously(context);
deleteLibrariesSynchronously(context, dirName);
}
}.start();
}
/**
* Delete the workaround libraries and directory synchronously.
* For testing purpose only.
* @param context
* Copy a library from a zip file to the application's private directory.
* This is used as a fallback when we are unable to load the library
* directly from the APK file (crbug.com/390618).
*
* @param context The context the code is running in.
* @param library Library name.
* @return name of the fallback copy of the library.
*/
public static void deleteWorkaroundLibrariesSynchronously(Context context) {
File libDir = getWorkaroundLibDir(context);
deleteDirectorySync(libDir);
}
public static String buildFallbackLibrary(Context context, String library) {
try {
String libName = System.mapLibraryName(library);
File fallbackLibDir = getLibDir(context, LOAD_FROM_APK_FALLBACK_DIR);
File fallbackLibFile = new File(fallbackLibDir, libName);
String pathInZipFile = Linker.getLibraryFilePathInZipFile(libName);
Map<String, File> dstFiles = Collections.singletonMap(pathInZipFile, fallbackLibFile);
@SuppressWarnings("deprecation")
private static String getJniNameInApk(String libName) {
// TODO(aurimas): Build.CPU_ABI has been deprecated. Replace it when final L SDK is public.
return "lib/" + Build.CPU_ABI + "/" + System.mapLibraryName(libName);
deleteObsoleteLibraries(fallbackLibDir, dstFiles.values());
unpackLibraries(context, dstFiles);
return fallbackLibFile.getAbsolutePath();
} catch (Exception e) {
String errorMessage = "Unable to load fallback for library " + library
+ " (" + (e.getMessage() == null ? e.toString() : e.getMessage()) + ")";
Log.e(TAG, errorMessage, e);
throw new UnsatisfiedLinkError(errorMessage);
}
}
private static void deleteDirectorySync(File dir) {
// Delete obsolete libraries from a library folder.
private static void deleteObsoleteLibraries(File libDir, Collection<File> keptFiles) {
try {
File[] files = dir.listFiles();
// Build a list of libraries that should NOT be deleted.
Set<String> keptFileNames = new HashSet<String>();
for (File k : keptFiles) {
keptFileNames.add(k.getName());
}
// Delete the obsolete libraries.
Log.i(TAG, "Deleting obsolete libraries in " + libDir.getPath());
File[] files = libDir.listFiles();
if (files != null) {
for (File file : files) {
if (!file.delete()) {
Log.e(TAG, "Failed to remove " + file.getAbsolutePath());
for (File f : files) {
if (!keptFileNames.contains(f.getName())) {
delete(f);
}
}
} else {
Log.e(TAG, "Failed to list files in " + libDir.getPath());
}
if (!dir.delete()) {
Log.w(TAG, "Failed to remove " + dir.getAbsolutePath());
// Delete the folder if no libraries were kept.
if (keptFileNames.isEmpty()) {
delete(libDir);
}
return;
} catch (Exception e) {
Log.e(TAG, "Failed to remove old libs, ", e);
Log.e(TAG, "Failed to remove obsolete libraries from " + libDir.getPath());
}
}
// Unpack libraries from a zip file to the file system.
private static void unpackLibraries(Context context,
Map<String, File> dstFiles) throws UnpackingException {
String zipFilePath = context.getApplicationInfo().sourceDir;
Log.i(TAG, "Opening zip file " + zipFilePath);
File zipFile = new File(zipFilePath);
ZipFile zipArchive = openZipFile(zipFile);
try {
for (Entry<String, File> d : dstFiles.entrySet()) {
String pathInZipFile = d.getKey();
File dstFile = d.getValue();
Log.i(TAG, "Unpacking " + pathInZipFile
+ " to " + dstFile.getAbsolutePath());
ZipEntry packedLib = zipArchive.getEntry(pathInZipFile);
if (needToUnpackLibrary(zipFile, packedLib, dstFile)) {
unpackLibraryFromZipFile(zipArchive, packedLib, dstFile);
setLibraryFilePermissions(dstFile);
}
}
} finally {
closeZipFile(zipArchive);
}
}
// Open a zip file.
private static ZipFile openZipFile(File zipFile) throws UnpackingException {
try {
return new ZipFile(zipFile);
} catch (ZipException e) {
throw new UnpackingException("Failed to open zip file " + zipFile.getPath());
} catch (IOException e) {
throw new UnpackingException("Failed to open zip file " + zipFile.getPath());
}
}
// Determine whether it is necessary to unpack a library from a zip file.
private static boolean needToUnpackLibrary(
File zipFile, ZipEntry packedLib, File dstFile) {
// Check if the fallback library already exists.
if (!dstFile.exists()) {
Log.i(TAG, "File " + dstFile.getPath() + " does not exist yet");
return true;
}
// Check last modification dates.
long zipTime = zipFile.lastModified();
long fallbackLibTime = dstFile.lastModified();
if (zipTime > fallbackLibTime) {
Log.i(TAG, "Not using existing fallback file because "
+ "the APK file " + zipFile.getPath()
+ " (timestamp=" + zipTime + ") is newer than "
+ "the fallback library " + dstFile.getPath()
+ "(timestamp=" + fallbackLibTime + ")");
return true;
}
// Check file sizes.
long packedLibSize = packedLib.getSize();
long fallbackLibSize = dstFile.length();
if (fallbackLibSize != packedLibSize) {
Log.i(TAG, "Not using existing fallback file because "
+ "the library in the APK " + zipFile.getPath()
+ " (" + packedLibSize + "B) has a different size than "
+ "the fallback library " + dstFile.getPath()
+ "(" + fallbackLibSize + "B)");
return true;
}
Log.i(TAG, "Reusing existing file " + dstFile.getPath());
return false;
}
// Unpack a library from a zip file to the filesystem.
private static void unpackLibraryFromZipFile(ZipFile zipArchive, ZipEntry packedLib,
File dstFile) throws UnpackingException {
// Open input stream for the library file inside the zip file.
InputStream in;
try {
in = zipArchive.getInputStream(packedLib);
} catch (IOException e) {
throw new UnpackingException(
"IO exception when locating library in the zip file", e);
}
// Ensure that the input stream is closed at the end.
try {
// Delete existing file if it exists.
if (dstFile.exists()) {
Log.i(TAG, "Deleting existing unpacked library file " + dstFile.getPath());
if (!dstFile.delete()) {
throw new UnpackingException(
"Failed to delete existing unpacked library file " + dstFile.getPath());
}
}
// Ensure that the library folder exists. Since this is added
// for increased robustness, we log errors and carry on.
try {
dstFile.getParentFile().mkdirs();
} catch (Exception e) {
Log.e(TAG, "Failed to make library folder", e);
}
// Create the destination file.
try {
if (!dstFile.createNewFile()) {
throw new UnpackingException("existing unpacked library file was not deleted");
}
} catch (IOException e) {
throw new UnpackingException("failed to create unpacked library file", e);
}
// Open the output stream for the destination file.
OutputStream out;
try {
out = new BufferedOutputStream(new FileOutputStream(dstFile));
} catch (FileNotFoundException e) {
throw new UnpackingException(
"failed to open output stream for unpacked library file", e);
}
// Ensure that the output stream is closed at the end.
try {
// Copy the library from the zip file to the destination file.
Log.i(TAG, "Copying " + packedLib.getName() + " from " + zipArchive.getName()
+ " to " + dstFile.getPath());
byte[] buffer = new byte[BUFFER_SIZE];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
} catch (IOException e) {
throw new UnpackingException(
"failed to copy the library from the zip file", e);
} finally {
close(out, "output stream");
}
} finally {
close(in, "input stream");
}
}
// Set up library file permissions.
private static void setLibraryFilePermissions(File libFile) {
// Change permission to rwxr-xr-x
Log.i(TAG, "Setting file permissions for " + libFile.getPath());
if (!libFile.setReadable(/* readable */ true, /* ownerOnly */ false)) {
Log.e(TAG, "failed to chmod a+r the temporary file");
}
if (!libFile.setExecutable(/* executable */ true, /* ownerOnly */ false)) {
Log.e(TAG, "failed to chmod a+x the temporary file");
}
if (!libFile.setWritable(/* writable */ true)) {
Log.e(TAG, "failed to chmod +w the temporary file");
}
}
// Close a closable and log a warning if it fails.
private static void close(Closeable closeable, String name) {
try {
closeable.close();
} catch (IOException e) {
// Warn and ignore.
Log.w(TAG, "IO exception when closing " + name, e);
}
}
// Close a zip file and log a warning if it fails.
// This needs to be a separate method because ZipFile is not Closeable in
// Java 6 (used on some older devices).
private static void closeZipFile(ZipFile file) {
try {
file.close();
} catch (IOException e) {
// Warn and ignore.
Log.w(TAG, "IO exception when closing zip file", e);
}
}
// Delete a file and log it.
private static void delete(File file) {
if (file.delete()) {
Log.i(TAG, "Deleted " + file.getPath());
} else {
Log.w(TAG, "Failed to delete " + file.getPath());
}
}
}
......@@ -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