java/com/facebook/soloader/NativeDeps.java (234 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 java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; /** * Class for getting native libraries dependencies for a DSO. Dependencies are read either from a * file specifying all dependencies, or from the ELF file itself. */ public final class NativeDeps { private static final int LIB_PREFIX_LEN = "lib".length(); private static final int LIB_SUFFIX_LEN = ".so".length(); private static final int LIB_PREFIX_SUFFIX_LEN = LIB_PREFIX_LEN + LIB_SUFFIX_LEN; private static final int DEFAULT_LIBS_CAPACITY = 512; private static final int INITIAL_HASH = 5381; private static boolean sInitialized = false; private static @Nullable byte[] sEncodedDeps; private static List<Integer> sPrecomputedLibs; private static Map<Integer, List<Integer>> sPrecomputedDeps; public static String[] getDependencies(String soName, File elfFile) throws IOException { if (sInitialized) { String[] deps = tryGetDepsFromPrecomputedDeps(soName); if (deps != null) { return deps; } } return MinElf.extract_DT_NEEDED(elfFile); } public static String[] getDependencies(String soName, ElfByteChannel bc) throws IOException { if (sPrecomputedDeps != null) { String[] deps = tryGetDepsFromPrecomputedDeps(soName); if (deps != null) { return deps; } } return MinElf.extract_DT_NEEDED(bc); } /** * Enables fetching dependencies from a deps file and specifies the path to the deps file. If * enabled, dependencies will be looked up in the deps file instead of extracting them from the * ELF file. The file is read only once when getDependencies is first called. If dependencies for * a specific library are not found or the file is corrupt, we fall back to extracting the * dependencies from the ELF file. */ public static boolean useDepsFile(byte[] apkId, String depsFilePath) throws IOException { if (!sInitialized) { synchronized (NativeDeps.class) { if (!sInitialized) { return readDepsFromFile(apkId, depsFilePath); } } } throw new IllegalStateException( "Trying to initialize NativeDeps but it was already initialized"); } // Given the offset where the dependencies for a library begin in // sEncodedDeps, stores indices for mapping from lib name hash to offset, // and from lib index to offset. private static void indexLib(int hash, int nameBegin) { sPrecomputedLibs.add(nameBegin); List<Integer> bucket = sPrecomputedDeps.get(hash); if (bucket == null) { bucket = new ArrayList<Integer>(); sPrecomputedDeps.put(hash, bucket); } bucket.add(nameBegin); } // Preprocess bytes from deps file. The deps file should be ascii encoded, // with the format: // <lib1_name> [dep_index1 dep_index2 ...]\n // <lib2_name> [dep_index1 dep_index2 ...]\n // // library names must not be prefixed with "lib" or suffixed with ".so". // dep_index is the 0-based index of the dependency in the deps file. // // We want to process the deps fast since the deps file can be large. // To do this fast, we read the bytes into memory, we store the // offsets in which each library starts, and we hash each library name // and map it to its offset. private static void indexDepsBytes(byte[] bytes, int offset) { boolean inLibName = true; int byteOffset = offset; int libHash = 0; int libNameBegin = 0; try { while (true) { if (inLibName) { libNameBegin = byteOffset; int nextByte; libHash = INITIAL_HASH; while ((nextByte = bytes[byteOffset]) > ' ') { libHash = ((libHash << 5) + libHash) + nextByte; ++byteOffset; } indexLib(libHash, libNameBegin); inLibName = nextByte != ' '; } else { while (bytes[byteOffset] != '\n') { ++byteOffset; } inLibName = true; } ++byteOffset; } } catch (IndexOutOfBoundsException e) { if (inLibName) { indexLib(libHash, libNameBegin); } } } private static int verifyBytesAndGetOffset(byte[] apkId, byte[] bytes) { if (apkId == null || apkId.length == 0) { return -1; } if (bytes.length < apkId.length + 4) { return -1; } int depsLen = ByteBuffer.wrap(bytes, apkId.length, 4).getInt(); if (bytes.length != apkId.length + 4 + depsLen) { return -1; } for (int i = 0; i < apkId.length; ++i) { if (apkId[i] != bytes[i]) { return -1; } } return apkId.length + 4; } // Reads deps file and builds indexes to quickly get deps from libraries. private static boolean readDepsFromFile(byte[] apkId, String depsFilePath) throws IOException { try (FileInputStream in = new FileInputStream(depsFilePath)) { sEncodedDeps = new byte[in.available()]; in.read(sEncodedDeps); int offset = verifyBytesAndGetOffset(apkId, sEncodedDeps); if (offset == -1) { sEncodedDeps = null; return false; } sPrecomputedDeps = new HashMap<>(DEFAULT_LIBS_CAPACITY); sPrecomputedLibs = new ArrayList<>(DEFAULT_LIBS_CAPACITY); indexDepsBytes(sEncodedDeps, offset); } catch (IOException e) { // Release bytes that are not needed anymore and propagate exception sEncodedDeps = null; throw e; } sInitialized = true; return true; } // Returns whether soName is encoded at a specified offset in sEncodedDeps private static boolean libIsAtOffset(String soName, int offset) { int i, j; for (i = LIB_PREFIX_LEN, j = offset; i < soName.length() - LIB_SUFFIX_LEN && j < sEncodedDeps.length; i++, j++) { if ((soName.codePointAt(i) & 0xFF) != sEncodedDeps[j]) { break; } } return i == soName.length() - LIB_SUFFIX_LEN; } // Hashes the name of a library private static int hashLib(String soName) { int libHash = INITIAL_HASH; for (int i = LIB_PREFIX_LEN; i < soName.length() - LIB_SUFFIX_LEN; ++i) { libHash = ((libHash << 5) + libHash) + soName.codePointAt(i); } return libHash; } // Returns the byte offset in sEncodedDeps for where dependencies for // soName start. // Returns -1 if soName does not exist in the deps file private static int getOffsetForLib(String soName) { int libHash = hashLib(soName); List<Integer> bucket = sPrecomputedDeps.get(libHash); if (bucket == null) { return -1; } for (int offset : bucket) { if (libIsAtOffset(soName, offset)) { return offset; } } return -1; } // Given the index of a library in the dependency file, returns a String with // the name of that library. The string is prefixed with "lib" and suffixed // with ".so". private static @Nullable String getLibString(int depIndex) { if (depIndex >= sPrecomputedLibs.size()) { return null; } int initialOffset = sPrecomputedLibs.get(depIndex); int byteOffset = initialOffset; while (byteOffset < sEncodedDeps.length && sEncodedDeps[byteOffset] > ' ') { ++byteOffset; } int libNameLength = (byteOffset - initialOffset) + LIB_PREFIX_SUFFIX_LEN; char[] libBytes = new char[libNameLength]; libBytes[0] = 'l'; libBytes[1] = 'i'; libBytes[2] = 'b'; for (int i = 0; i < libNameLength - LIB_PREFIX_SUFFIX_LEN; ++i) { libBytes[LIB_PREFIX_LEN + i] = (char) sEncodedDeps[initialOffset + i]; } libBytes[libNameLength - 3] = '.'; libBytes[libNameLength - 2] = 's'; libBytes[libNameLength - 1] = 'o'; return new String(libBytes); } private static @Nullable String[] getDepsForLibAtOffset(int offset, int libNameLength) { List<String> deps = new ArrayList<>(); int depIndex = 0; boolean hasDep = false; int depsOffset = offset + libNameLength - LIB_PREFIX_SUFFIX_LEN; int nextByte; for (int byteOffset = depsOffset; byteOffset < sEncodedDeps.length && ((nextByte = sEncodedDeps[byteOffset]) != '\n'); byteOffset++) { if (nextByte == ' ') { if (hasDep) { String dep = getLibString(depIndex); if (dep == null) { return null; } deps.add(dep); hasDep = false; depIndex = 0; } continue; } if (nextByte < '0' || nextByte > '9') { // Deps are corrupt return null; } hasDep = true; depIndex = depIndex * 10 + (nextByte - '0'); } if (hasDep) { String dep = getLibString(depIndex); if (dep == null) { return null; } deps.add(dep); } if (deps.size() == 0) { // If a library has no dependencies, then we don't know its // dependencies, it was just listed in the native deps file because // another library depends on this library. return null; } String[] depsArray = new String[deps.size()]; return deps.toArray(depsArray); } @Nullable static String[] tryGetDepsFromPrecomputedDeps(String soName) { if (!sInitialized) { return null; } if (soName.length() <= LIB_PREFIX_SUFFIX_LEN) { // soName must start with "lib" prefix and end with ".so" prefix, so // the length of the string must be at least 7. return null; } int offset = getOffsetForLib(soName); if (offset == -1) { return null; } return getDepsForLibAtOffset(offset, soName.length()); } }