Make SoLoader an external dependency
Reviewed By: bestander Differential Revision: D3535233 fbshipit-source-id: 9fddb654123a7606d46069a98e2f68dec7f520fa
This commit is contained in:
parent
2426b3b5ec
commit
dd06b74157
|
@ -266,6 +266,7 @@ dependencies {
|
|||
compile 'com.android.support:recyclerview-v7:23.0.1'
|
||||
compile 'com.facebook.fresco:fresco:0.11.0'
|
||||
compile 'com.facebook.fresco:imagepipeline-okhttp3:0.11.0'
|
||||
compile 'com.facebook.soloader:soloader:0.1.0'
|
||||
compile 'com.fasterxml.jackson.core:jackson-core:2.2.3'
|
||||
compile 'com.google.code.findbugs:jsr305:3.0.0'
|
||||
compile 'com.squareup.okhttp3:okhttp:3.2.0'
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.ErrnoException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that extracts libraries from an APK to the filesystem.
|
||||
*/
|
||||
public class ApkSoSource extends DirectorySoSource {
|
||||
|
||||
private static final String TAG = SoLoader.TAG;
|
||||
private static final boolean DEBUG = SoLoader.DEBUG;
|
||||
|
||||
/**
|
||||
* Make a new ApkSoSource that extracts DSOs from our APK instead of relying on the system to do
|
||||
* the extraction for us.
|
||||
*
|
||||
* @param context Application context
|
||||
*/
|
||||
public ApkSoSource(Context context) throws IOException {
|
||||
//
|
||||
// Initialize a normal DirectorySoSource that will load from our extraction directory. At this
|
||||
// point, the directory may be empty or contain obsolete libraries, but that's okay.
|
||||
//
|
||||
|
||||
super(SysUtil.createLibsDirectory(context), DirectorySoSource.RESOLVE_DEPENDENCIES);
|
||||
|
||||
//
|
||||
// Synchronize the contents of that directory with the library payload in our APK, deleting and
|
||||
// extracting as needed.
|
||||
//
|
||||
|
||||
try (JarFile apk = new JarFile(context.getApplicationInfo().publicSourceDir)) {
|
||||
File libsDir = super.soDirectory;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "synchronizing log directory: " + libsDir);
|
||||
}
|
||||
|
||||
Map<String, SoInfo> providedLibraries = findProvidedLibraries(apk);
|
||||
try (FileLocker lock = SysUtil.lockLibsDirectory(context)) {
|
||||
// Delete files in libsDir that we don't provide or that are out of date. Forget about any
|
||||
// libraries that are up-to-date already so we don't unpack them below.
|
||||
File extantFiles[] = libsDir.listFiles();
|
||||
for (int i = 0; i < extantFiles.length; ++i) {
|
||||
File extantFile = extantFiles[i];
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "considering libdir file: " + extantFile);
|
||||
}
|
||||
|
||||
String name = extantFile.getName();
|
||||
SoInfo so = providedLibraries.get(name);
|
||||
boolean shouldDelete =
|
||||
(so == null ||
|
||||
so.entry.getSize() != extantFile.length() ||
|
||||
so.entry.getTime() != extantFile.lastModified());
|
||||
boolean upToDate = (so != null && !shouldDelete);
|
||||
|
||||
if (shouldDelete) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "deleting obsolete or unexpected file: " + extantFile);
|
||||
}
|
||||
SysUtil.deleteOrThrow(extantFile);
|
||||
}
|
||||
|
||||
if (upToDate) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "found up-to-date library: " + extantFile);
|
||||
}
|
||||
providedLibraries.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Now extract any libraries left in providedLibraries; we removed all the up-to-date ones.
|
||||
for (SoInfo so : providedLibraries.values()) {
|
||||
JarEntry entry = so.entry;
|
||||
try (InputStream is = apk.getInputStream(entry)) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "extracting library: " + so.soName);
|
||||
}
|
||||
SysUtil.reliablyCopyExecutable(
|
||||
is,
|
||||
new File(libsDir, so.soName),
|
||||
entry.getSize(),
|
||||
entry.getTime());
|
||||
}
|
||||
|
||||
SysUtil.freeCopyBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the shared libraries provided in this APK and supported on this system. Each returend
|
||||
* SoInfo points to the most preferred version of that library bundled with the given APK: for
|
||||
* example, if we're on an armv7-a system and we have both arm and armv7-a versions of libfoo, the
|
||||
* returned entry for libfoo points to the armv7-a version of libfoo.
|
||||
*
|
||||
* The caller owns the returned value and may mutate it.
|
||||
*
|
||||
* @param apk Opened application APK file
|
||||
* @return Map of sonames to SoInfo instances
|
||||
*/
|
||||
private static Map<String, SoInfo> findProvidedLibraries(JarFile apk) {
|
||||
// Subgroup 1: ABI. Subgroup 2: soname.
|
||||
Pattern libPattern = Pattern.compile("^lib/([^/]+)/([^/]+\\.so)$");
|
||||
HashMap<String, SoInfo> providedLibraries = new HashMap<>();
|
||||
String[] supportedAbis = SysUtil.getSupportedAbis();
|
||||
Enumeration<JarEntry> entries = apk.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
Matcher m = libPattern.matcher(entry.getName());
|
||||
if (m.matches()) {
|
||||
String libraryAbi = m.group(1);
|
||||
String soName = m.group(2);
|
||||
int abiScore = SysUtil.findAbiScore(supportedAbis, libraryAbi);
|
||||
if (abiScore >= 0) {
|
||||
SoInfo so = providedLibraries.get(soName);
|
||||
if (so == null || abiScore < so.abiScore) {
|
||||
providedLibraries.put(soName, new SoInfo(soName, entry, abiScore));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return providedLibraries;
|
||||
}
|
||||
|
||||
private static final class SoInfo {
|
||||
public final String soName;
|
||||
public final JarEntry entry;
|
||||
public final int abiScore;
|
||||
|
||||
SoInfo(String soName, JarEntry entry, int abiScore) {
|
||||
this.soName = soName;
|
||||
this.entry = entry;
|
||||
this.abiScore = abiScore;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,11 @@
|
|||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'soloader',
|
||||
srcs = glob(['*.java']),
|
||||
proguard_config = 'soloader.pro',
|
||||
deps = [
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
# Be very careful adding new dependencies here, because this code
|
||||
# has to run very early in the app startup process.
|
||||
# Definitely do *not* depend on lib-base or guava.
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
android_prebuilt_aar(
|
||||
name = 'soloader',
|
||||
aar = ':soloader-binary-aar',
|
||||
visibility = ['PUBLIC'],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':soloader',
|
||||
remote_file(
|
||||
name = 'soloader-binary-aar',
|
||||
url = 'mvn:com.facebook.soloader:soloader:aar:0.1.0',
|
||||
sha1 = '918573465c94c6bc9bad48ef259f1e0cd6543c1b',
|
||||
)
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that finds shared libraries in a given directory.
|
||||
*/
|
||||
public class DirectorySoSource extends SoSource {
|
||||
|
||||
public static final int RESOLVE_DEPENDENCIES = 1;
|
||||
public static final int ON_LD_LIBRARY_PATH = 2;
|
||||
|
||||
protected final File soDirectory;
|
||||
private final int flags;
|
||||
|
||||
/**
|
||||
* Make a new DirectorySoSource. If {@code flags} contains {@code RESOLVE_DEPENDENCIES},
|
||||
* recursively load dependencies for shared objects loaded from this directory. (We shouldn't
|
||||
* need to resolve dependencies for libraries loaded from system directories: the dynamic linker
|
||||
* is smart enough to do it on its own there.)
|
||||
*/
|
||||
public DirectorySoSource(File soDirectory, int flags) {
|
||||
this.soDirectory = soDirectory;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadLibrary(String soName, int loadFlags) throws IOException {
|
||||
File soFile = new File(soDirectory, soName);
|
||||
if (!soFile.exists()) {
|
||||
return LOAD_RESULT_NOT_FOUND;
|
||||
}
|
||||
|
||||
if ((loadFlags & LOAD_FLAG_ALLOW_IMPLICIT_PROVISION) != 0 &&
|
||||
(flags & ON_LD_LIBRARY_PATH) != 0) {
|
||||
return LOAD_RESULT_IMPLICITLY_PROVIDED;
|
||||
}
|
||||
|
||||
if ((flags & RESOLVE_DEPENDENCIES) != 0) {
|
||||
String dependencies[] = MinElf.extract_DT_NEEDED(soFile);
|
||||
for (int i = 0; i < dependencies.length; ++i) {
|
||||
String dependency = dependencies[i];
|
||||
if (dependency.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SoLoader.loadLibraryBySoName(
|
||||
dependency,
|
||||
(loadFlags | LOAD_FLAG_ALLOW_IMPLICIT_PROVISION));
|
||||
}
|
||||
}
|
||||
|
||||
System.load(soFile.getAbsolutePath());
|
||||
return LOAD_RESULT_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File unpackLibrary(String soName) throws IOException {
|
||||
File soFile = new File(soDirectory, soName);
|
||||
if (soFile.exists()) {
|
||||
return soFile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Dyn {
|
||||
public static final int d_tag = 0x0;
|
||||
public static final int d_un = 0x4;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Ehdr {
|
||||
public static final int e_ident = 0x0;
|
||||
public static final int e_type = 0x10;
|
||||
public static final int e_machine = 0x12;
|
||||
public static final int e_version = 0x14;
|
||||
public static final int e_entry = 0x18;
|
||||
public static final int e_phoff = 0x1c;
|
||||
public static final int e_shoff = 0x20;
|
||||
public static final int e_flags = 0x24;
|
||||
public static final int e_ehsize = 0x28;
|
||||
public static final int e_phentsize = 0x2a;
|
||||
public static final int e_phnum = 0x2c;
|
||||
public static final int e_shentsize = 0x2e;
|
||||
public static final int e_shnum = 0x30;
|
||||
public static final int e_shstrndx = 0x32;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Phdr {
|
||||
public static final int p_type = 0x0;
|
||||
public static final int p_offset = 0x4;
|
||||
public static final int p_vaddr = 0x8;
|
||||
public static final int p_paddr = 0xc;
|
||||
public static final int p_filesz = 0x10;
|
||||
public static final int p_memsz = 0x14;
|
||||
public static final int p_flags = 0x18;
|
||||
public static final int p_align = 0x1c;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf32_Shdr {
|
||||
public static final int sh_name = 0x0;
|
||||
public static final int sh_type = 0x4;
|
||||
public static final int sh_flags = 0x8;
|
||||
public static final int sh_addr = 0xc;
|
||||
public static final int sh_offset = 0x10;
|
||||
public static final int sh_size = 0x14;
|
||||
public static final int sh_link = 0x18;
|
||||
public static final int sh_info = 0x1c;
|
||||
public static final int sh_addralign = 0x20;
|
||||
public static final int sh_entsize = 0x24;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Dyn {
|
||||
public static final int d_tag = 0x0;
|
||||
public static final int d_un = 0x8;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Ehdr {
|
||||
public static final int e_ident = 0x0;
|
||||
public static final int e_type = 0x10;
|
||||
public static final int e_machine = 0x12;
|
||||
public static final int e_version = 0x14;
|
||||
public static final int e_entry = 0x18;
|
||||
public static final int e_phoff = 0x20;
|
||||
public static final int e_shoff = 0x28;
|
||||
public static final int e_flags = 0x30;
|
||||
public static final int e_ehsize = 0x34;
|
||||
public static final int e_phentsize = 0x36;
|
||||
public static final int e_phnum = 0x38;
|
||||
public static final int e_shentsize = 0x3a;
|
||||
public static final int e_shnum = 0x3c;
|
||||
public static final int e_shstrndx = 0x3e;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Phdr {
|
||||
public static final int p_type = 0x0;
|
||||
public static final int p_flags = 0x4;
|
||||
public static final int p_offset = 0x8;
|
||||
public static final int p_vaddr = 0x10;
|
||||
public static final int p_paddr = 0x18;
|
||||
public static final int p_filesz = 0x20;
|
||||
public static final int p_memsz = 0x28;
|
||||
public static final int p_align = 0x30;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
public final class Elf64_Shdr {
|
||||
public static final int sh_name = 0x0;
|
||||
public static final int sh_type = 0x4;
|
||||
public static final int sh_flags = 0x8;
|
||||
public static final int sh_addr = 0x10;
|
||||
public static final int sh_offset = 0x18;
|
||||
public static final int sh_size = 0x20;
|
||||
public static final int sh_link = 0x28;
|
||||
public static final int sh_info = 0x2c;
|
||||
public static final int sh_addralign = 0x30;
|
||||
public static final int sh_entsize = 0x38;
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.ErrnoException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that retrieves libraries from an exopackage repository.
|
||||
*/
|
||||
public class ExoSoSource extends DirectorySoSource {
|
||||
|
||||
private static final String TAG = SoLoader.TAG;
|
||||
private static final boolean DEBUG = SoLoader.DEBUG;
|
||||
|
||||
/**
|
||||
* @param context Application context
|
||||
*/
|
||||
public ExoSoSource(Context context) throws IOException {
|
||||
//
|
||||
// Initialize a normal DirectorySoSource that will load from our extraction directory. At this
|
||||
// point, the directory may be empty or contain obsolete libraries, but that's okay.
|
||||
//
|
||||
|
||||
super(SysUtil.createLibsDirectory(context), DirectorySoSource.RESOLVE_DEPENDENCIES);
|
||||
|
||||
//
|
||||
// Synchronize the contents of that directory with the library payload in our APK, deleting and
|
||||
// extracting as needed.
|
||||
//
|
||||
|
||||
File libsDir = super.soDirectory;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "synchronizing log directory: " + libsDir);
|
||||
}
|
||||
|
||||
Map<String, File> providedLibraries = findProvidedLibraries(context);
|
||||
try (FileLocker lock = SysUtil.lockLibsDirectory(context)) {
|
||||
// Delete files in libsDir that we don't provide or that are out of date. Forget about any
|
||||
// libraries that are up-to-date already so we don't unpack them below.
|
||||
File extantFiles[] = libsDir.listFiles();
|
||||
for (int i = 0; i < extantFiles.length; ++i) {
|
||||
File extantFile = extantFiles[i];
|
||||
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "considering libdir file: " + extantFile);
|
||||
}
|
||||
|
||||
String name = extantFile.getName();
|
||||
File sourceFile = providedLibraries.get(name);
|
||||
boolean shouldDelete =
|
||||
(sourceFile == null ||
|
||||
sourceFile.length() != extantFile.length() ||
|
||||
sourceFile.lastModified() != extantFile.lastModified());
|
||||
boolean upToDate = (sourceFile != null && !shouldDelete);
|
||||
|
||||
if (shouldDelete) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "deleting obsolete or unexpected file: " + extantFile);
|
||||
}
|
||||
SysUtil.deleteOrThrow(extantFile);
|
||||
}
|
||||
|
||||
if (upToDate) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "found up-to-date library: " + extantFile);
|
||||
}
|
||||
providedLibraries.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Now extract any libraries left in providedLibraries; we removed all the up-to-date ones.
|
||||
for (String soName : providedLibraries.keySet()) {
|
||||
File sourceFile = providedLibraries.get(soName);
|
||||
try (InputStream is = new FileInputStream(sourceFile)) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "extracting library: " + soName);
|
||||
}
|
||||
SysUtil.reliablyCopyExecutable(
|
||||
is,
|
||||
new File(libsDir, soName),
|
||||
sourceFile.length(),
|
||||
sourceFile.lastModified());
|
||||
}
|
||||
|
||||
SysUtil.freeCopyBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the shared libraries provided through the exopackage directory and supported on this
|
||||
* system. Each returend SoInfo points to the most preferred version of that library included in
|
||||
* our exopackage directory: for example, if we're on an armv7-a system and we have both arm and
|
||||
* armv7-a versions of libfoo, the returned entry for libfoo points to the armv7-a version of
|
||||
* libfoo.
|
||||
*
|
||||
* The caller owns the returned value and may mutate it.
|
||||
*
|
||||
* @param context Application context
|
||||
* @return Map of sonames to providing files
|
||||
*/
|
||||
private static Map<String, File> findProvidedLibraries(Context context) throws IOException {
|
||||
File exoDir = new File(
|
||||
"/data/local/tmp/exopackage/"
|
||||
+ context.getPackageName()
|
||||
+ "/native-libs/");
|
||||
|
||||
HashMap<String, File> providedLibraries = new HashMap<>();
|
||||
for (String abi : SysUtil.getSupportedAbis()) {
|
||||
File abiDir = new File(exoDir, abi);
|
||||
if (!abiDir.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
File metadata = new File(abiDir, "metadata.txt");
|
||||
if (!metadata.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try (FileReader fr = new FileReader(metadata);
|
||||
BufferedReader br = new BufferedReader(fr)) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int sep = line.indexOf(' ');
|
||||
if (sep == -1) {
|
||||
throw new RuntimeException("illegal line in exopackage metadata: [" + line + "]");
|
||||
}
|
||||
|
||||
String soName = line.substring(0, sep) + ".so";
|
||||
String backingFile = line.substring(sep + 1);
|
||||
|
||||
if (!providedLibraries.containsKey(soName)) {
|
||||
providedLibraries.put(soName, new File(abiDir, backingFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return providedLibraries;
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.io.Closeable;
|
||||
|
||||
public final class FileLocker implements Closeable {
|
||||
|
||||
private final FileOutputStream mLockFileOutputStream;
|
||||
private final FileLock mLock;
|
||||
|
||||
public static FileLocker lock(File lockFile) throws IOException {
|
||||
return new FileLocker(lockFile);
|
||||
}
|
||||
|
||||
private FileLocker(File lockFile) throws IOException {
|
||||
mLockFileOutputStream = new FileOutputStream(lockFile);
|
||||
FileLock lock = null;
|
||||
try {
|
||||
lock = mLockFileOutputStream.getChannel().lock();
|
||||
} finally {
|
||||
if (lock == null) {
|
||||
mLockFileOutputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
mLock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
mLock.release();
|
||||
} finally {
|
||||
mLockFileOutputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,282 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
/**
|
||||
* Extract SoLoader boottsrap information from an ELF file. This is not a general purpose ELF
|
||||
* library.
|
||||
*
|
||||
* See specification at http://www.sco.com/developers/gabi/latest/contents.html. You will not be
|
||||
* able to verify the operation of the functions below without having read the ELF specification.
|
||||
*/
|
||||
public final class MinElf {
|
||||
|
||||
public static final int ELF_MAGIC = 0x464c457f;
|
||||
|
||||
public static final int DT_NULL = 0;
|
||||
public static final int DT_NEEDED = 1;
|
||||
public static final int DT_STRTAB = 5;
|
||||
|
||||
public static final int PT_LOAD = 1;
|
||||
public static final int PT_DYNAMIC = 2;
|
||||
|
||||
public static final int PN_XNUM = 0xFFFF;
|
||||
|
||||
public static String[] extract_DT_NEEDED(File elfFile) throws IOException {
|
||||
FileInputStream is = new FileInputStream(elfFile);
|
||||
try {
|
||||
return extract_DT_NEEDED(is.getChannel());
|
||||
} finally {
|
||||
is.close(); // Won't throw
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Treating {@code bb} as an ELF file, extract all the DT_NEEDED entries from its dynamic section.
|
||||
*
|
||||
* @param fc FileChannel referring to ELF file
|
||||
* @return Array of strings, one for each DT_NEEDED entry, in file order
|
||||
*/
|
||||
public static String[] extract_DT_NEEDED(FileChannel fc)
|
||||
throws IOException {
|
||||
|
||||
//
|
||||
// All constants below are fixed by the ELF specification and are the offsets of fields within
|
||||
// the elf.h data structures.
|
||||
//
|
||||
|
||||
ByteBuffer bb = ByteBuffer.allocate(8 /* largest read unit */);
|
||||
|
||||
// Read ELF header.
|
||||
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (getu32(fc, bb, Elf32_Ehdr.e_ident) != ELF_MAGIC) {
|
||||
throw new ElfError("file is not ELF");
|
||||
}
|
||||
|
||||
boolean is32 = (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x4) == 1);
|
||||
if (getu8(fc, bb, Elf32_Ehdr.e_ident + 0x5) == 2) {
|
||||
bb.order(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
// Offsets above are identical in 32- and 64-bit cases.
|
||||
|
||||
// Find the offset of the dynamic linking information.
|
||||
|
||||
long e_phoff = is32
|
||||
? getu32(fc, bb, Elf32_Ehdr.e_phoff)
|
||||
: get64(fc, bb, Elf64_Ehdr.e_phoff);
|
||||
|
||||
long e_phnum = is32
|
||||
? getu16(fc, bb, Elf32_Ehdr.e_phnum)
|
||||
: getu16(fc, bb, Elf64_Ehdr.e_phnum);
|
||||
|
||||
int e_phentsize = is32
|
||||
? getu16(fc, bb, Elf32_Ehdr.e_phentsize)
|
||||
: getu16(fc, bb, Elf64_Ehdr.e_phentsize);
|
||||
|
||||
if (e_phnum == PN_XNUM) { // Overflowed into section[0].sh_info
|
||||
|
||||
long e_shoff = is32
|
||||
? getu32(fc, bb, Elf32_Ehdr.e_shoff)
|
||||
: get64(fc, bb, Elf64_Ehdr.e_shoff);
|
||||
|
||||
long sh_info = is32
|
||||
? getu32(fc, bb, e_shoff + Elf32_Shdr.sh_info)
|
||||
: getu32(fc, bb, e_shoff + Elf64_Shdr.sh_info);
|
||||
|
||||
e_phnum = sh_info;
|
||||
}
|
||||
|
||||
long dynStart = 0;
|
||||
long phdr = e_phoff;
|
||||
|
||||
for (long i = 0; i < e_phnum; ++i) {
|
||||
long p_type = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_type)
|
||||
: getu32(fc, bb, phdr + Elf64_Phdr.p_type);
|
||||
|
||||
if (p_type == PT_DYNAMIC) {
|
||||
long p_offset = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_offset)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_offset);
|
||||
|
||||
dynStart = p_offset;
|
||||
break;
|
||||
}
|
||||
|
||||
phdr += e_phentsize;
|
||||
}
|
||||
|
||||
if (dynStart == 0) {
|
||||
throw new ElfError("ELF file does not contain dynamic linking information");
|
||||
}
|
||||
|
||||
// Walk the items in the dynamic section, counting the DT_NEEDED entries. Also remember where
|
||||
// the string table for those entries lives. That table is a pointer, which we translate to an
|
||||
// offset below.
|
||||
|
||||
long d_tag;
|
||||
int nr_DT_NEEDED = 0;
|
||||
long dyn = dynStart;
|
||||
long ptr_DT_STRTAB = 0;
|
||||
|
||||
do {
|
||||
d_tag = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_tag)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_tag);
|
||||
|
||||
if (d_tag == DT_NEEDED) {
|
||||
if (nr_DT_NEEDED == Integer.MAX_VALUE) {
|
||||
throw new ElfError("malformed DT_NEEDED section");
|
||||
}
|
||||
|
||||
nr_DT_NEEDED += 1;
|
||||
} else if (d_tag == DT_STRTAB) {
|
||||
ptr_DT_STRTAB = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_un)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_un);
|
||||
}
|
||||
|
||||
dyn += is32 ? 8 : 16;
|
||||
} while (d_tag != DT_NULL);
|
||||
|
||||
if (ptr_DT_STRTAB == 0) {
|
||||
throw new ElfError("Dynamic section string-table not found");
|
||||
}
|
||||
|
||||
// Translate the runtime string table pointer we found above to a file offset.
|
||||
|
||||
long off_DT_STRTAB = 0;
|
||||
phdr = e_phoff;
|
||||
|
||||
for (int i = 0; i < e_phnum; ++i) {
|
||||
long p_type = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_type)
|
||||
: getu32(fc, bb, phdr + Elf64_Phdr.p_type);
|
||||
|
||||
if (p_type == PT_LOAD) {
|
||||
long p_vaddr = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_vaddr)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_vaddr);
|
||||
|
||||
long p_memsz = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_memsz)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_memsz);
|
||||
|
||||
if (p_vaddr <= ptr_DT_STRTAB && ptr_DT_STRTAB < p_vaddr + p_memsz) {
|
||||
long p_offset = is32
|
||||
? getu32(fc, bb, phdr + Elf32_Phdr.p_offset)
|
||||
: get64(fc, bb, phdr + Elf64_Phdr.p_offset);
|
||||
|
||||
off_DT_STRTAB = p_offset + (ptr_DT_STRTAB - p_vaddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
phdr += e_phentsize;
|
||||
}
|
||||
|
||||
if (off_DT_STRTAB == 0) {
|
||||
throw new ElfError("did not find file offset of DT_STRTAB table");
|
||||
}
|
||||
|
||||
String[] needed = new String[nr_DT_NEEDED];
|
||||
|
||||
nr_DT_NEEDED = 0;
|
||||
dyn = dynStart;
|
||||
|
||||
do {
|
||||
d_tag = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_tag)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_tag);
|
||||
|
||||
if (d_tag == DT_NEEDED) {
|
||||
long d_val = is32
|
||||
? getu32(fc, bb, dyn + Elf32_Dyn.d_un)
|
||||
: get64(fc, bb, dyn + Elf64_Dyn.d_un);
|
||||
|
||||
needed[nr_DT_NEEDED] = getSz(fc, bb, off_DT_STRTAB + d_val);
|
||||
if (nr_DT_NEEDED == Integer.MAX_VALUE) {
|
||||
throw new ElfError("malformed DT_NEEDED section");
|
||||
}
|
||||
|
||||
nr_DT_NEEDED += 1;
|
||||
}
|
||||
|
||||
dyn += is32 ? 8 : 16;
|
||||
} while (d_tag != DT_NULL);
|
||||
|
||||
if (nr_DT_NEEDED != needed.length) {
|
||||
throw new ElfError("malformed DT_NEEDED section");
|
||||
}
|
||||
|
||||
return needed;
|
||||
}
|
||||
|
||||
private static String getSz(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
short b;
|
||||
while ((b = getu8(fc, bb, offset++)) != 0) {
|
||||
sb.append((char) b);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void read(FileChannel fc, ByteBuffer bb, int sz, long offset)
|
||||
throws IOException {
|
||||
bb.position(0);
|
||||
bb.limit(sz);
|
||||
if (fc.read(bb, offset) != sz) {
|
||||
throw new ElfError("ELF file truncated");
|
||||
}
|
||||
|
||||
bb.position(0);
|
||||
}
|
||||
|
||||
private static long get64(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 8, offset);
|
||||
return bb.getLong();
|
||||
}
|
||||
|
||||
private static long getu32(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 4, offset);
|
||||
return bb.getInt() & 0xFFFFFFFFL; // signed -> unsigned
|
||||
}
|
||||
|
||||
private static int getu16(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 2, offset);
|
||||
return bb.getShort() & (int) 0xFFFF; // signed -> unsigned
|
||||
}
|
||||
|
||||
private static short getu8(FileChannel fc, ByteBuffer bb, long offset)
|
||||
throws IOException {
|
||||
read(fc, bb, 1, offset);
|
||||
return (short) (bb.get() & 0xFF); // signed -> unsigned
|
||||
}
|
||||
|
||||
private static class ElfError extends RuntimeException {
|
||||
ElfError(String why) {
|
||||
super(why);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This is the base class for all the classes representing certain native library.
|
||||
* For loading native libraries we should always inherit from this class and provide relevant
|
||||
* information (libraries to load, code to test native call, dependencies?).
|
||||
* <p>
|
||||
* This instances should be singletons provided by DI.
|
||||
* <p>
|
||||
* This is a basic template but could be improved if we find the need.
|
||||
*/
|
||||
public abstract class NativeLibrary {
|
||||
private static final String TAG = NativeLibrary.class.getName();
|
||||
|
||||
private final Object mLock;
|
||||
private List<String> mLibraryNames;
|
||||
private Boolean mLoadLibraries;
|
||||
private boolean mLibrariesLoaded;
|
||||
private volatile UnsatisfiedLinkError mLinkError;
|
||||
|
||||
protected NativeLibrary(List<String> libraryNames) {
|
||||
mLock = new Object();
|
||||
mLoadLibraries = true;
|
||||
mLibrariesLoaded = false;
|
||||
mLinkError = null;
|
||||
mLibraryNames = libraryNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* safe loading of native libs
|
||||
* @return true if native libs loaded properly, false otherwise
|
||||
*/
|
||||
public boolean loadLibraries() {
|
||||
synchronized (mLock) {
|
||||
if (mLoadLibraries == false) {
|
||||
return mLibrariesLoaded;
|
||||
}
|
||||
try {
|
||||
for (String name: mLibraryNames) {
|
||||
SoLoader.loadLibrary(name);
|
||||
}
|
||||
initialNativeCheck();
|
||||
mLibrariesLoaded = true;
|
||||
mLibraryNames = null;
|
||||
} catch (UnsatisfiedLinkError error) {
|
||||
Log.e(TAG, "Failed to load native lib: ", error);
|
||||
mLinkError = error;
|
||||
mLibrariesLoaded = false;
|
||||
}
|
||||
mLoadLibraries = false;
|
||||
return mLibrariesLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loads libraries (if not loaded yet), throws on failure
|
||||
* @throws UnsatisfiedLinkError
|
||||
*/
|
||||
|
||||
public void ensureLoaded() throws UnsatisfiedLinkError {
|
||||
if (!loadLibraries()) {
|
||||
throw mLinkError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to make some concrete (quick and harmless) native call.
|
||||
* This avoids lazy-loading some phones (LG) use when we call loadLibrary. If there's a problem
|
||||
* we'll face an UnsupportedLinkError when first using the feature instead of here.
|
||||
* This check force a check right when intended.
|
||||
* This way clients of this library can know if it's loaded for sure or not.
|
||||
* @throws UnsatisfiedLinkError if there was an error loading native library
|
||||
*/
|
||||
protected void initialNativeCheck() throws UnsatisfiedLinkError {
|
||||
}
|
||||
|
||||
public UnsatisfiedLinkError getError() {
|
||||
return mLinkError;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* {@link SoSource} that does nothing and pretends to successfully load all libraries.
|
||||
*/
|
||||
public class NoopSoSource extends SoSource {
|
||||
@Override
|
||||
public int loadLibrary(String soName, int loadFlags) {
|
||||
return LOAD_RESULT_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File unpackLibrary(String soName) {
|
||||
throw new UnsupportedOperationException(
|
||||
"unpacking not supported in test mode");
|
||||
}
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.HashSet;
|
||||
import java.util.ArrayList;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.StatFs;
|
||||
import android.util.Log;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
/**
|
||||
* Note that {@link com.facebook.base.app.DelegatingApplication} will automatically register itself
|
||||
* with SoLoader before running application-specific code; most applications do not need to call
|
||||
* {@link #init} explicitly.
|
||||
*/
|
||||
@SuppressLint({
|
||||
"BadMethodUse-android.util.Log.v",
|
||||
"BadMethodUse-android.util.Log.d",
|
||||
"BadMethodUse-android.util.Log.i",
|
||||
"BadMethodUse-android.util.Log.w",
|
||||
"BadMethodUse-android.util.Log.e",
|
||||
})
|
||||
public class SoLoader {
|
||||
|
||||
/* package */ static final String TAG = "SoLoader";
|
||||
/* package */ static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Ordered list of sources to consult when trying to load a shared library or one of its
|
||||
* dependencies. {@code null} indicates that SoLoader is uninitialized.
|
||||
*/
|
||||
@Nullable private static SoSource[] sSoSources = null;
|
||||
|
||||
/**
|
||||
* Records the sonames (e.g., "libdistract.so") of shared libraries we've loaded.
|
||||
*/
|
||||
private static final Set<String> sLoadedLibraries = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Initializes native code loading for this app; this class's other static facilities cannot be
|
||||
* used until this {@link #init} is called. This method is idempotent: calls after the first are
|
||||
* ignored.
|
||||
*
|
||||
* @param context - application context.
|
||||
* @param isNativeExopackageEnabled - whether native exopackage feature is enabled in the build.
|
||||
*/
|
||||
public static synchronized void init(@Nullable Context context, boolean isNativeExopackageEnabled) {
|
||||
if (sSoSources == null) {
|
||||
ArrayList<SoSource> soSources = new ArrayList<>();
|
||||
|
||||
//
|
||||
// Add SoSource objects for each of the system library directories.
|
||||
//
|
||||
|
||||
String LD_LIBRARY_PATH = System.getenv("LD_LIBRARY_PATH");
|
||||
if (LD_LIBRARY_PATH == null) {
|
||||
LD_LIBRARY_PATH = "/vendor/lib:/system/lib";
|
||||
}
|
||||
|
||||
String[] systemLibraryDirectories = LD_LIBRARY_PATH.split(":");
|
||||
for (int i = 0; i < systemLibraryDirectories.length; ++i) {
|
||||
// Don't pass DirectorySoSource.RESOLVE_DEPENDENCIES for directories we find on
|
||||
// LD_LIBRARY_PATH: Bionic's dynamic linker is capable of correctly resolving dependencies
|
||||
// these libraries have on each other, so doing that ourselves would be a waste.
|
||||
File systemSoDirectory = new File(systemLibraryDirectories[i]);
|
||||
soSources.add(
|
||||
new DirectorySoSource(
|
||||
systemSoDirectory,
|
||||
DirectorySoSource.ON_LD_LIBRARY_PATH));
|
||||
}
|
||||
|
||||
//
|
||||
// We can only proceed forward if we have a Context. The prominent case
|
||||
// where we don't have a Context is barebones dalvikvm instantiations. In
|
||||
// that case, the caller is responsible for providing a correct LD_LIBRARY_PATH.
|
||||
//
|
||||
|
||||
if (context != null) {
|
||||
//
|
||||
// Prepend our own SoSource for our own DSOs.
|
||||
//
|
||||
|
||||
ApplicationInfo applicationInfo = context.getApplicationInfo();
|
||||
boolean isSystemApplication =
|
||||
(applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 &&
|
||||
(applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0;
|
||||
|
||||
try {
|
||||
if (isNativeExopackageEnabled) {
|
||||
soSources.add(0, new ExoSoSource(context));
|
||||
} else if (isSystemApplication) {
|
||||
soSources.add(0, new ApkSoSource(context));
|
||||
} else {
|
||||
// Delete the old libs directory if we don't need it.
|
||||
SysUtil.dumbDeleteRecrusive(SysUtil.getLibsDirectory(context));
|
||||
|
||||
int ourSoSourceFlags = 0;
|
||||
|
||||
// On old versions of Android, Bionic doesn't add our library directory to its internal
|
||||
// search path, and the system doesn't resolve dependencies between modules we ship. On
|
||||
// these systems, we resolve dependencies ourselves. On other systems, Bionic's built-in
|
||||
// resolver suffices.
|
||||
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
ourSoSourceFlags |= DirectorySoSource.RESOLVE_DEPENDENCIES;
|
||||
}
|
||||
|
||||
SoSource ourSoSource = new DirectorySoSource(
|
||||
new File(applicationInfo.nativeLibraryDir),
|
||||
ourSoSourceFlags);
|
||||
|
||||
soSources.add(0, ourSoSource);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
sSoSources = soSources.toArray(new SoSource[soSources.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn shared-library loading into a no-op. Useful in special circumstances.
|
||||
*/
|
||||
public static void setInTestMode() {
|
||||
sSoSources = new SoSource[]{new NoopSoSource()};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a shared library, initializing any JNI binding it contains.
|
||||
*
|
||||
* @param shortName Name of library to find, without "lib" prefix or ".so" suffix
|
||||
*/
|
||||
public static synchronized void loadLibrary(String shortName)
|
||||
throws UnsatisfiedLinkError
|
||||
{
|
||||
if (sSoSources == null) {
|
||||
// This should never happen during normal operation,
|
||||
// but if we're running in a non-Android environment,
|
||||
// fall back to System.loadLibrary.
|
||||
if ("http://www.android.com/".equals(System.getProperty("java.vendor.url"))) {
|
||||
// This will throw.
|
||||
assertInitialized();
|
||||
} else {
|
||||
// Not on an Android system. Ask the JVM to load for us.
|
||||
System.loadLibrary(shortName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
loadLibraryBySoName(System.mapLibraryName(shortName), 0);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack library and its dependencies, returning the location of the unpacked library file. All
|
||||
* non-system dependencies of the given library will either be on LD_LIBRARY_PATH or will be in
|
||||
* the same directory as the returned File.
|
||||
*
|
||||
* @param shortName Name of library to find, without "lib" prefix or ".so" suffix
|
||||
* @return Unpacked DSO location
|
||||
*/
|
||||
public static File unpackLibraryAndDependencies(String shortName)
|
||||
throws UnsatisfiedLinkError
|
||||
{
|
||||
assertInitialized();
|
||||
try {
|
||||
return unpackLibraryBySoName(System.mapLibraryName(shortName));
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ static void loadLibraryBySoName(String soName, int loadFlags) throws IOException {
|
||||
int result = sLoadedLibraries.contains(soName)
|
||||
? SoSource.LOAD_RESULT_LOADED
|
||||
: SoSource.LOAD_RESULT_NOT_FOUND;
|
||||
|
||||
for (int i = 0; result == SoSource.LOAD_RESULT_NOT_FOUND && i < sSoSources.length; ++i) {
|
||||
result = sSoSources[i].loadLibrary(soName, loadFlags);
|
||||
}
|
||||
|
||||
if (result == SoSource.LOAD_RESULT_NOT_FOUND) {
|
||||
throw new UnsatisfiedLinkError("could find DSO to load: " + soName);
|
||||
}
|
||||
|
||||
if (result == SoSource.LOAD_RESULT_LOADED) {
|
||||
sLoadedLibraries.add(soName);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ static File unpackLibraryBySoName(String soName) throws IOException {
|
||||
for (int i = 0; i < sSoSources.length; ++i) {
|
||||
File unpacked = sSoSources[i].unpackLibrary(soName);
|
||||
if (unpacked != null) {
|
||||
return unpacked;
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileNotFoundException(soName);
|
||||
}
|
||||
|
||||
private static void assertInitialized() {
|
||||
if (sSoSources == null) {
|
||||
throw new RuntimeException("SoLoader.init() not yet called");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
abstract public class SoSource {
|
||||
|
||||
/**
|
||||
* This SoSource doesn't know how to provide the given library.
|
||||
*/
|
||||
public static final int LOAD_RESULT_NOT_FOUND = 0;
|
||||
|
||||
/**
|
||||
* This SoSource loaded the given library.
|
||||
*/
|
||||
public static final int LOAD_RESULT_LOADED = 1;
|
||||
|
||||
/**
|
||||
* This SoSource did not load the library, but verified that the system loader will load it if
|
||||
* some other library depends on it. Returned only if LOAD_FLAG_ALLOW_IMPLICIT_PROVISION is
|
||||
* provided to loadLibrary.
|
||||
*/
|
||||
public static final int LOAD_RESULT_IMPLICITLY_PROVIDED = 2;
|
||||
|
||||
/**
|
||||
* Allow loadLibrary to implicitly provide the library instead of actually loading it.
|
||||
*/
|
||||
public static final int LOAD_FLAG_ALLOW_IMPLICIT_PROVISION = 1;
|
||||
|
||||
/**
|
||||
* Load a shared library library into this process. This routine is independent of
|
||||
* {@link #loadLibrary}.
|
||||
*
|
||||
* @param soName Name of library to load
|
||||
* @param loadFlags Zero or more of the LOAD_FLAG_XXX constants.
|
||||
* @return One of the LOAD_RESULT_XXX constants.
|
||||
*/
|
||||
abstract public int loadLibrary(String soName, int LoadFlags) throws IOException;
|
||||
|
||||
/**
|
||||
* Ensure that a shared library exists on disk somewhere. This routine is independent of
|
||||
* {@link #loadLibrary}.
|
||||
*
|
||||
* @param soName Name of library to load
|
||||
* @return File if library found; {@code null} if not.
|
||||
*/
|
||||
abstract public File unpackLibrary(String soName) throws IOException;
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.soloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.ErrnoException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileDescriptor;
|
||||
|
||||
/*package*/ final class SysUtil {
|
||||
|
||||
private static byte[] cachedBuffer = null;
|
||||
|
||||
/**
|
||||
* Copy from an inputstream to a named filesystem file. Take care to ensure that we can detect
|
||||
* incomplete copies and that the copied bytes make it to stable storage before returning.
|
||||
* The destination file will be marked executable.
|
||||
*
|
||||
* This routine caches an internal buffer between invocations; after making a sequence of calls
|
||||
* {@link #reliablyCopyExecutable} calls, call {@link #freeCopyBuffer} to release this buffer.
|
||||
*
|
||||
* @param is Stream from which to copy
|
||||
* @param destination File to which to write
|
||||
* @param expectedSize Number of bytes we expect to write; -1 if unknown
|
||||
* @param time Modification time to which to set file on success; must be in the past
|
||||
*/
|
||||
public static void reliablyCopyExecutable(
|
||||
InputStream is,
|
||||
File destination,
|
||||
long expectedSize,
|
||||
long time) throws IOException {
|
||||
destination.delete();
|
||||
try (FileOutputStream os = new FileOutputStream(destination)) {
|
||||
byte buffer[];
|
||||
if (cachedBuffer == null) {
|
||||
cachedBuffer = buffer = new byte[16384];
|
||||
} else {
|
||||
buffer = cachedBuffer;
|
||||
}
|
||||
|
||||
int nrBytes;
|
||||
if (expectedSize > 0) {
|
||||
fallocateIfSupported(os.getFD(), expectedSize);
|
||||
}
|
||||
|
||||
while ((nrBytes = is.read(buffer, 0, buffer.length)) >= 0) {
|
||||
os.write(buffer, 0, nrBytes);
|
||||
}
|
||||
|
||||
os.getFD().sync();
|
||||
destination.setExecutable(true);
|
||||
destination.setLastModified(time);
|
||||
os.getFD().sync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the internal buffer cache for {@link #reliablyCopyExecutable}.
|
||||
*/
|
||||
public static void freeCopyBuffer() {
|
||||
cachedBuffer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine how preferred a given ABI is on this system.
|
||||
*
|
||||
* @param supportedAbis ABIs on this system
|
||||
* @param abi ABI of a shared library we might want to unpack
|
||||
* @return -1 if not supported or an integer, smaller being more preferred
|
||||
*/
|
||||
public static int findAbiScore(String[] supportedAbis, String abi) {
|
||||
for (int i = 0; i < supportedAbis.length; ++i) {
|
||||
if (supportedAbis[i] != null && abi.equals(supportedAbis[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void deleteOrThrow(File file) throws IOException {
|
||||
if (!file.delete()) {
|
||||
throw new IOException("could not delete file " + file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an list of ABIs we supported on this device ordered according to preference. Use a
|
||||
* separate inner class to isolate the version-dependent call where it won't cause the whole
|
||||
* class to fail preverification.
|
||||
*
|
||||
* @return Ordered array of supported ABIs
|
||||
*/
|
||||
public static String[] getSupportedAbis() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return new String[]{Build.CPU_ABI, Build.CPU_ABI2};
|
||||
} else {
|
||||
return LollipopSysdeps.getSupportedAbis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-allocate disk space for a file if we can do that
|
||||
* on this version of the OS.
|
||||
*
|
||||
* @param fd File descriptor for file
|
||||
* @param length Number of bytes to allocate.
|
||||
*/
|
||||
public static void fallocateIfSupported(FileDescriptor fd, long length) throws IOException {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
LollipopSysdeps.fallocate(fd, length);
|
||||
}
|
||||
}
|
||||
|
||||
public static FileLocker lockLibsDirectory(Context context) throws IOException {
|
||||
File lockFile = new File(context.getApplicationInfo().dataDir, "libs-dir-lock");
|
||||
return FileLocker.lock(lockFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory into which we put our self-extracted native libraries.
|
||||
*
|
||||
* @param context Application context
|
||||
* @return File pointing to an existing directory
|
||||
*/
|
||||
/* package */ static File getLibsDirectory(Context context) {
|
||||
return new File(context.getApplicationInfo().dataDir, "app_libs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory into which we put our self-extracted native libraries and make sure it
|
||||
* exists.
|
||||
*/
|
||||
/* package */ static File createLibsDirectory(Context context) {
|
||||
File libsDirectory = getLibsDirectory(context);
|
||||
if (!libsDirectory.isDirectory() && !libsDirectory.mkdirs()) {
|
||||
throw new RuntimeException("could not create libs directory");
|
||||
}
|
||||
|
||||
return libsDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a directory and its contents.
|
||||
*
|
||||
* WARNING: Java APIs do not let us distinguish directories from symbolic links to directories.
|
||||
* Consequently, if the directory contains symbolic links to directories, we will attempt to
|
||||
* delete the contents of pointed-to directories.
|
||||
*
|
||||
* @param file File or directory to delete
|
||||
*/
|
||||
/* package */ static void dumbDeleteRecrusive(File file) throws IOException {
|
||||
if (file.isDirectory()) {
|
||||
for (File entry : file.listFiles()) {
|
||||
dumbDeleteRecrusive(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.delete() && file.exists()) {
|
||||
throw new IOException("could not delete: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulate Lollipop-specific calls into an independent class so we don't fail preverification
|
||||
* downlevel.
|
||||
*/
|
||||
private static final class LollipopSysdeps {
|
||||
public static String[] getSupportedAbis() {
|
||||
return Build.SUPPORTED_32_BIT_ABIS; // We ain't doing no newfangled 64-bit
|
||||
}
|
||||
|
||||
public static void fallocate(FileDescriptor fd, long length) throws IOException {
|
||||
try {
|
||||
Os.posix_fallocate(fd, 0, length);
|
||||
} catch (ErrnoException ex) {
|
||||
throw new IOException(ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# This script generates Java structures that contain the offsets of
|
||||
# fields in various ELF ABI structures. com.facebook.soloader.MinElf
|
||||
# uses these structures while parsing ELF files.
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
struct2java() {
|
||||
../../../../scripts/struct2java.py "$@"
|
||||
}
|
||||
|
||||
declare -a structs=(Elf32_Ehdr Elf64_Ehdr)
|
||||
structs+=(Elf32_Ehdr Elf64_Ehdr)
|
||||
structs+=(Elf32_Phdr Elf64_Phdr)
|
||||
structs+=(Elf32_Shdr Elf64_Shdr)
|
||||
structs+=(Elf32_Dyn Elf64_Dyn)
|
||||
|
||||
for struct in "${structs[@]}"; do
|
||||
cat > elfhdr.c <<EOF
|
||||
#include <elf.h>
|
||||
static const $struct a;
|
||||
EOF
|
||||
gcc -g -c -o elfhdr.o elfhdr.c
|
||||
cat > $struct.java <<EOF
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// AUTOMATICALLY GENERATED CODE. Regenerate with genstructs.sh.
|
||||
package com.facebook.soloader;
|
||||
EOF
|
||||
struct2java elfhdr.o $struct >> $struct.java
|
||||
done
|
||||
|
||||
rm -f elfhdr.o elfhdr.c
|
|
@ -1,6 +0,0 @@
|
|||
# Ensure that methods from LollipopSysdeps don't get inlined. LollipopSysdeps.fallocate references
|
||||
# an exception that isn't present prior to Lollipop, which trips up the verifier if the class is
|
||||
# loaded on a pre-Lollipop OS.
|
||||
-keep class com.facebook.soloader.SysUtil$LollipopSysdeps {
|
||||
public <methods>;
|
||||
}
|
Loading…
Reference in New Issue