Make SoLoader an external dependency

Reviewed By: bestander

Differential Revision: D3535233

fbshipit-source-id: 9fddb654123a7606d46069a98e2f68dec7f520fa
This commit is contained in:
Michał Gregorczyk 2016-07-08 13:52:31 -07:00 committed by Facebook Github Bot 2
parent 2426b3b5ec
commit dd06b74157
22 changed files with 9 additions and 1596 deletions

View File

@ -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'

View File

@ -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;
}
}
}

View File

@ -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',
)

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -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

View File

@ -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>;
}