Make SoLoader an external dependency
Reviewed By: bestander Differential Revision: D3535233 fbshipit-source-id: 9fddb654123a7606d46069a98e2f68dec7f520fa
This commit is contained in:
Родитель
2426b3b5ec
Коммит
dd06b74157
|
@ -266,6 +266,7 @@ dependencies {
|
||||||
compile 'com.android.support:recyclerview-v7:23.0.1'
|
compile 'com.android.support:recyclerview-v7:23.0.1'
|
||||||
compile 'com.facebook.fresco:fresco:0.11.0'
|
compile 'com.facebook.fresco:fresco:0.11.0'
|
||||||
compile 'com.facebook.fresco:imagepipeline-okhttp3: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.fasterxml.jackson.core:jackson-core:2.2.3'
|
||||||
compile 'com.google.code.findbugs:jsr305:3.0.0'
|
compile 'com.google.code.findbugs:jsr305:3.0.0'
|
||||||
compile 'com.squareup.okhttp3:okhttp:3.2.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_prebuilt_aar(
|
||||||
|
name = 'soloader',
|
||||||
android_library(
|
aar = ':soloader-binary-aar',
|
||||||
name = 'soloader',
|
visibility = ['PUBLIC'],
|
||||||
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',
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
project_config(
|
remote_file(
|
||||||
src_target = ':soloader',
|
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>;
|
|
||||||
}
|
|
Загрузка…
Ссылка в новой задаче