1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.pm.dex; 18 19 import android.content.pm.ApplicationInfo; 20 import android.content.pm.SharedLibraryInfo; 21 import com.android.server.pm.parsing.pkg.AndroidPackage; 22 import android.util.Slog; 23 import android.util.SparseArray; 24 25 import com.android.internal.os.ClassLoaderFactory; 26 import com.android.internal.util.ArrayUtils; 27 28 import java.io.File; 29 import java.util.List; 30 31 public final class DexoptUtils { 32 private static final String TAG = "DexoptUtils"; 33 34 // Shared libraries have more or less followed PCL behavior due to the way 35 // they were added to the classpath pre Q. 36 private static final String SHARED_LIBRARY_LOADER_TYPE = 37 ClassLoaderFactory.getPathClassLoaderName(); 38 DexoptUtils()39 private DexoptUtils() {} 40 41 /** 42 * Creates the class loader context dependencies for each of the application code paths. 43 * The returned array contains the class loader contexts that needs to be passed to dexopt in 44 * order to ensure correct optimizations. "Code" paths with no actual code, as specified by 45 * {@param pathsWithCode}, are ignored and will have null as their context in the returned array 46 * (configuration splits are an example of paths without code). 47 * 48 * A class loader context describes how the class loader chain should be built by dex2oat 49 * in order to ensure that classes are resolved during compilation as they would be resolved 50 * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is 51 * loaded in a different context (with a different set of class loaders or a different 52 * classpath), the compiled code will be rejected. 53 * 54 * Note that the class loader context only includes dependencies and not the code path itself. 55 * The contexts are created based on the application split dependency list and 56 * the provided shared libraries. 57 * 58 * All the code paths encoded in the context will be relative to the base directory. This 59 * enables stage compilation where compiler artifacts may be moved around. 60 * 61 * The result is indexed as follows: 62 * - index 0 contains the context for the base apk 63 * - index 1 to n contain the context for the splits in the order determined by 64 * {@code info.getSplitCodePaths()} 65 * 66 * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk} 67 * and pay attention to the way the classpath is created for the non isolated mode in: 68 * {@link android.app.LoadedApk#makePaths( 69 * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}. 70 */ getClassLoaderContexts(AndroidPackage pkg, List<SharedLibraryInfo> sharedLibraries, boolean[] pathsWithCode)71 public static String[] getClassLoaderContexts(AndroidPackage pkg, 72 List<SharedLibraryInfo> sharedLibraries, boolean[] pathsWithCode) { 73 // The base class loader context contains only the shared library. 74 String sharedLibrariesContext = ""; 75 if (sharedLibraries != null) { 76 sharedLibrariesContext = encodeSharedLibraries(sharedLibraries); 77 } 78 79 String baseApkContextClassLoader = encodeClassLoader( 80 "", pkg.getClassLoaderName(), sharedLibrariesContext); 81 if (pkg.getSplitCodePaths() == null) { 82 // The application has no splits. 83 return new String[] {baseApkContextClassLoader}; 84 } 85 86 // The application has splits. Compute their class loader contexts. 87 88 // First, cache the relative paths of the splits and do some sanity checks 89 String[] splitRelativeCodePaths = getSplitRelativeCodePaths(pkg); 90 91 // The splits have an implicit dependency on the base apk. 92 // This means that we have to add the base apk file in addition to the shared libraries. 93 String baseApkName = new File(pkg.getBaseCodePath()).getName(); 94 String baseClassPath = baseApkName; 95 96 // The result is stored in classLoaderContexts. 97 // Index 0 is the class loader context for the base apk. 98 // Index `i` is the class loader context encoding for split `i`. 99 String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length]; 100 classLoaderContexts[0] = pathsWithCode[0] ? baseApkContextClassLoader : null; 101 102 SparseArray<int[]> splitDependencies = pkg.getSplitDependencies(); 103 104 if (!pkg.isIsolatedSplitLoading() 105 || splitDependencies == null 106 || splitDependencies.size() == 0) { 107 // If the app didn't request for the splits to be loaded in isolation or if it does not 108 // declare inter-split dependencies, then all the splits will be loaded in the base 109 // apk class loader (in the order of their definition). 110 String classpath = baseClassPath; 111 for (int i = 1; i < classLoaderContexts.length; i++) { 112 if (pathsWithCode[i]) { 113 classLoaderContexts[i] = encodeClassLoader( 114 classpath, pkg.getClassLoaderName(), sharedLibrariesContext); 115 } else { 116 classLoaderContexts[i] = null; 117 } 118 // Note that the splits with no code are not removed from the classpath computation. 119 // i.e. split_n might get the split_n-1 in its classpath dependency even 120 // if split_n-1 has no code. 121 // The splits with no code do not matter for the runtime which ignores 122 // apks without code when doing the classpath checks. As such we could actually 123 // filter them but we don't do it in order to keep consistency with how the apps 124 // are loaded. 125 classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]); 126 } 127 } else { 128 // In case of inter-split dependencies, we need to walk the dependency chain of each 129 // split. We do this recursively and store intermediate results in classLoaderContexts. 130 131 // First, look at the split class loaders and cache their individual contexts (i.e. 132 // the class loader + the name of the split). This is an optimization to avoid 133 // re-computing them during the recursive call. 134 // The cache is stored in splitClassLoaderEncodingCache. The difference between this and 135 // classLoaderContexts is that the later contains the full chain of class loaders for 136 // a given split while splitClassLoaderEncodingCache only contains a single class loader 137 // encoding. 138 String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length]; 139 for (int i = 0; i < splitRelativeCodePaths.length; i++) { 140 splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i], 141 pkg.getSplitClassLoaderNames()[i]); 142 } 143 String splitDependencyOnBase = encodeClassLoader( 144 baseClassPath, pkg.getClassLoaderName()); 145 146 // Note that not all splits have dependencies (e.g. configuration splits) 147 // The splits without dependencies will have classLoaderContexts[config_split_index] 148 // set to null after this step. 149 for (int i = 1; i < splitDependencies.size(); i++) { 150 int splitIndex = splitDependencies.keyAt(i); 151 if (pathsWithCode[splitIndex]) { 152 // Compute the class loader context only for the splits with code. 153 getParentDependencies(splitIndex, splitClassLoaderEncodingCache, 154 splitDependencies, classLoaderContexts, splitDependencyOnBase); 155 } 156 } 157 158 // At this point classLoaderContexts contains only the parent dependencies. 159 // We also need to add the class loader of the current split which should 160 // come first in the context. 161 for (int i = 1; i < classLoaderContexts.length; i++) { 162 String splitClassLoader = encodeClassLoader("", 163 pkg.getSplitClassLoaderNames()[i - 1]); 164 if (pathsWithCode[i]) { 165 // If classLoaderContexts[i] is null it means that the split does not have 166 // any dependency. In this case its context equals its declared class loader. 167 classLoaderContexts[i] = classLoaderContexts[i] == null 168 ? splitClassLoader 169 : encodeClassLoaderChain(splitClassLoader, classLoaderContexts[i]) 170 + sharedLibrariesContext; 171 } else { 172 // This is a split without code, it has no dependency and it is not compiled. 173 // Its context will be null. 174 classLoaderContexts[i] = null; 175 } 176 } 177 } 178 179 return classLoaderContexts; 180 } 181 182 /** 183 * Creates the class loader context for the given shared library. 184 */ getClassLoaderContext(SharedLibraryInfo info)185 public static String getClassLoaderContext(SharedLibraryInfo info) { 186 String sharedLibrariesContext = ""; 187 if (info.getDependencies() != null) { 188 sharedLibrariesContext = encodeSharedLibraries(info.getDependencies()); 189 } 190 return encodeClassLoader( 191 "", SHARED_LIBRARY_LOADER_TYPE, sharedLibrariesContext); 192 } 193 194 /** 195 * Recursive method to generate the class loader context dependencies for the split with the 196 * given index. {@param classLoaderContexts} acts as an accumulator. Upton return 197 * {@code classLoaderContexts[index]} will contain the split dependency. 198 * During computation, the method may resolve the dependencies of other splits as it traverses 199 * the entire parent chain. The result will also be stored in {@param classLoaderContexts}. 200 * 201 * Note that {@code index 0} denotes the base apk and it is special handled. When the 202 * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}. 203 * {@code classLoaderContexts[0]} is not modified in this method. 204 * 205 * @param index the index of the split (Note that index 0 denotes the base apk) 206 * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits. 207 * It contains only the split class loader and not the the base. The split 208 * with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}. 209 * @param splitDependencies the dependencies for all splits. Note that in this array index 0 210 * is the base and splits start from index 1. 211 * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits 212 * start at index 1. 213 * @param splitDependencyOnBase the encoding of the implicit split dependency on base. 214 */ getParentDependencies(int index, String[] splitClassLoaderEncodingCache, SparseArray<int[]> splitDependencies, String[] classLoaderContexts, String splitDependencyOnBase)215 private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache, 216 SparseArray<int[]> splitDependencies, String[] classLoaderContexts, 217 String splitDependencyOnBase) { 218 // If we hit the base apk return its custom dependency list which is 219 // sharedLibraries + base.apk 220 if (index == 0) { 221 return splitDependencyOnBase; 222 } 223 // Return the result if we've computed the splitDependencies for this index already. 224 if (classLoaderContexts[index] != null) { 225 return classLoaderContexts[index]; 226 } 227 // Get the splitDependencies for the parent of this index and append its path to it. 228 int parent = splitDependencies.get(index)[0]; 229 String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache, 230 splitDependencies, classLoaderContexts, splitDependencyOnBase); 231 232 // The split context is: `parent context + parent dependencies context`. 233 String splitContext = (parent == 0) ? 234 parentDependencies : 235 encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies); 236 classLoaderContexts[index] = splitContext; 237 return splitContext; 238 } 239 encodeSharedLibrary(SharedLibraryInfo sharedLibrary)240 private static String encodeSharedLibrary(SharedLibraryInfo sharedLibrary) { 241 List<String> paths = sharedLibrary.getAllCodePaths(); 242 String classLoaderSpec = encodeClassLoader( 243 encodeClasspath(paths.toArray(new String[paths.size()])), 244 SHARED_LIBRARY_LOADER_TYPE); 245 if (sharedLibrary.getDependencies() != null) { 246 classLoaderSpec += encodeSharedLibraries(sharedLibrary.getDependencies()); 247 } 248 return classLoaderSpec; 249 } 250 encodeSharedLibraries(List<SharedLibraryInfo> sharedLibraries)251 private static String encodeSharedLibraries(List<SharedLibraryInfo> sharedLibraries) { 252 String sharedLibrariesContext = "{"; 253 boolean first = true; 254 for (SharedLibraryInfo info : sharedLibraries) { 255 if (!first) { 256 sharedLibrariesContext += "#"; 257 } 258 first = false; 259 sharedLibrariesContext += encodeSharedLibrary(info); 260 } 261 sharedLibrariesContext += "}"; 262 return sharedLibrariesContext; 263 } 264 265 /** 266 * Encodes the shared libraries classpathElements in a format accepted by dexopt. 267 * NOTE: Keep this in sync with the dexopt expectations! Right now that is 268 * a list separated by ':'. 269 */ encodeClasspath(String[] classpathElements)270 private static String encodeClasspath(String[] classpathElements) { 271 if (classpathElements == null || classpathElements.length == 0) { 272 return ""; 273 } 274 StringBuilder sb = new StringBuilder(); 275 for (String element : classpathElements) { 276 if (sb.length() != 0) { 277 sb.append(":"); 278 } 279 sb.append(element); 280 } 281 return sb.toString(); 282 } 283 284 /** 285 * Adds an element to the encoding of an existing classpath. 286 * {@see PackageDexOptimizer.encodeClasspath(String[])} 287 */ encodeClasspath(String classpath, String newElement)288 private static String encodeClasspath(String classpath, String newElement) { 289 return classpath.isEmpty() ? newElement : (classpath + ":" + newElement); 290 } 291 292 /** 293 * Encodes a single class loader dependency starting from {@param path} and 294 * {@param classLoaderName}. 295 * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]" 296 * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader. 297 */ encodeClassLoader(String classpath, String classLoaderName)298 /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) { 299 classpath.getClass(); // Throw NPE if classpath is null 300 String classLoaderDexoptEncoding = classLoaderName; 301 if (ClassLoaderFactory.isPathClassLoaderName(classLoaderName)) { 302 classLoaderDexoptEncoding = "PCL"; 303 } else if (ClassLoaderFactory.isDelegateLastClassLoaderName(classLoaderName)) { 304 classLoaderDexoptEncoding = "DLC"; 305 } else { 306 Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName); 307 } 308 return classLoaderDexoptEncoding + "[" + classpath + "]"; 309 } 310 311 /** 312 * Same as above, but appends {@param sharedLibraries} to the result. 313 */ encodeClassLoader(String classpath, String classLoaderName, String sharedLibraries)314 private static String encodeClassLoader(String classpath, String classLoaderName, 315 String sharedLibraries) { 316 return encodeClassLoader(classpath, classLoaderName) + sharedLibraries; 317 } 318 319 /** 320 * Links to dependencies together in a format accepted by dexopt. 321 * For the special case when either of cl1 or cl2 equals 322 * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split 323 * dependencies {@see encodeClassLoader} separated by ';'. 324 */ encodeClassLoaderChain(String cl1, String cl2)325 /*package*/ static String encodeClassLoaderChain(String cl1, String cl2) { 326 if (cl1.isEmpty()) return cl2; 327 if (cl2.isEmpty()) return cl1; 328 return cl1 + ";" + cl2; 329 } 330 331 /** 332 * Compute the class loader context for the dex files present in the classpath of the first 333 * class loader from the given list (referred in the code as the {@code loadingClassLoader}). 334 * Each dex files gets its own class loader context in the returned array. 335 * 336 * Example: 337 * If classLoadersNames = {"dalvik.system.DelegateLastClassLoader", 338 * "dalvik.system.PathClassLoader"} and classPaths = {"foo.dex:bar.dex", "other.dex"} 339 * The output will be 340 * {"DLC[];PCL[other.dex]", "DLC[foo.dex];PCL[other.dex]"} 341 * with "DLC[];PCL[other.dex]" being the context for "foo.dex" 342 * and "DLC[foo.dex];PCL[other.dex]" the context for "bar.dex". 343 * 344 * If any of the class loaders names is unsupported the method will return null. 345 * 346 * The argument lists must be non empty and of the same size. 347 * 348 * @param classLoadersNames the names of the class loaders present in the loading chain. The 349 * list encodes the class loader chain in the natural order. The first class loader has 350 * the second one as its parent and so on. 351 * @param classPaths the class paths for the elements of {@param classLoadersNames}. The 352 * the first element corresponds to the first class loader and so on. A classpath is 353 * represented as a list of dex files separated by {@code File.pathSeparator}. 354 * The return context will be for the dex files found in the first class path. 355 */ processContextForDexLoad(List<String> classLoadersNames, List<String> classPaths)356 /*package*/ static String[] processContextForDexLoad(List<String> classLoadersNames, 357 List<String> classPaths) { 358 if (classLoadersNames.size() != classPaths.size()) { 359 throw new IllegalArgumentException( 360 "The size of the class loader names and the dex paths do not match."); 361 } 362 if (classLoadersNames.isEmpty()) { 363 throw new IllegalArgumentException("Empty classLoadersNames"); 364 } 365 366 // Compute the context for the parent class loaders. 367 String parentContext = ""; 368 // We know that these lists are actually ArrayLists so getting the elements by index 369 // is fine (they come over binder). Even if something changes we expect the sizes to be 370 // very small and it shouldn't matter much. 371 for (int i = 1; i < classLoadersNames.size(); i++) { 372 if (!ClassLoaderFactory.isValidClassLoaderName(classLoadersNames.get(i)) 373 || classPaths.get(i) == null) { 374 return null; 375 } 376 String classpath = encodeClasspath(classPaths.get(i).split(File.pathSeparator)); 377 parentContext = encodeClassLoaderChain(parentContext, 378 encodeClassLoader(classpath, classLoadersNames.get(i))); 379 } 380 381 // Now compute the class loader context for each dex file from the first classpath. 382 String loadingClassLoader = classLoadersNames.get(0); 383 if (!ClassLoaderFactory.isValidClassLoaderName(loadingClassLoader)) { 384 return null; 385 } 386 String[] loadedDexPaths = classPaths.get(0).split(File.pathSeparator); 387 String[] loadedDexPathsContext = new String[loadedDexPaths.length]; 388 String currentLoadedDexPathClasspath = ""; 389 for (int i = 0; i < loadedDexPaths.length; i++) { 390 String dexPath = loadedDexPaths[i]; 391 String currentContext = encodeClassLoader( 392 currentLoadedDexPathClasspath, loadingClassLoader); 393 loadedDexPathsContext[i] = encodeClassLoaderChain(currentContext, parentContext); 394 currentLoadedDexPathClasspath = encodeClasspath(currentLoadedDexPathClasspath, dexPath); 395 } 396 return loadedDexPathsContext; 397 } 398 399 /** 400 * Returns the relative paths of the splits declared by the application {@code info}. 401 * Assumes that the application declares a non-null array of splits. 402 */ getSplitRelativeCodePaths(AndroidPackage pkg)403 private static String[] getSplitRelativeCodePaths(AndroidPackage pkg) { 404 String baseCodePath = new File(pkg.getBaseCodePath()).getParent(); 405 String[] splitCodePaths = pkg.getSplitCodePaths(); 406 String[] splitRelativeCodePaths = new String[ArrayUtils.size(splitCodePaths)]; 407 for (int i = 0; i < splitRelativeCodePaths.length; i++) { 408 File pathFile = new File(splitCodePaths[i]); 409 splitRelativeCodePaths[i] = pathFile.getName(); 410 // Sanity check that the base paths of the splits are all the same. 411 String basePath = pathFile.getParent(); 412 if (!basePath.equals(baseCodePath)) { 413 Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " + 414 baseCodePath); 415 } 416 } 417 return splitRelativeCodePaths; 418 } 419 } 420