java/com/facebook/soloader/DirectApkSoSource.java (138 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.soloader; import android.content.Context; import android.os.Build; import android.os.StrictMode; import android.text.TextUtils; import android.util.Log; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; /** * {@link SoSource} that directly finds shared libraries in a APK. The native libraries must be page * aligned and stored uncompressed in the APK. * * @see <a * href="https://developer.android.com/guide/topics/manifest/application-element#extractNativeLibs"> * android:extractNativeLibs</a> */ @ThreadSafe public class DirectApkSoSource extends SoSource { private final Set<String> mLibsInApk = Collections.synchronizedSet(new HashSet<String>()); private final @Nullable String mDirectApkLdPath; private final File mApkFile; public DirectApkSoSource(Context context) { super(); mDirectApkLdPath = getDirectApkLdPath(""); mApkFile = new File(context.getApplicationInfo().sourceDir); } public DirectApkSoSource(File apkFile) { super(); mDirectApkLdPath = getDirectApkLdPath(SysUtil.getBaseName(apkFile.getName())); mApkFile = apkFile; } @Override public int loadLibrary(String soName, int loadFlags, StrictMode.ThreadPolicy threadPolicy) throws IOException { if (SoLoader.sSoFileLoader == null) { throw new IllegalStateException("SoLoader.init() not yet called"); } if (!mLibsInApk.contains(soName) || TextUtils.isEmpty(mDirectApkLdPath)) { Log.d(SoLoader.TAG, soName + " not found on " + mDirectApkLdPath); return LOAD_RESULT_NOT_FOUND; } loadDependencies(soName, loadFlags, threadPolicy); try { loadFlags |= SoLoader.SOLOADER_LOOK_IN_ZIP; SoLoader.sSoFileLoader.load(mDirectApkLdPath + File.separator + soName, loadFlags); } catch (UnsatisfiedLinkError e) { Log.w(SoLoader.TAG, soName + " not found on DirectAPKSoSource: " + loadFlags, e); return LOAD_RESULT_NOT_FOUND; } Log.d(SoLoader.TAG, soName + " found on DirectAPKSoSource: " + loadFlags); return LOAD_RESULT_LOADED; } @Override public File unpackLibrary(String soName) throws IOException { throw new UnsupportedOperationException("DirectAPKSoSource doesn't support unpackLibrary"); } private @Nullable static String getDirectApkLdPath(String apkName) { final String classLoaderLdLibraryPath = Build.VERSION.SDK_INT >= 14 ? SoLoader.Api14Utils.getClassLoaderLdLoadLibrary() : null; if (classLoaderLdLibraryPath != null) { final String[] paths = classLoaderLdLibraryPath.split(":"); for (final String path : paths) { if (path.contains(apkName + ".apk!/")) { return path; } } } return null; } private static String[] getDependencies(String soName, ElfByteChannel bc) throws IOException { if (SoLoader.SYSTRACE_LIBRARY_LOADING) { Api18TraceUtils.beginTraceSection("SoLoader.getElfDependencies[", soName, "]"); } try { return NativeDeps.getDependencies(soName, bc); } finally { if (SoLoader.SYSTRACE_LIBRARY_LOADING) { Api18TraceUtils.endSection(); } } } private void loadDependencies(String soName, int loadFlags, StrictMode.ThreadPolicy threadPolicy) throws IOException { try (ZipFile mZipFile = new ZipFile(mApkFile)) { Enumeration<? extends ZipEntry> entries = mZipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry != null && entry.getName().endsWith("/" + soName)) { try (ElfByteChannel bc = new ElfZipFileChannel(mZipFile, entry)) { for (String dependency : getDependencies(soName, bc)) { if (mLibsInApk.contains(dependency) || dependency.startsWith("/")) { // Bionic dynamic linker could correctly resolving dependencies, we don't need // load them by ourselves. continue; } SoLoader.loadLibraryBySoName( dependency, loadFlags | LOAD_FLAG_ALLOW_IMPLICIT_PROVISION, threadPolicy); } } break; } } } } @Override protected void prepare(int flags) throws IOException { String subDir = null; if (!TextUtils.isEmpty(mDirectApkLdPath)) { final int i = mDirectApkLdPath.indexOf('!'); if (i >= 0 && i + 2 < mDirectApkLdPath.length()) { // Exclude `!` and `/` in the path subDir = mDirectApkLdPath.substring(i + 2); } } if (subDir == null) { return; } try (ZipFile mZipFile = new ZipFile(mApkFile)) { Enumeration<? extends ZipEntry> entries = mZipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry != null && entry.getName().startsWith(subDir) && entry.getName().endsWith(".so") && entry.getMethod() == ZipEntry.STORED) { final String soName = entry.getName().substring(subDir.length() + 1); mLibsInApk.add(soName); } } } } @Override public String toString() { return new StringBuilder() .append(getClass().getName()) .append("[root = ") .append(mDirectApkLdPath) .append(']') .toString(); } }